まだまとまってないけどコルーチンが使える言語でアクション作る時に使えるかもしれないメモ
前提
簡単なアクションゲームプログラミングをしてるとして, キャラクタークラスが毎フレーム呼ばれる Update() メソッドを持っており, Updateメソッド内で座標移動とかをする.
他キャラクターとの当たり判定を IsHitOtherCharacter() で取得できる
擬似コード1
class Character {
function Update(){
Move(10, 10)
if (IsHitOtherCharacter()){
Move(-10, -10) // 当たってたら座標を戻す
}
}
}
擬似コード1の問題点
擬似コード1に書いたキャラクタークラスがゲーム中に2体(A, B)が存在して,
AのUpdate()
→BのUpdate()
が呼ばれる場合,
BのMove()
が呼ばれる前にAのIsHitOtherCharacter()
が呼ばれるので,
A, B の Update() が呼ばれる順番によって結果が変わってしまう.
Aが移動後Bに当たってしまうが,Bが先に移動していれば当たらなかった,ということが充分ありえる. まぁ別に良いといえば良いケースも多いのかもしれないけど, 例えばキー入力を全て保持しててそれをもとに状況を再現 (リプレイデータの再生とか)(キー入力の同期をだけで通信対戦を実装するとか)をしたい時, キャラクターの更新順序次第で結果が変わると困る状況がありえる.気がする.
そういう時じゃなくても,一回の更新での移動量が大きくて, なおかつお互いに当たり判定を取るキャラクターが密集している時に 見た目にも挙動に違和感が起きる気がする.
擬似コード2
class Character {
function Update(){
Move(10, 10)
}
function OnHitOtherCharacter(){
Move(-10, -10) // 当たってたら座標を戻す
}
}
擬似コード2について
そこでまぁさっきみたいな問題が起きないために,あとパフォーマンスの都合上で, 擬似コード2みたいに大体キャラクタークラスにコールバックを設けて, 全てのキャラクターが更新が終わってからまとめて当たり判定を取り, まとめてコールバックを呼ぶようにすることが多い(多分)
でも,これでさっきの問題は起きなくなるけど,コードを書くのが面倒になる.
擬似コード3
class Character {
StateFunc = State_Walk
function State_Walk(){
}
function State_Attack(){
}
function OnHitOtherCharacter(){
switch StateFunc{
case State_Walk {
}
case State_Attack {
}
}
}
}
擬似コード2について
擬似コード3みたいに,キャラクターが複数の状態を持っていて,更に状態によって 他のキャラクターに当たってた挙動を変えたい場合,なんかステートの処理を書く部分と そのステートの時に当たった時の処理を書く部分が離れてコードの見た目上よろしくない感じがする. 単純に書いてて面倒くさそう.
これならIsHitOtherCharacter()
使ってた時の方が,ステート関数の中に直接書けたから楽だった.
そこでコルーチンですよ
function IsHitOtherCharacter() {
return yield(IS_HIT_CHECK)
}
そこで, キャラクターの更新関数はコルーチンにしてやって,
IsHitOtherCharacter()
関数の実装をコルーチンの中断にしてやれば良さそう.
つまり,一旦IsHitOtherCharacter()
を呼んだ段階で更新関数の実行を中断して,
当たり判定をとったあとでまとめて中断したキャラクターの更新関数を当たっているかどうかの情報をもとに再開させてやれば
コードを書くのも楽なまま,処理速度や更新順序で結果が食い違う問題は全部解決する!!
実はとっくにこんなん考えられてたりすることだったりして
ありそう.
ちなみに
僕が作ったことあるゲームは http://cute.sh/nyaocat/dangerous_world/index.html とかです