Welcome to WindowsClient.net | Sign in | Join

The Control Gallery is a directory of controls that you can use in your applications. The number in parenthesis indicates how many controls are available in the category.

Application (Game) Loop, FPS and Sprite like Animations

Control Author:

Posted: 05-10-2007

Views: 1,618

Downloads: 565

 
File Details

I will introduce you to 2 ways to create a game loop including a frame-based solution and a finer-grained method.  I will also show you a way to display the FPS.

 

Game Loop 1

The frame-based solution involves using a Rendering event handler of the CompositionTarget class.  CompositionTarget represents the display surface on which your application is being drawn.  The Rendering event is raised once per frame, each time WPF marshals the persisted rendering data in the visual tree across to the composition tree.  Note also that any change to the visual tree which forces an update to the composition tree will also raise it. 

 

In the constructor for your partial class derived from Window simply attach your custom event handler.

CompositionTarget.Rendering += MyEventHandler;

 

Game Loop 2

DispatcherTimer is a timer integrated into the Dispatcher queue.  In the constructor one can specify which Dispatcher to run on and at what priority to process the timer.  By default the DispatcherTimer runs on the Dispatcher of the current thread at Background priority.  It has an Interval property that holds the period of time between ticks.  The default value is 00:00:00.  The Tick event occurs when the timer interval has elapsed and the timer is enabled. 

 

In the constructor for your partial class derived from Window simply attach your custom event handler.

System.Windows.Threading.DispatcherTimer frameTimer;

public MyPartialClass()

{

 

  frameTimer = new System.Windows.Threading.DispatcherTimer();

  frameTimer.Tick += MyEventHandler;

  frameTimer.Interval = TimeSpan.FromSeconds(1.0 / 60.0);

  frameTimer.Start();

 

}

 

FPS

All that’s left is to fill in the details for MyEventHandler.  How about a FPS calculation.  Note that in the last line I set the Content of a FrameRateLabel.  FrameRateLabel is a Label control named FrameRateLabel contained in the .xaml file.

 

private double elapsed;

private double totalElapsed;

private int lastTick;

private int currentTick;

private int frameCount;

private double frameCountTime;

private int frameRate;

 

public Window1()

{

  InitializeComponent();

  //game loop 1 or 2

  this.lastTick = Environment.TickCount;

}

 

protected void MyEventHandler(object sender, EventArgs e)

{

  this.currentTick = Environment.TickCount;

  this.elapsed = (double)(this.currentTick - this.lastTick) / 1000.0;

  this.totalElapsed += this.elapsed;

  this.lastTick = this.currentTick;

 

  frameCount++;

  frameCountTime += elapsed;

  if (frameCountTime >= 1.0)

  {

    frameCountTime -= 1.0;

    frameRate = frameCount;

    frameCount = 0;

    this.FrameRateLabel.Content = "FPS: " + frameRate.ToString();

  }

}

 

Gotchas:

  • The DispatcherTimer is reevaluated at the top of every dispatcher loop.  If the Dispatcher is processing lots of messages, a DispatcherTimer may not be evaluated at the expected time.
    • One scenario in which this can occur is if the dispatcher queue is empty, and so WPF is not processing any messages. WPF depends on WM_TIMER to wake up the Dispatcher at the requested time. If another window has lots of activity that keeps the queue full, there may be a delay before it drains down to the timer priority.
    • Another scenario where the DispatcherTimer may become inaccurate is if a long-running operation is executing and the message pump stalls. This could lead to window ghosting and inaccuracies in timer.
  • If a .Net Timer is used in a WPF application, it is worth noting that the .Net Timer runs on a different thread then the user interface (UI) thread. In order to access objects on the user interface (UI) thread, it is necessary to push the operation onto the Dispatcher of the user interface (UI) thread. For an example of this, see the Disable Command Source Via System Timer sample.

SPRITE LIKE ANIMATION


Photobucket - Video and Image Hosting

 

 

THIS IS NOT THE ENTIRE SOURCE, ONLY KEY SNIPPETS.

 

<my:Sprite   x:Name="mySprite" xmlns:sys="clr-namespace:System;assembly=mscorlib">

  <my:Sprite.SourceUri>

    /explode.png

  </my:Sprite.SourceUri>

          

  <my:Sprite.FrameWidth>

    34

  </my:Sprite.FrameWidth>

 

  <my:Sprite.FrameSequence>

    <x:Array Type="{x:Type sys:Int32}">

      <sys:Int32>0</sys:Int32>

      <sys:Int32>1</sys:Int32>

      <sys:Int32>2</sys:Int32>

      <sys:Int32>3</sys:Int32>

      <sys:Int32>4</sys:Int32>

      <sys:Int32>5</sys:Int32>

      <sys:Int32>6</sys:Int32>

      <sys:Int32>7</sys:Int32>

    </x:Array>

  </my:Sprite.FrameSequence>

         

  </my:Sprite>

</DockPanel>

 

public partial class Window1 : Window

{

  System.Windows.Threading.DispatcherTimer frameTimer;

 

  private double elapsed;

  private double totalElapsed;

  private int lastTick;

  private int currentTick;

  private int frameCount;

  private double frameCountTime;

  private int frameRate;

 

  public Window1()

  {

    InitializeComponent();

 

    //game loop

    frameTimer = new System.Windows.Threading.DispatcherTimer();

    frameTimer.Tick += MainLoop;

    frameTimer.Interval = new TimeSpan(0,0,0,0,50);//alter the max frame rate here

    frameTimer.Start();

    //FPS

    this.lastTick = Environment.TickCount;

 

    //Sprite Code

    mySprite.Init();

  }

 

  protected void MainLoop(object sender, EventArgs e)

  {

    this.currentTick = Environment.TickCount;

    this.elapsed = (double)(this.currentTick - this.lastTick) / 1000.0;

    this.totalElapsed += this.elapsed;

    this.lastTick = this.currentTick;

 

    frameCount++;

    frameCountTime += elapsed;

    if (frameCountTime >= 1.0)

    {

      frameCountTime -= 1.0;

      frameRate = frameCount;

      frameCount = 0;

      this.FrameRateLabel.Content = "FPS: " + frameRate.ToString();

    }

 

    //Sprite Code

    mySprite.NextFrame();

  }

}

 




Page view counter