bitterharvest’s diary

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

YampaとOpenGL(GLUT)で簡単なシューティングゲームを作成する―グラフィックス

1.概略

ゲームの出力を行うのはグラフィックスである。広く利用されているのはOpenGLである。その簡易版はGLUTである。また、最近ではSDL(Simple DirectMedia Layer)も広く利用されている。このシューティングゲームではGLUTを用いる。

ゲームの進行状況を画面に出力するためには、初期化とその時々の画像の描画とが必要である。初期化は通常は定められた方法で行う。描画は登場者の位置情報を得て描く。このシューティングゲームでは、それぞれのサイクルの終了時に得られた登場者の状態から、その位置を獲得して、石や鳥を描く。

もう一度、このシューティングゲームのビデオを見ると、

鳥は緑色の多面体で、石は赤い球で表示される。そして、石が鳥にあたると鳥は消えてしまう。石のほうは、何もなかったかのようにそれまでの軌跡を継続する。では、プログラムがどのようになっているか説明する。

2.初期化

初期化のプログラムは次の通りである。

initGL :: IO ()
initGL = do
  _ <- getArgsAndInitialize
  _ <- createWindow "Shooting Game"
  initialDisplayMode $= [ WithDepthBuffer, DoubleBuffered ]
  depthFunc          $= Just Less
  clearColor         $= Color4 0 0 0 0
  light (Light 0)    $= Enabled
  lighting           $= Enabled
  lightModelAmbient  $= Color4 0.5 0.5 0.5 1
  diffuse (Light 0)  $= Color4 1 1 1 1
  blend              $= Enabled
  blendFunc          $= (SrcAlpha, OneMinusSrcAlpha)
  colorMaterial      $= Just (FrontAndBack, AmbientAndDiffuse)
  reshapeCallback    $= Just resizeScene
  return ()

resizeScene :: Size -> IO ()
resizeScene (Size w 0) = resizeScene (Size w 1) -- prevent divide by zero
resizeScene s@(Size width height) = do
  viewport   $= (Position 0 0, s)
  matrixMode $= Projection
  loadIdentity
  perspective 45 (w2/h2) 1 1000
  matrixMode $= Modelview 0
  where
   w2 = half width
   h2 = half height
   half z = realToFrac z / 2

3.描画

描画はdrawというプログラムで行う。メインのプログラムから登場者状態の識別リスト(型シグネチャではIL ObjOutout、本体ではoos)が渡されるので、このリストより登場者状態のリスト(elemsIL oos)を得る。そこから、(forM_を用いて)個々の搭乗者状態に対して、その種類と位置を得て、描画の関数draw'を実行する。

draw'では、種類から石であるのか鳥であるのかの情報を得て、石であれば球を、鳥であれば多面体を与えられた位置に描画する。

プログラムは次のようになっている。

draw :: IL ObjOutput -> IO ()
draw oos = do
  clear [ ColorBuffer, DepthBuffer ]
  loadIdentity
  forM_  (elemsIL oos)  (\oo -> draw' (ooKind oo) (position' (ooState oo)))
  flush
  
draw' :: ObjectKind -> Pos -> IO ()
draw' kind pos
  | kind == Stone = renderStone $ vector3 (unsafeCoerce (fst pos)) (unsafeCoerce (snd pos)) (-30)
  | kind == Bird  = renderBird  $ vector3 (unsafeCoerce (fst pos)) (unsafeCoerce (snd pos)) (-30)
  | otherwise     = error "Unexpected errors occur in graphics because of an undefined kind"
  where
    size2 :: GLdouble
    size2 = (fromInteger $ 6)/2
    green  = Color4 0.8 1.0 0.7 0.9 :: Color4 GLdouble
    greenG = Color4 0.8 1.0 0.7 1.0 :: Color4 GLdouble
    red    = Color4 1.0 0.7 0.8 1.0 :: Color4 GLdouble
    renderShapeAt s p = preservingMatrix $ do
      translate $ G.Vector3 (0.5 - size2 + vector3X p)
                            (0.5 - size2 + vector3Y p)
                            (0.5 - size2 + vector3Z p)
      renderObject Solid s
    renderStone   = (color red >>) . (renderShapeAt $ Sphere' 0.5 20 20)
    renderBird    = (color green >>) . (renderShapeAt $ Icosahedron)

4.モジュールGraphicsのプログラム

最後になるが、これまでのプログラムをmodule Graphicsとしてまとめたものを掲載しておく。

module Graphics where

import           FRP.Yampa.Vector3
import           Unsafe.Coerce

import           Graphics.UI.GLUT    hiding (Level, Vector3 (..), normalize)
import qualified Graphics.UI.GLUT    as G (Vector3 (..))

import Control.Monad (forM_)

import           Types
import           IdentityList

initGL :: IO ()
initGL = do
  _ <- getArgsAndInitialize
  _ <- createWindow "Shooting Game"
  initialDisplayMode $= [ WithDepthBuffer, DoubleBuffered ]
  depthFunc          $= Just Less
  clearColor         $= Color4 0 0 0 0
  light (Light 0)    $= Enabled
  lighting           $= Enabled
  lightModelAmbient  $= Color4 0.5 0.5 0.5 1
  diffuse (Light 0)  $= Color4 1 1 1 1
  blend              $= Enabled
  blendFunc          $= (SrcAlpha, OneMinusSrcAlpha)
  colorMaterial      $= Just (FrontAndBack, AmbientAndDiffuse)
  reshapeCallback    $= Just resizeScene
  return ()

resizeScene :: Size -> IO ()
resizeScene (Size w 0) = resizeScene (Size w 1) -- prevent divide by zero
resizeScene s@(Size width height) = do
  viewport   $= (Position 0 0, s)
  matrixMode $= Projection
  loadIdentity
  perspective 45 (w2/h2) 1 1000
  matrixMode $= Modelview 0
  where
   w2 = half width
   h2 = half height
   half z = realToFrac z / 2

draw :: IL ObjOutput -> IO ()
draw oos = do
  clear [ ColorBuffer, DepthBuffer ]
  loadIdentity
  forM_  (elemsIL oos)  (\oo -> draw' (ooKind oo) (position' (ooState oo)))
  flush
  
draw' :: ObjectKind -> Pos -> IO ()
draw' kind pos
  | kind == Stone = renderStone $ vector3 (unsafeCoerce (fst pos)) (unsafeCoerce (snd pos)) (-30)
  | kind == Bird  = renderBird  $ vector3 (unsafeCoerce (fst pos)) (unsafeCoerce (snd pos)) (-30)
  | otherwise     = error "Unexpected errors occur in graphics because of an undefined kind"
  where
    size2 :: GLdouble
    size2 = (fromInteger $ 6)/2
    green  = Color4 0.8 1.0 0.7 0.9 :: Color4 GLdouble
    greenG = Color4 0.8 1.0 0.7 1.0 :: Color4 GLdouble
    red    = Color4 1.0 0.7 0.8 1.0 :: Color4 GLdouble
    renderShapeAt s p = preservingMatrix $ do
      translate $ G.Vector3 (0.5 - size2 + vector3X p)
                            (0.5 - size2 + vector3Y p)
                            (0.5 - size2 + vector3Z p)
      renderObject Solid s
    renderStone   = (color red >>) . (renderShapeAt $ Sphere' 0.5 20 20)
    renderBird    = (color green >>) . (renderShapeAt $ Icosahedron)