Lesson 4 - Sounds, Music, Keyboard, and Mouse Input in MonoGame
In the previous lesson, Drawing and Writing in MonoGame, we showed how to render sprites and text.
Today, we're going to take a look at music, sound effects, and processing keyboard input.
Loading Content
Just like the sprites from previous lesson, sounds and music must be loaded
first as well. Sound effects are of the SoundEffect
type, while
music is of the Song
type. We'll add two new fields into our game
class:
private SoundEffect soundRow; public Song music_zardax;
Let's move on to the LoadContent()
method and load the sounds
from the content:
soundRow = Content.Load<SoundEffect>(@"Sounds\sound_row"); music_zardax = Content.Load<Song>(@"Sounds\music_zardax");
Music
Music playback is provided by the MediaPlayer
static class. We
are mainly interested in the Play()
method, which takes a
Song
object (the music we want to play) as its parameter. We'll
call the Play()
method at the end of the LoadContent()
method, that is when the music is loaded.
MediaPlayer.Play(music_zardax);
Let's try
For further development, we'll create public ChangeMusic()
method that will take a Song
as a parameter. If this song isn't
playing, it starts playing it. If the is already playing, the method does
nothing. With this check we can avoid situations in which, for example, we'd
come to a location where the music that is already playing is supposed to be
played. Without the check, the music would start playing again from the
beginning. The songs that are currently playing can be found in the
Query.ActiveSong
property. As you can see, the player has some kind
of playlist, but we won't be using it in our course. The method will look like
this:
public void ChangeMusic(Song song) { // Isn't the same song already playing? if (MediaPlayer.Queue.ActiveSong != song) MediaPlayer.Play(song); }
We'll change our code to play music using this method. In addition, to play
the music repeatedly, we have to set the IsRepeating
property. The
bottom of the LoadContent()
method now looks like this:
MediaPlayer.IsRepeating = true;
ChangeMusic(music_zardax);
To keep yourself sane, we recommend you to comment the
ChangeMusic()
method for development purposes, otherwise you may
start hating the music after few days of programming and testing the game
Sound Effects
Furthermore, we have sound effects, which are pretty simple. Whenever we need
to play these, we only have to call their Play()
method. Let's try
to play some sound effect at the end of the LoadContent()
method:
soundRow.Play();
Let's try it.
Keyboard
Of course, the player will control the game in some way. MonoGame provides us with broad support for controllers, such as keyboard, mouse, joystick or gamepad. We'll now focus on the keyboard.
The Keyboard
class is available for us to work with keyboard
input. For us, the most important is the GetState()
static method,
which returns the currently pressed key states. The state instance then keeps
the information about the particular key. The keyboard key state instance is of
the KeyboardState
type. We'll add a public variable of this type
into our game class:
public KeyboardState keyboardState;
We already know that we'll be using the Update()
method to check
the key states. Let's move to it and save the current key state instance, right
after the GamePad input processing for XBox:
keyboardState = Keyboard.GetState();
Now we have the current key state stored in keyboardState
. The
state instance has 3 useful methods:
IsKeyDown()
returns whether a particular key is pressed.IsKeyUp()
returns whether a particular key is released.GetPressedKeys()
returns an array of all pressed keys.
Individual keys are represented by the Keys
enumerated
type. A Keys
value (that is one particular key) is taken
as a parameter by the first 2 methods. The last method then returns an array of
Keys
values.
Let's check whether the Enter key is pressed, and if so, then let's play our sound:
if (keyboardState.IsKeyDown(Keys.Enter))
soundRow.Play();
Let's try.
We can see this solution has one drawback - the code is being executed constantly for as long as we hold the key pressed. There are dozens of sounds playing at the same time, giving us headache. Sometimes we may actually want the game to work like this, for example, if we held the gas pedal down, the car would keep accelerating, but as soon as we released it, the speed would drop. Sometimes, however, we want a key to be checked only once when it's pressed and held. Let's see how to achieve that. We'll declare another field that will keep the previous key state, right next to our key state field:
public KeyboardState keyboardState, previousKeyboardState;
We'll modify the logic inside the Update()
method to store the
previous key state inside our newly added previousKeyboardState
field:
previousKeyboardState = keyboardState; keyboardState = Keyboard.GetState();
Now we just need to check whether the Enter key was released the last time but now is pressed:
if (keyboardState.IsKeyDown(Keys.Enter) && previousKeyboardState.IsKeyUp(Keys.Enter))
soundRow.Play();
Let's try again.
Since we'll want to use this behavior more often, without typing the same
long condition everywhere, let's create a NewKey()
method for this
purpose. It'll take a Keys
enumeration type value as a parameter to
check whether the key is now held down, but wasn't the last time:
public bool NewKey(Keys key) { return keyboardState.IsKeyDown(key) && previousKeyboardState.IsKeyUp(key); }
The method is public because we'll use it later from outside our game class.
Finally, let's modify the Enter key processing in
Update()
:
if (NewKey(Keys.Enter))
soundRow.Play();
As you can see, the code is now more clear.
Mouse
We still have some time. Even if we won't use the mouse input in our Tetris
game, let's at least show how to use it. First, let's use the standard Windows
cursor. The cursor is disabled in the game window by default. To enable it, we
change the value of the IsMouseVisible
property inside the
Initialize()
method:
IsMouseVisible = true;
Such cursor is quite ugly, so we'll keep it disabled As we've already said, our game
won't use the mouse input, so we don't have any mouse cursor sprite available.
If you want, you may draw one by yourself and add it in. But for now, we'll draw
the "X"
character instead, to represent the cursor.
Just like we use the Keyboard
class for the keyboard input,
MonoGame has also the Mouse
class available for the mouse input. It
works exactly the same. As with the keyboard, we have the
MouseState
type here for the mouse. We'll add a new
mouseState
field of this type into our game class:
public MouseState mouseState;
Inside the Update()
method, we'll store the state the same way
as we did with the keyboard state:
mouseState = Mouse.GetState();
Now, let's move to the Draw()
method, where we'll draw the
"cursor" and display the current cursor coordinates and the mouse button
state.
Let's start with the cursor. We just need to use the X
and
Y
properties of the mouse state instance which specify the cursor
position. The cursor will be drawn as the last one, to be rendered above
everything else (the spriteBatch.End()
line must still be the last
one, of course):
spriteBatch.TextWithShadow(fontCourierNew, "X", new Vector2(mouseState.X, mouseState.Y), Color.White);
Now let's draw the mouse position and button state text:
string text = String.Format("{0},{1} {2}", mouseState.X, mouseState.Y, mouseState.LeftButton); spriteBatch.TextWithShadow(fontCourierNew, text, new Vector2(0, 700), Color.White);
Cursor and Rectangle Collision
Finally, we'll take a look at how to detect rectangle-point collisions. We
already know that MonoGame has the Vector2
structure. It even
provides another similar structures, from which we'll use Rectangle
and Point
. The difference between them is that the point has
integer coordinates and lacks methods such as vector sum. Nevertheless, it's
very common to use the vector type all the time in MonoGame projects, since it's
used by internal MonoGame methods, such as rendering methods.
Let's create a rectangle and set it to cover the robot in the background
approximately. After clicking the robot, we'll display the "Don't click me" text
Now, of course, we're just
messing around. In practice we'd be using some robot
object. Of
course, we'll eventually use objects, such as block
, in our
project. Let's add the following fields to the class:
private Rectangle robotRectangle;
Inside the Initialize()
method, we'll set the rectangle
approximately to the robot's coordinates, settings its top-left corner and its
dimensions:
robotRectangle = new Rectangle(775, 345, 115, 245);
The specific coordinates can be acquired by opening the background in MS
Paint or any other editor and selecting the area, which results in the editor
showing the selection coordinates and dimensions. Now let's move back to the
Draw()
method and add the text rendering:
if (robotRectangle.Contains(new Point(mouseState.X, mouseState.Y)) && (mouseState.LeftButton == ButtonState.Pressed)) spriteBatch.TextWithShadow(fontBlox, "Don't click me", new Vector2(600, 220), Color.Lime);
The button state is compared to the ButtonState
enumerated type
value, which can be Pressed
or Released
. Ideally, this
check should be in the Update()
method, but it doesn't matter for
our example. If we wanted more complex behavior, we'd create a
previousMouseState
field, just like for the keyboard.
In the next lesson, Dividing a MonoGame Project into Components, we'll take a look at how to divide our game project into several game components. The whole project with today's solution is available to download below.
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 19x (17.75 MB)
Application includes source codes