Saturday, March 03, 2007

Framerate GameComponent

Updated Again: Reformatted source again. Should be the last time. Check out this post for details.

Updated: Removed line numbers in code snippets after some constructive criticism from ziggy.

Here is my implementation of a Framerate (FPS) GameComponent. It is a DrawableGameComponent even though in it's current form it doesn't actually draw anything to the screen. Since there is currently no built in font/text rendering in XNA, this component currently only updates the game window title with the current FPS. It can be easily adapted to draw the framerate to the screen by using something like Nuclex.Fonts or BitmapFonts. But to keep implementation as simple as possible, this is left out of this component for now. This component exposes a CurrentFramerate property that you can use in your Game code to easily draw the FPS to the screen without having to recompile the TehOne.Xna.GameComponents code.

This Framerate component isn't that dissimilar to other framerate GameComponents that you may have seen out there. The main difference is that it has a base class of TehOne.Xna.GameComponents.XnaDrawableGameComponent instead of the regular DrawableGameComponent provided by Microsoft. To see the reason behind this, read up on my previous post here. There are two other main differences from this and other framerate components that you may have seen on the web.

  • The first difference:
  • This class exposes a CurrentFramerate property that you can then use in your game. I did this for two reasons: To allow you to have easy access to the current FPS from your game, so you can render text without having to integrate it into this component (and having to recompile it). And, the other reason is that now you can write code in your game that uses the FPS for some sort of logic. I was thinking that this would be useful for something like smoothing out animation based on the current FPS, or smoothing out movement or something else along those lines.
  • The second difference:
  • This class has an UpdateFrequency property. This property allows you to set how often you want to calculate the FPS. I don't know about you, but most of the games I play update the FPS more then once a second. So, I thought that I would implement this feature. You can set the frequency to anything higher then 99 as long as it is divisible by 100. This property is based on milliseconds, so if you want to update your FPS every 10th of a second, then you would set this property to 100. Likewise, if you want to update your FPS every other second, you would set this property to 2000.

Ok, now on to the code. (code license)

