在WPF/Silverlight中, 可以透過下列方式來實作Game Loop,分別是:
- DispatcherTimer - 可以在指定的時間間隔內, 處理佇列中的執行緒.
- Storyboard - 以時間軸控制動畫, 和DispatcherTimer的作法類似, 透過Duration屬性決定時間長短, 然後在Completed事件中再次呼叫Storyboard的Begin方法, 就可以達到類似計時器的迴圈.
- CompositionTarget.Rendering - 每當程式要顯示一個畫面(frame)時, 就會引發Rendering事件
下列程式碼將示範如何使用上述的方法, 我們會讓一個圓球在視窗內作移動, 當碰到邊界時會作反彈, 而圓球的座標是在Game Loop中作更新, 並顯示對應的FPS值, 如下圖所示:
xmal檔如下:
<Window x:Class="GameLoopTesting.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" SizeToContent="WidthAndHeight">
<Canvas x:Name="LayoutRoot" Background="Black" Width="800" Height="600">
<TextBlock x:Name="txtFPS"
Foreground="White"
FontFamily="Arial"
FontSize="24"
Text="FPS:"
Panel.ZIndex="99"/>
<Ellipse x:Name="ball"
Width="50"
Height="50"
Fill="LightBlue"
Stroke="Gray"
Canvas.Left="100"
Canvas.Top="100">
</Ellipse>
</Canvas>
</Window>
cs檔如下:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Threading;
using System.Windows.Media.Animation;
namespace GameLoopTesting
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public enum GameLoopType
{
DISPATCHERTIMER,
STORYBOARD,
RENDERING
}
/// <summary>
/// For object parameters
/// </summary>
private float radius;
private double dx = 1;
private double dy = 1;
private double x = 0;
private double y = 0;
/// <summary>
/// For FPS property
/// </summary>
private int _frameCounter = 0;
private double _timeSinceLastUpdate = 0.0;
private DateTime _lastUpdateTime = DateTime.MinValue;
private double _fpsValue = 0.0;
private Storyboard _sbTrigger;
public Window1()
{
InitializeComponent();
radius = (float)ball.Width/2;
_lastUpdateTime = DateTime.Now;
///Switch game loop type
SwitchGameLoop(GameLoopType.DISPATCHERTIMER);
}
void SwitchGameLoop(GameLoopType gameLoopType)
{
switch (gameLoopType)
{
case GameLoopType.DISPATCHERTIMER:
{
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(10);
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
}
break;
case GameLoopType.STORYBOARD:
{
_sbTrigger = new Storyboard();
_sbTrigger.Duration = TimeSpan.FromMilliseconds(10);
_sbTrigger.Completed += new EventHandler(sbTrigger_Completed);
_sbTrigger.Begin();
}
break;
case GameLoopType.RENDERING:
{
CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
}
break;
}
}
void timer_Tick(object sender, EventArgs e)
{
/// Calculate FPS value
/// -----------------------------------------------------------
_frameCounter++;
if ((DateTime.Now - _lastUpdateTime).Seconds >= 1)
{
txtFPS.Text = "FPS : " + _frameCounter;
_frameCounter = 0;
_lastUpdateTime = DateTime.Now;
}
//-----------------------------------------------------------
UpdateObjectPosition();
}
void sbTrigger_Completed(object sender, EventArgs e)
{
UpdateObjectPosition();
_sbTrigger.Begin();
}
void CompositionTarget_Rendering(object sender, EventArgs e)
{
/// Calculate FPS value
/// -----------------------------------------------------------
TimeSpan elapsedTime = DateTime.Now - _lastUpdateTime;
_frameCounter++;
_timeSinceLastUpdate += elapsedTime.TotalSeconds;
if (_frameCounter >= 100)
{
_fpsValue = (int)(_frameCounter / _timeSinceLastUpdate);
txtFPS.Text = "FPS : " + _fpsValue;
_frameCounter = 0;
_timeSinceLastUpdate = 0;
}
_lastUpdateTime = DateTime.Now;
//----------------------------------------------------------------
UpdateObjectPosition();
}
/// <summary>
/// Update the position of object
/// </summary>
private void UpdateObjectPosition()
{
x = ((double)Canvas.GetLeft(ball));
y = ((double)Canvas.GetTop(ball));
x += dx;
y += dy;
Canvas.SetLeft(ball, x);
Canvas.SetTop(ball, y);
if (x > LayoutRoot.Width - 2 * radius)
{
dx = -Math.Abs(dx);
}
if (x < 0)
{
dx = Math.Abs(dx);
}
if (y > LayoutRoot.Height - 2 * radius)
{
dy = -Math.Abs(dy);
}
if (y < 0)
{
dy = Math.Abs(dy);
}
}
}
}
總 結:
* 在WPF中使用Storyboard作Game Loop時, 會發生畫面更新到一半時, 卻停止更新,但Silverlight上卻不會, 原因目前還不知道.
* 透過上述的3種方式, 都可以作為遊戲的迴圈, 不過穩定度和解析度上有所差別.可參考這篇Game Loop部落格的詳細比較.
就穩定度來說 : CompositionTarget.Rendering > Storyboard > DispatcherTimer
Creating a Game Loop
Application (Game) Loop, FPS and Sprite like Animation
Game Loop