目次
その1 〜 序文
その2 〜 キャラの移動
その3−1 〜 コンポーネントの設計
【イマココ】その3−2 〜 システムの設計
その3−3 〜 メイン部分
その4−1 〜 剣を表示
その4−2 〜 アニメーションコンポーネント
その4-3 〜 アニメーションを動かす
その5-1~ あたり判定
その5-2~ やられアニメーション
その6 〜 これまでの振り返り
システム
3−1に引き続き、実装を進めます。
Component
ComponentContainer
ができたので、次に System
を作ってみましょう。
その2で作った(ECSの)システムは、単なる関数として、
fn update_velocity(velocity: &mut Velocity, input: &Input) {}
この様な形になっていました。
よく見ると、この関数名の velocity
と、引数の &mut Velocity
は、表現として冗長な気がしますね。
例えば、あとから、 Velocity
の名前を変えてしまった場合、 update_velocity
の方も変えることになり、面倒なことになりそうです。
そこで、同じ関数名で引数が違うだけで、別の機能を表現できる様にしました。
Rustでは関数のオーバーロードがありませんが、下記の様な形で実現できます。
pub(crate) trait SystemInterface {
type Update;
type Refer;
}
pub(crate) trait SystemProcess: SystemInterface {
fn process(update: &mut Self::Update, _ref: &Self::Refer);
}
pub(crate) struct System<U, R> {
phantom: PhantomData<(U, R)>,
}
impl<U, R> SystemInterface for System<U, R> {
type Update = U;
type Refer = R;
}
そして、update_velocity
に相当する関数は、下記の様に書きます。
impl SystemProcess for System<CContainer<Velocity>, CContainer<Input>> {
fn process(velocities: &mut Self::Update, inputs: &Self::Refer) {
velocities
.iter_mut()
.zip_entity(inputs)
.for_each(|(velocity, input)| {
velocity.x = 0f32;
velocity.y = 0f32;
if input.left {
velocity.x = -2f32;
}
if input.right {
velocity.x = 2f32;
}
if input.up {
velocity.y = -2f32;
}
if input.down {
velocity.y = 2f32;
}
});
}
}
こうしておくと、呼び出し側では、
System::process(&mut velocities, &inputs);
の様な形で呼び出せます。
関数名は process
で共通となり、引数の型によって機能が変わるようになっています。
System<CContainer<Velocity>, CContainer<Input>>::process
の中身は実装は、同じエンティティIDを持つ Velocity
Input
に対して、Input
の情報をVelocity
に反映させるようになっています。
敵の動きの実装
では、今回(その3)のゴールである、敵の動きを実装してみます。
まずは、敵の Velocity
です。
impl SystemProcess
for System<CContainer<Velocity>, (&CContainer<Position>, &CContainer<MoveTarget>)>
{
fn process(velocities: &mut Self::Update, pos_tgt: &Self::Refer) {
velocities
.iter_mut()
.zip_entity2(pos_tgt.0, pos_tgt.1)
.for_each(|(vel, pos, target)| {
let mut tmp = Vector::default();
tmp.x = target.x - pos.x;
tmp.y = target.y - pos.y;
vel.x = tmp.x / 50f32;
vel.y = tmp.y / 50f32;
});
}
}
敵の Velocity
は、自分の Position
と MoveTarget
との差分から計算する必要があります。
よって、System::Refer(Velocityを更新するために参照するデータ)の型は、
(&CContainer<Position>, &CContainer<MoveTarget>)
このようなタプルとなりました。
上記ソース内の、zip_entity2()
は、2つの ComponentContainer
を受け取って、同じエンティティIDのコンポーネントを順に返してくれるイテレータです。
敵かどうかの判定
では、 MoveTarget
自体は、どのように値を更新したら良いでしょうか?
MoveTarget
を決めるためには、他のキャラを見て、これが敵だと認識できなければなりません。
そこで、Team というコンポーネントを導入することにしました。
pub(crate) struct Team {
team_id: u32,
}
プレイヤーキャラ、敵キャラは、 Team
を持っていて、チームIDが違うもの同士は敵、という意味です。
MoveTarget
は、他のすべてのキャラのうち、自分から一定距離の範囲にいて、自分とは違うチームのキャラの、位置に設定される、という処理にしました。
impl SystemProcess for System<CContainer<MoveTarget>, (&CContainer<Team>, &CContainer<Position>)> {
fn process(move_targets: &mut Self::Update, team_pos: &Self::Refer) {
let (teams, positions) = team_pos;
move_targets
.iter_mut()
.zip_entity2(teams, positions)
.for_each(|(target, self_team, self_pos)| {
teams
.iter()
.filter(|(_, team)| team.team_id() != self_team.team_id())
.for_each(|(entity_id, _)| {
if let Some(pos) = CContainer::<Position>::get(positions, entity_id) {
let distance = pos.distance((self_pos.x, self_pos.y));
if distance < 100f32 {
target.x = pos.x;
target.y = pos.y;
} else {
target.x = self_pos.x;
target.y = self_pos.y;
}
}
});
});
}
}
ここまでで、敵の動きの実装は完了です。
次回、3−3で、メインの処理を実装します。