Page 3. Microsoft XNA's Game Time and Aspect Ratios

Prerequisites

Before you jump into the project, it's important to make sure you've got the general framework covered. If you're entirely new to Microsoft XNA and MonoGame, it's recommended to start with our introductory project. Please follow the walkthrough provided on Page 1: Microsoft XNA's Hello World w/ MonoGame.

List of Prerequisites:

  • A working installation of Visual Studio

  • MonoGame framework installed

  • .xnb of your desired font (Review Page 1 to make one)


Importing Libraries

Start by importing the necessary libraries. Open your Game1.cs file and include the following namespaces at the top.

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

Microsoft.Xna.Framework

This is the core MonoGame library that provides the fundamental game loop, input management, and other essential game development functionalities. It gives you the foundation to build upon for your game logic.

Microsoft.Xna.Framework.Graphics

This library specializes in rendering graphics and textures on the screen. It allows you to draw sprites, and shapes, and utilize graphical effects. This is what you'll use for any graphical elements, like animated text in this tutorial.


Building the Namespace and Game1 Class

After importing the essential libraries, you can move on to declaring your namespace and class. Within the Game1 class, you will initialize several key variables to manage game time and aspect ratios. Here's how the class should look:

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace YourGameNamespace // Replace with your namespace
{
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        // Frame rate variables
        int frameRate;
        int frameCounter;
        TimeSpan elapsedTime = TimeSpan.Zero;

        // Aspect ratio variables
        float aspectRatio;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            // Initialize frame rate variables
            frameRate = 0;
            frameCounter = 0;

            // Initialize aspect ratio
            aspectRatio = 4f / 3f;  // Default 4:3 aspect ratio
            this.TargetElapsedTime = TimeSpan.FromTicks(166667); // Sets to 60 FPS
        }
    }
}

Explaining Variables:

  • GraphicsDeviceManager graphics;: Manages the configuration and setup of the graphics device, including the game window.

  • SpriteBatch spriteBatch;: Enables drawing textures and fonts on the screen.

  • int frameRate, frameCounter;: These are utilized for calculating and displaying the current frame rate.

  • TimeSpan elapsedTime;: Keeps track of time elapsed, aiding in frame rate calculation.

  • float aspectRatio;: Helps in maintaining consistent aspect ratios across varied screen dimensions.

Understanding Variable Significance:

frameRate

The frameRate variable keeps track of the game's calculated frame rate, which is the frequency of screen updates, measured in frames per second (FPS). Knowing your frame rate can help you optimize your game for varying systems.

frameCounter

The frameCounter variable counts frames as they're rendered. It works in tandem with elapsedTime to compute the actual frame rate. Essentially, it keeps a tally of the number of frames drawn within a given time frame.

Understanding this.TargetElapsedTime = TimeSpan.FromTicks(166667);

  1. TargetElapsedTime: This property sets the amount of time that should elapse between each call to the Update method. It essentially sets the game's target frame rate.

  2. TimeSpan.FromTicks: This method converts a time in ticks to a TimeSpan object. A tick in .NET is 100 nanoseconds.

  3. 166,667 ticks: 166,667 ticks at 100 nanoseconds per tick equates to 16,666,700 nanoseconds or 16.6667 milliseconds. Since there are 1000 milliseconds in a second, dividing 1000 by 16.6667 gives us roughly 60. This is how the value of 166,667 ticks corresponds to 60 frames per second (FPS).

    • Formula to find ticks for other FPS:

      Ticks = (1 / Desired_FPS) * 10,000,000

      For example, for 30 FPS, the ticks would be (1 / 30) * 10,000,000 = 333,333.33, which you can round to 333,333 ticks.

Comparing TargetElapsedTime and elapsedTime

  1. Purpose:

    • TargetElapsedTime is what you set to control how often the game's Update method gets called.

    • elapsedTime is used for tracking how much time has actually passed since the game started or since you last checked it.

  2. Control vs. Measure:

    • TargetElapsedTime is a control variable that you set to tell the game engine your desired frame rate.

    • elapsedTime is a measurement variable that tells you the actual elapsed time during game execution. This can be useful for timing animations, game events, and more.

  3. Type:

    • TargetElapsedTime is a property of the Game class.

    • elapsedTime is a variable you define and manage within your code.

