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の制御なんじゃないかな。