Merge branch '38-game-configuration' into 'dev'

feat: Game configuration #38

See merge request softwaregrundprojekt/2025-2026/einzelprojekt/tutorium-moritz/bernroider-dominik/bernroider-dominik!7
This commit is contained in:
Dominik Bernroider
2025-11-24 13:57:55 +00:00
10 changed files with 103 additions and 36 deletions

2
Cargo.lock generated
View File

@@ -3622,6 +3622,8 @@ dependencies = [
"bevy",
"bevy_aseprite_ultra",
"bevy_dev_tools",
"serde",
"serde_json",
]
[[package]]

View File

@@ -17,3 +17,5 @@ lto = "thin"
bevy = "0.17.2"
bevy_aseprite_ultra = "0.7.0"
bevy_dev_tools = "0.17.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

5
assets/config.json Normal file
View File

@@ -0,0 +1,5 @@
{
"grid_width": 10,
"grid_height": 10,
"pom_speed": 2
}

View File

@@ -24,10 +24,10 @@
rust-toolchain = with fenix.packages.${system};
combine [
beta.rustc
beta.cargo
beta.rust-src
beta.rust-analyzer
stable.rustc
stable.cargo
stable.rust-src
stable.rust-analyzer
];
bevyDeps = with pkgs; [

17
src/config.rs Normal file
View File

@@ -0,0 +1,17 @@
use bevy::prelude::*;
use serde::Deserialize;
use std::fs::File;
use std::io::BufReader;
#[derive(Resource, Deserialize, Debug)]
pub struct GameConfig {
pub grid_width: u32,
pub grid_height: u32,
pub pom_speed: f32,
}
pub fn read_config() -> Option<GameConfig> {
let file = File::open("assets/config.json").ok()?;
let reader = BufReader::new(file);
serde_json::from_reader(reader).ok()
}

View File

@@ -1,4 +1,5 @@
pub mod components;
pub mod config;
pub mod errors;
pub mod messages;
pub mod plugins;

View File

@@ -1,9 +1,12 @@
use bevy::prelude::*;
use bevy_aseprite_ultra::prelude::*;
use bevy_dev_tools::fps_overlay::*;
use pomomon_garden::config::read_config;
use pomomon_garden::plugins;
fn main() {
let config = read_config().expect("Error reading config");
App::new()
.add_plugins((
DefaultPlugins.set(ImagePlugin::default_nearest()),
@@ -29,5 +32,6 @@ fn main() {
plugins::PomPlugin,
plugins::InputPlugin,
))
.insert_resource(config)
.run();
}

View File

@@ -1,16 +1,19 @@
use crate::{
components::tile::{Grid, Tile, TileState},
config::GameConfig,
states::AppState,
};
use bevy::prelude::*;
use bevy_aseprite_ultra::prelude::AseSlice;
pub const TILE_SIZE: f32 = 32.0;
pub const GRID_WIDTH: u32 = 10;
pub const GRID_HEIGHT: u32 = 10;
pub const GRID_START_X: f32 = -(GRID_WIDTH as f32 * TILE_SIZE) / 2.0 + TILE_SIZE / 2.0;
pub const GRID_START_Y: f32 = -(GRID_HEIGHT as f32 * TILE_SIZE) / 2.0 + TILE_SIZE / 2.0;
pub fn grid_start_x(grid_width: u32) -> f32 {
-(grid_width as f32 * TILE_SIZE) / 2.0 + TILE_SIZE / 2.0
}
pub fn grid_start_y(grid_height: u32) -> f32 {
-(grid_height as f32 * TILE_SIZE) / 2.0 + TILE_SIZE / 2.0
}
pub struct GridPlugin;
@@ -26,13 +29,15 @@ impl Plugin for GridPlugin {
}
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
let mut tiles = Vec::with_capacity(GRID_WIDTH as usize);
fn setup(mut commands: Commands, asset_server: Res<AssetServer>, config: Res<GameConfig>) {
let grid_width = config.grid_width;
let grid_height = config.grid_height;
let mut tiles = Vec::with_capacity(grid_width as usize);
for x in 0..GRID_WIDTH {
let mut column = Vec::with_capacity(GRID_HEIGHT as usize);
for x in 0..grid_width {
let mut column = Vec::with_capacity(grid_height as usize);
for y in 0..GRID_HEIGHT {
for y in 0..grid_height {
let tile_entity = commands
.spawn((
Tile { x, y },
@@ -42,7 +47,13 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
aseprite: asset_server.load("tiles/tile-unclaimed.aseprite"),
},
Sprite::default(),
Transform::from_translation(grid_to_world_coords(x, y, None)),
Transform::from_translation(grid_to_world_coords(
x,
y,
None,
grid_width,
grid_height,
)),
))
.id();
column.push(tile_entity);
@@ -51,8 +62,8 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
}
commands.insert_resource(Grid {
width: GRID_WIDTH,
height: GRID_HEIGHT,
width: grid_width,
height: grid_height,
tiles,
});
}
@@ -84,27 +95,36 @@ fn update_tile_colors(
}
}
pub fn world_to_grid_coords(world_pos: Vec3) -> (u32, u32) {
let x = ((world_pos.x - GRID_START_X + TILE_SIZE / 2.0) / TILE_SIZE).floor();
let y = ((world_pos.y - GRID_START_Y + TILE_SIZE / 2.0) / TILE_SIZE).floor();
pub fn world_to_grid_coords(world_pos: Vec3, grid_width: u32, grid_height: u32) -> (u32, u32) {
let start_x = grid_start_x(grid_width);
let start_y = grid_start_y(grid_height);
let x = ((world_pos.x - start_x + TILE_SIZE / 2.0) / TILE_SIZE).floor();
let y = ((world_pos.y - start_y + TILE_SIZE / 2.0) / TILE_SIZE).floor();
let mut x_u32 = x as u32;
let mut y_u32 = y as u32;
if x_u32 >= GRID_WIDTH {
x_u32 = GRID_WIDTH - 1;
if x_u32 >= grid_width {
x_u32 = grid_width - 1;
}
if y_u32 >= GRID_HEIGHT {
y_u32 = GRID_HEIGHT - 1;
if y_u32 >= grid_height {
y_u32 = grid_height - 1;
}
(x_u32, y_u32)
}
pub fn grid_to_world_coords(grid_x: u32, grid_y: u32, z: Option<f32>) -> Vec3 {
pub fn grid_to_world_coords(
grid_x: u32,
grid_y: u32,
z: Option<f32>,
grid_width: u32,
grid_height: u32,
) -> Vec3 {
Vec3::new(
GRID_START_X + grid_x as f32 * TILE_SIZE,
GRID_START_Y + grid_y as f32 * TILE_SIZE,
grid_start_x(grid_width) + grid_x as f32 * TILE_SIZE,
grid_start_y(grid_height) + grid_y as f32 * TILE_SIZE,
z.unwrap_or(0.0),
)
}

View File

@@ -3,6 +3,7 @@ use bevy::prelude::*;
use bevy::window::PrimaryWindow;
use crate::components::tile::{Grid, TileState};
use crate::config::GameConfig;
use crate::messages::{InteractStartMessage, InvalidMoveMessage, MoveMessage};
use crate::plugins::grid::world_to_grid_coords;
use crate::states::AppState;
@@ -28,6 +29,7 @@ fn move_click(
mouse_btn: Res<ButtonInput<MouseButton>>,
window: Single<&Window, With<PrimaryWindow>>,
camera: Single<(&Camera, &GlobalTransform), With<Camera2d>>,
config: Res<GameConfig>,
) {
if mouse_btn.just_pressed(MouseButton::Right) {
let (cam, cam_transform) = *camera;
@@ -38,7 +40,7 @@ fn move_click(
let Ok(world_pos) = cam.viewport_to_world(cam_transform, cursor_pos) else {
return;
};
let (x, y) = world_to_grid_coords(world_pos.origin);
let (x, y) = world_to_grid_coords(world_pos.origin, config.grid_width, config.grid_height);
println!("Move Click: ({}, {})", x, y);
move_messages.write(MoveMessage { x, y });
@@ -50,6 +52,7 @@ fn interact_click(
mouse_btn: Res<ButtonInput<MouseButton>>,
window: Single<&Window, With<PrimaryWindow>>,
camera: Single<(&Camera, &GlobalTransform), With<Camera2d>>,
config: Res<GameConfig>,
// for debug
grid: ResMut<Grid>,
tile_query: Query<&mut TileState>,
@@ -63,7 +66,7 @@ fn interact_click(
let Ok(world_pos) = cam.viewport_to_world(cam_transform, cursor_pos) else {
return;
};
let (x, y) = world_to_grid_coords(world_pos.origin);
let (x, y) = world_to_grid_coords(world_pos.origin, config.grid_width, config.grid_height);
println!("Interact Click: ({}, {})", x, y);
interact_messages.write(InteractStartMessage { x, y });

View File

@@ -1,14 +1,13 @@
use crate::components::pom::{GridPosition, MovingState, PathQueue, Pom};
use crate::components::tile::{Grid, TileState};
use crate::config::GameConfig;
use crate::messages::{InvalidMoveMessage, MoveMessage};
use crate::plugins::grid::{GRID_WIDTH, grid_to_world_coords};
use crate::plugins::grid::{TILE_SIZE, grid_to_world_coords};
use crate::states::*;
use crate::utils::pathfinding::find_path;
use bevy::prelude::*;
use bevy_aseprite_ultra::prelude::*;
const MOVE_SPEED: f32 = 2.0 * GRID_WIDTH as f32;
pub struct PomPlugin;
impl Plugin for PomPlugin {
@@ -23,7 +22,7 @@ impl Plugin for PomPlugin {
}
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
fn setup(mut commands: Commands, asset_server: Res<AssetServer>, config: Res<GameConfig>) {
commands.spawn((
Pom,
GridPosition { x: 0, y: 0 },
@@ -34,7 +33,13 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
animation: Animation::tag("sleep-sit-start").with_repeat(AnimationRepeat::Loop),
},
Sprite::default(),
Transform::from_translation(grid_to_world_coords(0, 0, Some(1.0))),
Transform::from_translation(grid_to_world_coords(
0,
0,
Some(1.0),
config.grid_width,
config.grid_height,
)),
));
}
@@ -82,12 +87,20 @@ fn move_pom(
&mut PathQueue,
&mut MovingState,
)>,
config: Res<GameConfig>,
) {
let dt = time.delta_secs();
let move_speed = config.pom_speed * TILE_SIZE;
for (mut transform, mut grid_pos, mut path_queue, mut moving_state) in query.iter_mut() {
if let Some(&target) = path_queue.steps.front() {
let target_pos = grid_to_world_coords(target.0, target.1, Some(1.0));
let target_pos = grid_to_world_coords(
target.0,
target.1,
Some(1.0),
config.grid_width,
config.grid_height,
);
let distance = transform.translation.distance(target_pos);
let dx = target.0 as i32 - grid_pos.x as i32;
@@ -100,14 +113,14 @@ fn move_pom(
_ => (),
}
if distance < MOVE_SPEED * dt {
if distance < move_speed * dt {
transform.translation = target_pos;
grid_pos.x = target.0;
grid_pos.y = target.1;
path_queue.steps.pop_front();
} else {
let direction = (target_pos - transform.translation).normalize();
transform.translation += direction * MOVE_SPEED * dt;
transform.translation += direction * move_speed * dt;
}
} else {
*moving_state = MovingState::Idle;