Compare commits

...

10 Commits

Author SHA1 Message Date
7750d192e5 Update dependencies 2025-03-10 21:43:54 +01:00
cd6408666f Update to bevy 0.15 2025-01-01 14:02:39 +01:00
d59aa18809 Make bevy 0.14 runnable by removing canvas effects 2025-01-01 12:40:02 +01:00
91dbf577bd wip: Start migration to bevy 0.14 2024-10-20 13:39:02 +02:00
d6f900a3e4 Fix canvas 2024-04-13 18:42:02 +02:00
5cb87a3a81 First steps to fixing canvas 2024-04-13 18:24:05 +02:00
4a98354069 Remove audio entity after playing 2024-04-12 15:39:34 +02:00
824231a86b Fix crudely for bevy 0.13 2024-04-12 15:29:08 +02:00
58d3258448 Fix orthographic projection 2024-02-18 13:11:05 +01:00
202db0be70 Update to Bevy 0.12 2024-02-18 12:48:34 +01:00
13 changed files with 2649 additions and 1846 deletions

3951
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,15 +4,14 @@ version = "0.1.0"
edition = "2021"
[dependencies]
itertools = "0.11.0"
rand = "0.8.5"
bevy_editor_pls = { git = "https://github.com/jakobhellermann/bevy_editor_pls" }
bevy_tweening = "0.8.0"
leafwing-input-manager = "0.10.0"
bevy_asset_loader = "0.17.0"
itertools = "0.14.0"
rand = "0.9.0"
bevy_tweening = "0.12.0"
leafwing-input-manager = "0.16.0"
bevy_asset_loader = "0.22.0"
[dependencies.bevy]
version = "0.11"
version = "0.15"
features = ["wayland", "wav"]
# [profile.dev.package."*"]

View File

