← 戻る

純粋関数プログラミングでゲームを実装しようとしたけどやり方がわからない

2019-05-23

要約: https://twitter.com/neguse/status/620140369984925696

以前Elmでゲームを作ろうとして、簡単なもの(これこれのクライアント側)なら作れたのだけど、ここからさらに複雑なものを作ろうとするとひとつ問題があって、まだ解き方がわかっていない。

どんな問題かというと、他の言語にあるような「参照」が必要なケースを、現実的な実行速度で実装するためにはどうすればいいのかというもの。

具体例をあげると、たとえば追尾ミサイル。何者かをロックして、ロック後は対象の位置に向かって都度方向を修正するような挙動をしたい。安直に考えると、以下のようになりそう。

type alias Object = Missile | Ship
type alias Missile = { x : Float, y : Float, angle : Float, target : Ship }
type alias Ship = { x : Float, y : Float, angle : Float }

update : List Object -> List Object
update ls =
  map updateObject ls

updateObject : Object -> Object
updateObject o =
  case o of
    Missile m ->
      updateMissile m
    Ship s ->
      updateShip s

updateMissile : Missile -> Missile
updateMissile m =
  -- TODO targetを使って方向を調整しつつ前進

しかし、この方法だとうまくいかない。target としてロック対象の Ship を参照しているかに見えるのだけど、target にははあくまでその瞬間の Ship の状態しか記録されておらず、次のフレームには Ship は移動しているため、この実装だと永遠に過去の位置に対して向き続けてしまう。そのため、フレームをまたいで、同じオブジェクトを参照し続ける仕組みが必要と思われる。

参考に、Monadius のソースコードを読んでみた。

https://github.com/tanakh/monadius/blob/master/src/Monadius.hs#L427

ゲーム全体の状態を gameObject のリストとして保持しているところは同様で、参照のための tag を各オブジェクトにもたせている。tag はオブジェクトごとにユニークになるようにしておいて、参照先が欲しい場合は filter でリニアサーチしている。…そう、リニアサーチなんですよ。Monadius ではあんまり参照が必要なオブジェクトが無さそうだったので実行時の効率への影響が少なそうなのだけど、これがもっと複雑なゲーム、オブジェクト数の多いゲームになるとあまり現実的な速度で動かせるイメージがない。

じゃあリニアサーチしなくて済むよう辞書にすれば…とも思うのだけど、純粋関数で辞書ってどう組むんですか?とか、辞書を毎フレーム作り直しててパフォーマンスどうなんですか?ってなるとよくわからなくなってしまった。ここが解決できて現実的な速度で動けばだいぶ前進すると思うのだけど、うーむ…

なにかいい方法があれば教えていただきたいです。