bitterharvest’s diary

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

YampaとOpenGL(GLUT)で簡単なシューティングゲームを作成する―メインプログラム

1.概略

ゲームを構成するプログラムの説明がほぼ終了したので、それらをまとめてゲームのプログラムとして機能させるメインプログラムを見ていく。Yampaではreactimateという関数を用意している。これを働かせるためには、初期化ルーチン、入力ルーチン、出力ルーチン、処理ルーチンを指定すればよい。初期化ルーチンでは画面の初期化を行う。入力ルーチンでは入力デバイスからの入力と一サイクルの時間を指定する。出力ルーチンは画面への出力である。処理ルーチンは一つのサイクルの中での処理方法を示すものである。

2.プログラムの作成

今までに説明したプログラムを利用して、メインプログラムを作成すると次のようになる。

main::IO()
main = reactimate init' input output (process objs)
  where
    stone1Obj = stoneObject (-8, 0)  (1, 15)
    stone2Obj = stoneObject (-8, 0)  (4, 17)
    birdObj   = birdObject  (-8, 10) (4, 0)
    objs      = listToIL [stone1Obj, stone2Obj, birdObj]

init' :: IO ()
init' = initGL


input :: Bool -> IO (DTime, Maybe Input)
input _ = do
  threadDelay 100000
  return (0.1, Nothing)

output :: Bool -> IL ObjOutput -> IO Bool
output _ oos = do
  draw oos
  return False

上記のプログラムで、processの引数として、登場者の識別リストobjsを渡している。石二つと鳥一羽をstone1Obj,stone2Obj,birdObjとして定義した後で、そのリストをlistToILによって識別リストに変換している。

また、inputのところでは、一サイクルの時間をreturnのところにある0.1で与えている。従って、これは0.1秒間隔で一つのサイクルが進行する。即ち、0.1秒間隔でサンプリングされていることと同じである。例えば、落下するものを観察しているとすると、0.1秒ごとの間隔でその位置をとらえていることになる。

一方で、threadDelay 100000は、スレッドを待機させるときに使う。ここでは、100000ナノ秒、即ち0.1秒、待たせている。スレッドを待機させることにより、ビデオの早送りやゆっくり送ることが実現できる。このプログラムでは観測している時間と待機時間が同じなので、等速でみることになるが、10倍速い速度で見たければ、 100000を 10000に変えてあげればよい。

2.プログラムを修正

上記のプログラムで描画してくれれば、すっきりしていて分かりやすいのだが、描画間でのコントロールがうまく働かないようで、うまく描画してくれない。そこで、「物理的なモデルに基づいてゲームを開発する:将来の発展のために」で紹介したようにサイクル終了時の処理を詳しく記述することにする。これが以下のプログラムである。なお、このプログラムは、module Mainとなっている。いつもは最後に全体のプログラムを示していたが、今回はこのプログラムが全体のプログラムである。

module Main where

import FRP.Yampa
import FRP.Yampa.Utilities
import Control.Concurrent
import Data.IORef
import Graphics.UI.GLUT hiding (Level,Vector3(..),normalize)

import Graphics
import Process
import IdentityList
import Objects
import Types

mainSF = (process objs) >>^ 
           (\ oos -> draw oos)
  where
    stone1Obj = stoneObject (-8, 0)  (1, 15)
    stone2Obj = stoneObject (-8, 0)  (4, 17)
    birdObj   = birdObject  (-8, 10) (4, 0)
    objs      = listToIL [stone1Obj, stone2Obj, birdObj]


main :: IO ()
main = do
    oldTime <- newIORef (0 :: Int)
    rh <- reactInit (initGL) (\_ _ b -> b >> return False) 
                    mainSF
    displayCallback $= return ()
    idleCallback $= Just (idle  oldTime rh)
    oldTime' <- get elapsedTime
    writeIORef oldTime oldTime' 
    mainLoop

idle :: IORef Int -> ReactHandle () (IO ()) -> IO ()
idle oldTime rh = do
    newTime'  <- get elapsedTime
    oldTime'  <- get oldTime
    let dt = (fromIntegral $ newTime' - oldTime')/1000
    react rh (dt, Nothing)
    writeIORef oldTime newTime'
    return ()   

上記のプログラムで、先ほどのプログラムでのoutputとprocessの部分はmainSFとなっている。また、init'はmainの中のreactInit (initGL)である。またスレッドの待機時間とサイクルの時間は下から4行目から3行目の部分である。