Silverlightはユーザの入力イベントに対してハンドラを記述するイベント駆動型プログラミングモデルが基本となっていますが、アクションゲームのようなプログラムではユーザからの入力が全くない場合でも常に動き続けなければいけないので、このプログラミングモデルとは相性が良くありません。
一般的にゲームプログラムは、メインの処理をループで回して常にゲーム処理を動かしておく構造にします。このときのメインループを、ゲームループと呼んだりします。通常のWindowsプログラムでは、メッセージループと呼ばれるメインループが存在するので、そこをメッセージがないときでも止まらないように作ればゲームループを簡単に実装することができます。
しかし、SilverlightではメインループはSilverlightプラグインの内部で処理されていて、我々の手の届かない場所にあります。そこでタイマ等の一定間隔で発生するイベントをゲームループのかわりに使います。
ゲームループの代用になるのは、主に次の3つです。
- System.Windows.Media.CompositionTarget.Renderingイベント
- DispatcherTimer
- StoryBoard
1のCompositionTarget.Renderingイベントというのは、Silverlightの画面更新時に発生するイベントで、処理落ちがなければ一定間隔で常に発生しています。このイベントの発生間隔は、Application.Host.Settings.MaxFrameRateで設定可能で、デフォルトでは60fpsになっています。(ただし元となるタイマの分解能が1msなので、60fpsに設定しても16ms間隔の62.5fpsになる)
2のDispatcherTimerは、発生間隔を100ナノ秒単位で指定できるタイマイベントですが、Silverlightでは1のCompositionTarget.Renderingが呼ばれるタイミングが最小分解能となっています。デフォルトの60fps設定の場合、16msが最小分解能となっており、DispatcherTimerに17msを設定すると実際には32ms間隔でしかイベントが発生しません。
3のStoryBoardは空のStoryBoardを作成し、Completedイベントが発生するたびにBeginメソッドで繰り返し開始することで、タイマイベントのように動作します。これは、DispatcherTimerのIntervalを0に設定するのと実質的に同じことで、どちらを使っても大差ないと思います。
特に理由がなければ、1のRenderingイベントが一番いいと思います。このイベントの使い方は次のようになります。
public partial class Page : UserControl
{
public Page()
{
InitializeComponent();
CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
}
void CompositionTarget_Rendering(object sender, EventArgs e)
{
OnTimer();
}
フレームレートの設定はHTMLからSilverlightプラグインを呼び出すときのパラメータで与えることもできますが、Appクラスで明示的に設定してしまった方がいいでしょう。
public partial class App : Application
{
public App()
{
this.Startup += this.Application_Startup;
this.Exit += this.Application_Exit;
this.UnhandledException += this.Application_UnhandledException;
this.Host.Settings.MaxFrameRate = 60; // 60fps
InitializeComponent();
}
デフォルト値が60なので60fpsなら何も指定しなくてもいいのですが、プラグインのパラメータでフレームレートを落としてゲームの速度を遅くするチートが可能になってしまうので、60fpsでも明示的に指定しておいた方がいいと思います。