@@ -1,9 +1,4 @@
struct FragmentInput {
@builtin(position) fragment_coordinate: vec4<f32>,
@location(0) world_position: vec4<f32>,
@location(1) world_normal: vec3<f32>,
@location(2) uv: vec2<f32>,
};
#import bevy_sprite::mesh2d_vertex_output::VertexOutput
struct TileLight {
color: vec4<f32>,
@@ -18,11 +13,11 @@ struct CanvasMaterial {
};
@group(1) @binding(0)
@group(2) @binding(0)
var<uniform> canvas_material: CanvasMaterial;
@fragment
fn fragment(input: FragmentInput) -> @location(0) vec4<f32> {
fn fragment(input: VertexOutput) -> @location(0) vec4<f32> {
var output_color = canvas_material.ambient_color;
var lighting = vec4<f32>();
@@ -33,6 +28,5 @@ fn fragment(input: FragmentInput) -> @location(0) vec4<f32> {
}
output_color += lighting;
return output_color;
}

View File

@@ -1,109 +1,130 @@
mod tile_light;
// mod tile_light;
use crate::grid;
use bevy::{
prelude::*,
reflect::{TypeUuid, TypePath},
render::{
render_resource::{encase, AsBindGroup, OwnedBindingResource, ShaderRef, ShaderType},
renderer::RenderQueue,
RenderApp, RenderSet,
},
sprite::{Material2d, Material2dPlugin, MaterialMesh2dBundle, RenderMaterials2d},
// reflect::TypePath,
// render::{
// render_resource::{encase, AsBindGroup, OwnedBindingResource, ShaderRef, ShaderType},
// renderer::RenderQueue,
// Extract, Render, RenderApp, RenderSet,
// },
// sprite::{Material2d, Material2dPlugin, MaterialMesh2dBundle},
};
use tile_light::{extract_tile_lights, TileLight, TileLightsUniform};
pub(crate) use tile_light::EmissiveTile;
// pub(crate) use tile_light::EmissiveTile;
pub struct CanvasPlugin;
impl Plugin for CanvasPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(Material2dPlugin::<CanvasMaterial>::default())
.add_systems(Startup, spawn_canvas);
// app.add_plugins(Material2dPlugin::<CanvasMaterial>::default())
// .add_systems(Startup, spawn_canvas);
app.add_systems(Startup, spawn_canvas);
app.sub_app_mut(RenderApp)
.add_systems(ExtractSchedule, extract_tile_lights)
.add_systems(Update, prepare_canvas_material.in_set(RenderSet::Prepare));
// app.sub_app_mut(RenderApp)
// .add_systems(ExtractSchedule, (extract_tile_lights, extract_canvas))
// .add_systems(
// Render,
// prepare_canvas_material.in_set(RenderSet::PrepareAssets),
// );
}
}
const CANVAS_COLOR: Color = Color::rgb(0.05, 0.05, 0.05);
const CANVAS_COLOR: Color = Color::srgb(0.075, 0.075, 0.075);
#[derive(AsBindGroup, TypeUuid, TypePath, Clone)]
#[uuid = "24f83f6e-e52d-41a6-bf1d-0e46e57a4995"]
struct CanvasMaterial {
#[uniform(0)]
ambient_color: Color,
#[uniform(0)]
tile_lights: tile_light::TileLightsUniform,
#[uniform(0)]
number_of_lights: u32,
}
// #[derive(AsBindGroup, TypePath, Asset, Clone)]
// struct CanvasMaterial {
// #[uniform(0)]
// ambient_color: Color,
// #[uniform(0)]
// tile_lights: tile_light::TileLightsUniform,
// #[uniform(0)]
// number_of_lights: u32,
// }
impl Default for CanvasMaterial {
fn default() -> Self {
Self {
ambient_color: CANVAS_COLOR,
number_of_lights: 0,
tile_lights: Default::default(),
}
}
}
// impl Default for CanvasMaterial {
// fn default() -> Self {
// Self {
// ambient_color: CANVAS_COLOR,
// number_of_lights: 0,
// tile_lights: Default::default(),
// }
// }
// }
#[derive(ShaderType)]
struct CanvasMaterialUniform {
ambient_color: Color,
tile_lights: tile_light::TileLightsUniform,
number_of_lights: u32,
}
// #[derive(ShaderType)]
// struct CanvasMaterialUniform {
// ambient_color: Color,
// tile_lights: tile_light::TileLightsUniform,
// number_of_lights: u32,
// }
impl Material2d for CanvasMaterial {
fn fragment_shader() -> ShaderRef {
"canvas_shader.wgsl".into()
}
}
// impl Material2d for CanvasMaterial {
// fn fragment_shader() -> ShaderRef {
// "canvas_shader.wgsl".into()
// }
// }
fn prepare_canvas_material(
materials: Res<RenderMaterials2d<CanvasMaterial>>,
canvas_query: Query<&Handle<CanvasMaterial>>,
tile_light_query: Query<&TileLight>,
render_queue: Res<RenderQueue>,
) {
let tile_lights = tile_light_query.iter().cloned().collect::<Vec<_>>();
let tile_lights_uniform = TileLightsUniform::from_lights(&tile_lights);
// fn prepare_canvas_material(
// materials: Res<RenderMaterials2d<CanvasMaterial>>,
// canvas_query: Query<&Handle<CanvasMaterial>>,
// tile_light_query: Query<&TileLight>,
// render_queue: Res<RenderQueue>,
// ) {
// let tile_lights = tile_light_query.iter().cloned().collect::<Vec<_>>();
// let tile_lights_uniform = TileLightsUniform::from_lights(&tile_lights);
for handle in canvas_query.iter() {
if let Some(material) = materials.get(handle) {
let binding = &material.bindings[0];
if let OwnedBindingResource::Buffer(cur_buffer) = binding {
let mut buffer = encase::UniformBuffer::new(Vec::new());
buffer
.write(&CanvasMaterialUniform {
ambient_color: CANVAS_COLOR,
tile_lights: tile_lights_uniform.clone(),
number_of_lights: tile_lights.len() as _,
})
.unwrap();
render_queue.write_buffer(cur_buffer, 0, buffer.as_ref());
}
}
}
}
// for handle in canvas_query.iter() {
// if let Some(material) = materials.get(&handle.id()) {
// let binding = &material.bindings[0];
// if let (_, OwnedBindingResource::Buffer(cur_buffer)) = binding {
// let mut buffer = encase::UniformBuffer::new(Vec::new());
// buffer
// .write(&CanvasMaterialUniform {
// ambient_color: CANVAS_COLOR,
// tile_lights: tile_lights_uniform.clone(),
// number_of_lights: tile_lights.len() as _,
// })
// .unwrap();
// render_queue.write_buffer(cur_buffer, 0, buffer.as_ref());
// }
// }
// }
// }
fn spawn_canvas(
mut commands: Commands,
mut mesh_assets: ResMut<Assets<Mesh>>,
mut canvas_material_assets: ResMut<Assets<CanvasMaterial>>,
) {
commands
.spawn(MaterialMesh2dBundle {
mesh: mesh_assets.add(Mesh::from(shape::Quad::default())).into(),
material: canvas_material_assets.add(CanvasMaterial::default()),
transform: Transform::from_scale(Vec3::splat(
grid::SEGMENT_SIZE * f32::from(grid::SIZE),
)),
// fn extract_canvas(
// mut commands: Commands,
// canvas_query: Extract<Query<(Entity, &Handle<CanvasMaterial>)>>,
// ) {
// for (entity, handle) in canvas_query.iter() {
// commands.get_or_spawn(entity).insert(handle.clone());
// }
// }
// fn spawn_canvas(
// mut commands: Commands,
// mut mesh_assets: ResMut<Assets<Mesh>>,
// mut canvas_material_assets: ResMut<Assets<CanvasMaterial>>,
// ) {
// commands
// .spawn(MaterialMesh2dBundle {
// mesh: mesh_assets.add(Mesh::from(Rectangle::default())).into(),
// material: canvas_material_assets.add(CanvasMaterial::default()),
// transform: Transform::from_scale(Vec3::splat(
// grid::SEGMENT_SIZE * f32::from(grid::SIZE),
// )),
// ..Default::default()
// })
// .insert(Name::new("Canvas"));
// }
fn spawn_canvas(mut commands: Commands) {
commands.spawn((
Sprite {
color: CANVAS_COLOR.into(),
..Default::default()
})
.insert(Name::new("Canvas"));
},
Transform::from_scale(Vec3::splat(grid::SEGMENT_SIZE * f32::from(grid::SIZE))),
));
}

View File

@@ -1,10 +1,9 @@
use bevy::{
math::Vec3Swizzles,
prelude::*,
render::{render_resource::ShaderType, Extract},
// render::{render_resource::ShaderType, Extract},
};
const MAX_TILE_LIGHTS: usize = 256;
// const MAX_TILE_LIGHTS: usize = 256;
#[derive(Component, Clone, Copy)]
pub(crate) struct EmissiveTile {
@@ -17,48 +16,48 @@ impl Default for EmissiveTile {
}
}
#[derive(Copy, Clone, Default, ShaderType, Component)]
pub(super) struct TileLight {
color: Color,
intensity: f32,
position: Vec2,
}
// #[derive(Copy, Clone, Default, ShaderType, Component)]
// pub(super) struct TileLight {
// color: Color,
// intensity: f32,
// position: Vec2,
// }
#[derive(ShaderType, Clone)]
pub(super) struct TileLightsUniform {
pub data: Box<[TileLight; MAX_TILE_LIGHTS]>,
}
// #[derive(ShaderType, Clone)]
// pub(super) struct TileLightsUniform {
// pub data: Box<[TileLight; MAX_TILE_LIGHTS]>,
// }
impl TileLightsUniform {
pub(super) fn from_lights(tile_lights: &[TileLight]) -> Self {
let mut tile_lights_uniform = TileLightsUniform::default();
// impl TileLightsUniform {
// pub(super) fn from_lights(tile_lights: &[TileLight]) -> Self {
// let mut tile_lights_uniform = TileLightsUniform::default();
let len = tile_lights.len().min(MAX_TILE_LIGHTS);
let src = &tile_lights[..len];
let dst = &mut tile_lights_uniform.data[..len];
dst.copy_from_slice(src);
// let len = tile_lights.len().min(MAX_TILE_LIGHTS);
// let src = &tile_lights[..len];
// let dst = &mut tile_lights_uniform.data[..len];
// dst.copy_from_slice(src);
tile_lights_uniform
}
}
// tile_lights_uniform
// }
// }
impl Default for TileLightsUniform {
fn default() -> Self {
Self {
data: Box::new([TileLight::default(); MAX_TILE_LIGHTS]),
}
}
}
// impl Default for TileLightsUniform {
// fn default() -> Self {
// Self {
// data: Box::new([TileLight::default(); MAX_TILE_LIGHTS]),
// }
// }
// }
pub(super) fn extract_tile_lights(
mut commands: Commands,
emissive_sprite_query: Extract<Query<(Entity, &EmissiveTile, &Sprite, &GlobalTransform)>>,
) {
for (entity, emissive_sprite, sprite, global_transform) in emissive_sprite_query.iter() {
commands.get_or_spawn(entity).insert(TileLight {
color: sprite.color,
intensity: emissive_sprite.intensity,
position: global_transform.translation().xy(),
});
}
}
// pub(super) fn extract_tile_lights(
// mut commands: Commands,
// emissive_sprite_query: Extract<Query<(Entity, &EmissiveTile, &Sprite, &GlobalTransform)>>,
// ) {
// for (entity, emissive_sprite, sprite, global_transform) in emissive_sprite_query.iter() {
// commands.get_or_spawn(entity).insert(TileLight {
// color: sprite.color,
// intensity: emissive_sprite.intensity,
// position: global_transform.translation().xy(),
// });
// }
// }

View File

@@ -31,7 +31,7 @@ pub struct EatenEvent(Option<Entity>);
pub struct Fruit;
pub fn eaten_event_sent(mut eaten_event_reader: EventReader<EatenEvent>) -> bool {
eaten_event_reader.iter().count() != 0
eaten_event_reader.read().count() != 0
}
fn spawn_fruit_system(mut commands: Commands, coordinate_query: Query<&grid::Coordinate>) {
@@ -56,18 +56,17 @@ fn spawn_fruit_system(mut commands: Commands, coordinate_query: Query<&grid::Coo
.expect("Should always be found.");
commands
.spawn(SpriteBundle {
sprite: Sprite {
color: Color::GREEN,
.spawn((
Sprite {
color: Srgba::GREEN.into(),
custom_size: Some(Vec2::splat(grid::SEGMENT_SIZE) * 0.6),
..Default::default()
},
transform: Transform::from_translation(Vec2::from(fruit_coordinate).extend(Z_HEIGHT)),
..Default::default()
})
Transform::from_translation(Vec2::from(fruit_coordinate).extend(Z_HEIGHT)),
))
.insert(fruit_coordinate)
.insert(Fruit)
.insert(crate::canvas::EmissiveTile { intensity: 1.0 })
// .insert(crate::canvas::EmissiveTile { intensity: 1.0 })
.insert(Name::new("Fruit"));
}
@@ -86,7 +85,7 @@ fn eat_fruit_system(
}
fn despawn_fruit_system(mut commands: Commands, mut event_reader: EventReader<EatenEvent>) {
for &EatenEvent(fruit) in event_reader.iter() {
for &EatenEvent(fruit) in event_reader.read() {
if let Some(fruit) = fruit {
commands.entity(fruit).despawn();
}

View File

@@ -1,7 +1,6 @@
use bevy::prelude::*;
// #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States)]
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States)]
pub enum LevelState {
#[default]
Begin,
@@ -10,12 +9,12 @@ pub enum LevelState {
}
#[derive(Resource)]
struct Level(u32);
pub struct Level(pub u32);
pub(super) struct LevelPlugin;
impl Plugin for LevelPlugin {
fn build(&self, app: &mut App) {
// app.add_state::<LevelState>();
app.insert_resource(Level(1));
}
}

View File

@@ -11,9 +11,6 @@ use bevy_asset_loader::prelude::*;
use bevy_tweening::TweeningPlugin;
use std::time::Duration;
#[cfg(debug_assertions)]
use bevy_editor_pls::prelude::*;
mod audio;
mod canvas;
mod fruit;
@@ -27,12 +24,22 @@ const WINDOW_WIDTH: f32 = WINDOW_HEIGHT * ASPECT_RATIO;
const WINDOW_HEIGHT: f32 = 720.;
const TICK_PERIOD: Duration = Duration::from_millis(125);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, States)]
enum AppState {
MainMenu,
InGame(LevelState),
}
impl Default for AppState {
fn default() -> Self {
if cfg!(debug_assertions) {
Self::InGame(LevelState::Begin)
} else {
Self::MainMenu
}
}
}
#[derive(Default)]
struct AppStateIter {
current: Option<AppState>,
@@ -60,37 +67,15 @@ impl Iterator for AppStateIter {
}
}
impl Default for AppState {
fn default() -> Self {
if cfg!(debug_assertions) {
Self::InGame(LevelState::Begin)
} else {
Self::MainMenu
}
}
}
impl States for AppState {
type Iter = AppStateIter;
fn variants() -> Self::Iter {
AppStateIter::default()
}
}
#[derive(Resource)]
struct Score(u32);
#[derive(Resource)]
struct Level(u32);
fn main() {
let mut app = App::new();
app.insert_resource(ClearColor(Color::BLACK))
.insert_resource(FixedTime::new(TICK_PERIOD))
.insert_resource(Time::<Fixed>::from_duration(TICK_PERIOD))
.insert_resource(Score(0))
.insert_resource(Level(0))
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
title: "Bevy-Snake".into(),
@@ -101,7 +86,7 @@ fn main() {
..Default::default()
}))
.init_collection::<audio::Assets>()
.add_state::<AppState>()
.init_state::<AppState>()
.add_plugins((
TweeningPlugin,
SnakePlugin,
@@ -115,8 +100,7 @@ fn main() {
#[cfg(debug_assertions)]
{
app.add_plugins(EditorPlugin::new())
.add_systems(Update, (bevy::window::close_on_esc, pause_system));
app.add_systems(Update, pause_system);
}
app.run();
@@ -126,25 +110,26 @@ fn setup_camera_system(mut commands: Commands) {
let grid_dimensions = f32::from(SIZE) * SEGMENT_SIZE * 1.2;
commands
.spawn(Camera2dBundle {
projection: OrthographicProjection {
.spawn((
Camera2d,
OrthographicProjection {
scaling_mode: ScalingMode::AutoMin {
min_width: grid_dimensions,
min_height: grid_dimensions,
},
..Default::default()
near: -1000.,
..OrthographicProjection::default_2d()
},
..Default::default()
})
))
.insert(Name::new("Orthographic Camera"));
}
fn update_score_system(mut score: ResMut<Score>, mut eaten_event: EventReader<fruit::EatenEvent>) {
score.0 += eaten_event.iter().count() as u32;
score.0 += eaten_event.read().count() as u32;
}
fn pause_system(keypress: Res<Input<KeyCode>>, mut time: ResMut<Time>) {
if keypress.just_pressed(KeyCode::P) {
fn pause_system(keypress: Res<ButtonInput<KeyCode>>, mut time: ResMut<Time<Virtual>>) {
if keypress.just_pressed(KeyCode::KeyP) {
if time.is_paused() {
time.unpause()
} else {

View File

@@ -20,7 +20,7 @@ pub struct SnakePlugin;
impl Plugin for SnakePlugin {
fn build(&self, app: &mut App) {
app.configure_set(
app.configure_sets(
Update,
SystemSet::Movement.after(SystemSet::CollisionDetection),
);
@@ -64,7 +64,7 @@ impl Plugin for SnakePlugin {
.run_if(in_state(AppState::InGame(LevelState::Begin))),
direction::change_direction_system.run_if(
in_state(AppState::InGame(LevelState::Begin))
.or_else(in_state(AppState::InGame(LevelState::InGame))),
.or(in_state(AppState::InGame(LevelState::InGame))),
),
bulge::add_bulge_system,
bulge::propagate_bulge_system,
@@ -107,7 +107,8 @@ struct SnakeBundle {
snake: Snake,
collision: Collision,
segments: Segments,
spatial_bundle: SpatialBundle,
transform: Transform,
visibility: Visibility,
}
#[derive(Component, Default, Debug)]
@@ -123,22 +124,21 @@ fn create_snake_segment(
grid_position: grid::Coordinate,
segment_number: u32,
) -> Entity {
let mut color = Color::RED;
let mut color = Srgba::RED;
color *= 0.99f32.powi(segment_number as _);
commands
.spawn(SpriteBundle {
sprite: Sprite {
color,
.spawn((
Sprite {
color: color.into(),
custom_size: Some(Vec2::splat(grid::SEGMENT_SIZE) * 0.9),
..Default::default()
},
transform: Transform::from_translation(Vec2::from(grid_position).extend(Z_HEIGHT)),
..Default::default()
})
Transform::from_translation(Vec2::from(grid_position).extend(Z_HEIGHT)),
))
.insert(SnakeSegment)
.insert(grid_position)
.insert(crate::canvas::EmissiveTile { intensity: 1.0 })
// .insert(crate::canvas::EmissiveTile { intensity: 1.0 })
.id()
}
@@ -156,15 +156,16 @@ fn setup_snake_system(mut commands: Commands) {
snake: Snake,
collision: Default::default(),
segments: Segments(vec![snake_head]),
spatial_bundle: Default::default(),
transform: Default::default(),
visibility: Default::default(),
})
.insert(Name::new("Snake"))
.insert(InputManagerBundle::<Direction> {
input_map: InputMap::new([
(KeyCode::Up, Direction::Up),
(KeyCode::Down, Direction::Down),
(KeyCode::Left, Direction::Left),
(KeyCode::Right, Direction::Right),
(Direction::Up, KeyCode::ArrowUp),
(Direction::Down, KeyCode::ArrowDown),
(Direction::Left, KeyCode::ArrowLeft),
(Direction::Right, KeyCode::ArrowRight),
]),
..Default::default()
})
@@ -175,9 +176,9 @@ fn eaten_event_system(
mut eaten_event_reader: EventReader<fruit::EatenEvent>,
mut tail_event_writer: EventWriter<AddTailEvent>,
) {
eaten_event_reader
.iter()
.for_each(|_| tail_event_writer.send(AddTailEvent));
eaten_event_reader.read().for_each(|_| {
tail_event_writer.send(AddTailEvent);
});
}
fn add_tail_system(
@@ -185,7 +186,7 @@ fn add_tail_system(
mut snake_query: Query<(Entity, &mut Segments), With<Snake>>,
mut tail_event_reader: EventReader<AddTailEvent>,
) {
for _ in tail_event_reader.iter() {
for _ in tail_event_reader.read() {
let (snake_entity, mut segments) = snake_query.single_mut();
let segment = create_snake_segment(
@@ -266,10 +267,10 @@ fn game_over_system(mut next_state: ResMut<NextState<AppState>>) {
}
fn collision_sound_system(mut commands: Commands, audio_assets: Res<audio::Assets>) {
commands.spawn(AudioBundle {
source: audio_assets.collision.clone(),
..Default::default()
});
commands.spawn((
AudioPlayer::<AudioSource>(audio_assets.collision.clone()),
PlaybackSettings::DESPAWN,
));
}
fn blip_sound_system(
@@ -277,11 +278,11 @@ fn blip_sound_system(
audio_assets: Res<audio::Assets>,
mut eaten_event_reader: EventReader<fruit::EatenEvent>,
) {
for _ in eaten_event_reader.iter() {
commands.spawn(AudioBundle {
source: audio_assets.blip.clone(),
..Default::default()
});
for _ in eaten_event_reader.read() {
commands.spawn((
AudioPlayer::<AudioSource>(audio_assets.blip.clone()),
PlaybackSettings::DESPAWN,
));
}
}
@@ -302,7 +303,7 @@ mod debug {
use super::*;
pub(super) fn add_tail(
keypress: Res<Input<KeyCode>>,
keypress: Res<ButtonInput<KeyCode>>,
mut tail_event_writer: EventWriter<AddTailEvent>,
) {
if keypress.just_pressed(KeyCode::Space) {

View File

@@ -22,7 +22,7 @@ pub(super) fn add_bulge_system(
mut tail_event_reader: EventReader<AddTailEvent>,
query: Query<Entity, With<SnakeHead>>,
) {
for _ in tail_event_reader.iter() {
for _ in tail_event_reader.read() {
let snake_head_entity = query.single();
commands.entity(snake_head_entity).insert(BulgeMarker);
}

View File

@@ -3,7 +3,7 @@ use crate::{audio, level::LevelState, AppState};
use bevy::prelude::*;
use leafwing_input_manager::prelude::*;
#[derive(Actionlike, Reflect, Component, Copy, Clone, Debug, PartialEq, Eq)]
#[derive(Actionlike, Reflect, Component, Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub(super) enum Direction {
Up,
Down,
@@ -62,9 +62,10 @@ pub(super) fn change_direction_system(
}
commands.entity(snake).insert(NextDirection(new_direction));
commands.spawn(AudioBundle {
source: audio_assets.tick.clone(),
..Default::default()
});
commands.spawn((
AudioPlayer::<AudioSource>(audio_assets.tick.clone()),
PlaybackSettings::DESPAWN,
));
}
}

View File

@@ -7,6 +7,9 @@ pub struct UiPlugin;
impl Plugin for UiPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, status::spawn_status_text)
.add_systems(Update, status::update_status_text);
.add_systems(
Update,
(status::update_level_text, status::update_score_text),
);
}
}

View File

@@ -1,61 +1,48 @@
use crate::{Level, Score};
use bevy::prelude::*;
use crate::{level::Level, Score};
use bevy::{color::palettes::css::GOLD, prelude::*};
#[derive(Component)]
pub(super) struct StatusText;
pub(super) struct LevelValue;
#[derive(Component)]
pub(super) struct ScoreValue;
pub(super) fn spawn_status_text(mut commands: Commands, asset_server: Res<AssetServer>) {
let font = asset_server.load("fonts/Audiowide-Regular.ttf");
commands.spawn((
StatusText,
TextBundle::from_sections([
TextSection::new(
"Level: ",
TextStyle {
font: font.clone(),
font_size: 32.0,
color: Color::WHITE,
},
),
TextSection::from_style(TextStyle {
font: font.clone(),
font_size: 32.0,
color: Color::WHITE,
}),
TextSection::new(
" Score: ",
TextStyle {
font: font.clone(),
font_size: 32.0,
color: Color::WHITE,
},
),
TextSection::from_style(TextStyle {
commands
.spawn((
Text::default(),
TextFont {
font,
font_size: 32.0,
color: Color::WHITE,
}),
])
.with_style(Style {
position_type: PositionType::Absolute,
left: Val::Px(10.0),
bottom: Val::Px(10.0),
..Default::default()
}),
));
..Default::default()
},
Node {
position_type: PositionType::Absolute,
left: Val::Px(10.0),
bottom: Val::Px(10.0),
..Default::default()
},
))
.with_child((TextSpan::new("Level: "), TextColor::WHITE))
.with_child((LevelValue, TextSpan::default(), TextColor(GOLD.into())))
.with_child((TextSpan::new(" Score: "), TextColor::WHITE))
.with_child((ScoreValue, TextSpan::default(), TextColor(GOLD.into())));
}
pub(super) fn update_status_text(
mut query: Query<&mut Text, With<StatusText>>,
score: Res<Score>,
pub(super) fn update_level_text(
mut level_query: Query<&mut TextSpan, With<LevelValue>>,
level: Res<Level>,
) {
let score = score.0;
let level = level.0;
for mut text in query.iter_mut() {
text.sections[1].value = format!("{level}");
text.sections[3].value = format!("{score}");
}
level_query.single_mut().0 = format!("{level}");
}
pub(super) fn update_score_text(
mut score_query: Query<&mut TextSpan, With<ScoreValue>>,
score: Res<Score>,
) {
let score = score.0;
score_query.single_mut().0 = format!("{score}");
}