bitterharvest’s diary

A Bitter Harvestは小説の題名。作者は豪州のPeter Yeldham。苦闘の末に勝ちえた偏見からの解放は命との引換になったという悲しい物語

物理的なモデルに基づいてゲームを開発する:FRP事始め

1.ゲームとエンジンの紹介

日本では、フリー・モナドやオペレーショナル・モナドがゲームのエンジンに向いているとひところ話題になっていた。一方、海外では関数型リアクティブ・プログラミング(Functional Reactive Programming:以下ではFRPと省略する)が主流である。1997年にマイクロソフト社のConal Elliottとエール大学のPaul Hudakが「Functional Reactive Animation」という題名の論文を発表したが、これがおそらくFRPの始まりである。2003年にはエール大学のAntony Courtneyらによって、YampaというFRPのライブラリが実装された。

近年、OpenGLSDLなどのグラフィックスやマルチメディア関連のライブラリが充実するにしたがって、FRPをシミュレーションやゲームで利用する人が増え始めている。Yampaを用いたゲームやシミュレーションをいくつ掲げておく。cuboidは3次元ゲームで、グラフィックスにはGLUT(OpenGLを使用するためのライブラリ)を用いてる。cuboidのコードを利用しながら、飛び跳ねるボールのプログラムの作成法をJekorが紹介している。この後、この記事でも、これを紹介する。Breakoutは跳ね返るボールを利用して上部のバーを崩すゲームである。グラフィックスはSDLを用いてる。SDLは音も扱えるエンジンだが、使えるようにするまでのノウハウが必要である。特にWindows8では扱いずらいが、いずれ、簡単に利用できるようになることと期待している。HelmSDLを用いたゲーム用のエンジンである。Windouw8へのインストールはしばらくお待ちくださいとなっている。

2.FRPに触れてみる

高さ\(p_0\)から速度\(v_0\)でボールを落としたときの、t秒後の高さと速度は次の式で与えられる。ここで、\(g=9.81 m / s^2\)は重力加速度である。また、自然落下の時は\(v_0=0\)である。
\begin{eqnarray}
y_t & = & y_0 + \int_0^t v_t dt \\
v_t & = & v_0 - \int_0^t g dt
\end{eqnarray}

自然落下するものの位置と速度をYampaを用いて書くと次のようになる。なお、このコードはJekorが動画で説明したものである(注意:このプログラムを実行する前にYampaをインストールしておくこと。インストールはcabal install Yampaで行える)。

import FRP.Yampa
import Control.Concurrent

type Pos = Double
type Vel = Double

fallingBall :: Pos -> SF () (Pos, Vel)
fallingBall y0 = (constant (-9.81) >>> integral) >>> ((integral >>^ (+ y0)) &&& identity)

main = reactimate (return ())
                       (\ _ -> threadDelay 100000 >> return (0.1, Nothing))
                       (\ _ (pos, vel) -> putStrLn ("pos: " ++ show pos ++ ", vel: " ++ show vel) >> return False)
                       (fallingBall 10.0)

fallingBallのところが自然落下の記述である。(constant (-9.81) >>> integral)の部分が\(v_t = - \int_0^t g dt\)の部分をあらわす。また、(integral >>^ (+ y0)) &&& identityの前半のintegral >>^ (+ y0)で\(y_t = y_0 + \int_0^t v_t dt\)を表し、identityは\(v_t\)を表す(自由落下なので、\(v_0=0\)が0であることに注意)。

mainのところのreactimateは入力・処理・出力のループである。reactimateはinit, input/sense, output/actuate, sfの4つのパラメータをとる。initは初期化だがここでは必要ないので、単にreturn()とする。sfはシグナル関数である。この場合はfallingBallで高さは10メートルになってる。input/senseのところは、時間とイベントを与える。ここでは、0.1秒の間隔でイベントなしとなっている。さらに、0.1秒の遅れを持たせ、リアルタイム性を醸し出してる。output/actuateのところではシグナ関数の出力を得る。

intimateをもう少し詳しく説明しておく。シグナル関数は、型SF a bで表現された抽象データ型である。これは型Time -> aのシグナルを型Time -> bのシグナルに変換する。Timeは連続的な時間を表すが、これはシグナル関数に対してある離散的な時間でのシグナルを計算するときに用いる。上記の自然落下であれば、落体の法則を表した式がシグナル関数として用いられていて、0.1秒ごとに高さと速度のサンプルを採取するために利用される。

input/senseは、(Bool -> (DTTime, Maybe a))となっている。これは、入力が偽の時に、前回サンプルした後に経過した時間と型aの新しいサンプルとの対を出力する。aがnothingの時は、前のサンプルが再び使用される。先のプログラムでは、0.1秒が経過したことが伝えられる。

output/actuateは、(Bool -> b -> IO Bool)となっている。これは、入力が真のとき、型bでのシグナル関数の出力を計算する。そして、処理を終了したいときは真を、継続したいときは偽を返す。先のプログラムでは、bはさらに0.1秒経過後を、aはnothingになっているので、前をたどることになるが、ここでは、落下が始まった時間、即ち0となる。処理を継続したいので、偽を返す。