Snakeゲームのクローンのチュートリアルをやる [後編]

前編からの続きです。

https://qiita.com/fujino_tech/items/ae9ff0683f4b248a71f9

読んでいるチュートリアルはこちらです。

https://mbuffett.com/posts/bevy-snake-tutorial/

# More snake-like movement

Direction、SnakeHeadのコードはそのままです。

```rust
fn spawn_snake(mut commands: Commands) {
    commands.spawn((
        SnakeHead {
            direction: Direction::Up,
        },
```

生成時に方向の初期値としてUpを設定しておきます。

ふたつのタイミングでシステムを動かしたくなったので、`on_timer`を使うことにします。

```rust
.add_systems(
    Update,
    (
        snake_movement_input.before(snake_movement),
        food_spawner.run_if(on_timer(Duration::from_secs_f32(1.0))),
        snake_movement.run_if(on_timer(Duration::from_secs_f32(0.5))),
    ),
)
```

`on_timer`モジュール？を追加しておきます。
```rust
use bevy::{prelude::*, time::common_conditions::on_timer, window::PrimaryWindow};
```

`snake_movement`と`snake_movement_input`自体の実装は以下のような感じで、そのままです。

```rust
fn snake_movement(mut heads: Query<(&mut Position, &SnakeHead)>) {
    if let Some((mut head_pos, head)) = heads.iter_mut().next() {
        match head.direction {
            Direction::Left => head_pos.x -= 1,
            Direction::Right => head_pos.x += 1,
            Direction::Up => head_pos.y += 1,
            Direction::Down => head_pos.y -= 1,
        }
    }
}

fn snake_movement_input(
    keyboard_input: Res<ButtonInput<KeyCode>>,
    mut heads: Query<&mut SnakeHead>,
) {
    if let Some(mut head) = heads.iter_mut().next() {
        let dir = if keyboard_input.pressed(KeyCode::ArrowLeft) {
            Direction::Left
        } else if keyboard_input.pressed(KeyCode::ArrowRight) {
            Direction::Right
        } else if keyboard_input.pressed(KeyCode::ArrowDown) {
            Direction::Down
        } else if keyboard_input.pressed(KeyCode::ArrowUp) {
            Direction::Up
        } else {
            head.direction
        };
        if dir != head.direction.opposite() {
            head.direction = dir;
        }
    }
}
```

# Adding a tail

SNAKE_SEGMENT_COLOR、SnakeSegmentコンポーネントの追加はそのままです。

```rust
#[derive(Resource, Default)]
struct SnakeSegments(Vec<Entity>);
```

リソースの作成には、Resourceの記述が必要になっているっぽいので、書きます。

リソースの挿入はそのままです。

```rust
fn spawn_segment(mut commands: Commands, position: Position) -> Entity{
    commands.spawn((
        SnakeSegment,
        Sprite {
            color: SNAKE_SEGMENT_COLOR,
            ..default()
        },
        position,
        Size::square(0.65),
    ))
    .id()
}
```

これまでどおりに、エンティティを生成し、最後にエンティティのid()を返す関数をつくります。

```rust
fn spawn_snake(mut commands: Commands, mut segments: ResMut<SnakeSegments>) {
    *segments = SnakeSegments(vec![
        commands.spawn((
            SnakeHead {
                direction: Direction::Up,
            },
            SnakeSegment,
            Sprite {
                color: SNAKE_HEAD_COLOR,
                ..default()
            },
            Position { x: 3, y: 3 },
            Size::square(0.8),
        ))
        .id(),
        spawn_segment(commands, Position { x: 3, y: 2 }),
    ]);
}
```

これもこれまでと同じように書いています。

SnakeSegmentコンポーネントはSnakeHeadコンポーネントのついたエンティティにもつけるんですね。

# Making the tail follow the snake

```rust
fn snake_movement(
    segments: ResMut<SnakeSegments>,
    mut heads: Query<(Entity, &SnakeHead)>,
    mut positions: Query<&mut Position>,
) {
    if let Some((head_entity, head)) = heads.iter_mut().next() {
        let segment_positions = segments
            .0.iter()
            .map(|e| *positions.get_mut(*e).unwrap())
            .collect::<Vec<Position>>();
        let mut head_pos = positions.get_mut(head_entity).unwrap();
        match &head.direction {
            Direction::Left => head_pos.x -= 1,
            Direction::Right => head_pos.x += 1,
            Direction::Up => head_pos.y += 1,
            Direction::Down => head_pos.y -= 1,
        };
        segment_positions
            .iter()
            .zip(segments.0.iter().skip(1))
            .for_each(|(pos, segment)| {
                *positions.get_mut(*segment).unwrap() = *pos;
            });
    }
}
```

```diff
- .iter()
+ .0.iter()
```

この変更があります。

SnakeSegments構造体は要素がひとつのタプル構造体のため、それにアクセスするには上記の記載が必要そうでした。

