Ported to bevy 0.15

master
Wynd 2025-01-02 22:15:03 +02:00
parent b72d550b4d
commit ea93d50e44
12 changed files with 1068 additions and 786 deletions

1291
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -2,11 +2,11 @@ cargo-features = ["codegen-backend"]
[package]
name = "avoid-rs"
version = "0.1.0"
version = "0.2.0"
edition = "2021"
[dependencies]
bevy = { version = "0.14", features = ["wav"] }
bevy = { version = "0.15", features = ["wav"] }
fastrand = "*"
[profile.dev]

View File

@ -10,39 +10,41 @@ impl Plugin for AnimationPlugin {
}
}
#[derive(Bundle, Default)]
pub struct AnimationBundle {
pub timer: AnimationTimer,
pub atlas: TextureAtlas,
pub indices: AnimationIndices,
}
#[derive(Component, Default)]
pub struct AnimationIndices {
pub first_index: usize,
pub last_index: usize,
pub struct SpriteAnimation {
pub first: usize,
pub last: usize,
pub timer: Timer,
}
#[derive(Component, Default, Deref, DerefMut)]
pub struct AnimationTimer(pub Timer);
impl SpriteAnimation {
pub fn new(last: usize, timer: Timer) -> Self {
Self {
first: 0,
last,
timer,
}
}
}
// #[derive(Component, Default, Deref, DerefMut)]
// pub struct AnimationTimer(pub Timer);
fn animation_tick(
time: Res<Time>,
mut query: Query<(
&mut AnimationTimer,
&mut TextureAtlas,
&AnimationIndices,
&Velocity,
)>,
mut query: Query<(&mut Sprite, &mut SpriteAnimation, &Velocity)>,
) {
for (mut timer, mut atlas, anim_indicies, velocity) in &mut query {
for (mut sprite, mut anim, velocity) in &mut query {
let Some(atlas) = &mut sprite.texture_atlas
else {
continue;
};
if velocity.length() > 0.0 {
timer.tick(time.delta());
if timer.just_finished() {
atlas.index = if atlas.index == anim_indicies.last_index {
anim_indicies.first_index
anim.timer.tick(time.delta());
if anim.timer.just_finished() {
atlas.index = if atlas.index == anim.last {
anim.first
}
else {
atlas.index + 1

View File

@ -7,6 +7,7 @@ impl Plugin for PathFollow2DPlugin {
}
#[derive(Component, Default)]
#[require(Transform)]
pub struct PathFollow2D {
pub progress: f32,
@ -82,10 +83,3 @@ impl PathFollow2D {
(Vec2::default(), Vec2::default())
}
}
#[derive(Bundle, Default)]
pub struct PathFollow2DBundle {
pub path: PathFollow2D,
pub transform: Transform,
}

View File

@ -13,7 +13,7 @@ pub struct Velocity(pub Vec2);
fn apply_velocity(time: Res<Time>, mut query: Query<(&mut Transform, &Velocity)>) {
for (mut transform, velocity) in &mut query {
transform.translation.x += velocity.x * time.delta_seconds();
transform.translation.y += velocity.y * time.delta_seconds();
transform.translation.x += velocity.x * time.delta_secs();
transform.translation.y += velocity.y * time.delta_secs();
}
}

View File

@ -1,6 +1,6 @@
use bevy::{color::palettes::css::RED, prelude::*};
use bevy::prelude::*;
use crate::common::velocity::VelocityPlugin;
use crate::{common::animation::SpriteAnimation, level::Unit};
pub struct EnemyPlugin;
@ -13,14 +13,15 @@ impl Plugin for EnemyPlugin {
}
#[derive(Component)]
#[require(Unit, SpriteAnimation)]
pub struct Enemy;
fn debug_direction(query: Query<&Transform, With<Enemy>>, mut gizmos: Gizmos) {
for enemy in &query {
fn debug_direction(query: Query<&Transform, With<Enemy>>, gizmos: Gizmos) {
// for enemy in &query {
// let start = enemy.translation.truncate();
// let end = enemy.rotation.xyz().truncate() - start;
// let end = start + Vec2::new(10.0, 0.0);
// gizmos.arrow_2d(start, end, RED);
}
// }
}
fn outside_bounds(

View File

@ -1,7 +1,4 @@
use bevy::{
input::gamepad::{GamepadConnection, GamepadEvent},
prelude::*,
};
use bevy::{input::gamepad::GamepadEvent, prelude::*};
pub struct GamepadPlugin;
@ -15,7 +12,7 @@ impl Plugin for GamepadPlugin {
pub struct GamepadOne(pub Gamepad);
fn setup(
mut commands: Commands,
commands: Commands,
gamepad1: Option<Res<GamepadOne>>,
mut gamepad_events: EventReader<GamepadEvent>,
) {
@ -25,19 +22,19 @@ fn setup(
continue;
};
match &conn.connection {
GamepadConnection::Connected(info) => {
if gamepad1.is_none() {
commands.insert_resource(GamepadOne(conn.gamepad));
}
}
GamepadConnection::Disconnected => {
if let Some(GamepadOne(id)) = gamepad1.as_deref() {
if *id == conn.gamepad {
commands.remove_resource::<GamepadOne>();
}
}
}
}
// match &conn.connection {
// GamepadConnection::Connected(_) => {
// if gamepad1.is_none() {
// commands.insert_resource(GamepadOne(conn.gamepad));
// }
// }
// GamepadConnection::Disconnected => {
// if let Some(GamepadOne(id)) = gamepad1.as_deref() {
// if *id == conn.gamepad {
// commands.remove_resource::<GamepadOne>();
// }
// }
// }
// }
}
}

View File

@ -1,14 +1,6 @@
use std::path::Path;
use bevy::{asset::AssetPath, prelude::*};
use bevy::{
asset::{io::AssetSourceId, AssetPath},
ecs::query::QueryIter,
prelude::*,
};
use crate::{
gamepad::GamepadOne, player::Player, score::Score, GameStartEvent, GameState, START_POSITION,
};
use crate::{player::Player, score::Score, GameState, START_POSITION};
pub struct HUDPlugin;
@ -22,7 +14,7 @@ impl Plugin for HUDPlugin {
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// let asset_path: AssetPath = "embedded://avoid_rs/assets/Xolonium-Regular.ttf".into();
let asset_path: AssetPath = "Xolonium-Regular.ttf".into();
let font: Handle<_> = asset_server.load(asset_path);
let font: Handle<Font> = asset_server.load(asset_path);
// let path = Path::new("avoid-rs").join("assets/Xolonium-Regular.ttf");
// let source = AssetSourceId::from("embedded");
@ -31,132 +23,104 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// score UI, top middle
commands
.spawn(NodeBundle {
style: Style {
.spawn((Node {
width: Val::Percent(100.0),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..Default::default()
},
..Default::default()
})
.with_children(|parent| {
parent.spawn((
TextBundle::from_section(
"0",
TextStyle {
},))
.with_child((
ScoreText,
Text::new("0"),
TextFont {
font: font.clone(),
font_size: 64.0,
..Default::default()
},
)
.with_text_justify(JustifyText::Center)
.with_style(Style {
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..Default::default()
}),
ScoreText,
TextLayout::new_with_justify(JustifyText::Center),
));
});
// game info, middle of the screen
commands
.spawn(NodeBundle {
style: Style {
.spawn((
IntroMessageText,
Node {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..Default::default()
},
..Default::default()
})
.with_children(|parent| {
// game info, middle of the screen
parent.spawn((
TextBundle::from_section(
"Dodge the creeps!",
TextStyle {
))
.with_child((
Text::new("Dodge the creeps!"),
TextFont {
font: font.clone(),
font_size: 64.0,
..Default::default()
},
)
.with_text_justify(JustifyText::Center)
.with_style(Style {
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..Default::default()
}),
IntroMessageText,
StartMenuUI,
TextLayout::new_with_justify(JustifyText::Center),
));
});
commands
.spawn(NodeBundle {
style: Style {
.spawn((
StartMenuUI,
Node {
width: Val::Percent(100.0),
height: Val::Percent(90.0),
align_items: AlignItems::End,
justify_content: JustifyContent::Center,
..Default::default()
},
..Default::default()
})
))
.with_children(|parent| {
parent
.spawn((
ButtonBundle {
style: Style {
StartButton,
Node {
width: Val::Px(200.0),
height: Val::Px(100.0),
border: UiRect::all(Val::Px(5.0)),
// horizontally center child text
justify_content: JustifyContent::Center,
// vertically center child text
align_items: AlignItems::Center,
..default()
..Default::default()
},
border_color: BorderColor(Color::BLACK),
border_radius: BorderRadius::all(Val::Px(10.0)),
background_color: Color::srgb(0.15, 0.15, 0.15).into(),
..default()
},
StartButton,
StartMenuUI,
BorderColor(Color::BLACK),
BorderRadius::all(Val::Px(10.0)),
BackgroundColor::from(Color::srgb(0.15, 0.15, 0.15)),
))
.with_children(|parent| {
parent.spawn(TextBundle::from_section(
"Start",
TextStyle {
.with_child((
Text::new("Start"),
TextFont {
font: font.clone(),
font_size: 64.0,
color: Color::srgb(0.9, 0.9, 0.9),
..Default::default()
},
TextColor::from(Color::srgb(0.9, 0.9, 0.9)),
));
});
});
}
#[derive(Component)]
#[derive(Component, Default)]
#[require(Text)]
pub struct ScoreText;
#[derive(Component)]
#[require(StartMenuUI, Text)]
pub struct IntroMessageText;
#[derive(Component)]
#[require(StartMenuUI, Button)]
pub struct StartButton;
#[derive(Component)]
#[derive(Component, Default)]
pub struct StartMenuUI;
fn start_button_logic(
current_state: Res<State<GameState>>,
mut state: ResMut<NextState<GameState>>,
mut score: ResMut<Score>,
gamepads: Res<ButtonInput<GamepadButton>>,
gamepad1: Option<Res<GamepadOne>>,
gamepads: Query<&Gamepad>,
mut button: Query<(&Interaction, &mut BorderColor), With<StartButton>>,
mut player_query: Query<(&mut Transform, &mut Visibility), With<Player>>,
mut ui_elems_query: Query<&mut Visibility, (With<StartMenuUI>, Without<Player>)>,
@ -169,13 +133,9 @@ fn start_button_logic(
let mut score_text = score_text_query.single_mut();
let (mut player_transform, mut player_visibility) = player_query.single_mut();
if let Some(gamepad) = gamepad1.as_deref() {
let start_btn = GamepadButton {
gamepad: gamepad.0,
button_type: GamepadButtonType::Start,
};
let gamepad = gamepads.single();
if gamepads.pressed(start_btn) {
if gamepad.just_pressed(GamepadButton::Start) {
start_game(
&mut score_text,
&mut score,
@ -184,10 +144,11 @@ fn start_button_logic(
&mut player_visibility,
&mut player_transform,
);
}
return;
}
let (interaction, mut border_color) = button.single_mut();
match *interaction {
Interaction::Pressed => {
start_game(
@ -213,7 +174,7 @@ fn start_game(
player_transform: &mut Transform,
) {
score.0 = 0;
score_text.sections[0].value = format!("{}", score.0);
score_text.0 = format!("{}", score.0);
state.set(GameState::Playing);

View File

@ -1,20 +1,19 @@
use std::f32::consts::PI;
use bevy::{
color::palettes::css::{LIME, RED},
math::bounding::{Aabb2d, BoundingCircle, BoundingVolume, IntersectsVolume},
math::bounding::{Aabb2d, BoundingCircle, IntersectsVolume},
prelude::*,
};
// use rand::Rng;
use crate::{
common::{
animation::{AnimationBundle, AnimationIndices, AnimationPlugin, AnimationTimer},
path_follow::{PathFollow2D, PathFollow2DBundle, PathFollow2DPlugin},
animation::{AnimationPlugin, SpriteAnimation},
path_follow::{PathFollow2D, PathFollow2DPlugin},
velocity::{Velocity, VelocityPlugin},
},
enemy::{Enemy, EnemyPlugin},
hud::{HUDPlugin, ScoreText, StartMenuUI},
hud::{HUDPlugin, StartMenuUI},
player::{Player, PlayerPlugin},
score::{Score, ScorePlugin},
GameOverEvent, GameState,
@ -42,8 +41,7 @@ impl Plugin for LevelPlugin {
}
fn setup(mut commands: Commands) {
commands.spawn(PathFollow2DBundle {
path: PathFollow2D {
commands.spawn(PathFollow2D {
progress: 0.0,
points: vec![
Vec2::new(0.0, 720.0),
@ -52,25 +50,17 @@ fn setup(mut commands: Commands) {
Vec2::new(0.0, 0.0),
],
looping: true,
..Default::default()
},
..Default::default()
});
}
#[derive(Resource, Deref, DerefMut)]
struct SpawnTimer(Timer);
#[derive(Bundle, Default)]
pub struct UnitBundle {
pub sprite: SpriteBundle,
#[derive(Component, Default)]
#[require(Sprite, Velocity, Collider)]
pub struct Unit;
pub velocity: Velocity,
pub animator: AnimationBundle,
}
#[derive(Component)]
#[derive(Component, Default)]
pub struct Collider;
#[derive(Event, Default)]
@ -115,43 +105,33 @@ fn spawn_enemy(
commands.spawn((
Enemy,
Collider,
UnitBundle {
sprite: SpriteBundle {
transform: Transform {
Transform {
translation: spawn_pos.extend(1.0),
rotation: direction,
scale: Vec2::new(0.75, 0.75).extend(1.0),
},
texture: asset_server.load(idx),
..Default::default()
},
animator: AnimationBundle {
atlas: TextureAtlas {
Sprite {
image: asset_server.load(idx),
texture_atlas: Some(TextureAtlas {
layout: texture_atlas_layouts,
index: 0,
},
timer: AnimationTimer(Timer::from_seconds(0.3, TimerMode::Repeating)),
indices: AnimationIndices {
first_index: 0,
last_index: 1,
},
}),
..Default::default()
},
velocity: Velocity(velocity),
},
SpriteAnimation::new(1, Timer::from_seconds(0.3, TimerMode::Repeating)),
Velocity(velocity),
));
}
}
fn check_for_collisions(
mut commands: Commands,
mut score: ResMut<Score>,
commands: Commands,
score: ResMut<Score>,
mut player_query: Query<(&mut Velocity, &Transform), With<Player>>,
collider_query: Query<(Entity, &Transform), (With<Collider>, With<Enemy>)>,
mut game_over_events: EventWriter<GameOverEvent>,
) {
let (mut player_velocity, player_transform) = player_query.single_mut();
let (player_velocity, player_transform) = player_query.single_mut();
for (collider_entity, collider_transform) in &collider_query {
let collision = player_collision(
@ -193,11 +173,10 @@ fn game_over(
commands.entity(enemy).despawn();
}
commands.spawn(AudioBundle {
source: asset_server.load("gameover.wav"),
// auto-despawn the entity when playback finishes
settings: PlaybackSettings::DESPAWN,
});
commands.spawn((
AudioPlayer::new(asset_server.load("gameover.wav")),
PlaybackSettings::DESPAWN,
));
}
fn player_collision(player: BoundingCircle, bounding_box: Aabb2d) -> bool {
@ -208,7 +187,7 @@ fn player_collision(player: BoundingCircle, bounding_box: Aabb2d) -> bool {
true
}
fn debug_gizmos(mut gizmos: Gizmos, query: Query<&PathFollow2D>) {
fn debug_gizmos(gizmos: Gizmos, query: Query<&PathFollow2D>) {
for path in &query {
// let x = path.points.len();
// for i in 0..x {

View File

@ -1,18 +1,7 @@
use std::default;
use bevy::{
asset::{embedded_asset, embedded_path},
diagnostic::FrameTimeDiagnosticsPlugin,
prelude::*,
render::camera::Viewport,
window::{WindowMode, WindowResolution},
};
use bevy::{diagnostic::FrameTimeDiagnosticsPlugin, prelude::*, window::WindowResolution};
use common::mouse::{Mouse, MousePlugin};
use gamepad::GamepadPlugin;
use hud::HUDPlugin;
use level::LevelPlugin;
use player::PlayerPlugin;
use score::ScorePlugin;
mod common;
mod enemy;
@ -42,25 +31,10 @@ struct GameOverEvent;
struct GameStartEvent;
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2dBundle {
camera: Camera {
viewport: Some(Viewport {
physical_position: UVec2::new(0, 0),
physical_size: UVec2::new(480, 720),
..Default::default()
}),
..Default::default()
},
transform: Transform {
translation: Vec3 {
x: (SCREEN_WIDTH / 2) as f32,
y: (SCREEN_HEIGHT / 2) as f32,
z: 1.0,
},
..Default::default()
},
..Default::default()
});
commands.spawn((
Camera2d,
Transform::from_xyz((SCREEN_WIDTH / 2) as f32, (SCREEN_HEIGHT / 2) as f32, 1.0),
));
}
fn debug_mouse(mouse: Res<Mouse>) {

View File

@ -1,19 +1,11 @@
use std::f32::consts::PI;
use bevy::{
input::gamepad::{GamepadConnection, GamepadEvent},
prelude::*,
render::view::VisibilityPlugin,
};
use bevy::prelude::*;
use crate::{
common::{
animation::{AnimationBundle, AnimationIndices, AnimationPlugin, AnimationTimer},
velocity::Velocity,
},
gamepad::GamepadOne,
level::{Collider, UnitBundle},
GameState, SCREEN_HEIGHT, SCREEN_WIDTH, START_POSITION,
common::{animation::SpriteAnimation, velocity::Velocity},
level::Unit,
GameState, START_POSITION,
};
pub struct PlayerPlugin;
@ -29,6 +21,7 @@ impl Plugin for PlayerPlugin {
}
#[derive(Component)]
#[require(Unit, SpriteAnimation)]
pub struct Player;
fn setup(
@ -37,52 +30,39 @@ fn setup(
mut texture_atlas_layouts: ResMut<Assets<TextureAtlasLayout>>,
) {
let layout = TextureAtlasLayout::from_grid(UVec2::splat(135), 2, 1, None, None);
let texture_atlas_layouts = texture_atlas_layouts.add(layout);
let layout_handle = texture_atlas_layouts.add(layout);
commands.spawn((
Player,
Collider,
UnitBundle {
sprite: SpriteBundle {
transform: Transform {
Transform {
translation: START_POSITION,
scale: Vec2::new(0.5, 0.5).extend(1.0),
..Default::default()
},
visibility: Visibility::Hidden,
texture: asset_server.load("player.png"),
..Default::default()
},
animator: AnimationBundle {
atlas: TextureAtlas {
layout: texture_atlas_layouts,
Visibility::Hidden,
Sprite {
image: asset_server.load("player.png"),
texture_atlas: Some(TextureAtlas {
layout: layout_handle,
index: 0,
},
timer: AnimationTimer(Timer::from_seconds(0.3, TimerMode::Repeating)),
indices: AnimationIndices {
first_index: 0,
last_index: 1,
},
},
}),
..Default::default()
},
SpriteAnimation::new(1, Timer::from_seconds(0.3, TimerMode::Repeating)),
));
}
fn move_player(
keys: Res<ButtonInput<KeyCode>>,
axes: Res<Axis<GamepadAxis>>,
gamepads: Res<ButtonInput<GamepadButton>>,
gamepad1: Option<Res<GamepadOne>>,
gamepads: Query<&Gamepad>,
mut query: Query<(&mut Transform, &mut Velocity), With<Player>>,
window: Query<&Window>,
) {
let (mut transform, mut velocity) = query.single_mut();
let gamepad = gamepads.single();
velocity.0 = Vec2::ZERO;
if let Some(gamepad) = gamepad1.as_deref() {
handle_gamepad_movement(&axes, gamepad, &gamepads, &mut velocity);
};
handle_gamepad_movement(gamepad, &mut velocity);
if keys.pressed(KeyCode::ArrowRight) {
velocity.x += 1.0;
@ -109,59 +89,26 @@ fn move_player(
transform.translation = transform.translation.clamp(Vec3::ZERO, screen_size);
}
fn handle_gamepad_movement(
axes: &Axis<GamepadAxis>,
gamepad: &GamepadOne,
gamepads: &Res<ButtonInput<GamepadButton>>,
velocity: &mut Velocity,
) {
let axis_lx = GamepadAxis {
gamepad: gamepad.0,
axis_type: GamepadAxisType::LeftStickX,
};
let axis_ly = GamepadAxis {
gamepad: gamepad.0,
axis_type: GamepadAxisType::LeftStickY,
};
fn handle_gamepad_movement(gamepad: &Gamepad, velocity: &mut Velocity) {
let axis_lx = gamepad.get(GamepadAxis::LeftStickX).unwrap_or_default();
let axis_ly = gamepad.get(GamepadAxis::LeftStickY).unwrap_or_default();
if let (Some(x), Some(y)) = (axes.get(axis_lx), axes.get(axis_ly)) {
let left_stick = Vec2::new(x, y);
let left_stick = Vec2::new(axis_lx, axis_ly);
if left_stick.length() > 0.25 {
*velocity = Velocity(left_stick);
return;
}
}
let up_btn = GamepadButton {
gamepad: gamepad.0,
button_type: GamepadButtonType::DPadUp,
};
let down_btn = GamepadButton {
gamepad: gamepad.0,
button_type: GamepadButtonType::DPadDown,
};
let left_btn = GamepadButton {
gamepad: gamepad.0,
button_type: GamepadButtonType::DPadLeft,
};
let right_btn = GamepadButton {
gamepad: gamepad.0,
button_type: GamepadButtonType::DPadRight,
};
if gamepads.pressed(right_btn) {
if gamepad.pressed(GamepadButton::DPadRight) {
velocity.x += 1.0;
}
if gamepads.pressed(left_btn) {
if gamepad.pressed(GamepadButton::DPadLeft) {
velocity.x -= 1.0;
}
if gamepads.pressed(up_btn) {
if gamepad.pressed(GamepadButton::DPadUp) {
velocity.y += 1.0;
}
if gamepads.pressed(down_btn) {
if gamepad.pressed(GamepadButton::DPadDown) {
velocity.y -= 1.0;
}
}

View File

@ -28,6 +28,6 @@ fn score_tick(
timer.tick(time.delta());
if timer.just_finished() {
score.0 += 1;
score_text.sections[0].value = format!("{}", score.0);
score_text.0 = format!("{}", score.0);
}
}