ユーザ用ツール

サイト用ツール


youtube:bevy-sokoban

差分

このページの2つのバージョン間の差分を表示します。

この比較画面へのリンク

両方とも前のリビジョン前のリビジョン
次のリビジョン
前のリビジョン
youtube:bevy-sokoban [2024/02/28 18:26] – 削除 - 外部編集 (不明な日付) 127.0.0.1youtube:bevy-sokoban [2024/02/29 11:49] (現在) freemikan
行 1: 行 1:
 +====== 倉庫番 ======
 +
 +{{youtube:sokoban.png?400|}}
 +
 +===== src/main.rs =====
 +
 +
 +<file rust>
 +use bevy::prelude::*;
 +use bevy::time::FixedTimestep;
 +
 +const ARENA_WIDTH: i32 = 10;
 +const ARENA_HEIGHT: i32 = 10;
 +const TILE_SIZE: Vec2 = Vec2::new(30.0, 30.0);
 +const SCREEN_SIZE: Vec2 = Vec2::new(
 +    TILE_SIZE.x * ARENA_WIDTH as f32,
 +    TILE_SIZE.y * ARENA_HEIGHT as f32,
 +);
 +const PLAYER_COLOR: Color = Color::AQUAMARINE;
 +const CARGO_COLOR: Color = Color::BEIGE;
 +const GOAL_COLOR: Color = Color::CRIMSON;
 +
 +fn main() {
 +    App::new()
 +        .add_plugins(DefaultPlugins.set(WindowPlugin {
 +            window: WindowDescriptor {
 +                title: "SOKOBAN".to_string(),
 +                width: SCREEN_SIZE.x,
 +                height: SCREEN_SIZE.y,
 +                ..default()
 +            },
 +            ..default()
 +        }))
 +        .insert_resource(ClearColor(Color::BLUE))
 +        .add_startup_system(setup)
 +        .add_system(bevy::window::close_on_esc)
 +        .add_system_set(
 +            SystemSet::new()
 +                .with_run_criteria(FixedTimestep::step(1.0 / 60.0)) // BUG: any other FPS of 60 may not handle keyboard input as expected
 +                .with_system(move_player_and_cargo)
 +                .with_system(position_translation.after(move_player_and_cargo))
 +                .with_system(check_level_complete.after(move_player_and_cargo))
 +                .with_system(complete_level.after(check_level_complete)),
 +        )
 +        .add_event::<LevelCompleteEvent>()
 +        .run();
 +}
 +
 +#[derive(Component, Debug, PartialEq, Eq, Clone, Copy)]
 +struct Position {
 +    x: i32,
 +    y: i32,
 +    z: i32,
 +}
 +
 +#[derive(Component)]
 +struct Cargo;
 +
 +#[derive(Component)]
 +struct Player;
 +
 +#[derive(Component)]
 +struct Goal;
 +
 +#[derive(Default)]
 +struct LevelCompleteEvent;
 +
 +fn setup(mut commands: Commands) {
 +    commands.spawn(Camera2dBundle::default());
 +
 +    // spawn Player
 +    commands
 +        .spawn(SpriteBundle {
 +            sprite: Sprite {
 +                color: PLAYER_COLOR,
 +                ..default()
 +            },
 +            transform: Transform {
 +                scale: Vec3::new(TILE_SIZE.x, TILE_SIZE.y, 0.0),
 +                ..default()
 +            },
 +            ..default()
 +        })
 +        .insert(Player)
 +        .insert(Position {
 +            x: ARENA_WIDTH / 2,
 +            y: 0,
 +            z: 3,
 +        });
 +
 +    // spawn Cargo
 +    for (x, y) in (0..ARENA_WIDTH)
 +        .step_by(2)
 +        .zip(std::iter::repeat(ARENA_HEIGHT - 3))
 +        .chain(
 +            (1..ARENA_WIDTH)
 +                .step_by(2)
 +                .zip(std::iter::repeat(ARENA_HEIGHT - 4)),
 +        )
 +    {
 +        commands
 +            .spawn(SpriteBundle {
 +                sprite: Sprite {
 +                    color: CARGO_COLOR,
 +                    ..default()
 +                },
 +                transform: Transform {
 +                    scale: Vec3::new(TILE_SIZE.x * 0.8, TILE_SIZE.y * 0.8, 0.0),
 +                    ..default()
 +                },
 +                ..default()
 +            })
 +            .insert(Cargo)
 +            .insert(Position { x, y, z: 2 });
 +    }
 +
 +    // spawn Goal
 +    for x in 0..ARENA_WIDTH {
 +        commands
 +            .spawn(SpriteBundle {
 +                sprite: Sprite {
 +                    color: GOAL_COLOR,
 +                    ..default()
 +                },
 +                transform: Transform {
 +                    scale: Vec3::new(TILE_SIZE.x * 0.9, TILE_SIZE.y * 0.9, 0.0),
 +                    ..default()
 +                },
 +                ..default()
 +            })
 +            .insert(Goal)
 +            .insert(Position {
 +                x,
 +                y: ARENA_HEIGHT - 1,
 +                z: 1,
 +            });
 +    }
 +}
 +
 +fn position_translation(mut query: Query<(&Position, &mut Transform)>) {
 +    for (p, mut t) in query.iter_mut() {
 +        t.translation = Vec3::new(
 +            p.x as f32 * TILE_SIZE.x - (SCREEN_SIZE.x / 2.0) + (TILE_SIZE.x / 2.0),
 +            p.y as f32 * TILE_SIZE.y - (SCREEN_SIZE.y / 2.0) + (TILE_SIZE.y / 2.0),
 +            p.z as f32,
 +        );
 +    }
 +}
 +
 +#[derive(Debug, Copy, Clone, PartialEq, Eq)]
 +enum Direction {
 +    Up,
 +    Down,
 +    Left,
 +    Right,
 +    None,
 +}
 +
 +fn move_player_and_cargo(
 +    keyboard_input: Res<Input<KeyCode>>,
 +    mut player_positions: Query<&mut Position, (With<Player>, Without<Cargo>)>,
 +    mut cargo_positions: Query<&mut Position, (With<Cargo>, Without<Player>)>,
 +) {
 +    let direction = if keyboard_input.just_pressed(KeyCode::Up) {
 +        println!("up");
 +        Direction::Up
 +    } else if keyboard_input.just_pressed(KeyCode::Down) {
 +        Direction::Down
 +    } else if keyboard_input.just_pressed(KeyCode::Left) {
 +        Direction::Left
 +    } else if keyboard_input.just_pressed(KeyCode::Right) {
 +        Direction::Right
 +    } else {
 +        Direction::None
 +    };
 +
 +    let mut player_position = player_positions.single_mut();
 +
 +    let next_position = |p: &Position, direction, step| match direction {
 +        Direction::Up => Position {
 +            x: p.x,
 +            y: p.y + step,
 +            z: p.z,
 +        },
 +        Direction::Down => Position {
 +            x: p.x,
 +            y: p.y - step,
 +            z: p.z,
 +        },
 +        Direction::Left => Position {
 +            x: p.x - step,
 +            y: p.y,
 +            z: p.z,
 +        },
 +        Direction::Right => Position {
 +            x: p.x + step,
 +            y: p.y,
 +            z: p.z,
 +        },
 +        _ => *p,
 +    };
 +
 +    let inside_arene =
 +        |p: &Position| p.x >= 0 && p.y >= 0 && p.x < ARENA_WIDTH && p.y < ARENA_HEIGHT;
 +
 +    // try to push cargo and update player and cargo position if it possible
 +    let new_player_position = next_position(&player_position, direction, 1);
 +    let new_cargo_position = next_position(&player_position, direction, 2);
 +    let new_cargo_position_empty = cargo_positions
 +        .iter()
 +        .all(|cp| !(cp.x == new_cargo_position.x && cp.y == new_cargo_position.y));
 +
 +    if let Some(mut cargo_forward) = cargo_positions
 +        .iter_mut()
 +        .find(|cp| cp.x == new_player_position.x && cp.y == new_player_position.y)
 +    {
 +        if new_cargo_position_empty && inside_arene(&new_cargo_position) {
 +            *cargo_forward = new_cargo_position;
 +            *player_position = new_player_position;
 +        }
 +    } else {
 +        if inside_arene(&new_player_position) {
 +            *player_position = new_player_position;
 +        }
 +    }
 +}
 +
 +fn check_level_complete(
 +    debug_input: Res<Input<KeyCode>>,
 +    cargo_positions: Query<&Position, (With<Cargo>, Without<Goal>)>,
 +    goal_positions: Query<&Position, (With<Goal>, Without<Cargo>)>,
 +    mut level_complete_writer: EventWriter<LevelCompleteEvent>,
 +) {
 +    if debug_input.just_pressed(KeyCode::D) {
 +        level_complete_writer.send_default();
 +        return;
 +    }
 +
 +    for cargo in cargo_positions.iter() {
 +        let maybe_on_goal = goal_positions
 +            .iter()
 +            .find(|gp| gp.x == cargo.x && gp.y == cargo.y);
 +        if maybe_on_goal.is_none() {
 +            return;
 +        }
 +    }
 +    level_complete_writer.send_default();
 +}
 +
 +fn complete_level(
 +    mut commands: Commands,
 +    events: EventReader<LevelCompleteEvent>,
 +    asset_server: Res<AssetServer>,
 +) {
 +    if !events.is_empty() {
 +        events.clear();
 +
 +        println!("Complete");
 +
 +        let font_size = 50.0;
 +
 +        // show screen message
 +        commands.spawn(
 +            TextBundle::from_section(
 +                "COMPLETE!",
 +                TextStyle {
 +                    font: asset_server.load("fonts/RubikGemstones-Regular.ttf"),
 +                    font_size,
 +                    color: Color::PINK,
 +                },
 +            )
 +            .with_text_alignment(TextAlignment::CENTER) // what does this effect?
 +            .with_style(Style {
 +                position_type: PositionType::Absolute,
 +                position: UiRect {
 +                    // hard-code position to display text center of screen
 +                    top: Val::Px(SCREEN_SIZE.y / 2.0 - 60.0 / 2.0),
 +                    left: Val::Px(SCREEN_SIZE.x / 2.0 - font_size * 2.5),
 +                    ..default()
 +                },
 +                ..default()
 +            }),
 +        );
 +    }
 +}
 +</file>
 +
 +===== Cargo.toml =====
 +
 +
 +<file>
 +[package]
 +name = "sokoban"
 +version = "0.1.0"
 +edition = "2021"
 +
 +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 +
 +[dependencies]
 +bevy = { version = "0.9.1", features = [ "dynamic" ] }
 +
 +[profile.dev]
 +opt-level = 1
 +
 +[profile.dev.package."*"]
 +opt-level = 3
 +</file>
  

特に明示されていない限り、本Wikiの内容は次のライセンスに従います: CC0 1.0 Universal
CC0 1.0 Universal Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki