Page 2. Microsoft XNA's Game Loops w/ Text Animation

Objective

The goal of this project is to introduce the concept of game loops in Microsoft XNA by creating a simple text animation that changes color and position. It will also teach the concept of clamping to ensure the text stays within the bounds of the window.


Prerequisites

Before you jump into the project, it's essential to make sure you've got the basics covered. If you're entirely new to Microsoft XNA and MonoGame, it's recommended to start with an 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)


Starting from Scratch

Once your new project is set up, go to the Game1.cs file. Remove all the code there, and we're going to build it up step by step for this text animation game.


Importing The Libraries

Before writing any code, import these key libraries:

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
  • Microsoft.Xna.Framework: This is the core library for XNA game development.

  • Microsoft.Xna.Framework.Graphics: This library is used for all graphical operations.

  • Microsoft.Xna.Framework.Input: This library lets you handle user input like keyboard and mouse events.

What are libraries

A library is a collection of pre-written code that you can include in your programs. Libraries are important because they speed up development, offer optimized functions, and allow you to focus on the unique aspects of your project. However, using them might limit customization and introduce dependencies. They are tools to make your coding more efficient, but they're not always the ideal solution for every project.


Namespace: The Address of Your Code

Before we even dive into the Game1 class, let's talk about a line of code that acts like the address of your 'coding house'. Yep, it's the namespace.

namespace GameLoops
  • Namespace: Think of a namespace as a folder on your computer. It organizes your code files, helping to prevent naming conflicts.

  • GameLoops: This is the name of your specific folder. You can choose any name, but it should be unique to avoid confusion. It helps the compiler know where to find your specific classes and methods.


Game1 Class Inside a Namespace

Now, let's nest our Game1 class safely inside our namespace, giving it its full address.

namespace GameLoops
{
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics; // Manages screen drawing
        SpriteBatch spriteBatch;        // Draws 2D Images
        SpriteFont font;                // Sets the text style
        Vector2 position;               // X, Y coordinates for text
        Vector2 velocity;               // Speed and direction of text
        Color color;                    // Text color

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

By putting the Game1 class inside the GameLoops namespace, we're essentially saying, "Hey, Game1 lives at the GameLoops address!" Make sure you put all your related classes in this same 'house' to make your life easier when you're coding.

  • GraphicsDeviceManager graphics: Manages screen drawing

  • SpriteBatch spriteBatch: Draws 2D images

  • SpriteFont font: Sets the text style

  • Vector2 position: X, Y coordinates for text

  • Vector2 velocity: Speed and direction of text

  • Color color: Text color

  • public Game1() is where you initialize these components


Initialize Method

This is where we set up our stage, placing all the props and making sure the lighting is just right.

protected override void Initialize()
{
    graphics.PreferredBackBufferWidth = 800;
    graphics.PreferredBackBufferHeight = 600;
    graphics.ApplyChanges();
    
    position = new Vector2(400, 300);
    velocity = new Vector2(2, 1);
    color = Color.White;                // Sets the text color
    base.Initialize();
}
  • PreferredBackBufferWidth & PreferredBackBufferHeight: This is where you adjust the window dimensions.

  • ApplyChanges: This makes sure changes take effect

  • position = new Vector2(400, 300);: Here, we're setting the initial x and y coordinates for our text on the screen. Think of Vector2 as a way to store two related numbers—in this case, the horizontal (x) and vertical (y) position of the text.

  • velocity = new Vector2(2, 1);: This vector represents the speed and direction of your text's movement. The x-value "2" makes the text move to the right, while the y-value "1" makes it move downwards.


LoadContent Method

Here you load all the content you need for the scene. Backgrounds, characters, and fonts all get picked up here.

protected override void LoadContent()
{
    spriteBatch = new SpriteBatch(GraphicsDevice);
    font = Content.Load<SpriteFont>("Fonts/MyFont");
}
  • spriteBatch: This is responsible for drawing 2D images on the screen.

  • font: This specifies the style and size of the text that will appear on the screen.


Update Method

This is where you direct the action scene by scene, directing objects where to move and what to do.

protected override void Update(GameTime gameTime)
{
    position += velocity;
    position.X = MathHelper.Clamp(position.X, 0, graphics.PreferredBackBufferWidth - 100);
    position.Y = MathHelper.Clamp(position.Y, 0, graphics.PreferredBackBufferHeight - 50);
    color.G += 1;
    if (color.G > 255) color.G = 0;
    base.Update(gameTime);
}

Let's break down each line:

  • position += velocity;: This adds to the velocity to the position for each frame. In essence, it's like saying newposition=oldposition+velocitynewposition=oldposition+velocitynew position=old position+velocitynew position=old position+velocity. For example, if your initial position is (400, 300) and your velocity is (2, 1), after one update, the new position becomes (402, 301)

