Last active
July 3, 2023 17:24
-
-
Save lumin3000/24e159847196a6dfce6db1624b264b4f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(¤t_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