Scalaでゲームプログラミング - vol3 システムの骨格を考える -


第1回、第2回の記事を見てるとなんだか拡散な感じがしますね。


そろそろシステムの基盤を作って、拡張してゆけるようにしましょう。
まずは個々の機能について分析してみます。

全ての基本となるGameクラス


Gameクラスに機能を集約させて動かします。
基本的には、初期化、更新、解放の3つの処理を持ちます。
ここでrun()にメインループを持たせ、全体を回します。

システムの基盤Game
object Game
{
    private def initialize() = {}
    private def update() = {}
    private def release() = {}

    def run() =
    {
        // ここにメインループ
    }
}

Renderクラス


MainFrameはやや機能を持ちすぎている気がします。
描画の機能だけを切り離してobjectとすることで様々な場所からアクセスできるようにします。

render.scala
import java.awt._
import javax.swing._
//---------------------------------------------------------
// Rendering Class
//---------------------------------------------------------
object Render
{
    private var mMainFrame : MainFrame = null

    def initalize(frame:MainFrame) = mMainFrame = frame
    def release() = { if( mMainFrame != null ) mMainFrame.release }

    //-----------------------------------------------------
    // update
    //-----------------------------------------------------
    def update() = try { mMainFrame.update() } catch { case ex => throw ex }
    
    def setTitle(str:String) = mMainFrame.setTitle(str)
}

毎フレーム呼ばれるupdate()


KeypadクラスにもRenderクラスにもupdate()と呼ばれる関数があります。
これからも毎フレーム呼び出されるupdateを持つクラスが現れるでしょう。
その為にもGameにupdateを持たせる関数を作りましょう。

Updater(Game内)
//-----------------------------------------------------
// Low Level System Functions
//-----------------------------------------------------
private var mSystemUpdaterList = List[()=>Unit]()
private def setUpdater(func:()=>Unit) = mSystemUpdaterList = func :: mSystemUpdaterList
    
private def update() : Unit = 
{
    try { mSystemUpdaterList.foreach(func=>func()) } catch { case ex => throw ex }
}


単なる関数オブジェクトのリストですが、Scalaだと簡単に書けるものですね。
実行される順番は後に追加されたものから実行されます。


ふむ、追加された順番にやって欲しいですね。

private def setUpdater(func:()=>Unit) = mSystemUpdaterList = mSystemUpdaterList ::: List(func)

先ほどはリストに関数を加える形でしたが、今回はリストとリストの結合です。


この機構は簡単に利用できます。

setUpdater( Keypad.update )
setUpdater( Render.update )


上記のように呼び出したい関数を登録して、毎フレームupdateを呼び出すだけです。


最後に呼ばれるrelease()


Renderクラスには解放処理があります。
こちらも連鎖的に実行するだけなのでupdateと同じ機構が利用できます。

private var mSystemReleaserList = List[()=>Unit]()
private def setReleaser(func:()=>Unit) = mSystemReleaserList = mSystemReleaserList ::: List(func)

private def release() : Unit =
{
    try{ mSystemReleaserList.foreach(func=>func()) } catch { case ex => throw ex }
}

Game内での流れ

エントリーポイント(main)では、Game.run()を呼び出すだけにしたいです。
そこでrun()内で初期化→メインループ→解放の流れを作ります。

Game.run()
def run() : Unit =
{
    try{ initialize() } catch { case ex => throw ex }
        
    while( !isEnd() )
    {
        try{ update() } catch { case ex => throw ex }
        Thread.sleep(20)
    }

    try{ release() } catch { case ex => throw ex }
}


initializeで初期化、whileでupdateを呼び出してメインループ、releaseで解放の流れです。
isEndについてはまだ出てきていませんでしたね。

終了条件
//-----------------------------------------------------
// End Flag
//-----------------------------------------------------
private def isEnd() : Boolean =
{
    if( Keypad.isKeyPressed( Keypad.KEY_ESCAPE ) ) return true
    false
}

要するにエスケープキー(ESC)の入力でメインループを抜けるという話です。


初期化(Game)


まず、Gameクラスの初期化をしましょう。
UpdaterやReleaserの機構について登録を行うだけです。

//-----------------------------------------------------
// initialize this Game
//-----------------------------------------------------
private var mInitialized = false

private def initialize() =
{
    if(!mInitialized){ in_initialize();  mInitialized = true; }
    else throw new Exception("Game Object cannot Reinitalize")
}

// -- protected reinitializing --
private def in_initialize() =
{
    val frame = new MainFrame // make screen

    // -- frameworks  initalize --
    Keypad.initalize( frame )
    Render.initalize( frame )

    setUpdater( Keypad.update ) // keystatus udpate
    setUpdater( Render.update ) // screen update

    setReleaser( Render.release )
}


2回呼ばれるのが嫌なので防止機能をつけてあります。(たぶん無いけどはまるとやだね)


本質はin_initializeです。まずはMainFrameを生成します。

val frame = new MainFrame // make screen


このframeを利用して各オブジェクトを初期化していきます。

Keypad.initalize( frame )
Render.initalize( frame )


最後に、毎フレーム呼ぶ必要のあるものや解放の必要のあるものを登録します。

setUpdater( Keypad.update ) // keystatus udpate
setUpdater( Render.update ) // screen update

setReleaser( Render.release )


以上で、初期化終了です。


全体像

エントリーポイント(main) → Game.run()
Game.run() = initialize() + update() + release()
Game → MainFrameの初期化
Game → Renderの初期化(MainFrame)
Game → Keypadの初期化(MainFrame)

Game.run()
//-----------------------------------------------------
// Main Loop
//-----------------------------------------------------
def run() : Unit =
{
    try{ initialize() } catch { case ex => throw ex }
        
    while( !isEnd() )
    {
        try{ update() } catch { case ex => throw ex }

        move()
        draw()

        Thread.sleep(20)
    }

    try{ release() } catch { case ex => throw ex }
}


moveとかdrawとかにゲームのシステムが入ってきます。
それ以外は周りの環境に関する処理なんです。


今回は全体像だったので若干分かりにくかったかも。
次回はたぶんFPSの制御なんじゃないかな。