Snake Tutorial Part 2: Sprites and Player Input
In this chapter, we'll go over creating a Sprite and polling player input to control its position.
In exploring sprites and input, we'll also be covering MonoGame's Content Pipeline and MGCB tool, and Ladybug's KeyboardMonitor
and ResourceCatalog
classes.
At the end of this chapter, your game application will produce a sprite that can be moved across the screen with the arrow keys.
Prerequisites
To complete the steps outlined in this chapter, you will need to have completed the steps in the previous chapter
In addition, this chapter will be introducing the MonoGame Content Builder, which requires the MGCB Tool. For information on installing the MGCB tool, see the Installation article.
For information on using the MGCB editor, click here
Preface: What is Content?
Up until now, our Game
instance has not had to render anything to the screen; all output so far has been to the console.
Now, as we start to work with graphical assets, we'll need to review Ladybug's concept of "Content".
In summary, Content is any external resource loaded into a game and used as an asset. These resources are usually image or audio resources, but can also involve data resources like JSON and XML.
The MGCB Editor
MonoGame/XNA, which is the underlying framework for Ladybug, handles Content through a ContentManager
class within the game code, and through the MGCB editor outside the code. If you don't have experience using the MGCB editor, it is recommended you review this article which details its basic usage.
Usage of the MGCB editor is essentially identical with Ladybug. The only addition to the process Ladybug adds is the ResourceCatalog
class, which is a wrapper over a ContentManager
that adds extra convenience.
Step 1: Getting a Texture for our Sprite
To draw a Sprite to the screen, we'll need to have an image file to load its texture from.
We'll be needing a texture for the snake's body segments, ideally something symmetrical and uniform. Some sample assets will be provided for this tutorial, but feel free to create your own if you wish.
You can download the sample assets for this tutorial here.
Adding the Texture to the Game
Once you have a texture created or downloaded, copy the file into core/Content/image/snake-body.png
. Although the file is now in the Content folder within our game's structure, it is not quite yet accessible to our game code. To make snake-body.png
available in our game, we'll need to add it using the MGCB Editor.
Using the MGCB Editor
Take a look at this article that describes the process for using the MGCB Editor. You will use this editor to open core/Content/Content.mgcb
and add the core/Content/image/snake-body.png
file using its interface.
Once you've added the snake-body.png
file in the tool, save it and close the MGCB Editor -- this is the only time we'll need to use it this chapter.
Clarification: Textures vs. Sprites
In casual conversation, the words texture and sprite are sometimes used interchangeably. However, in Ladybug they are quite different, and the distinction is important:
- Texture: An image file that is loaded into a
Scene
as a resource. - Sprite: An in-game visual representation of a texture, rendered at a particular location, size, scale, and rotation. Contains a reference to a texture, plus transform information about how/where/what portion that texture will be rendered.
It is possible (and sometimes appropriate) to work directly with textures in Ladybug, but for this project we're definitely interested in the added convenience of a Sprite
.
Step 2: Creating and Drawing our Sprite
Now that we have a texture we want to use for the body segments of our snake and we've successfully processed it through the MGCB editor, let's work on creating a Sprite with it and drawing it to the screen.
We're going to have to add some new items to MainScene.cs
to facilitate drawing our Sprite. For now, edit your MainScene.cs
file and add the new items from the following sample:
// core/scene/MainScene.cs
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; // <-- 1: New using directive
using Ladybug;
using Ladybug.Graphics; // <-- 2: New using directive
public class MainScene : Scene
{
private Sprite _snakeSprite; // <-- 3: New member variable
protected override void Initialize()
{
Console.WriteLine("Main Scene Initialized!");
}
// 4: New method
protected override void LoadContent()
{
Texture2D snakeTexture = ResourceCatalog.LoadResource<Texture2D>("snake", "image/snake-body");
_snakeSprite = new Sprite(snakeTexture);
}
protected override void Update(GameTime gameTime)
{
// Our update code will go here
}
protected override void Draw(GameTime gameTime)
{
// 5: New draw logic
SpriteBatch.Begin();
_snakeSprite.Draw(SpriteBatch);
SpriteBatch.End();
}
}
MainScene.cs New Updates Summary : Drawing
We added quite a few new items to our MainScene.cs file in the above sample. Let's go over the additions one at a time.
1-2: New Using Directives
We added MonoGame.Xna.Framework
and Ladybug.Graphics
to our using
directives. This is so that we can use Texture2D
and Sprite
, respectively.
3: New Member Variable
We created a new _snakeSprite
variable of type Sprite
. This will be the sprite that we draw to the screen.
4 New Override Method: LoadContent
We've added a new override method: LoadContent()
, which will be called when the scene is being set up.
In this new method, we load our snake-body.png
file into a ResourceCatalog
and assign the resulting resource to a local variable snakeTexture
. ResourceCatalog
's first argument contains a simple name we can use to reference the snake-body.png
texture later, while the second argument contains the file path to snake-body.png
relative to the core/Content
folder, without the file extension.
Now that we have snake-body.png
loaded into the texture snakeTexture
, we use that to create a Sprite and assign it to _snakeSprite
.
6: New Draw Logic
Now that we have something to draw, we can do something with our Draw()
method.
First, we have to start the draw process for the current frame by calling SpriteBatch.Begin()
. Similarly, we'll need to tell the SpriteBatch
when we're done drawing, so we call SpriteBatch.End()
at the end of the Draw()
method.
Between SpriteBatch.Begin()
and SpriteBatch.End()
, we actually draw our Sprite by calling _snakeSprite.Draw()
.
Success - Our First Sprite!
With the above adjustments to MainScene.cs
, running the game should result in our snake-body.png
texture being drawn to the screen. The size and position may not be what we expected, but it's there and that's a great starting point. We will adjust its size and position easily in future steps.
Step 3: Bringing In User Input
Drawing to the screen is a big step -- a vital part of creating a game. However, allowing a user to control what is drawn to the screen is equally important, if not more so.
To get the user's input, we'll be using Ladybug's KeyboardMonitor
, which as its name implies, monitors the keyboard for input.
To do so, we'll need to make a few more adjustments to our MainScene.cs
file:
// core/scene/MainScene.cs
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input; // <-- 1: New using directive
using Ladybug;
using Ladybug.Graphics;
using Ladybug.Input; // <-- 2: New using directive
public class MainScene : Scene
{
private Sprite _snakeSprite;
private KeyboardMonitor _keyboard; // <-- 3: New member variable
protected override void Initialize()
{
Console.WriteLine("Main Scene Initialized!");
_keyboard = new KeyboardMonitor(); // <-- 4: New initialization item
}
protected override void LoadContent()
{
Texture2D snakeTexture = ResourceCatalog.LoadResource<Texture2D>("snake", "image/snake-body");
_snakeSprite = new Sprite(snakeTexture);
}
protected override void Update(GameTime gameTime)
{
// 5: New update logic
_keyboard.BeginUpdate(Keyboard.GetState());
if (_keyboard.CheckButton(Keys.Space, InputState.Released))
{
Console.WriteLine("Space Pressed!");
}
_keyboard.EndUpdate();
}
protected override void Draw(GameTime gameTime)
{
SpriteBatch.Begin();
_snakeSprite.Draw(SpriteBatch);
SpriteBatch.End();
}
}
MainScene.cs New Updates Summary : User Input
We've added yet another series of new items and updates to MainScene.cs
. Let's go over each change now:
1-2: New Using Directives
We've added two new using
directives: Microsoft.Xna.Framework.Input
and Ladybug.Input
. These allow us to use Keyboard.GetState()
and KeyboardMonitor
, respectively.
3-4: New Member Variable and Initialization
We've added a new private member variable to store our KeyboardMonitor: _keyboard
. We then set this variable to its initial value in Initialize()
.
5: New Update Logic
Now that we have our KeyboardMonitor
initialized, we can poll for user input. Since this is something we'll need to do every frame, Update()
is the perfect place for this.
Similar to how SpriteBatch
works, we need to tell our KeyboardMonitor
when we want to start checking for input, and when we've finished checking for input. This is handled by _keyboard.BeginUpdate()
and _keyboardMonitor.EndUpdate()
.
Finally, to show that our game can see the user's input, we've added an input check on the spacebar that will print "Space Pressed!" to the console every time the spacebar is pressed, then released.
At this point, if you run the game, you will still see the sprite we set up in the previous step, and now if you press and release the spacebar, you should see "Space Pressed" in the console.
It may not look like it quite yet, but we're very close to getting a handle on the most vital parts of making a game!
Step 4: Putting it Together
Now that we've handled drawing to the screen and gathering input, let's combine them!
Lucky for us, this is very easy. Replace the contents of MainScene.cs
's Update()
method with the following:
protected override void Update(GameTime gameTime)
{
_keyboard.BeginUpdate(Keyboard.GetState());
if (_keyboard.CheckButton(Keys.Up, InputState.Down))
{
_snakeSprite.Transform.Move(0, -10);
}
if (_keyboard.CheckButton(Keys.Left, InputState.Down))
{
_snakeSprite.Transform.Move(-10, 0);
}
if (_keyboard.CheckButton(Keys.Right, InputState.Down))
{
_snakeSprite.Transform.Move(10, 0);
}
if (_keyboard.CheckButton(Keys.Down, InputState.Down))
{
_snakeSprite.Transform.Move(0, 10);
}
_keyboard.EndUpdate();
}
Now, when you press the arrow keys, the sprite will move ten pixels per frame in the direction pressed. InputState.Down
means "every frame the button is down", so holding the arrow keys will keep the Sprite moving.
Conclusion
In this chapter, we reviewed the concept of content, how Ladybug and MonoGame handle importing and processing content, and how to load and use content in our game. In addition, we covered basic user input, and illustrated how to use input to affect what is drawn to the screen.
With our MainScene
now drawing and updating objects within our game, we've got the foundations settled for our snake game.
Next Steps
Now that we've got a basic game foundation including sprite drawing and user input, it's time to make the snake that the player will be controlling!
When you're ready for the next step, head on over to the next chapter where we'll set up our Snake
class and get a controllable snake working.
Complete code for this chapter can be found here.