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:
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:
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:
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