Rust + Entity Component System で仕様変更に強いゲーム設計 その２ 〜 キャラの移動


## 目次

[その１ 〜 序文](https://qiita.com/mas-yo/items/e7d1e247e5d4a9f47e92)
【イマココ】[その２ 〜 キャラの移動](https://qiita.com/mas-yo/items/207244f04dd442cb567c)
[その３−１ 〜 コンポーネントの設計](https://qiita.com/mas-yo/items/e1f89bea5f84b7041462)
[その３−２ 〜 システムの設計](https://qiita.com/mas-yo/items/ca97fe6ed961d77be715)
[その３−３ 〜 メイン部分](https://qiita.com/mas-yo/items/9f0459dd942a874c6273)
[その４−１ 〜 剣を表示](https://qiita.com/mas-yo/items/daae411935d138d85c95)
[その４−２ 〜 アニメーションコンポーネント](https://qiita.com/mas-yo/items/678d775e4caf5e14abe7)
[その４－３ 〜 アニメーションを動かす](https://qiita.com/mas-yo/items/3db0a920cb76c75bca51)
[その５－１～ あたり判定](https://qiita.com/mas-yo/items/598ed52f5c08a0375ead)
[その５－２～ やられアニメーション](https://qiita.com/mas-yo/items/cf017ac1ebdd7ce25d86)
[その６ 〜 これまでの振り返り](https://qiita.com/mas-yo/items/22ead1dc226da4df54e2)


## はじめに

前回の記事では、RustとECSの簡単な紹介をしました。
今回から、実際にRust言語を使い、ECSの思想に則って、ゲームを作ってみたいと思います。

なお、RustのECSライブラリもgithubで公開されてたりしますが、今回は、ECSの仕組みの理解とその有用性の研究のためにやっているので、自前でECSシステムを構築していきます。
あまり抽象化や汎用化を意識せず、今回作るゲームに合わせたシステムで設計していきます。


環境は、webassembly が使えるゲームエンジンの [quicksilver](https://github.com/ryanisaacg/quicksilver) を、クラウドIDEの [gitpod](https://www.gitpod.io/) 上で動かして開発を進めます。

この構成だと、ブラウザ上で開発からテストまでできます。とっても便利！

今回は第一段階として、キーボード入力「Ａ」（左）、「Ｄ」（右）、「Ｗ」（上）、「Ｓ」（下）を検出して、画面上のキャラを動かす、というところまで、作ってみたいと思います。


## 今回つくったソースとテスト環境

今回作ったソースは、こちらで閲覧＆テストできます。（要githubアカウント）
https://gitpod.io/#https://github.com/mas-yo/rust-ecs-game/tree/step-2

上記リンクをクリックすると、gitpodのワークスペースが立ち上がります。
その後、ライブラリのインストールが始まります。（初回のみ）
１５分ほどかかります。

それが終わったら、画面下のコンソールから

```
$ cargo web start
```

とすると、ビルドが走ったあと、webサーバーが立ち上がります。


サーバーが立ち上がると、右下に、こんな通知が出るので、 `Expose` をクリック
![step1-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227302/d663c25b-a2af-5bcd-00d1-20ade75bfc80.png)

さらに、こんな通知が出るので、 `Open Browser` をクリック
![step1-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227302/a55fb40b-7f30-2d11-ca5c-4820a4005326.png)

すると、ゲーム画面が開きます。

![step1-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227302/bb99a19c-42bd-63f8-c4c4-5d941ce61fff.png)

ADWSキーで、黄緑色の円（これが操作キャラだと思ってください）が動くことが確認できると思います。

ソースだけみたい、という方は、こちらからどうぞ。
https://github.com/mas-yo/rust-ecs-game/tree/step-2

## ECSによる設計

では、今回行った作業を順に紹介します。

### コンポーネント
ECSの考え方で行くと、下記の４つのコンポーネントが必要になりそうです。

- 入力状態を保持する Input コンポーネント
- キャラの速度を保持する Velocity コンポーネント
- キャラの位置を保持する Position コンポーネント
- キャラの描画に必要な情報を保持する CharacterView コンポーネント

これらを、structとして定義してみます。

```Rust
use quicksilver::geom::Vector;

#[derive(Default)]
pub(crate) struct Input {
    pub left: bool,
    pub right: bool,
    pub up: bool,
    pub down: bool,
}

pub(crate) type Velocity = Vector;

pub(crate) type Position = Vector;

#[derive(Default)]
pub(crate) struct CharacterView {
    pub position: Vector,
    pub dir: f32,          //キャラの向き（未使用）
    pub radius: f32,
}
```

### システム

これらのコンポーネントを扱うシステムは、下記の４つになりそうです。

- Input の情報を元に、Verocity を更新するシステム
- Verocity の情報を元に、Position を更新するシステム
- Position の情報を元に、CharacterView を更新するシステム
- CharacterView の情報を元に、実際に画面に描画するシステム

とりあえずシンプルに、単なる関数として作ってみました。

```Rust
use crate::components::*;
use quicksilver::prelude::*;

pub(crate) fn update_velocity(input: &Input, velocity: &mut Velocity) {
    velocity.x = 0f32;
    velocity.y = 0f32;
    if input.left {
        velocity.x = -1f32;
    }
    if input.right {
        velocity.x = 1f32;
    }
    if input.up {
        velocity.y = -1f32;
    }
    if input.down {
        velocity.y = 1f32;
    }
}

pub(crate) fn update_position(velocity: &Velocity, position: &mut Position) {
    position.x += velocity.x;
    position.y += velocity.y;
}

pub(crate) fn update_character_view(position: &Position, view: &mut CharacterView) {
    view.position = *position;
    view.dir = 0f32;
    view.radius = 10f32;
}

pub(crate) fn update_window(view: &CharacterView, window: &mut Window) {
    window.draw(
        &Circle::new((view.position.x, view.position.y), view.radius),
        Col(Color::GREEN),
    );
}
```

各関数は、自分が関わっているコンポーネントのみに依存していることがポイントです。

この様にしておくことで、例えば描画周りを quicksilver でなく htmlのcanvasにしたいと
思ったら、canvas用のupdate_window を用意して、そこだけを差し替えれば良い、ということになります。


### main.rs

大元となる main.rs を忘れていました。
Game 構造体をつくり、そこに各コンポーネントを置きます。

そして、quicksilverのStateを下記の様に実装しました。
キーボードイベントをInputコンポーネントに反映するのと、各システムの関数を順に呼び出しています。

```Rust
use quicksilver::prelude::*;

mod components;
mod systems;
use components::*;
use systems::*;

#[derive(Default)]
struct Game {
    input: Input,
    position: Position,
    velocity: Velocity,
    character_view: CharacterView,
}

impl State for Game {
    fn new() -> Result<Game> {
        Ok(Self{position: Position{x: 50f32, y:50f32}, ..Default::default()})
    }

    /// Will happen at a fixed rate of 60 ticks per second under ideal conditions. Under non-ideal conditions,
    /// the game loop will do its best to still call the update at about 60 TPS.
    ///
    /// By default it does nothing
    fn update(&mut self, _window: &mut Window) -> Result<()> {
        update_velocity(&self.input, &mut self.velocity);
        update_position(&self.velocity, &mut self.position);
        update_character_view(&self.position, &mut self.character_view);
        Ok(())
    }
    /// Process an incoming event
    ///
    /// By default it does nothing
    fn event(&mut self, event: &Event, _: &mut Window) -> Result<()> {
        match event {
            Event::Key(key, state) => {
                let mut pressed = false;
                if *state == ButtonState::Pressed {
                    pressed = true;
                } else if *state == ButtonState::Released {
                    pressed = false;
                }
                match key {
                    Key::A => {
                        self.input.left = pressed;
                    }
                    Key::D => {
                        self.input.right = pressed;
                    }
                    Key::W => {
                        self.input.up = pressed;
                    }
                    Key::S => {
                        self.input.down = pressed;
                    }
                    _ => {}
                }
            }
            _ => {}
        }
        Ok(())
    }

    fn draw(&mut self, window: &mut Window) -> Result<()> {
        window.clear(Color::WHITE)?;
        update_window(&self.character_view, window);
        Ok(())
    }
}

fn main() {
    run::<Game>("Game", Vector::new(800, 600), Settings::default());
}
```


### エンティティは・・？

今回は、エンティティが１つ（プレイヤーキャラ）しか無いので、エンティティを考慮する必要がありませんでした。
次回から、エンティティの管理の部分も作ってみたいと思います。

[その３](https://qiita.com/mas-yo/items/e1f89bea5f84b7041462)につづく・・・