Understanding aspectRatio = 4f / 3f;

  1. Numbers 4 and 3: The aspect ratio 4:3 is a common aspect ratio used in various displays. It simply means that for every 4 units of width, there are 3 units of height. This ratio ensures that the display won't look stretched or squashed.

  2. The 'f' after the numbers: In C#, the f specifies that the number is a floating-point number. Floating-point numbers are used for decimal or fractional calculations. Without the 'f', C# would treat the numbers as doubles, and it would lead to a type mismatch error because aspectRatio is declared as a float.

  3. Division operation: The division operation 4f / 3f calculates the aspect ratio value. In this case, it would return approximately 1.3333.

  • Why use a float for aspect ratio and not an integer?

    • Answer: Using a float allows for more precise calculations. An aspect ratio is generally not a whole number, so using a float ensures you get the most accurate representation.

  1. Assigning to aspectRatio: The calculated value is then assigned to the aspectRatio variable, which can be used throughout the program to maintain the aspect ratio across different functionalities.

By setting the aspect ratio like this, you have a default value that can be used to guide how graphics and UI elements are scaled and positioned on the screen. This is particularly useful if you don't want to calculate the aspect ratio dynamically, or if you want to support a specific set of aspect ratios.


Setting Up Frame Rate Control

Firstly, you might ask yourself, "Why is controlling the frame rate important?" Controlling the frame rate ensures that your game runs consistently across different machines. While a faster machine might run your game at a very high frame rate, a slower one might struggle. By setting a standard frame rate, you make the gaming experience more consistent.

    protected override void Update(GameTime gameTime)
    {
        // Basic game update logic here ...

        // Frame rate control
        frameCounter++;
        elapsedTime += gameTime.ElapsedGameTime;

        if (elapsedTime > TimeSpan.FromSeconds(1))
        {
            elapsedTime -= TimeSpan.FromSeconds(1);
            frameRate = frameCounter;
            frameCounter = 0;
        }
    }

Explanation of Frame Rate Control Code

  • frameCounter++: Increment the frame counter by 1 for each frame rendered.

  • elapsedTime += gameTime.ElapsedGameTime: Add the time elapsed during the last frame to elapsedTime.

  • if (elapsedTime > TimeSpan.FromSeconds(1)): Here's a thinking point. Why do you think we check if elapsedTime is greater than one second?

    • Answer: Checking against one second allows us to update the frame rate once every second.

  • elapsedTime -= TimeSpan.FromSeconds(1): We subtract one second to reset elapsedTime.

  • frameRate = frameCounter: The frame rate is updated with the total number of frames counted in the last second.

  • frameCounter = 0: Resetting the frame counter for the next second.The frame rate control logic inside the Update() method updates the frame rate once every second. This is accomplished by counting the frames rendered using frameCounter and updating frameRate every second based on that count.


Handling Aspect Ratio in the Initialize Method

Understanding how to manage aspect ratios is crucial for delivering a game that looks good on multiple devices. Let's get you thinking:

  • Why is handling different aspect ratios important for a game?

    • Answer: Managing aspect ratios ensures that your game's visual elements are not distorted when displayed on screens with different dimensions.

Now let's dive into setting up aspect ratio control within the Initialize() method. As before, you'll be adding this to your existing Game1 class.

Updated Game1 Class with Aspect Ratio Control

    protected override void Initialize()
    {
        // Initialize previous logic here...

        // Aspect Ratio control
        aspectRatio = graphics.GraphicsDevice.Viewport.AspectRatio;

        // You can also enforce a specific aspect ratio like this:
        // graphics.PreferredBackBufferWidth = 800;
        // graphics.PreferredBackBufferHeight = (int)(800 / aspectRatio);
        // graphics.ApplyChanges();
        
        base.Initialize();
    }