Show Plain Text
Show Line #
Alternating Lines
Hide Comments
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using Microsoft.Xna.Framework;
  5.   
  6. namespace TehOne.Xna.GameComponents
  7. {
  8.     /// <summary>
  9.     /// This is a <see cref="XnaDrawableGameComponent"/> that is used to calculate and display the current framerate (FPS, FramesPerSecond) of the game.
  10.     /// <para>Extends <see cref="XnaDrawableGameComponent"/> so that we can use it's <see cref="XnaDrawableGameComponent.PreviouslyEnabled"/> property and take advantage of it auto-enabling and disabling when the game is focused/not-focused.</para>
  11.     /// </summary>
  12.     public partial class Framerate : XnaDrawableGameComponent
  13.     {
  14.         private double _fps = 0;
  15.         private float _elapsedRealTime = 0;
  16.         private string _gameWindowTitle;
  17.         private ushort _updateFrequency = 1000;
  18.         private float _updateFrequencyRatio;
  19.   
  20.         /// <summary>
  21.         /// The CurrentFramerate property represents the current frames per second (FPS) for the game.
  22.         /// </summary>
  23.         /// <value>The CurrentFramerate property gets the <see cref="_fps"/> data member.</value>
  24.         public double CurrentFramerate
  25.         {
  26.             get
  27.             {
  28.                 return _fps;
  29.             }
  30.         }
  31.   
  32.         /// <summary>
  33.         /// The UpdateFrequency property represents, in milliseconds, how often we are updating the <see cref="_fps"/> count.
  34.         /// </summary>
  35.         /// <value>The UpdateFrequency property gets/sets the <see cref="_updateFrequency"/> data member.</value>
  36.         /// <remarks>
  37.         /// This is used to set how often we are calculating the frames per second count.
  38.         /// <para>The default value is <b><c>1000</c></b>.</para>
  39.         /// </remarks>
  40.         /// <exception cref="System.ArgumentOutOfRangeException">This exception will be thrown when the <b><c>value</c></b> is set less then <b><c>100</c></b> or when <b><c>value</c></b> is not divisible by <b><c>100</c></b>.</exception>
  41.         public ushort UpdateFrequency
  42.         {
  43.             get
  44.             {
  45.                 return _updateFrequency;
  46.             }
  47.             set
  48.             {
  49.                 if(value % 100 != 0 || value < 100)
  50.                     throw new ArgumentOutOfRangeException("UpdateFrequency", value, "The UpdateFrequency for the Framerate must is based on milliseconds and must be must be a positive number that is greater then or equal to 100, and the number must be divisable by 100.");
  51.   
  52.                 _updateFrequency = value;
  53.   
  54.                 // Figure out the new ratio, this way we are reporting the correct frames per second even when we are not calculating ever second.
  55.                 _updateFrequencyRatio = 1000 / (float)_updateFrequency;
  56.             }
  57.         }
  58.   
  59.         /// <summary>
  60.         /// The main constructor for the class.
  61.         /// </summary>
  62.         /// <param name="game">The <see cref="Microsoft.Xna.Framework.Game" /> instance for this <see cref="XnaDrawableGameComponent"/> to use.</param>
  63.         /// <remarks>Sets the <see cref="_gameWindowTitle"/> data member to the value of <see cref="Microsoft.Xna.Framework.GameWindow.Title"/>.</remarks>
  64.         public Framerate(Game game) : base(game)
  65.         {
  66.             // Save the original game window title
  67.             _gameWindowTitle = game.Window.Title;
  68.   
  69.             // We are basing the ratio on 1 second (1000 milliseconds)
  70.             _updateFrequencyRatio = 1000 / (float)_updateFrequency;
  71.         }
  72.   
  73.         /// <summary>
  74.         /// Allows the game component to perform any initialization it needs to before starting
  75.         /// to run.  This is where it can query for any required services and load content.
  76.         /// </summary>
  77.         public override void Initialize()
  78.         {
  79.             base.Initialize();
  80.         }
  81.   
  82.         /// <summary>
  83.         /// Allows the game component to update itself.
  84.         /// </summary>
  85.         /// <param name="gameTime">Provides a snapshot of timing values.</param>
  86.         public override void Update(GameTime gameTime)
  87.         {
  88.             base.Update(gameTime);
  89.   
  90.             if (!this.Enabled)
  91.                 return;
  92.   
  93.             // The total real time in milliseconds since the last Update().
  94.             float elapsed = (float)gameTime.ElapsedRealTime.TotalMilliseconds;
  95.   
  96.             // Adds the Update() elapsed real time to the cumulative elapsed real time.
  97.             _elapsedRealTime += elapsed;
  98.   
  99.             // If the elapsed time is greater than our update frequency then: calculate the framerate, and reduce the elapsed real time count.
  100.             if(_elapsedRealTime > _updateFrequency)
  101.             {
  102.                 _fps = (_updateFrequency / elapsed) * _updateFrequencyRatio; // calculate the framerate
  103.                 _elapsedRealTime -= _updateFrequency; // adjust the elapsedRealTime
  104.             }
  105.         }
  106.   
  107.         /// <summary>
  108.         /// This is called when the game should draw itself.
  109.         /// </summary>
  110.         /// <param name="gameTime">Provides a snapshot of timing values.</param>
  111.         public override void Draw(GameTime gameTime)
  112.         {
  113.             base.Draw(gameTime);
  114.   
  115.             if (!this.Visible || !this.Enabled)
  116.                 return;
  117.   
  118.             this.Game.Window.Title = _gameWindowTitle + " FPS: " + _fps.ToString("F");
  119.         }
  120.   
  121.         /// <summary>
  122.         /// This method is the <see cref="System.EventHandler"/> for the <see cref="Microsoft.Xna.Framework.GameComponent.EnabledChanged"/> event.
  123.         /// </summary>
  124.         /// <param name="sender">The events sender</param>
  125.         /// <param name="args">The events arguments.</param>
  126.         protected override void OnEnabledChanged(object sender, EventArgs args)
  127.         {
  128.             if(!this.Enabled)
  129.                 this.Game.Window.Title = _gameWindowTitle;
  130.   
  131.             base.OnEnabledChanged(sender, args);
  132.         }
  133.   
  134.         /// <summary>
  135.         /// This method is the <see cref="System.EventHandler"/> for the <see cref="Microsoft.Xna.Framework.GameComponent.EnabledChanged"/> event.
  136.         /// </summary>
  137.         /// <param name="sender">The events sender</param>
  138.         /// <param name="args">The events arguments.</param>
  139.         protected override void OnVisibleChanged(object sender, EventArgs args)
  140.         {
  141.             if (!this.Visible)
  142.                 this.Game.Window.Title = _gameWindowTitle;
  143.   
  144.             base.OnVisibleChanged(sender, args);
  145.         }
  146.     }
  147. }


Ok, so now we need to put this into our game. Add a member to your class for the Framerate. It should look something like this:
Show Plain Text
Show Line #
Alternating Lines
Hide Comments
  1. public class Game1 : Microsoft.Xna.Framework.Game
  2.     {
  3.         ContentManager content;
  4.         GraphicsDeviceManager graphics;
  5.   
  6.         Framerate framerate;


Now we need to instantiate the Framerate and add it to the Game.Components collection. This is done in your constructor for your game. It should look something like this:
Show Plain Text
Show Line #
Alternating Lines
Hide Comments
  1. public Game1()
  2.         {
  3.             graphics = new GraphicsDeviceManager(this);
  4.             content = new ContentManager(Services);
  5.             
  6.             framerate = new Framerate(this);
  7.             framerate.UpdateFrequency = 100;
  8.   
  9.             this.Components.Add(framerate);


Note: We also set the UpdateFrequency to 100 which will cause our FPS to be calculated every 10th of a second instead of the default of every once per second.

Well, that is pretty much all there is to it. If you have done everything correctly, when you launch your (windowed) game, you should see the framerate in the window title.

1 comments:

ovideast@hotmail.com said...

Looks like your offine chm is broken. When I open 'er up, I can see the content tree, but no content.