Get up to 80 % extra points for free! More info:

Lesson 3 - Drawing and Writing in MonoGame

In the previous lesson, Adding Content in MonoGame, we added content into our MonoGame project.

So now we have everything ready to start working with the content. Let's go :)

Loading Content

We must first load the content, which, as we already know, we do in the LoadContent() method. Before moving to the method, we'll add all the necessary fields. In MonoGame, all non-animated sprites are stored as the Texture2D data types. For SpriteFonts we have the SpriteFont type.

So let's add the fields:

private Texture2D blocksBackground, clouds, background;
public SpriteFont fontCourierNew, fontBlox, fontBloxSmall;

Don't worry about the access modifiers. Music and fonts need to be public because we'll share them between game components in the future.

Let's finally move on to the LoadContent() method. After creating the spriteBatch instance, we'll start loading the content. We already know that we have to use the Content property, more precisely, its Load() method. The method is generic, so we have to specify the type of the content we're loading. Interestingly, we don't specify the file extension. Let's load the sprites:

background = Content.Load<Texture2D>(@"Sprites\background_level");
blocksBackground = Content.Load<Texture2D>(@"Sprites\background_blocks");
clouds = Content.Load<Texture2D>(@"Sprites\spr_clouds");

And fonts:

fontCourierNew = Content.Load<SpriteFont>(@"Fonts\font_courier_new");
fontBlox = Content.Load<SpriteFont>(@"Fonts\font_blox");
fontBloxSmall = Content.Load<SpriteFont>(@"Fonts\font_blox_small");

Everything is loaded, it was no big science. Now, let's draw.

Rendering Sprites

Let's move on to the Draw() method. We'll put our code right after clearing the display device. We've already learned that we render sprites using the spriteBatch instance. We have to begin our rendering with the Begin() method:

spriteBatch.Begin();

Then we can draw any sprite. You have probably noticed that they are in the PNG format. This is because the PNG format supports transparency using the alpha channel that can make different parts of the image semitransparent. I used this to make the robot background transparent at the top. Right below it, we'll draw the blocks background, then we'll draw the clouds right between the blocks and the robot.

The sprite itself is rendered using the Draw() method:

spriteBatch.Draw(blocksBackground, new Vector2(0, 0), Color.White);
spriteBatch.Draw(clouds, new Vector2(0, 0), Color.White);
spriteBatch.Draw(background, new Vector2(0, 0), Color.White);

We can see that the first parameter of the method is the texture to be rendered. The second parameter is the rendering position and the third one is the color. The method has additional overloads that allow us, for example, to stretch or rotate the image, but we won't be using it in our course.

The purpose of the texture is obvious. It can seem awkward that the coordinates are entered using a vector. Even stranger may be that they aren't integers, but decimals (of the float type). The reason vectors are used in MonoGame is because they allow using very simple solutions to various movements, directions, rotations, collisions, calculating distance between two objects, and so on. We'll use them only as coordinates in this course, but it's useful that we can, for example, work with more precise speeds instead of using integers, and add this speed to other vectors (object coordinates). The color determines the color shade of the texture. The white color doesn't change the texture appearance.

Finally we'll end the textures rendering using the End() method:

spriteBatch.End();

Let's start the game to see the result:

Robotris, a sample game made with MonoGame - Tetris From Scratch

By maintaining the rendered sprites order, the clouds are actually rendered in between of the other two backgrounds. We'll stick with the sprites for a little while and try to make the clouds moving. We'll create a new private position field of the Vector2 type. Inside the Initialize() method, we'll set it to the origin of the coordinate system (the upper left corner of the screen):

position = new Vector2(0, 0);

Now we'll change the cloud rendering line a bit. As the position, we'll pass the position field and multiply the color by 0.8f to make the clouds 20% transparent:

spriteBatch.Draw(clouds, position, Color.White * 0.8f);

Now the clouds are rendered at the position specified by the field. Inside the Update() method, we'll be decrementing the X component of the vector by 1:

position.X--;

After the movement, we'll need to check whether the clouds are still in the bounds of the screen. To do this, we'll check whether the X of the clouds is less than the negative width of the clouds image, which can be obtained from the Width property of the clouds texture:

if (position.X < -(clouds.Width))
    position.X = 0;

If you've been following the tutorial correctly, the clouds will be moving to the left and returning to the starting position when they leave the screen.

Now we'll change the rendering to render several clouds next to each other:

for (int i = 0; i < 6; i++)
     spriteBatch.Draw(clouds, new Vector2(position.X + i * clouds.Width, 0), Color.White * 0.8f);

We got a nice effect of an endless floating sky:

Robotris, a sample game made with MonoGame - Tetris From Scratch

Impressive, isn't it? :)

Changing Sprites Color

Let's try to change the clouds color by simply changing its individual color components. As we know, computer colors are composed of the red, green, and blue components. Changing the color within the entire spectrum would be too much work and the effect would be very similar to changing only few colors. We'll add two more fields into our class - change and direction:

private int change;
private int direction;

The change field value will alternate between 0 and 96, just like pendulum. The direction field will specify whether the change is increasing (1) or decreasing (-1).

In the Initialize() method, we'll set the change to 0 and the direction to 1 (increasing).

change = 0;
direction = 1;

In the Update() method, we'll add the direction to the change. This way, it'll increase with a positive direction and decrease with a negative direction. We'll also keep the change value between 0 and 96 using two conditions:

change += direction;
if (change >= 96)
    direction = -1;
if (change <= 0)
    direction = 1;

Done. Remember this pendulum principle, because it's useful in video games, and we'll use it at least twice elsewhere. Now let's look at the Draw() method. Here we'll declare the color variable, right before the for loop, and change its components using the change field. The components can simply be entered in the RGB format (red, green, blue) in the Color class constructor. After this, we just have to use the color for drawing the clouds:

Color color = new Color(128 + change, 255 - change, 128 + change);
for (int i = 0; i < 6; i++)
    spriteBatch.Draw(clouds, new Vector2(position.X + i * clouds.Width, 0), color * 0.8f);

The red component will have the base value of 128 + change. With the change, the final value will range between 128 and 224. The green component will range between 255 and 159, and the blue one also between 128 and 225. You can modify it as you want, but be careful not to reach value greater than 255 or less than 0. The program wouldn't crash with an error, but the color change would be very strange.

For a better effect, in practice, we'd store some nice colors in an array and then interpolate between them using the Lerp() method. Also, the clouds shouldn't be blue to not interfere with the color. But for our demonstration purposes, however, this is good enough.

Rendering Text

We already know that the text rendering is done using the DrawString() method of the SpriteBatch. Again, the method has several overloads for rotated text and so on, as you can see for yourself. Let's move on to the Draw() method and add the following lines right before spriteBatch.End(); line:

spriteBatch.DrawString(fontBlox, "headline large", new Vector2(100, 100), Color.Yellow);
spriteBatch.DrawString(fontBloxSmall, "headline small", new Vector2(100, 180), Color.Yellow);
spriteBatch.DrawString(fontCourierNew, "Lorem ipsum dolor sit amet", new Vector2(100, 240), Color.Red);

The result:

Robotris, a sample game made with MonoGame - Tetris From Scratch

The parameters are the same as for the sprites, except the one with the text. Don't forget that the Blox font is missing special characters. That's why we have the Courier font that can manage it.

The text can sometimes be difficult to see. We'll try to do something about it, like drawing a shadow under the text. So that we don't have to draw the shadow over and over again, we'll make a method for text with a shadow. And to practice a bit, we'll add it directly into the spriteBatch. To be more precise, we'll inherit from the SpriteBatch class.

Add a new class named BetterSpriteBatch to the Robotris project, inheriting the SpriteBatch class. First we'll add all the necessary using directives and make the class public using the public modifier:

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

namespace Robotris
{
    public class BetterSpriteBatch : SpriteBatch
    {

...

To inherit the class, we must create a constructor (although empty), calling the constructor of the parent class:

public BetterSpriteBatch(GraphicsDevice graphicsDevice): base(graphicsDevice)
{
}

Now we'll add our TextWithShadow() method, which renders the entered text twice - the first one as black text with a bit of transparency and shifted to the right down a bit, and the other one normally as we used to. We'll define the same parameters as in the original DrawString() method.

public void TextWithShadow(SpriteFont spriteFont, string text, Vector2 position, Color color)
{
    DrawString(spriteFont, text, new Vector2(position.X + 2, position.Y + 2), Color.Black * 0.8f);
    DrawString(spriteFont, text, position, color);
}

Now we'll return back to the RobotrisGame class and change the SpriteBatch type (at the beginning of the class, in the fields definition) to the BetterSpriteBatch type:

BetterSpriteBatch spriteBatch;

We'll make a similar modification at the beginning of the LoadContent() method:

spriteBatch = new BetterSpriteBatch(GraphicsDevice);

Inside the Draw() method, we'll change the DrawString() call to TextWithShadow(). Now let's run it. Much better, right?

That would be enough about sprites.

In the next lesson, Sounds, Music, Keyboard, and Mouse Input in MonoGame, we'll take a look at music, sound effects, and processing inputs.


 

Did you have a problem with anything? Download the sample application below and compare it with your project, you will find the error easily.

Download

By downloading the following file, you agree to the license terms

Downloaded 37x (15.05 MB)
Application includes source codes

 

Previous article
Adding Content in MonoGame
All articles in this section
Tetris From Scratch
Skip article
(not recommended)
Sounds, Music, Keyboard, and Mouse Input in MonoGame
Article has been written for you by David Capka Hartinger
Avatar
User rating:
5 votes
The author is a programmer, who likes web technologies and being the lead/chief article writer at ICT.social. He shares his knowledge with the community and is always looking to improve. He believes that anyone can do what they set their mind to.
Unicorn university David learned IT at the Unicorn University - a prestigious college providing education on IT and economics.
Activities