Explanation of Aspect Ratio Control Code

  • aspectRatio = graphics.GraphicsDevice.Viewport.AspectRatio;: Retrieve the aspect ratio of the screen. This line gets the aspect ratio of your game window.

  • How can knowing the screen's aspect ratio in real-time be beneficial?

    • Answer: Real-time knowledge allows you to dynamically adapt the game's layout, visuals, and other screen-dependent features.

  • graphics.PreferredBackBufferWidth = 800;: Here you set a preferred width for the back buffer, which is essentially your game's rendering area.

  • graphics.PreferredBackBufferHeight = (int)(800 / aspectRatio);: The height is calculated based on the aspect ratio and the preferred width. This ensures that the height will adjust according to the correct aspect ratio.

  • graphics.ApplyChanges();: This applies the changes made to the GraphicsDeviceManager.

Now you've established a method for handling different aspect ratios within your Initialize() method. This allows for a more adaptable and visually consistent game across different screens and resolutions.

Full Code

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace YourGameNamespace
{
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        SpriteFont font;
        Vector2 position;
        Vector2 velocity;
        Color color;

        // Frame rate variables
        int frameRate;
        int frameCounter;
        TimeSpan elapsedTime = TimeSpan.Zero;

        // Aspect ratio variables
        float aspectRatio;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            // Initialize frame rate variables
            frameRate = 0;
            frameCounter = 0;

            // Initialize aspect ratio
            aspectRatio = 4f / 3f;  // Default 4:3 aspect ratio
            this.TargetElapsedTime = TimeSpan.FromTicks(166667); // Sets to 60 FPS
        }

        protected override void Initialize()
        {
            // Handle aspect ratio in Initialize() here
            graphics.PreferredBackBufferWidth = 800;  
            graphics.PreferredBackBufferHeight = (int)(800 / aspectRatio);  
            graphics.ApplyChanges();

            base.Initialize();
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // TODO: Load your game content here
            font = Content.Load<SpriteFont>("Arial");
            position = new Vector2(400, 300);
            velocity = new Vector2(1, 1);
            color = Color.White;
        }

        protected override void Update(GameTime gameTime)
        {
            // Update frame rate counters
            elapsedTime += gameTime.ElapsedGameTime;
            if (elapsedTime > TimeSpan.FromSeconds(1))
            {
                elapsedTime -= TimeSpan.FromSeconds(1);
                frameRate = frameCounter;
                frameCounter = 0;
            }

            // TODO: Add your update logic here
            position += velocity;
            if (position.X > Window.ClientBounds.Width - 50 || position.X < 0)
            {
                velocity.X *= -1;
            }
            if (position.Y > Window.ClientBounds.Height - 50 || position.Y < 0)
            {
                velocity.Y *= -1;
            }

            color = new Color(frameRate % 255, 128, 128);

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            // Increment frame counter
            frameCounter++;

            spriteBatch.Begin();
            spriteBatch.DrawString(font, $"Hello, world! FPS: {frameRate}", position, color);
            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}

Knowledge Check

  1. Question 1: What is the purpose of the aspectRatio variable in the code?

  2. Question 2: Briefly explain how TimeSpan elapsedTime differs from this.TargetElapsedTime.

  3. Question 3: Describe in your own words why you would need to manage the frame rate in a game.

  4. Question 4: (Multiple Choice) Suppose you want to change the frame rate to 120 FPS. What value should TargetElapsedTime be set to in ticks?

    • A) 125,000

    • B) 83,333

    • C) 100,000

    • D) 50,000


Interactive Challenges

Challenge 1: Reduce Frame Rate to 10 FPS Your first task is to adjust TargetElapsedTime in your code to make the game run at 10 FPS.

Challenge 2: Automatic Frame Rate Toggle For this second challenge, let's make the frame rate change automatically every 5 seconds between 60 FPS and 30 FPS. You can use the elapsedTime variable to keep track of the time that has passed, and then switch the frame rate.

Last updated