これは、Visual Basic Advent Calendar 2012 12/25日の記事。
今回は、Yeildを使ったシューティングゲームの考察です。
シューティングゲームなどのアクションゲームは、画面上にたくさんあるキャラクターを同時に動かすという「マルチスレッド(並列実行)」を要求されるプログラムです。
それをシングルスレッドで擬似的に実現する上で、メモリが少ない当時に効率的にと考えられたのが「タスクシステム」です。
「タスク」とはゲームを構成する最小単位の要素であり、非オブジェクト指向であるC言語などでゲームにオブジェクト指向の考えを部分的に取り入れたのがタスクシステムです。「システム」と称するフレームワーク的なもの。
タスクシステムのメリットとしては、制御構造を動的かつ柔軟に変更出来る(プログラムのループを一部開放して、複数の制御処理を付けたり外したりできるようにしたもの)。
タスクシステムについては、下記サイトが参考になるでしょう。
コンピュータゲームのからくり 2001/07/10 タスクと呼ばれるテクニックを紹介
http://bit.ly/UFstKb
ゲームプログラム
http://homepage3.nifty.com/moha/programming.html
タスクステムについて(基本編)
http://www5f.biglobe.ne.jp/~kenmo/program/task/task2/task2.html
プログラミング/タスクシステム
http://t.co/v7TJyHBR
ビデオゲームの実装の超基礎理論 - itkz
http://www.nicovideo.jp/watch/sm2078250
本格的なシューティングゲームを実現するタスクシステム
http://codezine.jp/article/detail/297
近代的タスクシステムの構築
http://d.hatena.ne.jp/yaneurao/20090203
http://d.hatena.ne.jp/yaneurao/20090204
タスクシステムとゆるゆるなキャラクタ
http://marupeke296.com/GDEV_main.html
タスクシステムにコルーチンを組み込むには
http://blogs.wankuma.com/nagise/archive/2008/03/12/127443.aspx
関数ポインタの部分については、現在のオブジェクト指向では、すべてのゲームオブジェクトはupdateメソッドを持つ共通基底クラスを継承し、ゲームループ内では共通基底クラスのリストに対してupdateメソッドを呼び出して回して実装するポリモーフィズムによって行うことができます。
また、海外ではTaskという言葉を使わずにGameObjectとかGameEnttiyとかActorとかが多いようです。
タスクシステムについて、2chあたりの過去ログを読んでみると、無理にタスクシステムを構築する必要は無さそうです。制御順序を動的に変更する必要がなければ、オーソドックスな作りのままでいいかと。
タスクシステムは擬似的スレッドを実現するためのでしたが、もう1つスレッドに似た機能を実現するマイクロスレッド(microthread)があります。コルーチン(coroutine)、ファイバー(fiber)といった呼び方もあります。
タスクシステムが、ゲームループという1つの流れの中で、自機・敵機・弾を「わずかばかり動かしてループに戻す」という外側の仕組みだとすると、マイクロスレッド(microthread)は個々(自機・敵機・弾)である内側の仕組みに使われると私は思っております。
マイクロスレッドを使うと、動きの制御が直感的に組むことが出来るため、ゲームスクリプト内でキャラクターの動作制御の用途に使われていることが多いです。
例えば、初心者の方が「キャラクターを1フレームに1ドットずつ100 ドット動かす」といった場合、下記のようなコーディングをしてしまうことがあります。
For i As Integer = 1 To 100
myChar.MoveToRight(1)
Next
これでは結果的に失敗で、これを実行すると1フレームで100 ドット右に動いてしまいます。
ところが、Yeildステートメントを1行入れることで、1フレームに1ドットずつ100 ドット動かすことが出来てしまいます。
For i As Integer = 1 To 100
myChar.MoveToRight(1)
Yeild 0
Next
Yeildを使用することで、関数の実行を一時的に中断し、次回にその関数が呼び出されたときにその続きを実行することが出来ます。また、状態変数を減らせることも出来ます。
例 60フレームに1度、5フレーム間隔に5発弾を撃つ
http://www44.atwiki.jp/pioswiki/pages/33.html
マイクロスレッドの詳しい内容については、下記の記事を参考にして下さい。
イテレータとマイクロスレッド
http://ufcpp.net/study/csharp/sp2_microthread.html
第11回 快刀乱麻を断つ、第12回 百足競争、第13回 蛙の子は蛙
http://www.geocities.co.jp/SiliconValley-Oakland/9951/products/thdnhlec/index.html
プログラムはあとで書き直します。現在は、10個の赤い円がスピード違いで左から右に動作します。
Public Class Form1
Private mainGraphics As Graphics
Private BulletList As New List(Of Element)
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
Init()
End Sub
Private Sub Init()
Me.DoubleBuffered = True 'ダブルバッファリング
'▼描画用のGraphicsクラスの確保(技術的な処理)
If mainGraphics Is Nothing Then
'初回のみ生成
Dim bmp As New Bitmap(Me.ClientRectangle.Width, Me.ClientRectangle.Height)
Me.BackgroundImage = bmp
mainGraphics = Graphics.FromImage(bmp)
For i As Integer = 0 To 9
Dim elm As Element = New Element With {
.x = 0,
.y = i * 20,
.color = Color.Red,
.canvas = mainGraphics,
.canvasWidth = Me.ClientSize.Width,
.canvasHeight = Me.ClientSize.Height
}
BulletList.Add(New Bullet(elm, i + 1, 0))
Next
End If
Timer1.Enabled = True
End Sub
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
'描画されているものを消して全面を黒くする。
mainGraphics.Clear(Color.Black)
For Each obj As Element In BulletList
obj.Update()
Next
'フォームを再描画
Me.Invalidate()
End Sub
End Class
Public Class Element
Property x As Integer
Property y As Integer
Property color As Color
Property canvas As Graphics
Property canvasWidth As Integer
Property canvasHeight As Integer
Property updater As IEnumerator(Of Integer)
Public Sub New()
updater = GetUpdater()
End Sub
Public Overridable Iterator Function GetUpdater() As IEnumerable(Of Integer)
End Function
Protected Function IsFrameOut() As Boolean
Return x < 0 OrElse x > canvasWidth OrElse y < 0 OrElse y > canvasHeight
End Function
Public Sub Update()
updater.MoveNext()
End Sub
End Class
Public Class Bullet : Inherits Element
Dim vx, vy As Double
Public Sub New(elm As Element, vx As Double, vy As Double)
Me.x = elm.x
Me.y = elm.y
Me.color = elm.color
Me.canvas = elm.canvas
Me.canvasWidth = elm.canvasWidth
Me.canvasHeight = elm.canvasHeight
Me.vx = vx
Me.vy = vy
End Sub
Public Overrides Iterator Function GetUpdater() As IEnumerable(Of Integer)
While (1)
Me.x += Me.vx
Me.y += Me.vy
If Me.IsFrameOut() Then
Exit While
Else
CreateBullet()
Yield Nothing
End If
End While
End Function
Protected Sub CreateBullet()
Dim brash = New SolidBrush(color)
canvas.FillEllipse(brash, x, y, 10, 10)
End Sub
End Class