Skip to content

Instantly share code, notes, and snippets.

@lumin3000
Last active July 3, 2023 17:24
Show Gist options
  • Save lumin3000/24e159847196a6dfce6db1624b264b4f to your computer and use it in GitHub Desktop.
Save lumin3000/24e159847196a6dfce6db1624b264b4f to your computer and use it in GitHub Desktop.
src/area.rs
use bevy::{prelude::*, sprite::MaterialMesh2dBundle};
use bevy_rapier2d::prelude::{Collider, RigidBody};
use crate::common::{LEVEL_COLUMNS, LEVEL_ROWS, TILE_SIZE};
pub const WALL_THICKNESS: f32 = 10.0;
#[derive(Debug, Component)]
pub struct AreaWall;
pub fn setup_wall(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
){
let left_wall = -LEVEL_COLUMNS as f32 / 2.0 * TILE_SIZE -
WALL_THICKNESS / 2.0;
let right_wall = LEVEL_COLUMNS as f32 / 2.0 * TILE_SIZE +
WALL_THICKNESS / 2.0;
let top_wall = LEVEL_ROWS as f32 / 2.0 * TILE_SIZE + WALL_THICKNESS / 2.0;
let bottom_wall = -LEVEL_ROWS as f32 / 2.0 * TILE_SIZE - WALL_THICKNESS /
2.0;
let arena_height = top_wall - bottom_wall;
let arena_width = right_wall - left_wall;
let wall_color = Color::rgb(0.8, 0.8, 0.8);
let material_handle = materials.add(wall_color.into());
// left wall
commands.spawn((
AreaWall,
MaterialMesh2dBundle {
mesh: meshes
.add(
WALL_THICKNESS))
0.)),
shape::Quad::new(Vec2::new(WALL_THICKNESS, arena_height +
.into(),
)
.into(),
material: material_handle.clone(),
transform: Transform::from_translation(Vec3::new(left_wall, 0.,
..default()
},
RigidBody::Fixed,
Collider::cuboid(WALL_THICKNESS / 2.0, (arena_height +
WALL_THICKNESS) / 2.0),
));
// right wall
commands.spawn((
AreaWall,
MaterialMesh2dBundle {
mesh: meshes
.add(
shape::Quad::new(Vec2::new(WALL_THICKNESS, arena_height +
.into(),
)
.into(),
material: material_handle.clone(),
transform: Transform::from_translation(Vec3::new(right_wall, 0.,
..default()
},
RigidBody::Fixed,
WALL_THICKNESS))
0.)),
Collider::cuboid(WALL_THICKNESS / 2.0, (arena_height +
WALL_THICKNESS) / 2.0),
));
// top wall
commands.spawn((
AreaWall,
MaterialMesh2dBundle {
mesh: meshes
.add(
shape::Quad::new(Vec2::new(arena_width + WALL_THICKNESS,
.into(),
)
.into(),
material: material_handle.clone(),
transform: Transform::from_translation(Vec3::new(0.0, top_wall,
..default()
},
RigidBody::Fixed,
WALL_THICKNESS))
0.)),
Collider::cuboid((arena_width + WALL_THICKNESS) / 2.0,
WALL_THICKNESS / 2.0),
));
// bottom wall
commands.spawn((
AreaWall,
MaterialMesh2dBundle {
mesh: meshes
.add(
shape::Quad::new(Vec2::new(arena_width + WALL_THICKNESS,
.into(),
)
.into(),
material: material_handle.clone(),
transform: Transform::from_translation(Vec3::new(0.0,
bottom_wall, 0.)),
..default()
},
RigidBody::Fixed,
Collider::cuboid((arena_width + WALL_THICKNESS) / 2.0,
WALL_THICKNESS / 2.0),
));
}
WALL_THICKNESS))
src/bullet.rs
use bevy::prelude::*;
use bevy_rapier2d::prelude::*;
use crate::area::*;
use crate::common::{self, Direction, *};
use crate::enemy::Enemy;
use crate::level::LevelItem;
use crate::player::{PlayerLives, PlayerNo, Shield};
pub const BULLET_SPEED: f32 = 300.0;
#[derive(Component, PartialEq, Eq)]
pub enum Bullet {
Player,
Enemy, }
#[derive(Debug, Component)]
pub struct Explosion;
#[derive(Debug)]
pub struct ExplosionEvent {
pos: Vec3,
explosion_type: ExplosionType,
}
#[derive(Debug, PartialEq, Eq)]
pub enum ExplosionType {
BigExplosion,
BulletExplosion,
}
#[derive(Debug, Resource)]
pub struct ExplosionAssets {
pub big_explosion: Vec<Handle<Image>>,
pub bullet_explosion: Vec<Handle<Image>>,
}
pub fn setup_explosion_assets(mut commands: Commands, asset_server:
Res<AssetServer>) {
let mut big_explosion: Vec<Handle<Image>> = Vec::new();
big_explosion.push(asset_server.load("textures/big_explosion_1.png"));
big_explosion.push(asset_server.load("textures/big_explosion_2.png"));
big_explosion.push(asset_server.load("textures/big_explosion_3.png"));
big_explosion.push(asset_server.load("textures/big_explosion_4.png"));
big_explosion.push(asset_server.load("textures/big_explosion_5.png"));
let mut bullet_explosion: Vec<Handle<Image>> = Vec::new();
bullet_explosion.push(asset_server.load("textures/
bullet_explosion_1.png"));
bullet_explosion.push(asset_server.load("textures/
bullet_explosion_2.png"));
bullet_explosion.push(asset_server.load("textures/
bullet_explosion_3.png"));
commands.insert_resource(ExplosionAssets {
big_explosion,
bullet_explosion,
});
}
// p®_9yûR ̈
pub fn move_bullet(
mut q_bullet: Query<(&mut Transform, &common::Direction), With<Bullet>>,
time: Res<Time>, ){
for (mut bullet_transform, direction) in &mut q_bullet {
match direction {
common::Direction::Left => {
bullet_transform.translation.x -= BULLET_SPEED *
time.delta_seconds()
}
common::Direction::Right => {
bullet_transform.translation.x += BULLET_SPEED *
time.delta_seconds()
}
common::Direction::Up => {
bullet_transform.translation.y += BULLET_SPEED *
time.delta_seconds()
}
common::Direction::Down => {
bullet_transform.translation.y -= BULLET_SPEED *
time.delta_seconds()
}
} }
}
pub fn handle_bullet_collision(
mut commands: Commands,
q_bullets: Query<(Entity, &Bullet, &Transform)>,
q_level_items: Query<(&LevelItem, &GlobalTransform, &mut
TextureAtlasSprite)>,
q_area_wall: Query<(), With<AreaWall>>,
q_players: Query<(&Transform, &Children), With<PlayerNo>>,
q_shields: Query<Entity, With<Shield>>,
q_enemies: Query<&Transform, With<Enemy>>,
mut collision_er: EventReader<CollisionEvent>,
mut explosion_ew: EventWriter<ExplosionEvent>,
mut home_dying_ew: EventWriter<HomeDyingEvent>,
player_lives: Res<PlayerLives>,
multiplayer_mode: Res<MultiplayerMode>,
mut app_state: ResMut<NextState<AppState>>,
){
for event in collision_er.iter() {
match event {
CollisionEvent::Started(entity1, entity2, _flags)
| CollisionEvent::Stopped(entity1, entity2, _flags) => {
let bullet_entity = if q_bullets.contains(*entity1) {
*entity1
} else if q_bullets.contains(*entity2) {
*entity2
} else {
continue;
};
let other_entity = if bullet_entity == *entity1 {
*entity2
} else {
*entity1 };
println!(
"bullet: {:?}, collision entity1: {:?}, entity2: {:?}",
bullet_entity, entity1, entity2
);
let bullet =
q_bullets.get_component::<Bullet>(bullet_entity).unwrap();
let bullet_transform =
q_bullets.get_component::<Transform>(bullet_entity).unwrap();
ExplosionType::BulletExplosion,
});
info!("bullet hit something");
// SæN N*riOS
if q_level_items.contains(other_entity) {
info!("Bullet hit level item");
let level_item = q_level_items
.get_component::<LevelItem>(other_entity)
.unwrap();
let level_item_transform = q_level_items
.get_component::<GlobalTransform>(other_entity)
.unwrap();
dbg!(level_item);
// dbg!(bullet_transform);
// dbg!(level_item_transform);
match level_item {
LevelItem::Home => {
// Game Over
println!("Game over");
commands.entity(bullet_entity).despawn();
explosion_ew.send(ExplosionEvent {
pos: Vec3::new(
level_item_transform.translation().x,
level_item_transform.translation().y,
level_item_transform.translation().z,
),
explosion_type: ExplosionType::BigExplosion,
});
home_dying_ew.send_default();
}
LevelItem::StoneWall => {
commands.entity(bullet_entity).despawn();
commands.entity(other_entity).despawn();
explosion_ew.send(ExplosionEvent {
pos: Vec3::new(
bullet_transform.translation.x,
bullet_transform.translation.y,
bullet_transform.translation.z,
),
explosion_type:
}
LevelItem::IronWall => {
commands.entity(bullet_entity).despawn();
ExplosionType::BulletExplosion,
});
}
_ => {} }
}
explosion_ew.send(ExplosionEvent {
pos: Vec3::new(
bullet_transform.translation.x,
bullet_transform.translation.y,
bullet_transform.translation.z,
),
explosion_type:
if q_area_wall.contains(other_entity) {
info!("Bullet hit area wall");
commands.entity(bullet_entity).despawn();
explosion_ew.send(ExplosionEvent {
pos: Vec3::new(
bullet_transform.translation.x,
bullet_transform.translation.y,
bullet_transform.translation.z,
),
explosion_type: ExplosionType::BulletExplosion,
});
}
if *bullet == Bullet::Player &&
q_enemies.contains(other_entity) {
info!("Player bullet hit enemy");
let enemy_transform =
q_enemies.get_component::<Transform>(other_entity).unwrap();
commands.entity(bullet_entity).despawn();
commands.entity(other_entity).despawn();
explosion_ew.send(ExplosionEvent {
pos: Vec3::new(
enemy_transform.translation.x,
enemy_transform.translation.y,
enemy_transform.translation.z,
),
explosion_type: ExplosionType::BigExplosion,
});
}
if *bullet == Bullet::Enemy &&
q_players.contains(other_entity) {
info!("Enemy bullet hit player");
let player_children =
q_players.get_component::<Children>(other_entity).unwrap();
let mut player_has_shield = false;
for child in player_children.iter() {
if q_shields.contains(*child) {
player_has_shield = true;
break; }
}
let player_transform =
q_players.get_component::<Transform>(other_entity).unwrap();
commands.entity(bullet_entity).despawn();
<= 0 {
if player_has_shield {
info!("Player has shield");
explosion_ew.send(ExplosionEvent {
pos: Vec3::new(
player_transform.translation.x,
player_transform.translation.y,
player_transform.translation.z,
),
explosion_type: ExplosionType::BulletExplosion,
});
} else {
commands.entity(other_entity).despawn_recursive();
explosion_ew.send(ExplosionEvent {
pos: Vec3::new(
player_transform.translation.x,
player_transform.translation.y,
player_transform.translation.z,
),
explosion_type: ExplosionType::BigExplosion,
});
if player_lives.player1 <= 0 && player_lives.player2
app_state.set(AppState::GameOver);
}
if player_lives.player1 <= 0
&& *multiplayer_mode ==
MultiplayerMode::SinglePlayer
{
} }
} }
} }
app_state.set(AppState::GameOver);
}
pub fn spawn_bullet(
commands: &mut Commands,
game_texture_atlas: &Res<GameTextureAtlasHandles>,
bullet: Bullet,
translation: Vec3,
direction: Direction,
){ commands
.spawn(bullet)
.insert(SpriteSheetBundle {
texture_atlas: game_texture_atlas.bullet.clone(),
sprite: TextureAtlasSprite {
index: match direction {
common::Direction::Up => 0,
common::Direction::Right => 1,
common::Direction::Down => 2,
common::Direction::Left => 3,
},
..default()
},
transform: Transform {
translation: Vec3::new(translation.x, translation.y,
translation.z),
..default()
},
..default()
})
.insert((
Collider::cuboid(2.0, 2.0),
Sensor,
RigidBody::Dynamic,
}
ActiveEvents::COLLISION_EVENTS,
))
.insert(direction);
pub fn spawn_explosion(
mut commands: Commands,
mut explosion_er: EventReader<ExplosionEvent>,
explosion_assets: Res<ExplosionAssets>,
asset_server: Res<AssetServer>,
mut textures: ResMut<Assets<Image>>,
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
audio: Res<Audio>,
game_sounds: Res<GameSounds>,
){
let mut big_explosion_texture_atlas_builder =
TextureAtlasBuilder::default();
for handle in &explosion_assets.big_explosion {
let Some(texture) = textures.get(&handle) else {
warn!("{:?} did not resolve to an `Image` asset.",
asset_server.get_handle_path(handle));
continue;
};
big_explosion_texture_atlas_builder.add_texture(handle.clone(),
texture);
}
let big_explosion_texture_atlas = big_explosion_texture_atlas_builder
.finish(&mut textures)
.unwrap();
let big_explosion_texture_atlas_handle =
texture_atlases.add(big_explosion_texture_atlas);
let mut bullet_explosion_texture_atlas_builder =
TextureAtlasBuilder::default();
for handle in &explosion_assets.bullet_explosion {
let Some(texture) = textures.get(&handle) else {
warn!("{:?} did not resolve to an `Image` asset.",
asset_server.get_handle_path(handle));
continue; };
bullet_explosion_texture_atlas_builder.add_texture(handle.clone(),
texture);
}
let bullet_explosion_texture_atlas =
bullet_explosion_texture_atlas_builder
.finish(&mut textures)
.unwrap();
let bullet_explosion_texture_atlas_handle =
texture_atlases.add(bullet_explosion_texture_atlas);
for explosion in explosion_er.iter() {
commands.spawn((
Explosion,
SpriteSheetBundle {
sprite: TextureAtlasSprite::new(0),
texture_atlas: if explosion.explosion_type ==
ExplosionType::BigExplosion {
big_explosion_texture_atlas_handle.clone()
} else {
bullet_explosion_texture_atlas_handle.clone()
},
transform: Transform::from_translation(explosion.pos),
..default()
},
AnimationTimer(Timer::from_seconds(0.05, TimerMode::Repeating)),
AnimationIndices {
first: 0,
last: if explosion.explosion_type ==
ExplosionType::BigExplosion {
}, ));
4
} else {
2 },
if explosion.explosion_type == ExplosionType::BigExplosion {
audio.play(game_sounds.big_explosion.clone());
} else if explosion.explosion_type == ExplosionType::BulletExplosion {
audio.play(game_sounds.bullet_explosion.clone());
} }
}
pub fn animate_explosion(
mut commands: Commands,
mut q_explosion: Query<
(
Entity,
&mut AnimationTimer,
} }
}
&AnimationIndices,
&mut TextureAtlasSprite,
),
With<Explosion>,
>,
time: Res<Time>, ){
for (entity, mut timer, indices, mut sprite) in &mut q_explosion {
timer.0.tick(time.delta());
if timer.0.just_finished() {
sprite.index += 1;
if sprite.index > indices.last {
commands.entity(entity).despawn();
}
pub fn cleanup_bullets(mut commands: Commands, q_bullets: Query<Entity,
With<Bullet>>) {
for entity in &q_bullets {
commands.entity(entity).despawn_recursive();
} }
pub fn cleanup_explosions(mut commands: Commands, q_explosions: Query<Entity,
With<Explosion>>) {
for entity in &q_explosions {
commands.entity(entity).despawn_recursive();
} }
src/common.rs
use bevy::prelude::*;
// QsSaW0VþˆLepTŒR ep
pub const LEVEL_ROWS: i32 = 18;
pub const LEVEL_COLUMNS: i32 = 27;
pub const TILE_SIZE: f32 = 32.0;
// QsSaep‘Ï
pub const MAX_LEVELS: i32 = 2;
// T eöQq[Xv„eLNog Y'ep‘Ï
pub const MAX_LIVE_ENEMIES: i32 = 5;
// kÏQseLNoep‘Ï
pub const ENEMIES_PER_LEVEL: i32 = 12;
// WfQKR7e°[P_9•ô–”ÿ yÒÿ
pub const PLAYER_REFRESH_BULLET_INTERVAL: f32 = 0.5;
pub const ENEMY_REFRESH_BULLET_INTERVAL: f32 = 2.0;
// WfQK• ^¦0 Y'\ TŒ•)e>kÔO‹
pub const PLAYER_SPEED: f32 = 150.0;
pub const ENEMY_SPEED: f32 = 100.0;
pub const TANK_SIZE: f32 = 28.0;
pub const TANK_SCALE: f32 = 0.8;
// sprite z•t ̃z^•
pub const SPRITE_GAME_OVER_ORDER: f32 = 4.0;
pub const SPRITE_TREE_ORDER: f32 = 3.0;
pub const SPRITE_PLAYER_ORDER: f32 = 2.0;
#[derive(Debug, Clone, Eq, PartialEq, Hash, States, Default)]
pub enum AppState {
#[default]
StartMenu,
Playing,
Paused,
GameOver,
}
#[derive(Resource, Debug, PartialEq, Eq)]
pub enum MultiplayerMode {
SinglePlayer,
TwoPlayers,
}
// e1T
#[derive(Component, Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
Left,
Right,
Up,
Down,
}
#[derive(Component, Clone, Default, Debug)]
pub struct AnimationTimer(pub Timer);
#[derive(Component, Clone, Default, Debug)]
pub struct AnimationIndices {
pub first: usize,
pub last: usize,
}
// WfQKR7e°[P_9‹¡eöVh
#[derive(Component, Deref, DerefMut)]
pub struct TankRefreshBulletTimer(pub Timer);
#[derive(Default)]
pub struct HomeDyingEvent;
#[derive(Debug, Resource)]
pub struct GameSounds {
pub start_menu: Handle<AudioSource>,
pub mode_switch: Handle<AudioSource>,
pub bullet_explosion: Handle<AudioSource>,
pub big_explosion: Handle<AudioSource>,
pub player_fire: Handle<AudioSource>,
pub game_over: Handle<AudioSource>,
pub game_pause: Handle<AudioSource>,
}
#[derive(Debug, Resource)]
pub struct GameTextureAtlasHandles {
pub bullet: Handle<TextureAtlas>,
pub shield: Handle<TextureAtlas>,
pub born: Handle<TextureAtlas>,
pub player1: Handle<TextureAtlas>,
pub player2: Handle<TextureAtlas>,
pub enemies: Handle<TextureAtlas>,
}
pub fn setup_game_sounds(mut commands: Commands, asset_server:
Res<AssetServer>) {
commands.insert_resource(GameSounds {
start_menu: asset_server.load("sounds/start_menu.ogg"),
mode_switch: asset_server.load("sounds/mode_switch.ogg"),
bullet_explosion: asset_server.load("sounds/bullet_explosion.ogg"),
big_explosion: asset_server.load("sounds/big_explosion.ogg"),
player_fire: asset_server.load("sounds/player_fire.ogg"),
game_over: asset_server.load("sounds/game_over.ogg"),
game_pause: asset_server.load("sounds/game_pause.ogg"),
}); }
pub fn setup_game_texture_atlas(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
){
// p®_9
let bullet_texture_handle = asset_server.load("textures/bullet.bmp");
let bullet_texture_atlas =
TextureAtlas::from_grid(bullet_texture_handle, Vec2::new(7.0, 8.0),
4, 1, None, None);
let bullet_texture_atlas_handle =
texture_atlases.add(bullet_texture_atlas);
// OÝb¤vþ
let shield_texture_handle = asset_server.load("textures/shield.bmp");
let shield_texture_atlas = TextureAtlas::from_grid(
shield_texture_handle,
Vec2::new(31.0, 31.0),
1,
2,
None,
None, );
let shield_texture_atlas_handle =
texture_atlases.add(shield_texture_atlas);
// s©[¶1
let player1_texture_handle = asset_server.load("textures/tank1.bmp");
let player1_texture_atlas = TextureAtlas::from_grid(
player1_texture_handle,
Vec2::new(TANK_SIZE, TANK_SIZE),
2,
4,
None,
None, );
let player1_texture_atlas_handle =
texture_atlases.add(player1_texture_atlas);
// s©[¶2
let player2_texture_handle = asset_server.load("textures/tank2.bmp");
let player2_texture_atlas = TextureAtlas::from_grid(
player2_texture_handle,
Vec2::new(TANK_SIZE, TANK_SIZE),
2,
4,
None,
None,
);
let player2_texture_atlas_handle =
texture_atlases.add(player2_texture_atlas);
// Qúu eHgœ
let born_texture_handle = asset_server.load("textures/born.bmp");
let born_texture_atlas =
TextureAtlas::from_grid(born_texture_handle, Vec2::new(32.0, 32.0),
4, 1, None, None);
let born_texture_atlas_handle = texture_atlases.add(born_texture_atlas);
let enemies_texture_handle = asset_server.load("textures/enemies.bmp");
let enemies_texture_atlas = TextureAtlas::from_grid(
enemies_texture_handle,
Vec2::new(TANK_SIZE, TANK_SIZE),
8,
8,
None,
None,
);
let enemies_texture_atlas_handle =
texture_atlases.add(enemies_texture_atlas);
commands.insert_resource(GameTextureAtlasHandles {
bullet: bullet_texture_atlas_handle,
shield: shield_texture_atlas_handle,
}); }
born: born_texture_atlas_handle,
player1: player1_texture_atlas_handle,
player2: player2_texture_atlas_handle,
enemies: enemies_texture_atlas_handle,
src/enemy.rs
use bevy::prelude::*;
use bevy_rapier2d::prelude::*;
use rand::Rng;
use crate::{
bullet::{spawn_bullet, Bullet},
common::{
self, AnimationIndices, AnimationTimer, GameTextureAtlasHandles,
TankRefreshBulletTimer,
ENEMIES_PER_LEVEL, ENEMY_REFRESH_BULLET_INTERVAL, ENEMY_SPEED,
MAX_LIVE_ENEMIES,
TANK_SCALE, TANK_SIZE, TILE_SIZE,
},
level::{EnemiesMarker, LevelItem},
player::PlayerNo,
};
// _SRMQsSau b v„eLNoep‘Ï
#[derive(Resource)]
pub struct LevelSpawnedEnemies(pub i32);
#[derive(Component)]
pub struct Enemy;
// •lT ‹¡eöVh
#[derive(Component)]
pub struct EnemyChangeDirectionTimer(pub Timer);
pub fn auto_spawn_enemies(
mut commands: Commands,
mut level_spawned_enemies: ResMut<LevelSpawnedEnemies>,
q_enemies: Query<&Transform, With<Enemy>>,
q_enemies_marker: Query<&GlobalTransform, With<EnemiesMarker>>,
q_players: Query<&Transform, With<PlayerNo>>,
game_texture_atlas: Res<GameTextureAtlasHandles>,
){
if q_enemies.into_iter().len() >= MAX_LIVE_ENEMIES as usize {
// b W:N [Xm;eLNo]ò•3⁄4R0g Y'P<
return; }
if level_spawned_enemies.0 == ENEMIES_PER_LEVEL {
// g,QsSa]òu b eLNoep‘Ï•3⁄4g Y'P<
} }
return; }
let mut marker_positions = Vec::new();
for enemy_marker in &q_enemies_marker {
// –2kbplayer1_marker•Øg*R YËS
if enemy_marker.translation() == Vec3::ZERO {
continue; }
marker_positions.push(enemy_marker.clone());
}
if marker_positions.len() > 0 {
// –•g:W0p1
let mut rng = rand::thread_rng();
let choosed_pos = marker_positions
.get(rng.gen_range(0..marker_positions.len()))
.unwrap()
.translation();
// N €ý•Ýy»b W:WfQK•Ç•Ñ
for enemy_pos in &q_enemies {
if choosed_pos.distance(enemy_pos.translation) < 2. * TILE_SIZE {
return;
} }
for player_pos in &q_players {
if choosed_pos.distance(player_pos.translation) < 2. * TILE_SIZE {
return; }
}
spawn_enemy(choosed_pos, &mut commands, &game_texture_atlas);
level_spawned_enemies.0 += 1;
pub fn spawn_enemy(
pos: Vec3,
commands: &mut Commands,
game_texture_atlas: &Res<GameTextureAtlasHandles>, ){
// –•g: ̃œ‚r
let indexes: Vec<i32> = enemies_sprite_index_sets()
.iter()
.map(|v| *v.get(0).unwrap())
.collect();
let mut rng = rand::thread_rng();
let choosed_index = indexes
.get(rng.gen_range(0..indexes.len()))
.unwrap()
.clone();
commands.spawn((
Enemy,
SpriteSheetBundle {
sprite: TextureAtlasSprite {
index: choosed_index as usize,
..default()
},
texture_atlas: game_texture_atlas.enemies.clone(),
transform: Transform {
translation: pos,
scale: Vec3::splat(TANK_SCALE),
..default()
},
..default()
},
TankRefreshBulletTimer(Timer::from_seconds(
ENEMY_REFRESH_BULLET_INTERVAL,
TimerMode::Repeating,
)),
EnemyChangeDirectionTimer(Timer::from_seconds(1.0, TimerMode::Once)),
AnimationTimer(Timer::from_seconds(0.2, TimerMode::Repeating)),
AnimationIndices {
first: choosed_index as usize,
last: choosed_index as usize + 1,
},
common::Direction::Up,
RigidBody::Dynamic,
Collider::cuboid(TANK_SIZE * TANK_SCALE / 2.0, TANK_SIZE *
TANK_SCALE / 2.0),
ActiveEvents::COLLISION_EVENTS,
LockedAxes::ROTATION_LOCKED,
));
}
// TODO SÑs°s©[¶T N;R ̈e;Qû
// TODO h g—SïŽ2...Ï
pub fn enemies_move(
mut q_enemies: Query<
(
&mut Transform,
&mut common::Direction,
&mut TextureAtlasSprite,
&mut AnimationIndices,
&mut EnemyChangeDirectionTimer,
),
With<Enemy>,
>,
q_level_items: Query<(&LevelItem, &GlobalTransform)>,
time: Res<Time>, ){
for (mut transform, mut direction, mut sprite, mut indices, mut timer) in
&mut q_enemies {
timer.0.tick(time.delta());
if !timer.0.finished() {
match *direction {
common::Direction::Up => {
transform.translation.y += ENEMY_SPEED *
time.delta_seconds();
}
common::Direction::Right => {
transform.translation.x += ENEMY_SPEED *
time.delta_seconds();
}
common::Direction::Down => {
transform.translation.y -= ENEMY_SPEED *
time.delta_seconds();
}
common::Direction::Left => {
transform.translation.x -= ENEMY_SPEED *
time.delta_seconds();
} }
continue; }
// ‘Íe°• bée1T
let mut can_left = true;
let mut can_right = true;
let mut can_up = true;
let mut can_down = true;
// _SRMSï•p•ï_„
for (level_item, level_item_transform) in &q_level_items {
if *level_item == LevelItem::Tree {
continue;
}
if (level_item_transform.translation().x -
transform.translation.x).abs()
< (TANK_SIZE + TILE_SIZE) / 2.0 - 5.0
{
if level_item_transform.translation().y >
transform.translation.y
&& level_item_transform.translation().y -
transform.translation.y < TILE_SIZE
{
can_up = false;
}
if level_item_transform.translation().y <
transform.translation.y
&& transform.translation.y -
level_item_transform.translation().y < TILE_SIZE
{
can_down = false;
} }
if (level_item_transform.translation().y -
transform.translation.y).abs()
< (TANK_SIZE + TILE_SIZE) / 2. - 5.0
{
if level_item_transform.translation().x >
transform.translation.x
&& level_item_transform.translation().x -
transform.translation.x < TILE_SIZE
{
can_right = false;
}
if level_item_transform.translation().x <
transform.translation.x
&& transform.translation.x -
level_item_transform.translation().x < TILE_SIZE
{
can_left = false;
} }
}
usize;
// dbg!(can_left);
// dbg!(can_right);
// dbg!(can_up);
// dbg!(can_down);
if !can_left && !can_right && !can_up && !can_down {
continue; }
// h9cngC‘Í–•g:N N*e1T
let mut rng = rand::thread_rng();
let choosed_direction = loop {
let rand = rng.gen_range(0..9);
match rand {
0 => {
if can_up {
break common::Direction::Up;
}
}
1 | 2 => {
if can_left {
break common::Direction::Left;
} }
3 | 4 => {
if can_right {
break common::Direction::Right;
}
}
5 | 6 | 7 | 8 => {
if can_down {
break common::Direction::Down;
} }
_ => {} }
};
dbg!(choosed_direction);
// ‹3⁄4•ne1T TŒsprite
*direction = choosed_direction;
sprite.index = new_sprite_index(sprite.index as i32, *direction) as
dbg!(sprite.index);
*indices = AnimationIndices {
first: sprite.index,
last: sprite.index + 1,
};
// ‘Í•n•lT ‹¡eöVh
timer.0.reset();
}
}
pub fn enemies_attack(
mut q_players: Query<
(&Transform, &common::Direction, &mut TankRefreshBulletTimer),
With<Enemy>,
>,
time: Res<Time>,
mut commands: Commands,
game_texture_atlas: Res<GameTextureAtlasHandles>,
){
for (transform, direction, mut refresh_bullet_timer) in &mut q_players {
refresh_bullet_timer.tick(time.delta());
if refresh_bullet_timer.just_finished() {
spawn_bullet(
&mut commands,
&game_texture_atlas,
Bullet::Enemy,
transform.translation,
direction.clone(),
); }
pub fn handle_enemy_collision(
mut q_enemies: Query<&mut EnemyChangeDirectionTimer, With<Enemy>>,
mut collision_er: EventReader<CollisionEvent>,
){
for event in collision_er.iter() {
match event {
CollisionEvent::Started(entity1, entity2, _flags)
| CollisionEvent::Stopped(entity1, entity2, _flags) => {
let enemy_entity = if q_enemies.contains(*entity1) {
*entity1
} else if q_enemies.contains(*entity2) {
*entity2
} }
ty)
} }
} else {
continue;
};
// ‘Í•n•lT ‹¡eöVh
let mut change_direction_timer = q_enemies
.get_component_mut::<EnemyChangeDirectionTimer>(enemy_enti
.unwrap();
change_direction_timer.0.reset();
} }
}
} }
// WfQKyûR ̈R ̈u;d-e>
pub fn animate_enemies(
time: Res<Time>,
mut query: Query<
(
&mut AnimationTimer,
&AnimationIndices,
&mut TextureAtlasSprite,
),
With<Enemy>,
>,
){
for (mut timer, indices, mut sprite) in &mut query {
timer.0.tick(time.delta());
if timer.0.just_finished() {
// R cbR0N N N*sprite
sprite.index = if sprite.index == indices.last {
indices.first
} else {
sprite.index + 1
};
pub fn cleanup_enemies(mut commands: Commands, q_enemies: Query<Entity,
With<Enemy>>) {
for entity in &q_enemies {
commands.entity(entity).despawn_recursive();
} }
pub fn reset_level_spawned_enemies(mut level_spawned_enemies:
ResMut<LevelSpawnedEnemies>) {
level_spawned_enemies.0 = 0;
}
pub fn enemies_sprite_index_sets() -> Vec<Vec<i32>> {
vec![
// N SóN ]æ + QvNÖSï€ýindex
vec![0, 8, 16, 24, 1, 9, 17, 25],
vec![2, 10, 18, 26, 3, 11, 19, 27],
vec![4, 12, 20, 28, 5, 13, 21, 29],
vec![6, 14, 22, 30, 7, 15, 23, 31],
vec![32, 40, 48, 56, 33, 41, 49, 57],
vec![34, 42, 50, 58, 35, 43, 51, 59],
vec![36, 44, 52, 60, 37, 45, 53, 61],
vec![38, 46, 54, 62, 39, 47, 55, 63],
] }
pub fn new_sprite_index(current_index: i32, direction: common::Direction) ->
i32 {
let index_sets = enemies_sprite_index_sets();
for index_set in index_sets {
if index_set.contains(&current_index) {
info!("found index_set");
match direction {
} }
}
return 0;
common::Direction::Up => {
return *index_set.get(0).unwrap();
}
common::Direction::Right => {
return *index_set.get(1).unwrap();
}
common::Direction::Down => {
return *index_set.get(2).unwrap();
}
common::Direction::Left => {
return *index_set.get(3).unwrap();
}
}
src/level.rs
use crate::{
common::{
AnimationIndices, AnimationTimer, AppState, HomeDyingEvent,
ENEMIES_PER_LEVEL,
LEVEL_COLUMNS, LEVEL_ROWS, MAX_LEVELS, SPRITE_TREE_ORDER, TILE_SIZE,
},
enemy::{Enemy, LevelSpawnedEnemies},
player::PlayerNo,
};
use bevy::prelude::*;
use bevy_ecs_ldtk::prelude::*;
use bevy_rapier2d::prelude::*;
pub const LEVEL_TRANSLATION_OFFSET: Vec3 = Vec3::new(
-LEVEL_COLUMNS as f32 / 2.0 * TILE_SIZE,
-LEVEL_ROWS as f32 / 2. * TILE_SIZE,
0.0,
);
// QsSaW0VþQC}
#[derive(Component, Clone, PartialEq, Eq, Debug, Default)]
pub enum LevelItem {
#[default]
None,
// wóXTM
StoneWall,
// •4XTM
IronWall,
// h g(
Tree,
// l4
Water,
// [¶
Home,
}
// QsSaplayer1OM•nh ‹°
#[derive(Component, Default)]
pub struct Player1Marker;
// QsSaplayer2OM•nh ‹°
#[derive(Component, Default)]
pub struct Player2Marker;
// QsSaeLNoOM•nh ‹°
#[derive(Component, Default)]
pub struct EnemiesMarker;
#[derive(Clone, Debug, Default, Bundle)]
pub struct ColliderBundle {
pub collider: Collider,
pub rigid_body: RigidBody,
}
#[derive(Clone, Debug, Default, Bundle)]
pub struct AnimationBundle {
pub timer: AnimationTimer,
pub indices: AnimationIndices,
}
#[derive(Bundle, LdtkEntity)]
pub struct StoneWallBundle {
#[from_entity_instance]
level_item: LevelItem,
#[from_entity_instance]
#[bundle]
pub collider_bundle: ColliderBundle,
// #[sprite_sheet_bundle("path/to/asset.png", tile_width, tile_height,
columns, rows, padding, offset, index)]
#[sprite_sheet_bundle("textures/map.bmp", 32.0, 32.0, 7, 1, 0.0, 0.0, 0)]
#[bundle]
sprite_bundle: SpriteSheetBundle,
}
#[derive(Bundle, LdtkEntity)]
pub struct IronWallBundle {
#[from_entity_instance]
level_item: LevelItem,
#[from_entity_instance]
#[bundle]
pub collider_bundle: ColliderBundle,
#[sprite_sheet_bundle("textures/map.bmp", 32.0, 32.0, 7, 1, 0.0, 0.0, 1)]
#[bundle]
sprite_bundle: SpriteSheetBundle,
}
#[derive(Bundle, LdtkEntity)]
pub struct TreeBundle {
#[from_entity_instance]
level_item: LevelItem,
#[sprite_sheet_bundle("textures/map.bmp", 32.0, 32.0, 7, 1, 0.0, 0.0, 2)]
#[bundle]
sprite_bundle: SpriteSheetBundle,
}
#[derive(Bundle, LdtkEntity)]
pub struct WaterBundle {
#[from_entity_instance]
level_item: LevelItem,
#[from_entity_instance]
#[bundle]
pub collider_bundle: ColliderBundle,
#[sprite_sheet_bundle("textures/map.bmp", 32.0, 32.0, 7, 1, 0.0, 0.0, 3)]
#[bundle]
sprite_bundle: SpriteSheetBundle,
#[from_entity_instance]
#[bundle]
pub annimation_bundle: AnimationBundle,
}
#[derive(Bundle, LdtkEntity)]
pub struct HomeBundle {
#[from_entity_instance]
level_item: LevelItem,
#[from_entity_instance]
#[bundle]
pub collider_bundle: ColliderBundle,
#[sprite_sheet_bundle("textures/map.bmp", 32.0, 32.0, 7, 1, 0.0, 0.0, 5)]
#[bundle]
sprite_bundle: SpriteSheetBundle,
}
#[derive(Bundle, LdtkEntity)]
pub struct Player1MarkerBundle {
marker: Player1Marker,
}
#[derive(Bundle, LdtkEntity)]
pub struct Player2MarkerBundle {
marker: Player2Marker,
#[sprite_sheet_bundle]
#[bundle]
sprite_bundle: SpriteSheetBundle,
}
#[derive(Bundle, LdtkEntity)]
pub struct EnemiesMarkerBundle {
marker: EnemiesMarker,
#[sprite_sheet_bundle]
#[bundle]
sprite_bundle: SpriteSheetBundle,
}
impl From<&EntityInstance> for ColliderBundle {
fn from(entity_instance: &EntityInstance) -> ColliderBundle {
} }
} }
match entity_instance.identifier.as_ref() {
"StoneWall" | "IronWall" | "Water" | "Home" => ColliderBundle {
collider: Collider::cuboid(TILE_SIZE / 2., TILE_SIZE / 2.),
rigid_body: RigidBody::Fixed,
},
_ => ColliderBundle::default(),
}
impl From<&EntityInstance> for AnimationBundle {
fn from(entity_instance: &EntityInstance) -> AnimationBundle {
match entity_instance.identifier.as_ref() {
"Water" => AnimationBundle {
timer: AnimationTimer(Timer::from_seconds(0.2,
TimerMode::Repeating)),
indices: AnimationIndices { first: 3, last: 4 },
},
_ => AnimationBundle::default(),
}
impl From<&EntityInstance> for LevelItem {
fn from(entity_instance: &EntityInstance) -> LevelItem {
match entity_instance.identifier.as_ref() {
"StoneWall" => LevelItem::StoneWall,
"IronWall" => LevelItem::IronWall,
"Tree" => LevelItem::Tree,
} }
}
"Water" => LevelItem::Water,
"Home" => LevelItem::Home,
_ => LevelItem::None,
pub fn setup_levels(
mut commands: Commands,
asset_server: Res<AssetServer>,
q_ldtk_world: Query<(), With<Handle<LdtkAsset>>>, ){
if q_ldtk_world.iter().len() > 0 {
// NÎPausedr¶` •ÛQeeöeà— Q•load ldtk
return;
}
commands.spawn(LdtkWorldBundle {
ldtk_handle: asset_server.load("levels.ldtk"),
transform: Transform::from_translation(Vec3::ZERO +
LEVEL_TRANSLATION_OFFSET),
..Default::default()
});
}
pub fn spawn_ldtk_entity(
mut commands: Commands,
entity_query: Query<(Entity, &Transform, &EntityInstance),
Added<EntityInstance>>,
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
asset_server: Res<AssetServer>,
){
for (entity, transform, entity_instance) in entity_query.iter() {
if entity_instance.identifier == *"Tree" {
let map_texture_handle = asset_server.load("textures/map.bmp");
let map_texture_atlas = TextureAtlas::from_grid(
map_texture_handle,
Vec2::new(32.0, 32.0),
7,
1,
None,
None, );
let map_texture_atlas_handle =
texture_atlases.add(map_texture_atlas);
let mut translation = transform.translation +
LEVEL_TRANSLATION_OFFSET;
translation.z = SPRITE_TREE_ORDER;
commands.spawn((
LevelItem::Tree,
SpriteSheetBundle {
texture_atlas: map_texture_atlas_handle,
sprite: TextureAtlasSprite {
index: 2,
} }
} }
)); }
} }
..default()
},
transform: Transform::from_translation(translation),
..default()
},
// l4R ̈u;d-e>
pub fn animate_water(
time: Res<Time>,
mut query: Query<(
&LevelItem,
&mut AnimationTimer,
&AnimationIndices,
&mut TextureAtlasSprite,
)>, ){
for (level_item, mut timer, indices, mut sprite) in &mut query {
if *level_item == LevelItem::Water {
timer.0.tick(time.delta());
if timer.0.just_finished() {
// R cbR0N N N*sprite
sprite.index = if sprite.index == indices.last {
indices.first
} else {
sprite.index + 1
};
pub fn auto_switch_level(
mut commands: Commands,
q_enemies: Query<(), With<Enemy>>,
q_players: Query<Entity, With<PlayerNo>>,
q_level_items: Query<Entity, With<LevelItem>>,
mut level_selection: ResMut<LevelSelection>,
mut level_spawned_enemies: ResMut<LevelSpawnedEnemies>,
mut app_state: ResMut<NextState<AppState>>,
){
// ]òu b v„eLNoep‘Ï•3⁄4R0g Y'P< ^vN eLNoQh•è–5N¡ÿ R cbR0N N QsSa
if level_spawned_enemies.0 == ENEMIES_PER_LEVEL && q_enemies.iter().len()
== 0 {
if let LevelSelection::Index(index) = *level_selection {
if index as i32 == MAX_LEVELS - 1 {
} }
}
// TODO n8b ۆR)
info!("win the game!");
app_state.set(AppState::StartMenu);
} else {
// N N QsSa
info!("Switch to next level, index={}", index + 1);
*level_selection = LevelSelection::Index(index + 1);
level_spawned_enemies.0 = 0;
// ‘Íe°u b s©[¶
for player in &q_players {
commands.entity(player).despawn_recursive();
}
for level_item in &q_level_items {
commands.entity(level_item).despawn_recursive();
} }
pub fn animate_home(
mut home_dying_er: EventReader<HomeDyingEvent>,
mut q_level_items: Query<(&LevelItem, &mut TextureAtlasSprite)>,
mut app_state: ResMut<NextState<AppState>>,
){
for _ in home_dying_er.iter() {
for (level_item, mut sprite) in &mut q_level_items {
if *level_item == LevelItem::Home {
} }
}
sprite.index = 6;
app_state.set(AppState::GameOver);
}
pub fn cleanup_level_items(mut commands: Commands, q_level_items:
Query<Entity, With<LevelItem>>) {
for entity in &q_level_items {
commands.entity(entity).despawn_recursive();
}
}
pub fn cleanup_ldtk_world(
mut commands: Commands,
q_ldtk_world: Query<Entity, With<Handle<LdtkAsset>>>,
){
for entity in &q_ldtk_world {
commands.entity(entity).despawn_recursive();
}
}
pub fn reset_level_selection(mut level_selection: ResMut<LevelSelection>) {
*level_selection = LevelSelection::Index(0);
}
src/main.rs
mod area;
mod bullet;
mod common;
mod enemy;
mod level;
mod player;
mod ui;
use area::*;
use bullet::*;
use common::*;
use enemy::*;
use level::*;
use player::*;
use ui::*;
use bevy::prelude::*;
use bevy_ecs_ldtk::prelude::*;
use bevy_inspector_egui::quick::WorldInspectorPlugin;
use bevy_rapier2d::prelude::*;
const BACKGROUND_COLOR: Color = Color::BLACK;
// TODO WfQKx°dž[ü•ôˆ«•ëyûR ̈
fn main() {
App::new()
.register_type::<PlayerNo>()
.add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest()))
.add_plugin(RapierPhysicsPlugin::<NoUserData>::pixels_per_meter(100.0)
)
// .add_plugin(RapierDebugRenderPlugin::default())
.add_plugin(LdtkPlugin)
// .add_plugin(WorldInspectorPlugin)
.add_event::<ExplosionEvent>()
.add_event::<SpawnPlayerEvent>()
.add_event::<HomeDyingEvent>()
.add_state::<AppState>()
.insert_resource(ClearColor(BACKGROUND_COLOR))
.insert_resource(MultiplayerMode::SinglePlayer)
.insert_resource(LevelSelection::Index(0))
.insert_resource(LevelSpawnedEnemies(0))
.insert_resource(PlayerLives {
)
u)))
)))
)))
)))
enu)))
player2: 3,
})
.register_ldtk_entity::<level::StoneWallBundle>("StoneWall")
.register_ldtk_entity::<level::IronWallBundle>("IronWall")
.register_ldtk_entity::<level::WaterBundle>("Water")
.register_ldtk_entity::<level::HomeBundle>("Home")
.register_ldtk_entity::<level::Player1MarkerBundle>("Player1")
.register_ldtk_entity::<level::Player2MarkerBundle>("Player2")
.register_ldtk_entity::<level::EnemiesMarkerBundle>("Enemies")
.add_startup_system(setup_camera)
.add_startup_system(setup_rapier)
.add_startup_system(setup_wall)
.add_startup_system(setup_explosion_assets)
.add_startup_system(setup_game_sounds)
.add_startup_system(setup_game_texture_atlas)
.add_system(setup_start_menu.in_schedule(OnEnter(AppState::StartMenu))
.add_system(cleanup_level_items.in_schedule(OnEnter(AppState::StartMen
.add_system(cleanup_ldtk_world.in_schedule(OnEnter(AppState::StartMenu
.add_system(cleanup_players.in_schedule(OnEnter(AppState::StartMenu)))
.add_system(cleanup_born.in_schedule(OnEnter(AppState::StartMenu)))
.add_system(cleanup_bullets.in_schedule(OnEnter(AppState::StartMenu)))
.add_system(cleanup_explosions.in_schedule(OnEnter(AppState::StartMenu
.add_system(cleanup_enemies.in_schedule(OnEnter(AppState::StartMenu)))
.add_system(reset_player_lives.in_schedule(OnEnter(AppState::StartMenu
.add_system(reset_level_selection.in_schedule(OnEnter(AppState::StartM
player1: 3,
.add_system(reset_level_spawned_enemies.in_schedule(OnEnter(AppState::
StartMenu)))
Menu)))
u)))
.add_system(reset_multiplayer_mode.in_schedule(OnEnter(AppState::Start
.add_system(start_game.in_set(OnUpdate(AppState::StartMenu)))
.add_system(switch_multiplayer_mode.in_set(OnUpdate(AppState::StartMen
.add_system(despawn_screen::<OnStartMenuScreen>.in_schedule(OnExit(App
State::StartMenu)))
.add_system(setup_levels.in_schedule(OnEnter(AppState::Playing)))
.add_system(spawn_ldtk_entity.in_set(OnUpdate(AppState::Playing)))
.add_system(auto_spawn_players.in_set(OnUpdate(AppState::Playing)))
))
)
.add_system(players_move.in_set(OnUpdate(AppState::Playing)))
.add_system(players_attack.in_set(OnUpdate(AppState::Playing)))
.add_system(animate_players.in_set(OnUpdate(AppState::Playing)))
.add_system(animate_shield.in_set(OnUpdate(AppState::Playing)))
.add_system(animate_born.in_set(OnUpdate(AppState::Playing)))
.add_system(remove_shield.in_set(OnUpdate(AppState::Playing)))
.add_system(animate_water.in_set(OnUpdate(AppState::Playing)))
.add_system(animate_home.in_set(OnUpdate(AppState::Playing)))
.add_system(spawn_explosion.in_set(OnUpdate(AppState::Playing)))
.add_system(animate_explosion.in_set(OnUpdate(AppState::Playing)))
.add_system(handle_bullet_collision.in_set(OnUpdate(AppState::Playing)
.add_system(auto_switch_level.in_set(OnUpdate(AppState::Playing)))
.add_system(auto_spawn_enemies.in_set(OnUpdate(AppState::Playing)))
.add_system(animate_enemies.in_set(OnUpdate(AppState::Playing)))
.add_system(enemies_attack.in_set(OnUpdate(AppState::Playing)))
.add_system(enemies_move.in_set(OnUpdate(AppState::Playing)))
.add_system(handle_enemy_collision.in_set(OnUpdate(AppState::Playing))
.add_system(move_bullet.in_set(OnUpdate(AppState::Playing)))
.add_system(pause_game.in_set(OnUpdate(AppState::Playing)))
.add_system(unpause_game.in_set(OnUpdate(AppState::Paused)))
.add_system(setup_game_over.in_schedule(OnEnter(AppState::GameOver)))
.add_system(animate_game_over.in_set(OnUpdate(AppState::GameOver)))
.add_system(animate_players.in_set(OnUpdate(AppState::GameOver)))
.add_system(animate_shield.in_set(OnUpdate(AppState::GameOver)))
.add_system(animate_water.in_set(OnUpdate(AppState::GameOver)))
.add_system(animate_home.in_set(OnUpdate(AppState::GameOver)))
.add_system(animate_explosion.in_set(OnUpdate(AppState::GameOver)))
.add_system(animate_enemies.in_set(OnUpdate(AppState::GameOver)))
.add_system(despawn_screen::<OnGameOverScreen>.in_schedule(OnExit(AppS
tate::GameOver)))
.run();
}
fn setup_camera(mut commands: Commands) {
commands.spawn(Camera2dBundle::default());
}
fn setup_rapier(mut rapier_config: ResMut<RapierConfiguration>) {
rapier_config.gravity = Vec2::ZERO;
}
src/player.rs
use bevy::prelude::*;
use bevy_rapier2d::prelude::*;
use crate::bullet::*;
use crate::common::{self, *};
use crate::level::Player2Marker;
use crate::level::{Player1Marker, LEVEL_TRANSLATION_OFFSET};
// Qúu OÝb¤vþ
#[derive(Component)]
pub struct Shield;
// Qúu OÝb¤vþ‹¡eö
#[derive(Component)]
pub struct ShieldRemoveTimer(pub Timer);
// Qúu ryeH
#[derive(Component)]
pub struct Born;
#[derive(Component)]
pub struct BornRemoveTimer(pub Timer);
#[derive(Debug, Clone, Copy, Component, Reflect, Default)]
#[reflect(Component)]
pub struct PlayerNo(pub u32);
#[derive(Debug)]
pub struct SpawnPlayerEvent {
pos: Vec2,
player_no: PlayerNo,
}
#[derive(Debug, Resource)]
pub struct PlayerLives {
pub player1: i8,
pub player2: i8,
}
pub fn auto_spawn_players(
mut commands: Commands,
q_players: Query<&PlayerNo>,
q_player1_marker: Query<&Transform, With<Player1Marker>>,
q_player2_marker: Query<&Transform, With<Player2Marker>>,
mut spawn_player_er: EventReader<SpawnPlayerEvent>,
mut spawning_player1: Local<bool>,
mut spawning_player2: Local<bool>,
multiplayer_mode: Res<MultiplayerMode>,
mut player_lives: ResMut<PlayerLives>,
game_texture_atlas: Res<GameTextureAtlasHandles>,
){
let mut player1_exists = false; let mut player2_exists = false; for player in &q_players {
if player.0 == 1 {
player1_exists = true;
}
if player.0 == 2 {
player2_exists = true;
}
}
if !player1_exists {
for player1_marker in &q_player1_marker {
if !*spawning_player1 && player_lives.player1 > 0 {
} }
// Qúu R ̈u;
spawn_born(
player1_marker.translation + LEVEL_TRANSLATION_OFFSET,
PlayerNo(1),
&mut commands,
&game_texture_atlas,
);
*spawning_player1 = true;
}
if !player2_exists && *multiplayer_mode == MultiplayerMode::TwoPlayers {
for player2_marker in &q_player2_marker {
if !*spawning_player2 && player_lives.player2 > 0 {
// Qúu R ̈u;
spawn_born(
player2_marker.translation + LEVEL_TRANSLATION_OFFSET,
PlayerNo(2),
&mut commands,
&game_texture_atlas,
);
*spawning_player2 = true;
}
} }
// Qúu R ̈u;[ŒkÕT ÿ •ÛˆLplayerR ^ú
for spawn_player_event in spawn_player_er.iter() {
dbg!(spawn_player_event);
// OÝb¤vþ
let shield = commands
.spawn((
Shield,
SpriteSheetBundle {
texture_atlas: game_texture_atlas.shield.clone(),
transform: Transform::from_translation(Vec3::new(0.0,
0.0, -1.0)), // • •Çz•tc§R6sprite order
..default()
},
AnimationTimer(Timer::from_seconds(0.2,
TimerMode::Repeating)),
AnimationIndices { first: 0, last: 1 },
ShieldRemoveTimer(Timer::from_seconds(5.0, TimerMode::Once)),
))
.id();
// WfQK
let tank = commands
.spawn((
spawn_player_event.player_no,
SpriteSheetBundle {
texture_atlas: if spawn_player_event.player_no.0 == 1 {
game_texture_atlas.player1.clone()
} else {
game_texture_atlas.player2.clone()
},
transform: Transform {
translation:
spawn_player_event.pos.extend(SPRITE_PLAYER_ORDER),
scale: Vec3::splat(TANK_SCALE),
..default()
},
..default()
},
TankRefreshBulletTimer(Timer::from_seconds(
PLAYER_REFRESH_BULLET_INTERVAL,
TimerMode::Once,
)),
} }
common::Direction::Up,
AnimationTimer(Timer::from_seconds(0.2,
TimerMode::Repeating)),
AnimationIndices { first: 0, last: 1 },
RigidBody::Dynamic,
Velocity::zero(),
// W _bx°džOS–2kbVàROTATION_LOCKEDˆ«W0_bSaOO
Collider::ball(TANK_SIZE * TANK_SCALE / 2.0 + 2.0),
ActiveEvents::COLLISION_EVENTS,
LockedAxes::ROTATION_LOCKED,
)) .id();
commands.entity(tank).add_child(shield);
// u T}gaepQÏ\
if spawn_player_event.player_no.0 == 1 {
player_lives.player1 -= 1;
} else if spawn_player_event.player_no.0 == 2 {
player_lives.player2 -= 1;
}
// ‘Í•nr¶`
if spawn_player_event.player_no.0 == 1 {
*spawning_player1 = false;
} else if spawn_player_event.player_no.0 == 2 {
*spawning_player2 = false;
}
pub fn spawn_born(
pos: Vec3,
player_no: PlayerNo,
commands: &mut Commands,
game_texture_atlas: &Res<GameTextureAtlasHandles>,
){
// Qúu ryeH
println!("spawn born once");
commands.spawn((
Born,
player_no,
SpriteSheetBundle {
texture_atlas: game_texture_atlas.born.clone(),
)); }
transform: Transform::from_translation(pos),
..default()
},
AnimationTimer(Timer::from_seconds(0.2, TimerMode::Repeating)),
AnimationIndices { first: 0, last: 3 },
BornRemoveTimer(Timer::from_seconds(2.0, TimerMode::Once)),
// s©[¶yûR ̈WfQK
pub fn players_move(
keyboard_input: Res<Input<KeyCode>>,
mut query: Query<(
&PlayerNo,
&mut Velocity,
&mut common::Direction,
&mut TextureAtlasSprite,
&mut AnimationIndices,
)>, ){
for (player_no, mut velocity, mut direction, mut sprite, mut indices) in
&mut query {
if player_no.0 == 1
&& keyboard_input.any_just_released([KeyCode::W, KeyCode::S,
KeyCode::A, KeyCode::D])
{
velocity.linvel = Vec2::ZERO;
continue; }
if player_no.0 == 2
&& keyboard_input.any_just_released([
KeyCode::Up,
KeyCode::Down,
KeyCode::Left,
KeyCode::Right,
]) {
velocity.linvel = Vec2::ZERO;
continue; }
// N k!Sê€ýyûR ̈N N*e1T
if (player_no.0 == 1 && keyboard_input.pressed(KeyCode::W))
|| (player_no.0 == 2 && keyboard_input.pressed(KeyCode::Up))
{
} }
// WfQKyûR ̈R ̈u;d-e>
pub fn animate_players(
velocity.linvel = Vec2::new(0.0, PLAYER_SPEED);
*direction = common::Direction::Up;
} else if (player_no.0 == 1 && keyboard_input.pressed(KeyCode::S))
|| (player_no.0 == 2 && keyboard_input.pressed(KeyCode::Down))
{
velocity.linvel = Vec2::new(0.0, -PLAYER_SPEED);
*direction = common::Direction::Down;
} else if (player_no.0 == 1 && keyboard_input.pressed(KeyCode::A))
|| (player_no.0 == 2 && keyboard_input.pressed(KeyCode::Left))
{
velocity.linvel = Vec2::new(-PLAYER_SPEED, 0.0);
*direction = common::Direction::Left;
} else if (player_no.0 == 1 && keyboard_input.pressed(KeyCode::D))
|| (player_no.0 == 2 && keyboard_input.pressed(KeyCode::Right))
{
velocity.linvel = Vec2::new(PLAYER_SPEED, 0.0);
*direction = common::Direction::Right;
} else {
continue; }
match *direction {
common::Direction::Up => {
sprite.index = 0;
*indices = AnimationIndices { first: 0, last: 1 };
}
common::Direction::Right => {
sprite.index = 2;
*indices = AnimationIndices { first: 2, last: 3 };
}
common::Direction::Down => {
sprite.index = 4;
*indices = AnimationIndices { first: 4, last: 5 };
}
common::Direction::Left => {
sprite.index = 6;
*indices = AnimationIndices { first: 6, last: 7 };
} }
time: Res<Time>,
mut query: Query<
(
&mut AnimationTimer,
&AnimationIndices,
&mut TextureAtlasSprite,
),
With<PlayerNo>,
>,
){
for (mut timer, indices, mut sprite) in &mut query {
timer.0.tick(time.delta());
if timer.0.just_finished() {
} }
}
// R cbR0N N N*sprite
sprite.index = if sprite.index == indices.last {
indices.first
} else {
sprite.index + 1
};
// s©[¶e;Qû
pub fn players_attack(
keyboard_input: Res<Input<KeyCode>>,
mut q_players: Query<(
&PlayerNo,
&Transform,
&common::Direction,
&mut TankRefreshBulletTimer,
)>,
time: Res<Time>,
mut commands: Commands,
audio: Res<Audio>,
game_sounds: Res<GameSounds>,
game_texture_atlas: Res<GameTextureAtlasHandles>,
){
for (player_no, transform, direction, mut refresh_bullet_timer) in &mut
q_players {
refresh_bullet_timer.tick(time.delta());
if (player_no.0 == 1 && keyboard_input.just_pressed(KeyCode::Space))
|| (player_no.0 == 2 &&
keyboard_input.just_pressed(KeyCode::Return))
} }
{
if refresh_bullet_timer.finished() {
} }
spawn_bullet(
&mut commands,
&game_texture_atlas,
Bullet::Player,
transform.translation,
direction.clone(),
);
audio.play(game_sounds.player_fire.clone());
refresh_bullet_timer.reset();
// OÝb¤vþR ̈u;d-e>
pub fn animate_shield(
time: Res<Time>,
mut query: Query<
(
&mut AnimationTimer,
&AnimationIndices,
&mut TextureAtlasSprite,
),
With<Shield>,
>,
){
for (mut timer, indices, mut sprite) in &mut query {
timer.0.tick(time.delta());
if timer.0.just_finished() {
} }
}
// R cbR0N N N*sprite
sprite.index = if sprite.index == indices.last {
indices.first
} else {
sprite.index + 1
};
// yû–dOÝb¤vþ
pub fn remove_shield(
mut commands: Commands,
time: Res<Time>,
mut query: Query<(Entity, &mut ShieldRemoveTimer), With<Shield>>, ){
for (entity, mut timer) in query.iter_mut() {
timer.0.tick(time.delta());
if timer.0.finished() {
commands.entity(entity).despawn();
} }
}
// Qúu R ̈u;d-e>
pub fn animate_born(
mut commands: Commands,
time: Res<Time>,
mut query: Query<
(
Entity,
&PlayerNo,
&Transform,
&mut AnimationTimer,
&AnimationIndices,
&mut TextureAtlasSprite,
&mut BornRemoveTimer,
),
With<Born>,
>,
mut spawn_player_ew: EventWriter<SpawnPlayerEvent>, ){
for (entity, player_no, transform, mut timer, indices, mut sprite, mut
born_remove_timer) in
&mut query {
timer.0.tick(time.delta());
born_remove_timer.0.tick(time.delta());
if timer.0.just_finished() {
// R cbR0N N N*sprite
sprite.index = if sprite.index == indices.last {
indices.first
} else {
sprite.index + 1
};
}
} }
}
if born_remove_timer.0.finished() {
commands.entity(entity).despawn();
spawn_player_ew.send(SpawnPlayerEvent {
pos: transform.translation.truncate(),
player_no: player_no.clone(),
});
pub fn cleanup_players(mut commands: Commands, q_players: Query<Entity,
With<PlayerNo>>) {
for entity in &q_players {
commands.entity(entity).despawn_recursive();
} }
pub fn cleanup_born(mut commands: Commands, q_born: Query<Entity,
With<Born>>) {
for entity in &q_born {
commands.entity(entity).despawn_recursive();
} }
pub fn reset_player_lives(mut player_lives: ResMut<PlayerLives>) {
player_lives.player1 = 3;
player_lives.player2 = 3;
}
src/ui.rs
use std::time::Duration;
use bevy::prelude::*;
use crate::common::{AppState, GameSounds, MultiplayerMode,
SPRITE_GAME_OVER_ORDER};
#[derive(Component)]
pub struct OnStartMenuScreen;
#[derive(Component)]
pub struct OnStartMenuScreenMultiplayerModeFlag;
#[derive(Component)]
pub struct OnGameOverScreen;
pub fn setup_start_menu(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
audio: Res<Audio>,
game_sounds: Res<GameSounds>,
){ commands
.spawn((
NodeBundle {
style: Style {
size: Size::new(Val::Percent(100.), Val::Percent(100.)),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..default()
},
..default()
},
OnStartMenuScreen,
))
.with_children(|parent| {
parent.spawn(ImageBundle {
issues/1169
image: asset_server.load("textures/title.bmp").into(),
..default()
});
// TODO \ texture_atlas vôc¥u(NŽuiÿ issue https://github.com/bevyengine/bevy/
parent.spawn((
ImageBundle {
image: asset_server.load("textures/tank.png").into(),
style: Style {
size: Size::new(Val::Px(20.), Val::Px(20.)),
margin: UiRect::all(Val::Px(10.0)),
position_type: PositionType::Absolute,
position: UiRect {
top: Val::Px(412.),
left: Val::Px(520.),
..default()
},
..default()
},
..default()
},
OnStartMenuScreenMultiplayerModeFlag,
));
});
audio.play(game_sounds.start_menu.clone());
}
pub fn setup_game_over(
mut commands: Commands,
asset_server: Res<AssetServer>,
audio: Res<Audio>,
game_sounds: Res<GameSounds>,
){
let game_over_texture = asset_server.load("textures/game_over.bmp"); commands.spawn((
OnGameOverScreen,
SpriteBundle {
texture: game_over_texture,
transform: Transform::from_translation(Vec3::new(0., -400.,
SPRITE_GAME_OVER_ORDER)),
..default()
},
));
audio.play(game_sounds.game_over.clone());
}
pub fn animate_game_over(
mut q_game_over: Query<&mut Transform, With<OnGameOverScreen>>,
mut app_state: ResMut<NextState<AppState>>,
time: Res<Time>,
mut stop_secs: Local<f32>, ){
for mut transform in &mut q_game_over {
// N yûgame overVþrG
if transform.translation.y < 0. {
transform.translation.y += time.delta_seconds() * 150.;
*stop_secs = 0.0;
} else {
} }
}
// P\ ̃•1yÒT ÿ R cbR0Start Menu
*stop_secs += time.delta_seconds();
if *stop_secs > 1.0 {
app_state.set(AppState::StartMenu);
}
pub fn start_game(keyboard_input: Res<Input<KeyCode>>, mut app_state:
ResMut<NextState<AppState>>) {
if keyboard_input.any_just_pressed([KeyCode::Return, KeyCode::Space]) {
info!("Switch app state to playing");
app_state.set(AppState::Playing);
} }
pub fn switch_multiplayer_mode(
keyboard_input: Res<Input<KeyCode>>,
mut multiplayer_mode: ResMut<MultiplayerMode>,
mut q_multiplayer_mode_flag: Query<&mut Style,
With<OnStartMenuScreenMultiplayerModeFlag>>,
audio: Res<Audio>,
game_sounds: Res<GameSounds>,
){
if keyboard_input.any_just_pressed([KeyCode::Up, KeyCode::Down]) {
for mut style in &mut q_multiplayer_mode_flag {
if *multiplayer_mode == MultiplayerMode::SinglePlayer {
style.position.top = Val::Px(440.);
*multiplayer_mode = MultiplayerMode::TwoPlayers;
} else if *multiplayer_mode == MultiplayerMode::TwoPlayers {
style.position.top = Val::Px(412.);
*multiplayer_mode = MultiplayerMode::SinglePlayer;
}
audio.play(game_sounds.mode_switch.clone());
} }
}
pub fn pause_game(
mut app_state: ResMut<NextState<AppState>>,
keyboard_input: Res<Input<KeyCode>>,
audio: Res<Audio>,
game_sounds: Res<GameSounds>,
mut cold_start: Local<Duration>,
time: Res<Time>,
){
// XžR Q·T/R ̈–2kb pause_game TŒ unpause_game •ýO e6R0inputÿ [ü•ôPaued<->PlayingN e-_as *cold_start += time.delta();
if cold_start.as_millis() > 100 {
} }
if keyboard_input.just_released(KeyCode::Escape) {
info!("Pause game");
audio.play(game_sounds.game_pause.clone());
app_state.set(AppState::Paused);
*cold_start = Duration::ZERO;
}
pub fn unpause_game(
mut app_state: ResMut<NextState<AppState>>,
keyboard_input: Res<Input<KeyCode>>,
mut cold_start: Local<Duration>,
time: Res<Time>,
){
*cold_start += time.delta();
if cold_start.as_millis() > 100 {
if keyboard_input.just_released(KeyCode::Escape) {
info!("Unpause game");
app_state.set(AppState::Playing);
*cold_start = Duration::ZERO;
} }
}
pub fn despawn_screen<T: Component>(to_despawn: Query<Entity, With<T>>, mut
commands: Commands) {
for entity in &to_despawn {
commands.entity(entity).despawn_recursive();
} }
pub fn reset_multiplayer_mode(mut multiplayer_mode: ResMut<MultiplayerMode>) {
*multiplayer_mode = MultiplayerMode::SinglePlayer;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment