2010年4月15日 星期四

Game Loop Compare- DispacterTimer, Storyboard, Composition.Rendering

Game Loop 對寫遊戲來說是一個很重要的步驟, 通常我們會將一些不需長時間且不用大量計算的事情, 放在Game Loop裡面去作, 例如:碰撞偵測, 動畫撥放, 更新繪圖座標, 檢查滑鼠或鍵盤的狀態..等.

WPF/Silverlight中, 可以透過下列方式來實作Game Loop,分別是:

  • DispatcherTimer - 可以在指定的時間間隔內, 處理佇列中的執行緒.
  • Storyboard - 以時間軸控制動畫, 和DispatcherTimer的作法類似, 透過Duration屬性決定時間長短, 然後在Completed事件中再次呼叫StoryboardBegin方法, 就可以達到類似計時器的迴圈.
  • CompositionTarget.Rendering - 每當程式要顯示一個畫面(frame)時, 就會引發Rendering事件

下列程式碼將示範如何使用上述的方法, 我們會讓一個圓球在視窗內作移動, 當碰到邊界時會作反彈, 而圓球的座標是在Game Loop中作更新, 並顯示對應的FPS值, 如下圖所示:

[圖 1]利用Game Loop作動畫

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

2010年4月13日 星期二

如何在Silverlight的應用程式上作Debug

在開發Silverlight的應用程式時, 你可能要用到Debug功能, 來找出程式的錯誤, 但在Silverlight專案執行Debug功能, 可能會出現下面訊息, 而無法除錯.



解決的方法如下 :
Visual Studio 2008 SP1 的 Silverlight 3 Tools 包括:
* Visual Basic 和 C# 專案範本
* Intellisense 和 XAML 程式碼產生器
* Silverlight 應用程式偵錯
* 適用於 Mac 的 Silverlight 應用程式遠端偵錯
* Web 參考支援
* WCF 範本
* Team Build 和命令列建置支援
* 支援快取的透明平台擴充程式
* 支援 Silverlight 3 瀏覽器外用應用程式

安裝上述工具後, 即可解決上述問題.
另外若預設瀏覽器可能不是IE, 可能會發生執行Silverlight的程式沒有畫面, 此時就必須將IE設定為預設瀏覽器, 作法如下:
  1. 開啟 Internet Explorer 瀏覽器視窗。
  2. 選取 [工具] > [網際網路選項]。
  3. 選取 [程式集] 標籤。
  4. 在 [預設網頁瀏覽器] 區段中按一下 [設成預設值]。
  5. 按一下 [確定]

2010年4月11日 星期日

Touch Game Framework

最近要開發一些Touch的遊戲, 所以構思了整個Game Framework.

下面的表格列出之前survey的一些game engine, 不過對我來說還是有一些技術性的問題,例如: touch功能的支援, UI的建立, 軟體收費, 支援平台等等.


[圖] XNA, Unity, Blender的比較表

所以最後構思的遊戲架構如下:


應用程式 - 選用Silverlight平台, 主要優點如下:
  • 豐富的圖形及使用者互動功能: Silverlight 包含了 WPF技術的子集,可大幅擴充瀏覽器中用來建立 UI 的項目. 另外, 它具有向量式的繪圖能力, 3D繪圖和製作動畫等等.
  • 影音播放 - 它會以資料流方式處理視訊和音訊. 它可以調整視訊品質, 以配合從行動裝置到桌面瀏覽器, 甚至是到 720p HDTV 視訊模式等任何裝置.
  • 支援Windows 7Touch功能 - Silverlight 在3.0版本後就支援Windows 7的觸控功能, 可參考這篇文章的簡介 - Silverlight 3.0 + Multi-touch
  • 跨瀏覽器、跨平台的支援 - 它可以在所有常見的 Web 瀏覽器 (包括 Microsoft Internet ExplorerMozilla Firefox Apple Safari) 以及 Microsoft Windows 和 Apple Mac OS 上執行Silverlight, 可參考 =>支援的平台和瀏覽器
物理引擎 - 選用Farseer Physics Engine. 它是一個在.NET平台上使用的物理引擎(它是open source), 主要用來開發Silverlight和XNA2D遊戲. 它提供很多物理相關的特性, 例如: 重力, 作用力, 力矩,碰撞,彈簧和關節..等等.


參考文章 :
Farseer Physics Engine 3.0

msdn - Silverlight