mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-11 00:07:57 -06:00
feat: directional rendering, interactivity
This commit is contained in:
44
src/ecs/interact.rs
Normal file
44
src/ecs/interact.rs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
use bevy_ecs::{
|
||||||
|
event::{EventReader, EventWriter},
|
||||||
|
query::With,
|
||||||
|
system::{Query, ResMut},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
ecs::{GlobalState, PlayerControlled, Velocity},
|
||||||
|
error::GameError,
|
||||||
|
game::events::GameEvent,
|
||||||
|
input::commands::GameCommand,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handles
|
||||||
|
pub fn interact_system(
|
||||||
|
mut events: EventReader<GameEvent>,
|
||||||
|
mut state: ResMut<GlobalState>,
|
||||||
|
mut players: Query<(&PlayerControlled, &mut Velocity)>,
|
||||||
|
mut errors: EventWriter<GameError>,
|
||||||
|
) {
|
||||||
|
// Get the player's velocity (handling to ensure there is only one player)
|
||||||
|
let mut velocity = match players.single_mut() {
|
||||||
|
Ok((_, velocity)) => velocity,
|
||||||
|
Err(e) => {
|
||||||
|
errors.write(GameError::InvalidState(format!("Player not found: {}", e)).into());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle events
|
||||||
|
for event in events.read() {
|
||||||
|
match event {
|
||||||
|
GameEvent::Command(command) => match command {
|
||||||
|
GameCommand::MovePlayer(direction) => {
|
||||||
|
velocity.direction = *direction;
|
||||||
|
}
|
||||||
|
GameCommand::Exit => {
|
||||||
|
state.exit = true;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,11 @@ use glam::Vec2;
|
|||||||
use crate::{
|
use crate::{
|
||||||
entity::{direction::Direction, graph::Graph, traversal},
|
entity::{direction::Direction, graph::Graph, traversal},
|
||||||
error::{EntityError, GameResult},
|
error::{EntityError, GameResult},
|
||||||
texture::{directional::DirectionalAnimatedTexture, sprite::Sprite},
|
texture::{
|
||||||
|
animated::AnimatedTexture,
|
||||||
|
directional::DirectionalAnimatedTexture,
|
||||||
|
sprite::{AtlasTile, Sprite},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A tag component for entities that are controlled by the player.
|
/// A tag component for entities that are controlled by the player.
|
||||||
@@ -17,12 +21,21 @@ use crate::{
|
|||||||
pub struct PlayerControlled;
|
pub struct PlayerControlled;
|
||||||
|
|
||||||
/// A component for entities that have a sprite, with a layer for ordering.
|
/// A component for entities that have a sprite, with a layer for ordering.
|
||||||
|
///
|
||||||
|
/// This is intended to be modified by other entities allowing animation.
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct Renderable {
|
pub struct Renderable {
|
||||||
pub sprite: Sprite,
|
pub sprite: AtlasTile,
|
||||||
pub layer: u8,
|
pub layer: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A component for entities that have a directional animated texture.
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct DirectionalAnimated {
|
||||||
|
pub textures: [Option<AnimatedTexture>; 4],
|
||||||
|
pub stopped_textures: [Option<AnimatedTexture>; 4],
|
||||||
|
}
|
||||||
|
|
||||||
/// A unique identifier for a node, represented by its index in the graph's storage.
|
/// A unique identifier for a node, represented by its index in the graph's storage.
|
||||||
pub type NodeId = usize;
|
pub type NodeId = usize;
|
||||||
|
|
||||||
@@ -112,7 +125,7 @@ impl Position {
|
|||||||
#[derive(Default, Component)]
|
#[derive(Default, Component)]
|
||||||
pub struct Velocity {
|
pub struct Velocity {
|
||||||
pub direction: Direction,
|
pub direction: Direction,
|
||||||
pub speed: f32,
|
pub speed: Option<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Bundle)]
|
#[derive(Bundle)]
|
||||||
@@ -121,6 +134,7 @@ pub struct PlayerBundle {
|
|||||||
pub position: Position,
|
pub position: Position,
|
||||||
pub velocity: Velocity,
|
pub velocity: Velocity,
|
||||||
pub sprite: Renderable,
|
pub sprite: Renderable,
|
||||||
|
pub directional_animated: DirectionalAnimated,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
@@ -131,4 +145,5 @@ pub struct GlobalState {
|
|||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
pub struct DeltaTime(pub f32);
|
pub struct DeltaTime(pub f32);
|
||||||
|
|
||||||
|
pub mod interact;
|
||||||
pub mod render;
|
pub mod render;
|
||||||
|
|||||||
@@ -1,15 +1,36 @@
|
|||||||
use crate::ecs::{render, Position, Renderable};
|
use crate::ecs::{DeltaTime, DirectionalAnimated, Position, Renderable, Velocity};
|
||||||
use crate::entity::graph::Graph;
|
|
||||||
use crate::error::{EntityError, GameError, TextureError};
|
use crate::error::{EntityError, GameError, TextureError};
|
||||||
use crate::map::builder::Map;
|
use crate::map::builder::Map;
|
||||||
use crate::texture::sprite::{Sprite, SpriteAtlas};
|
use crate::texture::sprite::SpriteAtlas;
|
||||||
use bevy_ecs::entity::Entity;
|
use bevy_ecs::entity::Entity;
|
||||||
use bevy_ecs::event::EventWriter;
|
use bevy_ecs::event::EventWriter;
|
||||||
use bevy_ecs::query::With;
|
|
||||||
use bevy_ecs::system::{NonSendMut, Query, Res};
|
use bevy_ecs::system::{NonSendMut, Query, Res};
|
||||||
use sdl2::render::{Canvas, Texture};
|
use sdl2::render::{Canvas, Texture};
|
||||||
use sdl2::video::Window;
|
use sdl2::video::Window;
|
||||||
|
|
||||||
|
/// Updates the directional animated texture of an entity.
|
||||||
|
pub fn directional_render_system(
|
||||||
|
dt: Res<DeltaTime>,
|
||||||
|
mut renderables: Query<(&Velocity, &mut DirectionalAnimated, &mut Renderable)>,
|
||||||
|
mut errors: EventWriter<GameError>,
|
||||||
|
) {
|
||||||
|
for (velocity, mut texture, mut renderable) in renderables.iter_mut() {
|
||||||
|
let texture = if velocity.speed.is_none() {
|
||||||
|
texture.stopped_textures[velocity.direction.as_usize()].as_mut()
|
||||||
|
} else {
|
||||||
|
texture.textures[velocity.direction.as_usize()].as_mut()
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(texture) = texture {
|
||||||
|
texture.tick(dt.0);
|
||||||
|
renderable.sprite = *texture.current_tile();
|
||||||
|
} else {
|
||||||
|
errors.write(TextureError::RenderFailed(format!("Entity has no texture")).into());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct MapTextureResource(pub Texture<'static>);
|
pub struct MapTextureResource(pub Texture<'static>);
|
||||||
pub struct BackbufferResource(pub Texture<'static>);
|
pub struct BackbufferResource(pub Texture<'static>);
|
||||||
|
|
||||||
@@ -19,7 +40,7 @@ pub fn render_system(
|
|||||||
mut backbuffer: NonSendMut<BackbufferResource>,
|
mut backbuffer: NonSendMut<BackbufferResource>,
|
||||||
mut atlas: NonSendMut<SpriteAtlas>,
|
mut atlas: NonSendMut<SpriteAtlas>,
|
||||||
map: Res<Map>,
|
map: Res<Map>,
|
||||||
renderables: Query<(Entity, &Renderable, &Position)>,
|
mut renderables: Query<(Entity, &mut Renderable, &Position)>,
|
||||||
mut errors: EventWriter<GameError>,
|
mut errors: EventWriter<GameError>,
|
||||||
) {
|
) {
|
||||||
// Clear the main canvas first
|
// Clear the main canvas first
|
||||||
@@ -40,13 +61,18 @@ pub fn render_system(
|
|||||||
.map(|e| errors.write(TextureError::RenderFailed(e.to_string()).into()));
|
.map(|e| errors.write(TextureError::RenderFailed(e.to_string()).into()));
|
||||||
|
|
||||||
// Render all entities to the backbuffer
|
// Render all entities to the backbuffer
|
||||||
for (_, renderable, position) in renderables.iter() {
|
for (_, mut renderable, position) in renderables.iter_mut() {
|
||||||
let pos = position.get_pixel_pos(&map.graph);
|
let pos = position.get_pixel_pos(&map.graph);
|
||||||
match pos {
|
match pos {
|
||||||
Ok(pos) => {
|
Ok(pos) => {
|
||||||
|
let dest = crate::helpers::centered_with_size(
|
||||||
|
glam::IVec2::new(pos.x as i32, pos.y as i32),
|
||||||
|
glam::UVec2::new(renderable.sprite.size.x as u32, renderable.sprite.size.y as u32),
|
||||||
|
);
|
||||||
|
|
||||||
renderable
|
renderable
|
||||||
.sprite
|
.sprite
|
||||||
.render(backbuffer_canvas, &mut atlas, pos)
|
.render(backbuffer_canvas, &mut atlas, dest)
|
||||||
.err()
|
.err()
|
||||||
.map(|e| errors.write(TextureError::RenderFailed(e.to_string()).into()));
|
.map(|e| errors.write(TextureError::RenderFailed(e.to_string()).into()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,9 @@
|
|||||||
include!(concat!(env!("OUT_DIR"), "/atlas_data.rs"));
|
include!(concat!(env!("OUT_DIR"), "/atlas_data.rs"));
|
||||||
|
|
||||||
use crate::constants::CANVAS_SIZE;
|
use crate::constants::CANVAS_SIZE;
|
||||||
use crate::ecs::render::{render_system, BackbufferResource, MapTextureResource};
|
use crate::ecs::interact::interact_system;
|
||||||
use crate::ecs::{DeltaTime, GlobalState, PlayerBundle, PlayerControlled, Position, Renderable, Velocity};
|
use crate::ecs::render::{directional_render_system, render_system, BackbufferResource, MapTextureResource};
|
||||||
|
use crate::ecs::{DeltaTime, DirectionalAnimated, GlobalState, PlayerBundle, PlayerControlled, Position, Renderable, Velocity};
|
||||||
use crate::entity::direction::Direction;
|
use crate::entity::direction::Direction;
|
||||||
use crate::entity::{graph, traversal};
|
use crate::entity::{graph, traversal};
|
||||||
use crate::error::{GameError, GameResult, TextureError};
|
use crate::error::{GameError, GameResult, TextureError};
|
||||||
@@ -101,18 +102,6 @@ impl Game {
|
|||||||
|
|
||||||
let map = Map::new(constants::RAW_BOARD)?;
|
let map = Map::new(constants::RAW_BOARD)?;
|
||||||
let pacman_start_node = map.start_positions.pacman;
|
let pacman_start_node = map.start_positions.pacman;
|
||||||
let player = PlayerBundle {
|
|
||||||
player: PlayerControlled,
|
|
||||||
position: Position::AtNode(pacman_start_node),
|
|
||||||
velocity: Velocity::default(),
|
|
||||||
sprite: Renderable {
|
|
||||||
sprite: Sprite::new(
|
|
||||||
SpriteAtlas::get_tile(&atlas, "pacman/full.png")
|
|
||||||
.ok_or_else(|| GameError::Texture(TextureError::AtlasTileNotFound("pacman/full.png".to_string())))?,
|
|
||||||
),
|
|
||||||
layer: 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut textures = [None, None, None, None];
|
let mut textures = [None, None, None, None];
|
||||||
let mut stopped_textures = [None, None, None, None];
|
let mut stopped_textures = [None, None, None, None];
|
||||||
@@ -140,6 +129,21 @@ impl Game {
|
|||||||
stopped_textures[direction.as_usize()] = Some(AnimatedTexture::new(stopped_tiles, 0.1)?);
|
stopped_textures[direction.as_usize()] = Some(AnimatedTexture::new(stopped_tiles, 0.1)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let player = PlayerBundle {
|
||||||
|
player: PlayerControlled,
|
||||||
|
position: Position::AtNode(pacman_start_node),
|
||||||
|
velocity: Velocity::default(),
|
||||||
|
sprite: Renderable {
|
||||||
|
sprite: SpriteAtlas::get_tile(&atlas, "pacman/full.png")
|
||||||
|
.ok_or_else(|| GameError::Texture(TextureError::AtlasTileNotFound("pacman/full.png".to_string())))?,
|
||||||
|
layer: 0,
|
||||||
|
},
|
||||||
|
directional_animated: DirectionalAnimated {
|
||||||
|
textures,
|
||||||
|
stopped_textures,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
world.insert_non_send_resource(atlas);
|
world.insert_non_send_resource(atlas);
|
||||||
world.insert_non_send_resource(event_pump);
|
world.insert_non_send_resource(event_pump);
|
||||||
world.insert_non_send_resource(canvas);
|
world.insert_non_send_resource(canvas);
|
||||||
@@ -160,7 +164,7 @@ impl Game {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
schedule.add_systems((handle_input, render_system).chain());
|
schedule.add_systems((handle_input, interact_system, directional_render_system, render_system).chain());
|
||||||
|
|
||||||
// Spawn player
|
// Spawn player
|
||||||
world.spawn(player);
|
world.spawn(player);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
|
event::EventWriter,
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
system::{Commands, NonSendMut, Res},
|
system::{Commands, NonSendMut, Res},
|
||||||
};
|
};
|
||||||
@@ -41,17 +42,17 @@ impl Default for Bindings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_input(bindings: Res<Bindings>, mut commands: Commands, mut pump: NonSendMut<&'static mut EventPump>) {
|
pub fn handle_input(bindings: Res<Bindings>, mut writer: EventWriter<GameEvent>, mut pump: NonSendMut<&'static mut EventPump>) {
|
||||||
for event in pump.poll_iter() {
|
for event in pump.poll_iter() {
|
||||||
match event {
|
match event {
|
||||||
Event::Quit { .. } => {
|
Event::Quit { .. } => {
|
||||||
commands.trigger(GameEvent::Command(GameCommand::Exit));
|
writer.write(GameEvent::Command(GameCommand::Exit));
|
||||||
}
|
}
|
||||||
Event::KeyDown { keycode: Some(key), .. } => {
|
Event::KeyDown { keycode: Some(key), .. } => {
|
||||||
let command = bindings.key_bindings.get(&key).copied();
|
let command = bindings.key_bindings.get(&key).copied();
|
||||||
if let Some(command) = command {
|
if let Some(command) = command {
|
||||||
tracing::info!("triggering command: {:?}", command);
|
tracing::info!("triggering command: {:?}", command);
|
||||||
commands.trigger(GameEvent::Command(command));
|
writer.write(GameEvent::Command(command));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|||||||
Reference in New Issue
Block a user