以下のあたりが、まだ理解できていません。
```rust
segment_positions
    .iter()
    .zip(segments.iter().skip(1))
    .for_each(|(pos, segment)| {
        *positions.get_mut(*segment).unwrap() = *pos;
    });
```

`segment_positions`と`segments.iter().skip(1)`を同期させて、ひとつのタプルに詰め合わせ、それに対してfor_eachしてるっぽいです。

https://doc.rust-lang.org/beta/std/iter/fn.zip.html

`segments`のほうはひとつ飛ばしをしていますが、その情報が`segment_positions`にも反映され、どちらも一つ目の要素を飛ばして読むように同期しているようです。

# Growing the snake

```rust
fn snake_eating(
    mut commands: Commands,
    mut growth_writer: EventWriter<GrowthEvent>,
    food_positions: Query<(Entity, &Position), With<Food>>,
    head_positions: Query<&Position, With<SnakeHead>>,
) {
    for head_pos in head_positions.iter() {
        for (food_entity, food_position) in food_positions.iter() {
            if head_pos == food_position {
                commands.entity(food_entity).despawn();
                growth_writer.send(GrowthEvent);
            }
        }
    }
}
```

今は`despawn`のやり方が変わって、こんな感じらしいです。

GrowthEventとAppへの追加はそのままです。

```rust
.add_systems(
    Update,
    (
        snake_movement_input.before(snake_movement),
        food_spawner.run_if(on_timer(Duration::from_secs_f32(1.0))),
        snake_movement.run_if(on_timer(Duration::from_secs_f32(0.5))),
        snake_eating.after(snake_movement),
    ),
)
```

`.after()`で順番を指定します。

```rust
#[derive(Resource, Default)]
struct LastTailPosition(Option<Position>);
```

リソース指定し、`insert_resource`しておきます。

```rust
*last_tail_position = LastTailPosition(Some(*segment_positions.last().unwrap()));
```

作成したリソースにSnakeSegmentsの最後の要素を入れておきます。

```rust
fn snake_growth(
    commands: Commands,
    last_tail_position: Res<LastTailPosition>,
    mut segments: ResMut<SnakeSegments>,
    mut growth_reader: EventReader<GrowthEvent>,
) {
    if growth_reader.read().next().is_some() {
        segments
            .0
            .push(spawn_segment(commands, last_tail_position.0.unwrap()));
    }
}
```

今のイベントの読み出し方は上記のように書いたら動きました。

```rust
.add_systems(
    Update,
    (
        snake_movement_input.before(snake_movement),
        food_spawner.run_if(on_timer(Duration::from_secs_f32(1.0))),
        snake_movement.run_if(on_timer(Duration::from_secs_f32(0.5))),
        snake_eating.after(snake_movement),
        snake_growth.after(snake_eating),
    ),
)
```

今回の項目では、Updateは最終的に上記のようになりました。

# Hitting the wall (or our tail)

```rust
#[derive(Event)]
struct GrowthEvent;
```

イベントを定義し、`add_event`します。

`game_over_writer.send(GameOverEvent)`の記述はそのままです。

```rust
fn game_over(
    mut commands: Commands,
    mut reader: EventReader<GameOverEvent>,
    segments_res: ResMut<SnakeSegments>,
    food: Query<Entity, With<Food>>,
    segments: Query<Entity, With<SnakeSegment>>,
) {
    if reader.read().next().is_some() {
        for entity in food.iter().chain(segments.iter()) {
            commands.entity(entity).despawn();
        }
        spawn_snake(commands, segments_res);
    }
}
```
イベントが発行されていたら、処理します。

https://doc.rust-lang.org/beta/std/iter/struct.Chain.html

chainでFoodとSnakeSegmentsをつなげて処理します。

そして、新しい蛇を生み出します。

```rust
.add_systems(
    Update,
    (
        snake_movement_input.before(snake_movement),
        food_spawner.run_if(on_timer(Duration::from_secs_f32(1.0))),
        snake_movement.run_if(on_timer(Duration::from_secs_f32(0.5))),
        snake_eating.after(snake_movement),
        snake_growth.after(snake_eating),
        game_over.after(snake_movement),
    ),
)
```

最後にゲームオーバー用のシステムを追加して完成です。

# 終わりに

早足となりましたが、Snakeゲームクローンのチュートリアルを終えました。

バージョンによって書き方がだいぶ違うところもあり、試行錯誤が多少ありましたが、RustやBevyが出すコンパイルエラーのメッセージや、VSCodeの拡張機能での型情報の補完があることで、試行錯誤の手数は少なくて済んだような印象があります。

リファレンスを読むのにも慣れてきたかなあと勝手に評価しています。

この経験を活かして、これからも何か小さいゲームをつくって、RustとBevyを楽しんでいきたいと思います。

ここまで読んでいただき、ありがとうございました。