  • MathHelper.Clamp(): The Clamp function restricts a value to lie within a specified range. It's a way to ensure the text stays within the screen's bounds. If position.X goes below 0, it will be set to 0; if it goes above graphics.PreferredBackBufferWidth - 100, it gets set back to that value

  • color.G += 1;: Here, you're incrementing the Green (G) component of the text color by 1 in each frame. Color values range from 0-255, which leads to the next line

  • if (color.G > 255) color.G = 0;: This resets the green component back to 0 if it goes over 255, creating a loop of color changes.


Showtime: Draw Method

Finally, it's time for the actors to perform! This is where all your plans come to life on the stage.

protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Color.CornflowerBlue);
    spriteBatch.Begin();
    spriteBatch.DrawString(font, "Hello, World!", position, color);
    spriteBatch.End();
    base.Draw(gameTime);
}

I hope this clarifies the process and adds some fun to the coding process!


Full Code

Here's the full code for the project:

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

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

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

        protected override void Initialize()
        {
            graphics.PreferredBackBufferWidth = 800; // Set the window width
            graphics.PreferredBackBufferHeight = 600; // Set the window height
            graphics.ApplyChanges(); // Apply the changes to the graphics device
        
            position = new Vector2(400, 300);
            velocity = new Vector2(2, 1);
            color = Color.White;
            base.Initialize();
        }


        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            font = Content.Load<SpriteFont>("Fonts/MyFont");
        }

        protected override void Update(GameTime gameTime)
        {
            // Move text position
            position += velocity;

            // Clamping the position
            position.X = MathHelper.Clamp(position.X, 0, graphics.PreferredBackBufferWidth - 100);
            position.Y = MathHelper.Clamp(position.Y, 0, graphics.PreferredBackBufferHeight - 50);

            // Change color
            color.G += 1;
            if (color.G > 255) color.G = 0;
            
            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);
            spriteBatch.Begin();
            spriteBatch.DrawString(font, "Hello, World!", position, color);
            spriteBatch.End();
            base.Draw(gameTime);
        }
    }
}

Challenge yourself

  1. Update the Update() function so that when the text reaches the border of the screen, it bounces in an opposite direction


Solution

protected override void Update(GameTime gameTime)
{
    // Move text position
    position += velocity;

    // Bounce off right and left walls with a slight offset
    if (position.X <= 0 || position.X >= graphics.PreferredBackBufferWidth - 100)
    {
        velocity.X *= -1.1f;  // Reverse and slightly increase speed
    }

    // Bounce off top and bottom walls with a slight offset
    if (position.Y <= 0 || position.Y >= graphics.PreferredBackBufferHeight - 50)
    {
        velocity.Y *= -1.1f;  // Reverse and slightly increase speed
    }

    // Clamp position to make sure it stays within screen bounds
    position.X = MathHelper.Clamp(position.X, 0, graphics.PreferredBackBufferWidth - 100);
    position.Y = MathHelper.Clamp(position.Y, 0, graphics.PreferredBackBufferHeight - 50);

    // Change color
    color.G += 1;
    if (color.G > 255) color.G = 0;

    base.Update(gameTime);
}

The changes are in the conditionals that detect when the text is about to exit the screen. The velocity is reversed (multiplied by -1) and slightly increased by 10% (multiplied by 1.1) whenever the text hits a boundary, thus causing it to "bounce" back. We still keep the MathHelper.Clamp as a safety measure to ensure the text remains within screen bounds.


Knowledge Check

  1. What is the role of the GraphicsDeviceManager and SpriteBatch classes in the Game1 class? How do they differ?

  2. In the Update() method, why is it necessary to use MathHelper.Clamp() for the text position?

  3. Explain how the velocity vector affects the movement of the text. What would happen if its x or y values were negative?


Practice Challenges

Challenge 1: Play with Color

Modify the Update() method to gradually cycle the text color through the entire RGB spectrum, rather than just modifying the green component. You can do this by incrementing and looping each color component (R, G, B) individually using a switch statement.

// Define colorPhase somewhere in your class
int colorPhase = 0;

// Add this part to your Update() method
switch (colorPhase)
{
    case 0:  // Modify Red component
        // Your code here
        break;

    case 1:  // Modify Green component
        // Your code here
        break;

    case 2:  // Modify Blue component
        // Your code here
        break;
}

Challenge 2: Managing Game Time

Create a system that allows the text to move at a constant speed regardless of the frame rate. You'll need to use the GameTime object passed to the Update() method. The ElapsedGameTime property can be helpful for this.

Last updated