目次
その1 〜 序文
その2 〜 キャラの移動
その3−1 〜 コンポーネントの設計
その3−2 〜 システムの設計
【イマココ】その3−3 〜 メイン部分
その4−1 〜 剣を表示
その4−2 〜 アニメーションコンポーネント
その4-3 〜 アニメーションを動かす
その5-1~ あたり判定
その5-2~ やられアニメーション
その6 〜 これまでの振り返り
その3−1、3−2で、コンポーネントとシステムが出来上がりました。
では、それらを駆動するメイン部分である Game
構造体を作ってみます。
Game 構造体
このような作りにしました。
struct Game {
next_entity_id: EntityID,
inputs: CContainer<Input>,
teams: CContainer<Team>,
move_targets: CContainer<MoveTarget>,
positions: CContainer<Position>,
velocities: CContainer<Velocity>,
character_views: CContainer<CharacterView>,
}
next_entity_id
は、次に生成するエンティティのエンティティIDです。
エンティティを作るたびに、インクリメントしていきます。
他は、各コンポーネントのコンテナです。
次に、プレイヤーキャラを生成するメソッドと、敵キャラを生成するメソッドです。
impl Game {
fn create_hero(&mut self) {
let entity_id = self.next_entity_id;
self.inputs.push(entity_id, Input::default());
self.teams.push(entity_id, Team::new(0));
self.positions.push(
entity_id,
Position {
x: 150f32,
y: 150f32,
},
);
self.velocities.push(entity_id, Velocity::default());
self.character_views.push(
entity_id,
CharacterView {
color: Color::GREEN,
radius: 10f32,
..Default::default()
},
);
self.next_entity_id = self.next_entity_id + 1;
}
fn create_enemy(&mut self) {
let entity_id = self.next_entity_id;
self.move_targets.push(entity_id, MoveTarget::default());
self.teams.push(entity_id, Team::new(1));
self.positions
.push(entity_id, Position { x: 10f32, y: 10f32 });
self.velocities.push(entity_id, Velocity::default());
self.character_views.push(
entity_id,
CharacterView {
color: Color::RED,
radius: 15f32,
..Default::default()
},
);
self.next_entity_id = self.next_entity_id + 1;
}
}
それぞれ、必要なコンポーネントを生成して追加しています。
次に、quicksilverの trait State
の実装です。
impl State for Game {
fn new() -> Result<Game> {
let mut game = Self::default();
game.create_hero();
game.create_enemy();
Ok(game)
}
/// 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<()> {
System::process(
&mut self.move_targets,
&(&self.teams, &self.positions),
);
System::process(&mut self.velocities, &self.inputs);
System::process(&mut self.velocities, &(&self.positions, &self.move_targets));
System::process(&mut self.positions, &self.velocities);
System::process(&mut self.character_views, &self.positions );
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.inputs.iter_mut().for_each(|(_, i)| {
i.left = pressed;
});
}
Key::D => {
self.inputs.iter_mut().for_each(|(_, i)| {
i.right = pressed;
});
}
Key::W => {
self.inputs.iter_mut().for_each(|(_, i)| {
i.up = pressed;
});
}
Key::S => {
self.inputs.iter_mut().for_each(|(_, i)| {
i.down = pressed;
});
}
_ => {}
}
}
_ => {}
}
Ok(())
}
fn draw(&mut self, window: &mut Window) -> Result<()> {
window.clear(Color::WHITE)?;
System::process(window, &self.character_views);
Ok(())
}
}
Game
生成時に、プレイヤーキャラと敵キャラを生成するようにしました。
update()
では、System::process()
を順に更新するコンポーネントを引数として渡しながら、呼んでいます。
ただし、Window
の更新だけは、draw()
で行います。
event()
の処理では、キー入力情報に従って Input
コンポーネントを更新しています。
完成!
ここまでの実装で、プレイヤーキャラに加えて、敵キャラも実装することができました。
そして、エンティティ、コンポーネント、システムの基本的な設計が完了しました。
めでたしめでたし。
次からは、いろんな機能を追加しながら、この設計が、「仕様変更に強い」かどうかを、検証していきます。
次回につづく・・・