🗂
Rustのゲームエンジンbevyにて倉庫番を実装
Cargo.toml
[package]
name = "reversi"
version = "0.1.0"
edition = "2021"
[dependencies]
bevy = "0.10"
bevy_prototype_debug_lines = "0.10"
main.rs
pub mod components;
pub mod resources;
pub mod states;
mod systems;
use bevy::prelude::*;
use bevy_prototype_debug_lines::*;
use systems::*;
use resources::*;
use components::*;
use states::*;
fn main() {
App::new()
.init_resource::<Stage>()
.init_resource::<ObjectMoveTimer>()
.insert_resource(ClearColor(COLOR_BACK_GROUND))
.add_state::<PlayerState>()
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
title: "soukoban!".into(),
resolution: (WINDOW_WIDTH, WINDOW_HEIGHT).into(),
..default()
}),
..default()
}))
.add_startup_system(spawn_camera)
.add_startup_system(spawn_stage)
.add_system(bevy::window::close_on_esc)
.add_plugin(DebugLinesPlugin::default())
.add_system(draw_grid)
.add_system(
input_direction
.run_if(in_state(PlayerState::Stop))
)
.add_system(
reflect_position
.in_schedule(OnExit(PlayerState::Stop))
)
.add_system(
object_movement
.run_if(in_state(PlayerState::Move))
)
.add_system(
debug_print_stage
.in_schedule(OnExit(PlayerState::Move))
)
.run();
}
components.rs
use super::resources::*;
use bevy::prelude::*;
pub const X_BOTTOM: f32 = -250.0;
pub const Y_BOTTOM: f32 = -250.0;
pub const WINDOW_WIDTH: f32 = 500.0;
pub const WINDOW_HEIGHT: f32 = 500.0;
pub const GRID_SIZE: f32 = WINDOW_WIDTH / GRID_X_LENGTH as f32;
#[derive(Component)]
pub struct Player {}
#[derive(Component)]
pub struct Box {}
#[derive(Component, Debug)]
pub struct Position {
pub x: i8,
pub y: i8,
}
impl Position {
pub fn translation(&self)->Vec3{
let x: f32 = self.x as f32 * GRID_SIZE + GRID_SIZE/2.0 + X_BOTTOM;
let y: f32 = self.y as f32 * GRID_SIZE + GRID_SIZE/2.0 + Y_BOTTOM;
return Vec3::new(x, y, 0.0);
}
}
#[derive(Component)]
pub struct MoveDirection {
pub vec3: Vec3,
}
resources.rs
use std::usize;
use bevy::prelude::*;
pub const GRID_X_LENGTH: i8 = 8;
pub const GRID_Y_LENGTH: i8 = 8;
pub const COLOR_PLAYER: Color = Color::rgb(0.8, 0.4, 0.1);
pub const COLOR_BOX: Color = Color::rgb(0.1, 0.4, 0.8);
pub const COLOR_WALL: Color = Color::rgb(0.2, 0.2, 0.2);
pub const COLOR_BACK_GROUND: Color = Color::rgb(0.6, 0.6, 0.6);
pub const OBJECT_MOVE_TIME: f32 = 0.5;
pub const STAGE: &str = "
########:
#B #:
# B ###:
# P #:
###B# #:
# B ##:
# #:
########:
";
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum Object {
Wall,
Player,
Box,
Empty,
}
impl Object {
pub fn color(&self)->Color{
match self {
Object::Player => COLOR_PLAYER,
Object::Wall => COLOR_WALL,
Object::Box => COLOR_BOX,
Object::Empty => panic!(),
}
}
}
#[derive(Resource)]
pub struct Stage {
pub squares:[[Object; GRID_X_LENGTH as usize]; GRID_Y_LENGTH as usize]
}
impl Default for Stage {
fn default() -> Self {
let mut squares =
[[Object::Empty; GRID_X_LENGTH as usize]; GRID_Y_LENGTH as usize];
let mut i_y:isize = (GRID_Y_LENGTH - 1) as isize;
let mut i_x:usize = 0;
for char in STAGE.chars() {
match char {
'\n' => continue,
' ' => squares[i_y as usize][i_x] = Object::Empty,
':' => {
i_y -= 1;
i_x = 0;
continue;
},
'#' => squares[i_y as usize][i_x] = Object::Wall ,
'P' => squares[i_y as usize][i_x] = Object::Player,
'B' => squares[i_y as usize][i_x] = Object::Box,
_ =>{
panic!();
},
}
i_x += 1;
}
Stage {
squares: squares
}
}
}
impl Stage {
pub fn swap (&mut self, (x1, y1): (usize, usize), (x2, y2): (usize, usize)){
let buff = self.squares[y1][x1];
self.squares[y1][x1] = self.squares[y2][x2];
self.squares[y2][x2] = buff;
}
}
#[derive(Resource)]
pub struct ObjectMoveTimer {
pub timer: Timer,
}
impl Default for ObjectMoveTimer {
fn default() -> Self {
ObjectMoveTimer {
timer: Timer::from_seconds(OBJECT_MOVE_TIME, TimerMode::Repeating),
}
}
}
states.rs
use bevy::prelude::*;
#[derive(States, Debug, Clone, Copy, Eq, PartialEq, Hash, Default)]
pub enum PlayerState {
Move,
#[default]
Stop,
}
systems.rs
use super::resources::*;
use super::components::*;
use bevy::prelude::*;
use bevy::window::PrimaryWindow;
use bevy_prototype_debug_lines::*;
use super::states::*;
pub const OBJECT_MOVE_SPEED: f32 = GRID_SIZE / OBJECT_MOVE_TIME;
pub fn spawn_camera(mut commands: Commands){
commands.spawn(Camera2dBundle::default());
}
pub fn draw_grid(
window_query: Query<&Window, With<PrimaryWindow>>,
mut lines: ResMut<DebugLines>
) {
let window = window_query.get_single().unwrap();
let half_win_width = 0.5 * window.width();
let half_win_height = 0.5 * window.height();
let x_space = window.width() / GRID_X_LENGTH as f32;
let y_space = window.height() / GRID_Y_LENGTH as f32;
let mut i = -1. * half_win_height;
while i < half_win_height {
lines.line(
Vec3::new(-1. * half_win_width, i, 0.0),
Vec3::new(half_win_width, i, 0.0),
0.0,
);
i += y_space;
}
i = -1. * half_win_width;
while i < half_win_width {
lines.line(
Vec3::new(i, -1. * half_win_height, 0.0),
Vec3::new(i, half_win_height, 0.0),
0.0,
);
i += x_space;
}
lines.line(
Vec3::new(0., -1. * half_win_height, 0.0),
Vec3::new(0., half_win_height, 0.0),
0.0,
);
}
pub fn spawn_stage (
mut commands: Commands,
stage: Res<Stage>
){
for i_y in 0..GRID_Y_LENGTH {
for i_x in 0..GRID_X_LENGTH {
let positon = Position { x: i_x, y: i_y };
let squares = stage.squares[i_y as usize][i_x as usize];
if squares == Object::Empty {
continue;
}
if squares == Object::Player {
commands.spawn((
SpriteBundle {
sprite: Sprite {
color: squares.color(),
..default()
},
transform: Transform {
scale: Vec3::new(GRID_SIZE, GRID_SIZE, 10.0),
translation: positon.translation(),
..default()
},
..default()
},
positon,
Player{},
));
} else if squares == Object::Wall {
commands.spawn((
SpriteBundle {
sprite: Sprite {
color: squares.color(),
..default()
},
transform: Transform {
scale: Vec3::new(GRID_SIZE, GRID_SIZE, 10.0),
translation: positon.translation(),
..default()
},
..default()
},
));
} else if squares == Object::Box {
commands.spawn((
SpriteBundle {
sprite: Sprite {
color: squares.color(),
..default()
},
transform: Transform {
scale: Vec3::new(GRID_SIZE, GRID_SIZE, 10.0),
translation: positon.translation(),
..default()
},
..default()
},
positon,
Box {},
));
}
}
}
}
pub fn input_direction (
mut commands: Commands,
mut player_query: Query<(Entity, &Position), With<Player>>,
mut box_query: Query<(Entity, &Position), (With<Box>, Without<Player>)>,
keyboard_input: Res<Input<KeyCode>>,
mut stage: ResMut<Stage>,
){
if let Ok((player_entity, player_posision)) = player_query.get_single_mut() {
let direction = if keyboard_input.pressed(KeyCode::Left){
Vec3::new(-1.0, 0.0, 0.0)
} else if keyboard_input.pressed(KeyCode::Right){
Vec3::new(1.0, 0.0, 0.0)
} else if keyboard_input.pressed(KeyCode::Up){
Vec3::new(0.0, 1.0, 0.0)
} else if keyboard_input.pressed(KeyCode::Down){
Vec3::new(0.0, -1.0, 0.0)
} else {
Vec3::ZERO
};
if direction == Vec3::ZERO {
return;
}
// 移動に伴い座標の値が変化する座標を保存
let mut swap_position_list: Vec<Position> = Vec::new();
swap_position_list.push(Position { x: player_posision.x, y: player_posision.y });
// 移動先に障害物にないか調査
let mut i_x = (player_posision.x as isize + direction.x as isize) as usize;
let mut i_y = (player_posision.y as isize + direction.y as isize) as usize;
while stage.squares[i_y][i_x] != Object::Empty && stage.squares[i_y][i_x] != Object::Wall {
swap_position_list.push(Position { x: i_x as i8, y: i_y as i8});
i_x = (i_x as isize + direction.x as isize) as usize;
i_y = (i_y as isize + direction.y as isize) as usize;
}
swap_position_list.push(Position { x: i_x as i8, y: i_y as i8});
// 移動先に壁があった場合は移動をしない
if stage.squares[i_y][i_x] == Object::Wall {
return;
}
// 移動に併せて配列stageの中身を変更
for index in (1..swap_position_list.len()).rev() {
stage.swap(
(swap_position_list[index].x as usize, swap_position_list[index].y as usize),
(swap_position_list[index-1].x as usize, swap_position_list[index-1].y as usize)
);
}
for (box_entity, box_position) in box_query.iter_mut() {
if swap_position_list.iter().any(|position|
position.x == box_position.x && position.y == box_position.y)
{
commands.entity(box_entity).insert(MoveDirection{
vec3: direction
});
}
}
commands.entity(player_entity)
.insert(MoveDirection{
vec3: direction
});
commands.insert_resource(NextState(Some(PlayerState::Move)));
}
}
pub fn reflect_position (
mut object_query: Query<(&mut Position, &MoveDirection)>,
) {
for (mut position, move_direction) in object_query.iter_mut() {
position.x = (position.x as isize + move_direction.vec3.x as isize) as i8;
position.y = (position.y as isize + move_direction.vec3.y as isize) as i8;
}
}
pub fn object_movement (
mut commands: Commands,
mut object_move_timer: ResMut<ObjectMoveTimer>,
mut object_query: Query<(Entity, &Position, &mut Transform, &MoveDirection)>,
time: Res<Time>,
) {
for (entity, posision, mut transform, move_direction) in object_query.iter_mut(){
transform.translation += move_direction.vec3 * OBJECT_MOVE_SPEED * time.delta_seconds();
if object_move_timer.timer.finished() {
transform.translation = posision.translation();
commands.entity(entity)
.remove::<MoveDirection>();
commands.insert_resource(NextState(Some(PlayerState::Stop)));
}
}
object_move_timer.timer.tick(time.delta());
}
pub fn debug_print_stage (
stage: Res<Stage>,
) {
for i_y in (0..GRID_Y_LENGTH).rev() {
for i_x in 0..GRID_X_LENGTH {
let c = match stage.squares[i_y as usize][i_x as usize] {
Object::Player => 'P',
Object::Box => 'B',
Object::Wall => '#',
Object::Empty => ' ',
};
print!("{}", c);
}
print!("\n");
}
}
Discussion