mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-06 01:15:42 -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::{
|
||||
entity::{direction::Direction, graph::Graph, traversal},
|
||||
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.
|
||||
@@ -17,12 +21,21 @@ use crate::{
|
||||
pub struct PlayerControlled;
|
||||
|
||||
/// 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)]
|
||||
pub struct Renderable {
|
||||
pub sprite: Sprite,
|
||||
pub sprite: AtlasTile,
|
||||
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.
|
||||
pub type NodeId = usize;
|
||||
|
||||
@@ -112,7 +125,7 @@ impl Position {
|
||||
#[derive(Default, Component)]
|
||||
pub struct Velocity {
|
||||
pub direction: Direction,
|
||||
pub speed: f32,
|
||||
pub speed: Option<f32>,
|
||||
}
|
||||
|
||||
#[derive(Bundle)]
|
||||
@@ -121,6 +134,7 @@ pub struct PlayerBundle {
|
||||
pub position: Position,
|
||||
pub velocity: Velocity,
|
||||
pub sprite: Renderable,
|
||||
pub directional_animated: DirectionalAnimated,
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
@@ -131,4 +145,5 @@ pub struct GlobalState {
|
||||
#[derive(Resource)]
|
||||
pub struct DeltaTime(pub f32);
|
||||
|
||||
pub mod interact;
|
||||
pub mod render;
|
||||
|
||||
@@ -1,15 +1,36 @@
|
||||
use crate::ecs::{render, Position, Renderable};
|
||||
use crate::entity::graph::Graph;
|
||||
use crate::ecs::{DeltaTime, DirectionalAnimated, Position, Renderable, Velocity};
|
||||
use crate::error::{EntityError, GameError, TextureError};
|
||||
use crate::map::builder::Map;
|
||||
use crate::texture::sprite::{Sprite, SpriteAtlas};
|
||||
use crate::texture::sprite::SpriteAtlas;
|
||||
use bevy_ecs::entity::Entity;
|
||||
use bevy_ecs::event::EventWriter;
|
||||
use bevy_ecs::query::With;
|
||||
use bevy_ecs::system::{NonSendMut, Query, Res};
|
||||
use sdl2::render::{Canvas, Texture};
|
||||
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 BackbufferResource(pub Texture<'static>);
|
||||
|
||||
@@ -19,7 +40,7 @@ pub fn render_system(
|
||||
mut backbuffer: NonSendMut<BackbufferResource>,
|
||||
mut atlas: NonSendMut<SpriteAtlas>,
|
||||
map: Res<Map>,
|
||||
renderables: Query<(Entity, &Renderable, &Position)>,
|
||||
mut renderables: Query<(Entity, &mut Renderable, &Position)>,
|
||||
mut errors: EventWriter<GameError>,
|
||||
) {
|
||||
// Clear the main canvas first
|
||||
@@ -40,13 +61,18 @@ pub fn render_system(
|
||||
.map(|e| errors.write(TextureError::RenderFailed(e.to_string()).into()));
|
||||
|
||||
// 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);
|
||||
match 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
|
||||
.sprite
|
||||
.render(backbuffer_canvas, &mut atlas, pos)
|
||||
.render(backbuffer_canvas, &mut atlas, dest)
|
||||
.err()
|
||||
.map(|e| errors.write(TextureError::RenderFailed(e.to_string()).into()));
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
include!(concat!(env!("OUT_DIR"), "/atlas_data.rs"));
|
||||
|
||||
use crate::constants::CANVAS_SIZE;
|
||||
use crate::ecs::render::{render_system, BackbufferResource, MapTextureResource};
|
||||
use crate::ecs::{DeltaTime, GlobalState, PlayerBundle, PlayerControlled, Position, Renderable, Velocity};
|
||||
use crate::ecs::interact::interact_system;
|
||||
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::{graph, traversal};
|
||||
use crate::error::{GameError, GameResult, TextureError};
|
||||
@@ -101,18 +102,6 @@ impl Game {
|
||||
|
||||
let map = Map::new(constants::RAW_BOARD)?;
|
||||
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 stopped_textures = [None, None, None, None];
|
||||
@@ -140,6 +129,21 @@ impl Game {
|
||||
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(event_pump);
|
||||
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
|
||||
world.spawn(player);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use bevy_ecs::{
|
||||
event::EventWriter,
|
||||
resource::Resource,
|
||||
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() {
|
||||
match event {
|
||||
Event::Quit { .. } => {
|
||||
commands.trigger(GameEvent::Command(GameCommand::Exit));
|
||||
writer.write(GameEvent::Command(GameCommand::Exit));
|
||||
}
|
||||
Event::KeyDown { keycode: Some(key), .. } => {
|
||||
let command = bindings.key_bindings.get(&key).copied();
|
||||
if let Some(command) = command {
|
||||
tracing::info!("triggering command: {:?}", command);
|
||||
commands.trigger(GameEvent::Command(command));
|
||||
writer.write(GameEvent::Command(command));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
||||
Reference in New Issue
Block a user