iwks 3400 lab 31 jk bennett - university of colorado denver · image with a composition of its...
TRANSCRIPT
![Page 1: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/1.jpg)
1
IWKS 3400 – Lab 31
JK Bennett
This lab consists of four parts, each of which demonstrates an aspect of 2D game development. Each part adds
functionality. You will first just put a sprite on the screen; then you will make the sprite move, add another
sprite, and add collision detection; then you will add user input; finally, you will add sound. By the end of this
lab, you will be ready to start creating 2D games. It’s probably a good idea to read through the entire lab before
starting. This write-up is long, but don’t be intimidated by the length, it is detailed in an effort to ensure that
everything is explained fully. Feedback on whether this goal has been accomplished is most welcome.
First, a few terms. Lobão’s definitions are quite good:
“Sprite: A sprite is a 2D image that can be manipulated independently from the rest of a game scene.
This term is used often to describe the image displayed or the class used by the game to display the
image (which includes properties such as velocity, position, width, height, and so on). Because the
computer always draws the 2D image as a rectangle, a sprite usually encompasses transparent areas so it
provides the illusion of a nonrectangular drawing. The term animated sprite refers to a sprite whose
images change at predetermined time intervals, to generate the illusion of movement (such as a walking
man or a spinning wheel).
Texture: A texture refers to a 2D image loaded in a 3D model, which can be seen from any point of
view, depending on the position of the model and the position of the camera used to render the scene.
You can use textures to help create the illusion of a highly detailed model, when a detailed image is
mapped over a simple 3D model.
Billboard: In the 3D world, a billboard is a texture that is mapped to a special plane that is always
perpendicular to the camera axis. Using 3D-like images in billboarding is an effective technique for
creating game components-such as a tree, a road sign, or a torch in the wall-without the need to create
highly detailed models. This allows more detailed scenes with the same rendering processing power.
Background: A 2D game scene is usually composed of a background image with many sprites
displayed over it. When this background is a moving image, you have a scrolling background, which is
the main characteristic in games called scrollers. It’s also worth mentioning parallax scrolling, a special
scrolling technique in which the 2D game has more than one scrolling background with different
scrolling speeds, which provides the illusion of a 3D environment. For example, while the player
character moves to the left, trees and bushes behind it move at the player’s speed, mountains “far away”
from the character move slowly, and clouds in the sky move very slowly.
Tiles: These are small images used as tiles to compose a bigger image, usually a level background. For
example, platform games typically use tiles to create different platform levels based on the same basic
1 The lab is derived in part from Chapter 2 of Beginning XNA 3.0 Game Programming, Lobão et al., Apress, 2009, and in part from
Chapter 13 of XNA Game Studio 4.0 Programming, Miller and Johnson, Addison Wesley, 2011.
![Page 2: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/2.jpg)
2
images. The term tiled map is often used to describe game levels created with tiles, and sometimes to
describe files with the information needed to create such levels based on tiles. A classic example of the
use of tiles is for building a terrain. Role-playing games (RPGs) usually provide a level editor
application that lets you build the levels by picking different tiles from the application and joining them
together.”
Screen Coordinates
In graphs with which you are probably familiar, the origin is at the lower left, and the x and y axes increase
going right and up, respectively. For historical reasons (having to do with how cathode ray tubes work), the
origin of our screen coordinates is at the upper left, and the x and y axes increase going right and down,
respectively. In addition, screen coordinates are directly related to screen resolution. For example, if the
monitor (or window) is configured to a resolution of 800 x 600, the x axis will have 800 pixels (each pixel is an
independent point on the screen) and the y axis will have 600 pixels.
PART 1.
Drawing a Sprite Using MonoGame
We will now create a simple example in MonoGame to display a sprite in a given position on the screen.
Begin by create a new MonoGame Windows Project. Use Lab3_Mono as the project name (which will also be
the name of the project namespace that is created). Be sure to place the project directory on the desktop.
Creating the Sprite Class
To group the sprite image and some associated properties (such as position, size, and velocity), we will create a
simple class, which will be extended as we explore new concepts in this lab. To create the class, right-click the
project name (not the solution name) in the Solution Explorer window and choose Add New Item (you can also
just Add ► class). In the New Item dialog box, choose Class as the item type and name it clsSprite.cs. Make
your clsSprite.cs file look like this:
using Microsoft.Xna.Framework.Graphics; // for Texture2D using Microsoft.Xna.Framework; // for Vector2 namespace Lab3_Mono { class clsSprite { public Texture2D texture { get; set; } // sprite texture, read-only property public Vector2 position { get; set; } // sprite position on screen public Vector2 size { get; set; } // sprite size in pixels public clsSprite(Texture2D newTexture, Vector2 newPosition, Vector2 newSize) { texture = newTexture; position = newPosition; size = newSize; }
![Page 3: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/3.jpg)
3
public void Draw(SpriteBatch spriteBatch) { spriteBatch.Draw(texture, position, Color.White); } } }
Note the use of C# properties in the definition of the clsSprite class. We learned about properties in Lab 2.
Properties are also explained in Chapter 19 of Whitaker; you should review that material before proceeding (this
will explain the “get-set” usage, which is different from what you might have seen in other programming
languages). The three properties are:
1. texture: Stores the sprite image using MonoGame’s Texture2D class. The MonoGame Sprite class has
many properties and methods to help deal with textures. The texture is stored in this class as a 2D grid of
what are sometimes called texels. Similar to pixels, which are the smallest unit that can be drawn on the
screen, texels are the smallest unit that can be stored by the graphics processing unit (GPU); they include
color and transparency values.
2. size: Stores the sprite’s size using MonoGame’s Vector2 class. This class has two properties, X and Y,
which are used to store the image width and height.
3. position: Stores the position of the sprite using MonoGame’s Vector2 class. The X and Y properties of
the class store the screen coordinates for upper left corner of the sprite.
For now, this class stores only the sprite properties, and does not include any methods.
Adding the Sprite Image
The first step in creating a sprite is to include a new image in your game, so you can access it through the
MonoGame Content Project. You may choose any image you would like to use for this example, as long as it is
in one of the formats supported by MonoGame (use .png and .bmp for now; jpg is problematic when working
with transparent areas). The easiest course of action is to click here and then save the enclosed bitmap on your
desktop as ball.bmp. This image is a 64 x 64 pixel image of a blue ball with a magenta background, which was
created with Windows Paint.2
To add your image to your project:
First, from outside of Visual Studio, copy the ball.bmp file to your project’s Content directory.
Then, inside Visual Studio, open the project’s Content folder in the Solution Explorer window, and
double-click on Content.mgcb. This will open the MonoGame Content Pipeline Tool, as shown below:
2 MonoGame allows you to create transparent sections in your sprite in two ways. You can use an advanced image editor, such as
Photoshop, GIMP (http://www.gimp.org ), or Paint.NET (http://www.getpaint.net ) to create image files with transparent areas.
Alternatively, you can simply color the areas you do not want to show with magenta (the default color used to represent transparency -
this can be changed inside of your project if needed). In our example, the background of the ball image will not be drawn. When
creating images with magenta areas in Windows Paint, do not save them in JPG format, as this format does not preserve the original
colors when saving.
![Page 4: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/4.jpg)
4
Right-click on the Content icon in the tool’s Project window, select Add ► Existing Item, then select
“ball.bmp.”
Finally, save the changes to the Content Pipeline (File ► Save)
If you want to make sure that the image you just loaded can be processed by MonoGame, right-click on
“ball.bmp” and select “Rebuild.” If you get a green check, you are good to go.
After including the image in the game solution, click on the image name in the Content Pipeline tool “Project”
window. You will see the recently included image’s Properties displayed in the window below. The Properties
window presents information that the content importer and the content processor used for this content. Look
under “Processor Parameters,” and under “ColorKeyColor,” magenta will be displayed. Note also that
“ColorKeyEnabled” is set to “true.” This tells you that any magenta areas in the image will be transparent.
![Page 5: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/5.jpg)
5
Drawing the Sprite on the Screen
Once we have an image, the next step is to include the code for drawing it on the screen. To do this, we will
need a SpriteBatch (a MonoGame class that draws sprites on the screen) and the texture that will be used as the
sprite image (in this case, you will load this texture into your clsSprite class). There are (at least) two ways to
accomplish this particular task. In this case, we can read the texture from the clsSprite class and draw it in the
Draw method of the Game1 class, or we can extend the clsSprite class to create a Draw method that will draw
the sprite. In this lab, we went with Option 2, by including the following new method in the clsSprite class (this
code was already entered):
public void Draw(SpriteBatch spriteBatch) { spriteBatch.Draw(texture, position, Color.White); }
The Draw method has many overloads, which allow you to draw only part of the original texture, to scale or
rotate the image, and so on. Here, you are using the simplest one, which receives only three arguments: the
texture to draw, the position in screen coordinates (both are already properties of clsSprite class), and a color
channel modulation used to tint the image. Using any color other than white in this last parameter draws the
image with a composition of its original colors and the color tone.3
Now let’s make the necessary modifications to the Game1 class. Your new Windows Game project has already
created a SpriteBatch object, so we will begin by creating a clsSprite object in the Game1 class. This definition
has to be included at the beginning of the class, just after where the device and SpriteBatch objects that were
automatically created. Of course, these objects need valid values before they can be used. This is accomplished
in the LoadContent method, which is where we include graphics initialization. Because the project already
creates the SpriteBatch object, all we need to do is create the clsSprite object instance. The Game1.cs code to
accomplish all of these objectives is as follows (additions are shown in yellow; only the first part of Game1.cs is
relevant at this point):
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; namespace Lab3_Mono { /// <summary> /// This is the main type for your game. /// </summary> public class Game1 : Game { GraphicsDeviceManager graphics;
3 Note: For information about the other Draw method overloads, see the MSDN documentation for the XNA 4.0 SpriteBatch (which
provides correct information, since MonoGame is an open-source implementation of XNA 4.0. Draw method
(http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.graphics.spritebatch.draw.aspx ). For example, if you want to
rotate your image, look for the overloads that expect the rotation parameter; or use the SpriteEffects parameter, if you just want to flip
the sprite horizontally or vertically. Overloads with a scale parameter allow you to change the size of the sprite, which can be used in
many ways, such as to create a zoom effect.
![Page 6: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/6.jpg)
6
SpriteBatch spriteBatch; clsSprite mySprite1; public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; // changing the back buffer size changes the window size (in windowed mode) graphics.PreferredBackBufferWidth = 700; graphics.PreferredBackBufferHeight = 500; } /// <summary> /// Allows the game to perform any initialization it needs to before starting to run. /// This is where it can query for any required services and load any non-graphic /// related content. Calling base.Initialize will enumerate through any components /// and initialize them as well. /// </summary> protected override void Initialize() { // TODO: Add your initialization logic here base.Initialize(); } /// <summary> /// LoadContent will be called once per game and is the place to load /// all of your content. /// </summary> protected override void LoadContent() { // Create a new SpriteBatch, which can be used to draw textures. spriteBatch = new SpriteBatch(GraphicsDevice); mySprite1 = new clsSprite(Content.Load<Texture2D>("ball"), new Vector2(0f, 0f), new Vector2(64f, 64f)); } /// <summary> /// UnloadContent will be called once per game and is the place to unload /// game-specific content. /// </summary> protected override void UnloadContent() { // TODO: Unload any non ContentManager content here } /// <summary> /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// </summary> /// <param name="gameTime">Provides a snapshot of timing values.</param> protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit();
![Page 7: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/7.jpg)
7
// TODO: Add your update logic here base.Update(gameTime); } /// <summary> /// This is called when the game should draw itself. /// </summary> /// <param name="gameTime">Provides a snapshot of timing values.</param> protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); // Draw the sprite using Alpha Blend, which uses transparency information if available spriteBatch.Begin(); mySprite1.Draw(spriteBatch); spriteBatch.End(); base.Draw(gameTime); } } }
Note that this code uses Vector2 (0f, 0f) to define a zeroed 2D vector, but you could also use the Vector2.Zero
static property instead. MonoGame offers many such properties to improve code readability.
Even though we included only a single code line (for creating the mySprite1object), many things are going on
here. You created your sprite class by using the content manager to load the Texture2D based on the image asset
name: ball. You also defined the sprite position as (0, 0), and decided on the sprite size: 64 pixels wide and 64
pixels tall.
For the Sprite Batch’s creation, we pass the graphics device as a parameter. This device (represented here by the
GraphicsDevice variable) is your entry point to the graphics handling layer, and through it you perform all
graphical operations. Here, you are informing the SpriteBatch which device it should use when drawing the
sprites. In the next section, you will see how to use the device to change the program’s window size.
Drawing the Sprite
We have also included code to draw the sprite using the SpriteBatch object that we created.
You use the SpriteBatch, as its name suggests, to draw a batch of sprites, grouping one or more calls to its Draw
method inside a block started by a call to the Begin method and closed by a call to the End method. The Begin
method can also receive parameters that will be used when rendering every sprite in the block. A very
interesting optional parameter (not used here) of the Begin method is transformMatrix, which receives a
transformation matrix that will apply transformations (scale, rotation, or translation) to the entire batch of
sprites being drawn. We will learn more about transformation matrices later in the semester.
![Page 8: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/8.jpg)
8
Cleaning Up
In most programming languages (those without built-in garbage collection like C#), it is a good programming
practice to destroy everything you created when the program ends. We can also do this explicitly in C# (which
might be appropriate if we had C++ code in our game, in addition to C# code, or if we were coding for a
memory-limited device). To do this, you need to dispose of the texture of clsSprite that was created in the
LoadContent method. You do this in the UnloadContent method. If you wish to try this (you need not), the code
to be added for disposing of the object follows:
/// <summary> /// UnloadContent will be called once per game and is the place to unload /// all content. /// </summary> protected override void UnloadContent() { // TODO: Unload any non ContentManager content here // Free the previously allocated resources mySprite1.texture.Dispose(); spriteBatch.Dispose(); }
Note that we could also create a Dispose method in the clsSprite class to dispose of the texture, and call that
method from the UnloadContent method. This would represent a more “object-oriented” coding practice, and
would be appropriate for a more complex game.
Game Logic
The default Game1.cs game code created by MonoGame includes a place to insert game logic. We won’t touch
this code for now, but you should see the following code in the file:
/// <summary> /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// </summary> /// <param name="gameTime">Provides a snapshot of timing values.</param> protected override void Update(GameTime gameTime) { // Allows the game to exit if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); // TODO: Add your update logic here base.Update(gameTime); }
Compile (Build ► Build Solution) and run (Debug ► Begin Debugging) the program. Doing so should
produce the following result on the screen (a window with the ball sprite positioned in the upper-left corner [the
(0, 0) position] of the program window).
![Page 9: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/9.jpg)
9
If for some reason your code is not working, here are complete copies of clsSprite.cs and Game1.cs at this point
in the lab.
TASK: Compile and run your code. Go through program.cs, Game1.cs, and clsSprite.cs and insert comments
explaining every interesting line of code. If you like, you can use “/* */” comment notation to distinguish your
comments from the comments inserted by MonoGame. It is perfectly acceptable, and in fact encouraged, for
you to work together on this portion of the lab.
Changing the Window Size
If you want to change the size of the window (for example, to a 700 x 500 window), you can inform the device
about the new dimensions (through the graphics object) in the Game1 constructor, by including the following
code lines just after the creation of the graphics object:
public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; //changing the back buffer size changes the window size (in windowed mode) graphics.PreferredBackBufferWidth = 700; graphics.PreferredBackBufferHeight = 500; }
Make this change and observe the result. These lines change the backbuffer width and height, which is reflected
in the window size, because we are working in windowed mode. This backbuffer is part of the technique used to
draw the game scene without image flickering, called double buffering. With double buffering, two buffers are
used to draw and display the game scene. While the first one is presented to the player, the second, invisible
one (the backbuffer) is being drawn. After the drawing is finished, the backbuffer content is moved to the
screen, so the player doesn’t see only part of the scene if it takes too long to be drawn (the bad visual effect
known as flickering). Fortunately, we don’t need to worry about such details, because MonoGame hides this
complexity. But now you know why the property is called PreferredBackBufferWidth, instead of something
like PreferredWindowsWidth.
![Page 10: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/10.jpg)
10
Save a copy of your work this far, e.g., by saving a copy of the project, and creating a zip file of the
project contents named something like <YourName>-Lab3-Part1.zip.
PART 2.
In this part of the lab we are going to add code that puts two balls on the screen, sets them in motion, and
performs collision detection, so that the balls bounce off the walls, and off one another.
Moving the Sprite on the Screen
Because we work directly with screen coordinates when creating 2D games, moving a sprite is simple. All we
need to do is draw the sprite in a different position. By incrementing the x coordinate of the sprite position, the
sprite moves to the right; by decrementing the x coordinate, the sprite moves to the left. If we want to move the
sprite down on the screen, we need to increment the y coordinate. We move the sprite up by decrementing the y
coordinate. (Recall that the (0, 0) point in screen coordinates is the upper-left corner of the window.)
MonoGame provides a specific place to do the game calculations: the Update overridable method. We can move
the sprite by simply adding one line in the code, incrementing the X position of the sprite, according to the
following line of code:
mySprite1.position.X += 1;
Because we use the sprite’s position property when rendering the sprite in the Draw method, by including this
line, we will see the sprite moving across the window, to the right, until it disappears from the screen.
To create a more interesting game-like sprite, let’s do something a little more sophisticated. First, we will create
a new property in the clsSprite class, velocity, that defines the sprite velocity on both the x and y axes. Then we
will modify the class constructor to receive and store the screen coordinates, so that we can include a method
that moves the sprite according to the given velocity. We will keep the sprite from moving off the screen by
detecting when it reaches the edge of our window. To do this, the sprite needs to know the screen size so it can
check the sprite’s position with respect to the screen. We will modify the clsSprite constructor to pass this
information when the sprite is created (there are of course other ways to do this, but this one is easy). Modify
the Game1.cs code as follows:
mySprite1 = new clsSprite(Content.Load<Texture2D>("ball"), new Vector2(0f, 0f), new Vector2(64f, 64f), graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight);
Modify the clsSprite code as follows:
class clsSprite { public Texture2D texture { get; set; } // sprite texture, read-only property public Vector2 position { get; set; } // sprite position on screen public Vector2 size { get; set; } // sprite size in pixels public Vector2 velocity { get; set; } // sprite velocity private Vector2 screenSize { get; set; } // screen size public clsSprite(Texture2D newTexture, Vector2 newPosition, Vector2 newSize, int ScreenWidth, int ScreenHeight) {
![Page 11: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/11.jpg)
11
texture = newTexture; position = newPosition; size = newSize; screenSize = new Vector2(ScreenWidth, ScreenHeight); }
We will set our sprite’s velocity to (X,Y) (recall that velocity is a vector) in the LoadContent method, after the
sprite-creation code, thus informing the sprite that it should move X pixels per update on the x axis, and Y
pixels per update on the y axis. This way, the sprite will move across the screen.
We need to create a method, let’s call it Move, in the clsSprite class that moves the sprite according to the sprite
velocity, while respecting the screen boundaries. This function will check to see if the sprite is impinging on a
screen boundary, and if so, invert the X or Y velocity component, as appropriate (why does this work?). The
code for this method is as follows:
public void Move() { // if we´ll move out of the screen, invert velocity // checking right boundary if (position.X + size.X + velocity.X > screenSize.X) velocity = new Vector2(-velocity.X, velocity.Y); // checking bottom boundary if (position.Y + size.Y + velocity.Y > screenSize.Y) velocity = new Vector2(velocity.X, -velocity.Y); // checking left boundary if (position.X + velocity.X < 0) velocity = new Vector2(-velocity.X, velocity.Y); // checking top boundary if (position.Y + velocity.Y < 0) velocity = new Vector2(velocity.X, -velocity.Y); // since we adjusted the velocity, just add it to the current position position += velocity; }
Checking for left and top screen boundaries is a direct test, because the sprite position is given by its upper-left
corner. However, when checking if the sprite will leave the screen on the right, we must add the sprite width to
the sprite’s X position to make the sprite bounce with its right corner, or it would leave the screen before
bouncing back. Similarly, when checking if the sprite is leaving through the bottom of the screen, we must add
the sprite height to its Y position so the sprite will bounce with its bottom edge.
Study (but do not yet compile and run) this code until it makes sense. In particular, why is the velocity
component added to the position and size component before comparing with screen dimensions? Hint: the
sprite is moving while we are making the comparison.
Finally, the Update method of the Game1 class has to be modified to call the new Move method, as follows:
// Allows the game to exit ... // Move the sprite mySprite1.Move();
![Page 12: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/12.jpg)
12
Coding for Collision Detection
Making the sprite bounce on the window borders is already a simple collision-detection test, but in 2D games,
we often want to test for collisions between sprites. There are many collision-detection algorithms for 2D and
3D applications. We will use a simple and reliable algorithm known as the Bounding Box Algorithm.
Basically, the algorithm assumes that all sprites are composed of one or more rectangles. If the rectangles of
two sprites overlap, the sprites are said to have collided.
An easy way to implement the bounding-box test is simply to check if the x,y position of the upper-bound
corner in the first box (which wraps the first sprite we want to test) is inside the second box (which wraps the
second sprite to test). In other words, we check whether the X and Y values of the box being tested are less
than or equal to the corresponding X and Y values of the other box, plus the width of the other box.
We will implement this test in the clsSprite class, as a method (named Collides) that will receive a sprite as a
parameter, and test the received sprite against the current sprite. If there is a collision, the method will return
true. The basic code for this test is as follows (add this code to clsSprite):
public bool Collides(clsSprite otherSprite) { // check if two sprites intersect return (this.position.X + this.size.X > otherSprite.position.X && this.position.X < otherSprite.position.X + otherSprite.size.X && this.position.Y + this.size.Y > otherSprite.position.Y && this.position.Y < otherSprite.position.Y + otherSprite.size.Y); }
In this code sample, two boxes will overlap only if both the x and y coordinates of the Rectangle 2 are within
range (X to X+width, Y to Y+height) of Rectangle 1. Looking at the left figure below, we see that the y
coordinate for Rectangle 2 is not greater than the y coordinate plus the height of Rectangle 1. This means the
boxes might be colliding. But when we check the x coordinate of Rectangle 2, we see that it is greater than the x
coordinate plus the width of Rectangle 1, which means that no collision is possible.
The right side of the figure illustrates a case in which we do have a collision. In this case, both the x and y
coordinates of Rectangle 2 are within the range of Rectangle 1. In the code sample above, we also do the
opposite test, checking if the x and y coordinates of Rectangle 1 are within the range of Rectangle 2. Because
we are only checking one point, it is possible for Rectangle 2’s top-left corner to be outside Rectangle 1, but for
the top-left corner of Rectangle 1 to be inside Rectangle 2.
Width X,Y
X,Y
X,Y
X,Y
Width
Width Width
Height
Height Height
Height Rect. 1
Rect. 2 Rect. 2
Rect. 1
Non-Overlapping Rectangles Overlapping Rectangles
![Page 13: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/13.jpg)
13
Study these figures, and the code sample, until this all makes sense.
Now we want to actually test two sprites colliding. To do that, we need two sprites. First, declare a second
sprite:
clsSprite mySprite1; clsSprite mySprite2;
Then, in the LoadContent method, include the code for the sprite creation and set initial velocity:
protected override void LoadContent() { // Load a 2D texture sprite mySprite1 = new clsSprite(Content.Load<Texture2D>("ball"), new Vector2(0f, 0f), new Vector2(64f, 64f), graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight); mySprite2 = new clsSprite(Content.Load<Texture2D>("ball"), new Vector2(218f, 118f), new Vector2(64f, 64f), graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight); // Create a SpriteBatch to render the sprite spriteBatch = new SpriteBatch(graphics.GraphicsDevice); // set the speed the sprites will move mySprite1.velocity = new Vector2(5, 5); mySprite2.velocity = new Vector2(3, -3);
Now make the second sprite move (in Update):
// Move the sprites mySprite1.Move(); mySprite2.Move();
Finally, in the Draw method, we include the code (do not add this code yet) for drawing the new sprite. The
code for drawing the two sprites follows:
protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.CornflowerBlue); // TODO: Add your drawing code here spriteBatch.Begin(); mySprite1.Draw(spriteBatch); mySprite2.Draw(spriteBatch); spriteBatch.End(); base.Draw(gameTime); }
We have now created two sprites, gave them an initial velocity, and perform the boundary and collision
detection just described. We began by replicating the sprite-creation code for the second sprite, and including
![Page 14: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/14.jpg)
14
the code for testing collisions in the Update method of the Game1 class. In the Update method, we included the
code to move the first and second sprites, and to simulate “bouncing off the walls.”
If you compile and run your code now, the balls should bounce off of the walls but not off of each other. Let’s
fix that.
On a collision, we will store the velocity of mySprite1 in a new tempVelocity variable, set the velocity of
mySprite1 to the velocity to mySprite2, and then set the velocity of mySprite2 to tempVelocity, thus changing
the velocity between the sprites. Why is this a reasonable simulation of “bouncing”?
We need to include a call to Collides in the Update method, and change the velocity between the sprites, as
follows:
if (mySprite1.Collides(mySprite2)) { Vector2 tempVelocity = mySprite1.velocity; mySprite1.velocity = mySprite2.velocity; mySprite2.velocity = tempVelocity; }
We could also simply invert the velocities, e.g.:
if (mySprite1.CircleCollides(mySprite2)) { mySprite1.velocity *= -1; mySprite2.velocity *= -1; } …
After you try the first method (later), come back and try the second. See which simulated collision behavior
looks better to you. Since simple is usually better, we will go with Option 2.
Compile and run this code and watch it for a while. Do you notice that sometimes the balls bounce when they
have not yet touuched? This is because squares have corners, and if we are using a square to represent a circle,
their corners will occasionally be the point of intersection. We will fix this issue next.
Here is our code so far. Study this code until every line makes sense:
Game1.cs
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; namespace Lab3_Mono { /// <summary> /// This is the main type for your game. /// </summary> public class Game1 : Game
![Page 15: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/15.jpg)
15
{ GraphicsDeviceManager graphics; SpriteBatch spriteBatch; clsSprite mySprite1; clsSprite mySprite2; public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; // changing the back buffer size changes the window size (in windowed mode) graphics.PreferredBackBufferWidth = 700; graphics.PreferredBackBufferHeight = 500; } /// <summary> /// Allows the game to perform any initialization it needs to before starting to run. /// This is where it can query for any required services and load any non-graphic /// related content. Calling base.Initialize will enumerate through any components /// and initialize them as well. /// </summary> protected override void Initialize() { // TODO: Add your initialization logic here base.Initialize(); } /// <summary> /// LoadContent will be called once per game and is the place to load /// all of your content. /// </summary> protected override void LoadContent() { // Create a new SpriteBatch, which can be used to draw textures. spriteBatch = new SpriteBatch(GraphicsDevice); // Load a 2D texture sprite mySprite1 = new clsSprite(Content.Load<Texture2D>("ball"), new Vector2(0f, 0f), new Vector2(64f, 64f), graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight); mySprite2 = new clsSprite(Content.Load<Texture2D>("ball"), new Vector2(218f, 118f), new Vector2(64f, 64f), graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight); // Create a SpriteBatch to render the sprite spriteBatch = new SpriteBatch(graphics.GraphicsDevice); // set the speed the sprites will move mySprite1.velocity = new Vector2(5, 5); mySprite2.velocity = new Vector2(3, -3); } /// <summary> /// UnloadContent will be called once per game and is the place to unload /// game-specific content. /// </summary> protected override void UnloadContent() { // TODO: Unload any non ContentManager content here
![Page 16: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/16.jpg)
16
} /// <summary> /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// </summary> /// <param name="gameTime">Provides a snapshot of timing values.</param> protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit(); // Move the sprites mySprite1.Move(); mySprite2.Move(); if (mySprite1.Collides(mySprite2)) { mySprite1.velocity *= -1; mySprite2.velocity *= -1; } base.Update(gameTime); } /// <summary> /// This is called when the game should draw itself. /// </summary> /// <param name="gameTime">Provides a snapshot of timing values.</param> protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); // Draw the sprite using Alpha Blend, which uses transparency information if available spriteBatch.Begin(); mySprite1.Draw(spriteBatch); mySprite2.Draw(spriteBatch); spriteBatch.End(); base.Draw(gameTime); } } }
clsSprite.cs using Microsoft.Xna.Framework.Graphics; // for Texture2D using Microsoft.Xna.Framework; // for Vector2 namespace Lab3_Mono { class clsSprite { public Texture2D texture { get; set; } // sprite texture, read-only property public Vector2 position { get; set; } // sprite position on screen public Vector2 size { get; set; } // sprite size in pixels
![Page 17: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/17.jpg)
17
public Vector2 velocity { get; set; } // sprite velocity private Vector2 screenSize { get; set; } // screen size public clsSprite(Texture2D newTexture, Vector2 newPosition, Vector2 newSize, int ScreenWidth, int ScreenHeight) { texture = newTexture; position = newPosition; size = newSize; screenSize = new Vector2(ScreenWidth, ScreenHeight); } public void Move() { // if we´ll move out of the screen, invert velocity // checking right boundary if (position.X + size.X + velocity.X > screenSize.X) velocity = new Vector2(-velocity.X, velocity.Y); // checking bottom boundary if (position.Y + size.Y + velocity.Y > screenSize.Y) velocity = new Vector2(velocity.X, -velocity.Y); // checking left boundary if (position.X + velocity.X < 0) velocity = new Vector2(-velocity.X, velocity.Y); // checking top boundary if (position.Y + velocity.Y < 0) velocity = new Vector2(velocity.X, -velocity.Y); // since we adjusted the velocity, just add it to the current position position += velocity; } public bool Collides(clsSprite otherSprite) { // check if two sprites intersect return (this.position.X + this.size.X > otherSprite.position.X && this.position.X < otherSprite.position.X + otherSprite.size.X && this.position.Y + this.size.Y > otherSprite.position.Y && this.position.Y < otherSprite.position.Y + otherSprite.size.Y); } public void Draw(SpriteBatch spriteBatch) { spriteBatch.Draw(texture, position, Color.White); } } }
When you ran your code, you saw the sprites moving and bouncing against each other and against the window
borders. However, if the boxes happen to collide diagonally, the circles will bounce before they really “hit”
each other. You may have noticed that the sprites look like they are bouncing when they are still separated.
There is an easy fix for this when we are dealing with circular objects. When testing for collisions between
circles, we can simply check if the distance between the circle centers is less than the sum of their radii. If so,
we have a collision. This provides a precise way to test for circle collisions. To change the clsSprite code to
![Page 18: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/18.jpg)
18
support collisions between two circle sprites, we first create two new read-only properties, center and radius,
which are calculated according to the other sprite properties, as follows:
class clsSprite { … public Vector2 center { get { return position + (size / 2); } } // sprite center public float radius { get { return size.X / 2; } } // sprite radius We then add a CircleCollides method to clsSprite, as follows: public bool CircleCollides(clsSprite otherSprite) { // Check if two circle sprites collided return (Vector2.Distance(this.center, otherSprite.center) < this.radius + otherSprite.radius); }
Finally, we change the Update method of the Game1 class to call CircleCollides instead of
Collides:
if (mySprite1.CircleCollides(mySprite2)) { Vector2 tempVelocity = mySprite1.velocity; mySprite1.velocity = mySprite2.velocity; mySprite2.velocity = tempVelocity; }
Compile and run the resulting code. Here are complete instances of the Lab3-Part2 Game1.cs and clsSprite.cs
code. (Be sure to delete all after “.cs” after you download the code.)
The result should be some bouncing sprites!
TASK: Go through Game1.cs, and clsSprite.cs and insert comments explaining every line of code not
previously documented. If you like, you can use “/* */” comment notation to distinguish your comments from
the comments inserted by MonoGame. It is perfectly acceptable, and in fact encouraged, for you to work
together on this portion of the lab.
Save a copy of your work this far, e.g., by saving a copy of the project, and creating a zip file of the
project contents named something like <YourName>-Lab3-Part2.zip.
PART 3
In this part of Lab 3 we will see how to incorporate user input to our game. We will take control of one of the
balls with either the keyboard or mouse, and we will show you how to interface with the Xbox game controller
(gamepad).
![Page 19: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/19.jpg)
19
Adding User Input
When you create a new MonoGame Windows Game 4.0 project type, the Update method of the Game1 class
already includes code for dealing with user input:
// Allows the game to exit if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit();
This code introduces the GamePad class: the basic entry point to get user input from the Xbox gamepad. If you
explore the GamePad properties and methods using Visual C# 2017 Express IntelliSense (you may not know
what this is yet - check out https://docs.microsoft.com/en-us/visualstudio/ide/visual-csharp-intellisense?view=vs-
2017), you will see how to use the GetState method to get the current state of buttons (Buttons structure), the
thumbsticks (ThumbSticks structure), directional pad (DPad structure), and the controller triggers (Triggers
structure). There is also a property to inform you if the gamepad is connected (IsConnected). Another
interesting detail is that you can vibrate the gamepad by calling the SetVibration method of the GamePad class.
Similar to the GamePad class, there are corresponding classes for the keyboard (KeyBoard) and mouse (Mouse).
Let’s see how we can use this information to incorporate user input. First, in the Game1 class, we need to
remove (comment out) the code that sets the starting velocity of mySprite2 in the LoadContent method, and
remove (comment out) the call to mySprite2.Move in the Update method. Do this now.
These changes will prevent mySprite2 from moving by itself. We also need to change the collision-detection
code, simplifying it to only invert the mySprite1 velocity, as follows:
if (mySprite1.CircleCollides(mySprite2)) { mySprite1.velocity *= -1; // mySprite2.velocity *= -1; }
Now, to make the second sprite move according to gamepad, mouse or keyboard input, we include code for this
purpose in the Update method of the Game1 class (here, all but the keyboard code is commented out, just to
keep things simple for now), as follows:
// Move the sprites mySprite1.Move(); // mySprite2.Move(); if (mySprite1.CircleCollides(mySprite2)) { mySprite1.velocity *= -1; // mySprite2.velocity *= -1; } // Change the sprite 2 position using the left thumbstick of the Xbox controller
![Page 20: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/20.jpg)
20
// Vector2 LeftThumb = GamePad.GetState(PlayerIndex.One).ThumbSticks.Left; // mySprite2.position += new Vector2(LeftThumb.X, -LeftThumb.Y) * 5; // Change the sprite 2 position using the keyboard KeyboardState keyboardState = Keyboard.GetState(); if (keyboardState.IsKeyDown(Keys.Up)) mySprite2.position += new Vector2(0, -5); if (keyboardState.IsKeyDown(Keys.Down)) mySprite2.position += new Vector2(0, 5); if (keyboardState.IsKeyDown(Keys.Left)) mySprite2.position += new Vector2(-5, 0); if (keyboardState.IsKeyDown(Keys.Right)) mySprite2.position += new Vector2(5, 0); // Make sprite 2 follow the mouse //if (mySprite2.position.X < Mouse.GetState().X) // mySprite2.position += new Vector2(5, 0); //if (mySprite2.position.X > Mouse.GetState().X) // mySprite2.position += new Vector2(-5, 0); //if (mySprite2.position.Y < Mouse.GetState().Y) // mySprite2.position += new Vector2(0, 5); //if (mySprite2.position.Y > Mouse.GetState().Y) // mySprite2.position += new Vector2(0, -5); base.Update(gameTime);
In this code, we are adding a Vector2 to mySprite2.position. For the gamepad, this vector is five times the value
of the left thumbstick, except that we invert the Y property of the left thumbstick. If this seems odd, recall that
in screen coordinates, the X position increments from left to right, and the Y position increments from the top to
the bottom of the screen. The values of the X and Y properties of the thumbsticks range from -1 to 1, according
to how much the thumbstick is pushed to the right or the bottom (positive values) or left and up (negative
values). Therefore, you must invert the y coordinate to move the ball as expected. The multiplication by five is
simply to make the ball move faster, according to the gamepad input.
To make the gamepad vibrate when mySprite1collides with mySprite2 is also easy. Simply change the
collision-detection code in the Update method of the Game1 class, as follows:
if (mySprite1.CircleCollides(mySprite2)) { mySprite1.velocity *= -1; GamePad.SetVibration(PlayerIndex.One, 1.0f, 1.0f); } else GamePad.SetVibration(PlayerIndex.One, 0f, 0f);
Note that we need to set the gamepad vibration to zero when the sprites are not colliding; otherwise, it would
keep on vibrating continuously. The second and third arguments of the SetVibration method range from 0 to 1
(a floating point number), and define the speed for the left (low-frequency) and right (high-frequency) motors.
You can include code in your program to generate different types of vibrations depending on the game
conditions, for example, if the game collision is on the left or on the right of the player character.
![Page 21: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/21.jpg)
21
Compile and run the program with these changes. You should be able to move one of the sprites with the arrow
keys, and cause it to collide. If you have problems, here is a complete copy of the correct code:
Game1.cs using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; namespace Lab3_Mono { /// <summary> /// This is the main type for your game. /// </summary> public class Game1 : Game { GraphicsDeviceManager graphics; SpriteBatch spriteBatch; clsSprite mySprite1; clsSprite mySprite2; public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; // changing the back buffer size changes the window size (in windowed mode) graphics.PreferredBackBufferWidth = 700; graphics.PreferredBackBufferHeight = 500; } /// <summary> /// Allows the game to perform any initialization it needs to before starting to run. /// This is where it can query for any required services and load any non-graphic /// related content. Calling base.Initialize will enumerate through any components /// and initialize them as well. /// </summary> protected override void Initialize() { // TODO: Add your initialization logic here base.Initialize(); } /// <summary> /// LoadContent will be called once per game and is the place to load /// all of your content. /// </summary> protected override void LoadContent() { // Create a new SpriteBatch, which can be used to draw textures. spriteBatch = new SpriteBatch(GraphicsDevice); // Load a 2D texture sprite mySprite1 = new clsSprite(Content.Load<Texture2D>("ball"), new Vector2(0f, 0f), new Vector2(64f, 64f), graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight); mySprite2 = new clsSprite(Content.Load<Texture2D>("ball"), new Vector2(218f, 118f), new Vector2(64f, 64f), graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight);
![Page 22: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/22.jpg)
22
// Create a SpriteBatch to render the sprite spriteBatch = new SpriteBatch(graphics.GraphicsDevice); // set the speed the sprites will move mySprite1.velocity = new Vector2(5, 5); // mySprite2.velocity = new Vector2(3, -3); } /// <summary> /// UnloadContent will be called once per game and is the place to unload /// game-specific content. /// </summary> protected override void UnloadContent() { // TODO: Unload any non ContentManager content here } /// <summary> /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// </summary> /// <param name="gameTime">Provides a snapshot of timing values.</param> protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit(); // Move the sprites mySprite1.Move(); // mySprite2.Move(); if (mySprite1.CircleCollides(mySprite2)) { mySprite1.velocity *= -1; GamePad.SetVibration(PlayerIndex.One, 1.0f, 1.0f); } else GamePad.SetVibration(PlayerIndex.One, 0f, 0f); // Change the sprite 2 position using the left thumbstick of the Xbox controller // Vector2 LeftThumb = GamePad.GetState(PlayerIndex.One).ThumbSticks.Left; // mySprite2.position += new Vector2(LeftThumb.X, -LeftThumb.Y) * 5; // Change the sprite 2 position using the keyboard KeyboardState keyboardState = Keyboard.GetState(); if (keyboardState.IsKeyDown(Keys.Up)) mySprite2.position += new Vector2(0, -5); if (keyboardState.IsKeyDown(Keys.Down)) mySprite2.position += new Vector2(0, 5); if (keyboardState.IsKeyDown(Keys.Left)) mySprite2.position += new Vector2(-5, 0); if (keyboardState.IsKeyDown(Keys.Right)) mySprite2.position += new Vector2(5, 0); // Make sprite 2 follow the mouse //if (mySprite2.position.X < Mouse.GetState().X) // mySprite2.position += new Vector2(5, 0);
![Page 23: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/23.jpg)
23
//if (mySprite2.position.X > Mouse.GetState().X) // mySprite2.position += new Vector2(-5, 0); //if (mySprite2.position.Y < Mouse.GetState().Y) // mySprite2.position += new Vector2(0, 5); //if (mySprite2.position.Y > Mouse.GetState().Y) // mySprite2.position += new Vector2(0, -5); base.Update(gameTime); } /// <summary> /// This is called when the game should draw itself. /// </summary> /// <param name="gameTime">Provides a snapshot of timing values.</param> protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); // Draw the sprite using Alpha Blend, which uses transparency information if available spriteBatch.Begin(); mySprite1.Draw(spriteBatch); mySprite2.Draw(spriteBatch); spriteBatch.End(); base.Draw(gameTime); } } }
clsSprite.cs using Microsoft.Xna.Framework.Graphics; // for Texture2D using Microsoft.Xna.Framework; // for Vector2 namespace Lab3_Mono { class clsSprite { public Texture2D texture { get; set; } // sprite texture, read-only property public Vector2 position { get; set; } // sprite position on screen public Vector2 size { get; set; } // sprite size in pixels public Vector2 velocity { get; set; } // sprite velocity private Vector2 screenSize { get; set; } // screen size public Vector2 center { get { return position + (size / 2); } } // sprite center public float radius { get { return size.X / 2; } } // sprite radius public clsSprite(Texture2D newTexture, Vector2 newPosition, Vector2 newSize, int ScreenWidth, int ScreenHeight) { texture = newTexture; position = newPosition; size = newSize; screenSize = new Vector2(ScreenWidth, ScreenHeight); } public void Move() { // if we´ll move out of the screen, invert velocity
![Page 24: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/24.jpg)
24
// checking right boundary if (position.X + size.X + velocity.X > screenSize.X) velocity = new Vector2(-velocity.X, velocity.Y); // checking bottom boundary if (position.Y + size.Y + velocity.Y > screenSize.Y) velocity = new Vector2(velocity.X, -velocity.Y); // checking left boundary if (position.X + velocity.X < 0) velocity = new Vector2(-velocity.X, velocity.Y); // checking top boundary if (position.Y + velocity.Y < 0) velocity = new Vector2(velocity.X, -velocity.Y); // since we adjusted the velocity, just add it to the current position position += velocity; } public bool Collides(clsSprite otherSprite) { // check if two sprites intersect return (this.position.X + this.size.X > otherSprite.position.X && this.position.X < otherSprite.position.X + otherSprite.size.X && this.position.Y + this.size.Y > otherSprite.position.Y && this.position.Y < otherSprite.position.Y + otherSprite.size.Y); } public bool CircleCollides(clsSprite otherSprite) { // Check if two circle sprites collided return (Vector2.Distance(this.center, otherSprite.center) < this.radius + otherSprite.radius); } public void Draw(SpriteBatch spriteBatch) { spriteBatch.Draw(texture, position, Color.White); } } }
Move the sprite with the keyboard. Now change the code to move the sprite with the mouse. When the sprites
overlap, mySprite1bounces. If we run this Lab using an Xbox, the gamepad would vibrate when the sprites
collide. Note that if you move the controlled sprite to overlap the bouncing sprite, the sprites oscillate rapidly.
Why is this so, and how might you correct this problem? For extra credit, implement a working solution to this
problem and demonstrate it in class.
TASK: Go through Game1.cs, and clsSprite.cs and insert comments explaining every line of code not
previously documented. If you like, you can use “/* */” comment notation to distinguish your comments from
the comments inserted by MonoGame. It is perfectly acceptable, and in fact encouraged, for you to work
together on this portion of the lab.
Save a copy of your work this far, e.g., by saving a copy of the project, and creating a zip file of the
project contents named something like <YourName>-Lab3-Part3.zip.
![Page 25: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/25.jpg)
25
Part 4
In the last part of this lab, we will add audio to our simple game. We will include background music and sound
effects when the balls collide. MonoGame processes sound using the same structure that it uses to manage
graphics: the Content Pipeline. To MonoGame, sound is just another type of game content. MonoGame
supports audio files in (at least) WAV, WMA, and MP34 formats.
There are a variety ways to add sound to a MonoGame game:
(1) the SoundEffect and SoundEffectInstance classes, which provide a simple and modestly-featured set of
methods used to play “fire and forget” sounds, such as might be used for collisions or explosions;
(2) the MediaPlayer and Song classes, which provide support for playing longer sounds, such as background
music or victory songs;
(3) the AudioListener and AudioEmitter classes, which support positional sound, allowing us to position
both the source of the sound and the location of our “ear” in the game, while continuing to also use the
SoundEffect classes; and
(4) the powerful (and complex) Microsoft Cross-Platform Audio Creation Tool, known as XACT.
Of these four, methods 1-3 all make use of the MonoGame Content Pipeline. XACT does not; it generates “pre-
compiled” files that bypass the Content Pipeline. We will demonstrate each of these four techniques in this part
of Lab 3. We will not cover how to generate the desired sound files themselves. Tools such as Audacity
(https://www.audacityteam.org/) can be used to build complex sound files that can then be processed and played
by MonoGame. A good introduction to Audacity can be found here:
https://www.marquette.edu/ctl/e-learning/documents/AudacityTutorial.pdf.
Note that most sounds and music are protected intellectual property. Do not use others’ sounds or music
without permission, and even if you have permission, be sure to appropriately credit the author/owner.
Before we begin, we need some sounds. Locate two sound files “chord.wav” and “notify.wav”. You can
search for these files on the C: drive (these files are installed by default in Windows, as system event sounds;
alternatively, you can choose any available .wav files), or you can download this zip file to your desktop (don’t
forget to unpack the zip file before proceeding). There are four files in this zip file; we will eventually need
them all for this lab. Outside of Visual Studio, copy the four files to the Content project sub-directory.
Installing XACT (Windows Only)
If you are using an Inworks computer, you can skip this step. If you are using your own computer, you must
install XACT software as described below or you will be unable to complete that part of the lab. XACT is only
available for Windows.
4 There are a number of online admonitions to not use MP3 format in your games, asserting that its use could result in legal challenges above a certain threshold. To the best of my knowledge (*but I am not a lawyer*), MP3 technology became license-free in the United States in April 2017, when U.S. Patent 6,009,399 expired. If this might matter to you, seek the advice of competent counsel.
![Page 26: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/26.jpg)
26
Although MonoGame is a very complete clone of Microsoft XNA 4.0, at least as of MonoGame version 3.7,
XACT software must be installed separately before MonoGame can make use of its output. Although XACT
was created for an older version of Visual Studio, installing XACT is not difficult, as we describe below:
1. Download the XNA installer from Microsoft, or here is a local copy. The desired file is called
XNAGS40_setup.exe.
2. Open a command prompt with administrator privileges(Start Menu; search for cmd.exe; right-click and
Run As Administrator) and cd to the directory containing XNAGS40_setup.exe.
3. Run the command: XNAGS40_setup.exe /x
4. You will now be prompted where to extract. Choose a convenient location and click OK. This will
create a couple files, the most important being redists.msi.
5. Run this file (just type redists.msi and hit <enter> at the command line, or double click on the file in
Windows Explorer). This will in turn create a directory structure in Program Files (or Program Files
x86 on 64-bit Windows ) called Microsoft XNA.
6. Close the command prompt, navigate to that folder in Windows Explorer, then open XNA Game
Studio\v4.0\setup:
7. Run xnags_shared.msi then xnags_platform_tools.msi, in that order. Take default options when asked.
8. Open C:\Program Files (x86)\Microsoft XNA\XNA Game Studio\v4.0\Tools, and you should see the
following:
![Page 27: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/27.jpg)
27
9. All of the XACT tools should now be found in the Start Menu, under Microsoft XNA Game Studio 4.0.
We will use these tools in a bit.
Using SoundEffect to Play Audio Content
To play sounds using SoundEffect we first have to add the file to the content project of our game. Right-click
the content project and select Add ► Existing Item. Then, navigate to the chord.wav audio file and click
“Open.” Examine the properties of the added wav file to determine its asset name (in this case, “chord”). Now
we need to add code to Game1.cs to create an instance of SoundEffect, and to load this effect into the content
pipeline.
(Note: Do not confuse an instance of the SoundEffect class with a SoundEffectInstance, which is a more robust
sound effect handler that allows us to control the playback of a single audio stream from a SoundEffect. We
create a SoundEffectInstance by calling the CreateInstance method on the SoundEffect. A SoundEffect can
create many instances. Each instance provides a number of controls that enable more advanced playback
scenarios, including Play, Pause, Stop, and Resume.
Add the following code to Game1.cs:
… in the using statements
using Microsoft.Xna.Framework.Audio;
… in the class instance variable definitions
// Create a SoundEffect resource SoundEffect soundEffect;
… and in LoadContent()
spriteBatch = new SpriteBatch(GraphicsDevice); // Load the SoundEffect resource soundEffect = Content.Load<SoundEffect>("chord");
Now that we have loaded the resources for the audio file, we need to play it. We will play the chord.wav file
whenever the two sprites collide using a mechanism for sound playback sometimes called “fire and forget”. It is
called fire and forget because after you call the Play method, you don’t need to manage the playback of the
audio stream. The audio file plays from the start of the file to the end of the file. Although this limits the control
you have as a developer on how the playback occurs, it is really simple to use.
To play the SoundEffect using fire and forget, add the following code to Game1.cs:
if (mySprite1.CircleCollides(mySprite2)) {
![Page 28: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/28.jpg)
28
mySprite1.velocity *= -1; GamePad.SetVibration(PlayerIndex.One, 1.0f, 1.0f); soundEffect.Play(); }
Compile and run your code. You should hear a “boink” whenever the two sprites collide.
The Play method returns a boolean value that specifies whether the sound was able to play or not. If Play returns
true, then the sound plays. If it false returns, there are too many sounds currently playing, so it cannot play. We
do not need to worry about this, since we are only playing one sound at this point.
The Play method also contains an overload with three parameters to control the volume, pitch, and panning of
the playback. Replace soundEffect.Play() with:
soundEffect.Play(1.0f, 0.0f, 0.0f);
The first parameter is a float value that determines the volume of the sound. The value can range from 0, which
indicates no sound, to 1.0, which means the sound should be full volume with respect to the master volume
setting. Use the MasterVolume static property of SoundEffect to set the master volume setting for all
SoundEffects. The MasterVolume values range from 0 to 1 with 1 as the default value. The second parameter is
a float value that determines the pitch that the sound plays in. The pitch value ranges from -1, which lowers the
pitch by one octave, to 1, which raises the pitch by one octave. A value of 0 plays the sound as it is stored in the
file. The third parameter controls the pan of the playback. Panning determines how much of the playback occurs
from the left and right speakers. The values rage from -1, which comes from only the left speaker, to 1, which
comes from only from the right speaker. A value of 0 plays the sound as it was loaded from the file.
Experiment with different soundEffect.Play parameter values.
SoundEffect exposes two additional properties that are useful. The first is the Duration property that returns the
length as a TimeSpan of the SoundEffect. The other is Name that returns the name of the SoundEffect.
If we need finer control, such as looping or applying effects, we can create a SoundEffectInstance using the
SoundEffect.CreateInstance() call. We should also create a separate instance if we want to have multiple
concurrent instances of the same sound effect playing. All instances of the same SoundEffect share resources,
so, while the number of simultaneous supported sounds varies from platform to platform (based upon memory
limitations), the number is large. Let’s experiment with SoundEffectInstance. If you have not done so already,
add notify.wav, explosion.wav and music.wav to the content pipeline project. Now, modify Game1.cs as shown
below:
… in the class instance variable definitions
// Create a SoundEffect resource SoundEffect soundEffect;
![Page 29: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/29.jpg)
29
// Create some sound resources SoundEffect soundEffect1; SoundEffect soundEffect2; SoundEffectInstance seInstance; // Since we will loop the music, we only want to play it once
bool playMusic = true;
… in LoadContent() soundEffect = Content.Load<SoundEffect>("chord");
// Load the SoundEffect resource soundEffect1 = Content.Load<SoundEffect>("chord"); soundEffect2 = Content.Load<SoundEffect>("music"); // Create a SoundEffect instance that can be manipulated later seInstance = soundEffect2.CreateInstance(); seInstance.IsLooped = true;
… and in Update()
if (mySprite1.CircleCollides(mySprite2)) { mySprite1.velocity *= -1; GamePad.SetVibration(PlayerIndex.One, 1.0f, 1.0f); soundEffect1.Play(1.0f, 0.0f, 0.0f); if (playMusic) { seInstance.Play(); playMusic = false; } } else GamePad.SetVibration(PlayerIndex.One, 0f, 0f);
Compile and run this code. Now, in addition to the “boink” on every collision, you should hear weird chanting
that starts at the first collision, and continues thereafter.
Here is our code at this point:
Game1.cs:
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Audio; namespace Lab3_Mono { /// <summary> /// This is the main type for your game. /// </summary> public class Game1 : Game { GraphicsDeviceManager graphics; SpriteBatch spriteBatch;
![Page 30: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/30.jpg)
30
clsSprite mySprite1; clsSprite mySprite2; // Create some sound resources SoundEffect soundEffect1; SoundEffect soundEffect2; SoundEffectInstance seInstance; // Since we will loop the music, we only want to play it once bool playMusic = true; public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; // changing the back buffer size changes the window size (in windowed mode) graphics.PreferredBackBufferWidth = 700; graphics.PreferredBackBufferHeight = 500; } /// <summary> /// Allows the game to perform any initialization it needs to before starting to run. /// This is where it can query for any required services and load any non-graphic /// related content. Calling base.Initialize will enumerate through any components /// and initialize them as well. /// </summary> protected override void Initialize() { // TODO: Add your initialization logic here base.Initialize(); } /// <summary> /// LoadContent will be called once per game and is the place to load /// all of your content. /// </summary> protected override void LoadContent() { // Create a new SpriteBatch, which can be used to draw textures. spriteBatch = new SpriteBatch(GraphicsDevice); // Load the SoundEffect resource soundEffect1 = Content.Load<SoundEffect>("chord"); soundEffect2 = Content.Load<SoundEffect>("music"); // Create a SoundEffect instance that can be manipulated later seInstance = soundEffect2.CreateInstance(); seInstance.IsLooped = true; // Load a 2D texture sprite mySprite1 = new clsSprite(Content.Load<Texture2D>("ball"), new Vector2(0f, 0f), new Vector2(64f, 64f), graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight); mySprite2 = new clsSprite(Content.Load<Texture2D>("ball"), new Vector2(218f, 118f), new Vector2(64f, 64f), graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight); // Create a SpriteBatch to render the sprite spriteBatch = new SpriteBatch(graphics.GraphicsDevice);
![Page 31: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/31.jpg)
31
// set the speed the sprites will move mySprite1.velocity = new Vector2(5, 5); // mySprite2.velocity = new Vector2(3, -3); } /// <summary> /// UnloadContent will be called once per game and is the place to unload /// game-specific content. /// </summary> protected override void UnloadContent() { // TODO: Unload any non ContentManager content here } /// <summary> /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// </summary> /// <param name="gameTime">Provides a snapshot of timing values.</param> protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit(); // Move the sprites mySprite1.Move(); // mySprite2.Move(); if (mySprite1.CircleCollides(mySprite2)) { mySprite1.velocity *= -1; GamePad.SetVibration(PlayerIndex.One, 1.0f, 1.0f); soundEffect1.Play(1.0f, 0.0f, 0.0f); if (playMusic) { seInstance.Play(); playMusic = false; } } else GamePad.SetVibration(PlayerIndex.One, 0f, 0f); // Change the sprite 2 position using the left thumbstick of the Xbox controller // Vector2 LeftThumb = GamePad.GetState(PlayerIndex.One).ThumbSticks.Left; // mySprite2.position += new Vector2(LeftThumb.X, -LeftThumb.Y) * 5; // Change the sprite 2 position using the keyboard KeyboardState keyboardState = Keyboard.GetState(); if (keyboardState.IsKeyDown(Keys.Up)) mySprite2.position += new Vector2(0, -5); if (keyboardState.IsKeyDown(Keys.Down)) mySprite2.position += new Vector2(0, 5); if (keyboardState.IsKeyDown(Keys.Left)) mySprite2.position += new Vector2(-5, 0); if (keyboardState.IsKeyDown(Keys.Right)) mySprite2.position += new Vector2(5, 0); // Make sprite 2 follow the mouse
![Page 32: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/32.jpg)
32
//if (mySprite2.position.X < Mouse.GetState().X) // mySprite2.position += new Vector2(5, 0); //if (mySprite2.position.X > Mouse.GetState().X) // mySprite2.position += new Vector2(-5, 0); //if (mySprite2.position.Y < Mouse.GetState().Y) // mySprite2.position += new Vector2(0, 5); //if (mySprite2.position.Y > Mouse.GetState().Y) // mySprite2.position += new Vector2(0, -5); base.Update(gameTime); } /// <summary> /// This is called when the game should draw itself. /// </summary> /// <param name="gameTime">Provides a snapshot of timing values.</param> protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); // Draw the sprite using Alpha Blend, which uses transparency information if available spriteBatch.Begin(); mySprite1.Draw(spriteBatch); mySprite2.Draw(spriteBatch); spriteBatch.End(); base.Draw(gameTime); } } }
In the example we just completed, the “music” was encoded as a .wav file. This allowed us to use the sound
effect classes to play it. If we want to play an mp3 file, we need to use the MediaPlayer and Song classes, as we
will now describe.
Using the MediaPlayer and Song Classes to Play Audio Content
Playing a song in MonoGame is quite easy. First, we need a song. From among the mp3 files that you own,
select one, and add it to the project’s content pipeline as we have done before. In this example, I have added
AC/DC track “Back in Black.” You will of course choose you own song.
Add the following code to Game1.cs:
… in the using statements
using Microsoft.Xna.Framework.Media;
… in the class instance variable definitions
Song song;
![Page 33: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/33.jpg)
33
… and in LoadContent()
// Change "acdc" to the file prefix of your mp3 song. song = Content.Load<Song>("acdc");
MediaPlayer.Play(song); // play it // Uncomment the following line will also loop the song
// MediaPlayer.IsRepeating = true; MediaPlayer.MediaStateChanged += MediaPlayer_MediaStateChanged;
Finally, add a new MediaStateChanged event handler that will be called when the song completes, decreasing
the volume and playing the song again:
void MediaPlayer_MediaStateChanged(object sender, System.EventArgs e) { // 0.0f is silent, 1.0f is full volume MediaPlayer.Volume -= 0.1f; MediaPlayer.Play(song); }
Compile and run your code. Now, there will be three sounds. Your song will start immediately, and play
continuously. When the song ends, it will restart, but at a reduced volume. You will also hear the “boink” on
every collision, and you will hear the weird chanting music that starts at the first collision, and continues
thereafter (you might want to comment this out at some point). We used an “event handler” to catch and
respond to a MediaStateChanged event (in this case, when the song completes), which decreases the volume and
plays the song again.
This is probably a good time to add the means to control the volume. Add the following code to Update():
// Media Player Volume if (Keyboard.GetState().IsKeyDown(Keys.RightControl)) // Down { if (MediaPlayer.Volume <= 0.1f) MediaPlayer.Volume = 0.0f; else MediaPlayer.Volume -= 0.1f; } if (Keyboard.GetState().IsKeyDown(Keys.LeftControl)) // Up { if (MediaPlayer.Volume >= 0.9f) MediaPlayer.Volume = 1.0f; else MediaPlayer.Volume += 0.1f; } // SoundEffect Volume if (Keyboard.GetState().IsKeyDown(Keys.RightShift)) // Down { if (SoundEffect.MasterVolume <= 0.1f) SoundEffect.MasterVolume = 0.0f; else SoundEffect.MasterVolume -= 0.1f; } if (Keyboard.GetState().IsKeyDown(Keys.LeftShift)) // Up {
![Page 34: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/34.jpg)
34
if (SoundEffect.MasterVolume >= 0.9f) SoundEffect.MasterVolume = 1.0f; else SoundEffect.MasterVolume += 0.1f; }
Compile and run your code. The right and left control keys should now control the song volume, and the right
and left shift keys should control the “boink” and chanting volume. How cool is that?
Here is our code so far:
Game1.cs:
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Media; namespace Lab3_Mono { /// <summary> /// This is the main type for your game. /// </summary> public class Game1 : Game { GraphicsDeviceManager graphics; SpriteBatch spriteBatch; clsSprite mySprite1; clsSprite mySprite2; // Create some sound resources SoundEffect soundEffect1; SoundEffect soundEffect2; SoundEffectInstance seInstance; // Since we will loop the music, we only want to play it once bool playMusic = true; Song song; AudioListener listener; AudioEmitter emitter; public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; // changing the back buffer size changes the window size (in windowed mode) graphics.PreferredBackBufferWidth = 700; graphics.PreferredBackBufferHeight = 500; } void MediaPlayer_MediaStateChanged(object sender, System.EventArgs e) { // 0.0f is silent, 1.0f is full volume MediaPlayer.Volume -= 0.1f; MediaPlayer.Play(song); }
![Page 35: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/35.jpg)
35
/// <summary> /// Allows the game to perform any initialization it needs to before starting to run. /// This is where it can query for any required services and load any non-graphic /// related content. Calling base.Initialize will enumerate through any components /// and initialize them as well. /// </summary> protected override void Initialize() { // TODO: Add your initialization logic here base.Initialize(); } /// <summary> /// LoadContent will be called once per game and is the place to load /// all of your content. /// </summary> protected override void LoadContent() { // Create a new SpriteBatch, which can be used to draw textures. spriteBatch = new SpriteBatch(GraphicsDevice); // Load the SoundEffect resource soundEffect1 = Content.Load<SoundEffect>("chord"); soundEffect2 = Content.Load<SoundEffect>("music"); // Change "acdc" to the file prefix of your mp3 song. song = Content.Load<Song>("acdc"); MediaPlayer.Play(song); // play it // Uncomment the following line will also loop the song // MediaPlayer.IsRepeating = true; MediaPlayer.MediaStateChanged += MediaPlayer_MediaStateChanged; // Create a SoundEffect instance that can be manipulated later seInstance = soundEffect2.CreateInstance(); seInstance.IsLooped = true; listener = new AudioListener(); emitter = new AudioEmitter(); // Load a 2D texture sprite mySprite1 = new clsSprite(Content.Load<Texture2D>("ball"), new Vector2(0f, 0f), new Vector2(64f, 64f), graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight); mySprite2 = new clsSprite(Content.Load<Texture2D>("ball"), new Vector2(218f, 118f), new Vector2(64f, 64f), graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight); // Create a SpriteBatch to render the sprite spriteBatch = new SpriteBatch(graphics.GraphicsDevice); // set the speed the sprites will move mySprite1.velocity = new Vector2(5, 5); // mySprite2.velocity = new Vector2(3, -3); } /// <summary> /// UnloadContent will be called once per game and is the place to unload /// game-specific content. /// </summary> protected override void UnloadContent()
![Page 36: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/36.jpg)
36
{ // TODO: Unload any non ContentManager content here } /// <summary> /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// </summary> /// <param name="gameTime">Provides a snapshot of timing values.</param> protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit(); // Move the sprites mySprite1.Move(); // mySprite2.Move(); if (mySprite1.CircleCollides(mySprite2)) { mySprite1.velocity *= -1; GamePad.SetVibration(PlayerIndex.One, 1.0f, 1.0f); soundEffect1.Play(1.0f, 0.0f, 0.0f); if (playMusic) { seInstance.Play(); playMusic = false; } } else GamePad.SetVibration(PlayerIndex.One, 0f, 0f); // Change the sprite 2 position using the left thumbstick of the Xbox controller // Vector2 LeftThumb = GamePad.GetState(PlayerIndex.One).ThumbSticks.Left; // mySprite2.position += new Vector2(LeftThumb.X, -LeftThumb.Y) * 5; // Change the sprite 2 position using the keyboard KeyboardState keyboardState = Keyboard.GetState(); if (keyboardState.IsKeyDown(Keys.Up)) mySprite2.position += new Vector2(0, -5); if (keyboardState.IsKeyDown(Keys.Down)) mySprite2.position += new Vector2(0, 5); if (keyboardState.IsKeyDown(Keys.Left)) mySprite2.position += new Vector2(-5, 0); if (keyboardState.IsKeyDown(Keys.Right)) mySprite2.position += new Vector2(5, 0); // Make sprite 2 follow the mouse //if (mySprite2.position.X < Mouse.GetState().X) // mySprite2.position += new Vector2(5, 0); //if (mySprite2.position.X > Mouse.GetState().X) // mySprite2.position += new Vector2(-5, 0); //if (mySprite2.position.Y < Mouse.GetState().Y) // mySprite2.position += new Vector2(0, 5); //if (mySprite2.position.Y > Mouse.GetState().Y) // mySprite2.position += new Vector2(0, -5); // Media Player Volume
![Page 37: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/37.jpg)
37
if (Keyboard.GetState().IsKeyDown(Keys.RightControl)) // Down { if (MediaPlayer.Volume <= 0.1f) MediaPlayer.Volume = 0.0f; else MediaPlayer.Volume -= 0.1f; } if (Keyboard.GetState().IsKeyDown(Keys.LeftControl)) // Up { if (MediaPlayer.Volume >= 0.9f) MediaPlayer.Volume = 1.0f; else MediaPlayer.Volume += 0.1f; } // SoundEffect Volume if (Keyboard.GetState().IsKeyDown(Keys.RightShift)) // Down { if (SoundEffect.MasterVolume <= 0.1f) SoundEffect.MasterVolume = 0.0f; else SoundEffect.MasterVolume -= 0.1f; } if (Keyboard.GetState().IsKeyDown(Keys.LeftShift)) // Up { if (SoundEffect.MasterVolume >= 0.9f) SoundEffect.MasterVolume = 1.0f; else SoundEffect.MasterVolume += 0.1f; } base.Update(gameTime); } /// <summary> /// This is called when the game should draw itself. /// </summary> /// <param name="gameTime">Provides a snapshot of timing values.</param> protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); // Draw the sprite using Alpha Blend, which uses transparency information if available spriteBatch.Begin(); mySprite1.Draw(spriteBatch); mySprite2.Draw(spriteBatch); spriteBatch.End(); base.Draw(gameTime); } } }
![Page 38: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/38.jpg)
38
Positional Audio Playback Using the AudioListener and AudioEmitter Classes5
In this portion of the lab, we will learn how to create sound that appears to come from a certain position, and to
play the player’s “ear” at a particular position in the virtual space of the game. We will only work with one
sound effect for this example.
Modify Game1.cs as shown below (many of these modifications will just be commenting things out):
… in the class instance variable definitions
// Create some sound resources // SoundEffect soundEffect1; bool playMusic = true; bool playMusic = false;
AudioListener listener; AudioEmitter emitter;
… in LoadContent()
// Change "acdc" to the file prefix of your mp3 song. // song = Content.Load<Song>("acdc"); // MediaPlayer.Play(song); // play it // Uncomment the following line will also loop the song // MediaPlayer.IsRepeating = true;
// MediaPlayer.MediaStateChanged += MediaPlayer_MediaStateChanged; listener = new AudioListener(); emitter = new AudioEmitter(); // Play the positional sound
seInstance.Apply3D(listener, emitter); seInstance.Play();
… in Update()
// soundEffect1.Play(1.0f, 0.0f, 0.0f);
…
/* KeyboardState keyboardState = Keyboard.GetState(); if (keyboardState.IsKeyDown(Keys.Up)) mySprite2.position += new Vector2(0, -5); if (keyboardState.IsKeyDown(Keys.Down)) mySprite2.position += new Vector2(0, 5); if (keyboardState.IsKeyDown(Keys.Left)) mySprite2.position += new Vector2(-5, 0); if (keyboardState.IsKeyDown(Keys.Right)) mySprite2.position += new Vector2(5, 0);
*/
…
// Move the listening position
5 This example derives from a 2015 posting: https://www.gamefromscratch.com/post/2015/07/25/MonoGame-Tutorial-Audio.aspx.
![Page 39: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/39.jpg)
39
if (Keyboard.GetState().IsKeyDown(Keys.Left)) { listener.Position = new Vector3(listener.Position.X - 0.1f, listener.Position.Y, listener.Position.Z); seInstance.Apply3D(listener, emitter); } if (Keyboard.GetState().IsKeyDown(Keys.Right)) { listener.Position = new Vector3(listener.Position.X + 0.1f, listener.Position.Y, listener.Position.Z); seInstance.Apply3D(listener, emitter); } if (Keyboard.GetState().IsKeyDown(Keys.Up)) { listener.Position = new Vector3(listener.Position.X, listener.Position.Y + 0.1f, listener.Position.Z); seInstance.Apply3D(listener, emitter); } if (Keyboard.GetState().IsKeyDown(Keys.Down)) { listener.Position = new Vector3(listener.Position.X, listener.Position.Y - 0.1f, listener.Position.Z); seInstance.Apply3D(listener, emitter);
}
In this example, we loaded a single SoundEffect and started it looping infinitely. We then created an
AudioListener and AudioEmitter instance. The AudioListener represents the location of our ear within the
virtual world, while the AudioEmitter represents the position of the sound effect. The default location of both is
a Vector3 at (0,0,0). We set the position of a SoundEffect by calling Apply3D(). In the Update() call, if the
user presses an arrow key, we updated the Position of the AudioListener accordingly. After changing the
position of a sound, we have to call Apply3D again. As we press the arrow keys the audio pans and changes
volume to correspond with the updated position.
Compile and run your code. The apparent position of the sound (or, more precisely, our position relative to the
sound), changes when we press the arrow keys. How cool is that?
Creating Audio Content with XACT
The Microsoft Cross-Platform Audio Creations Tool (XACT) is a powerful graphical audio-authoring tool that
enables us to manage large numbers of source wave files and to control their playback in the game. Using the
XACT graphical interface, we can load existing wave files, group the files, and layer them with effects. We can
also use XACT to set up audio streaming6. MonoGame calls are used to trigger playback. The XACT tool
provides many features, we will only cover the basics of the graphical interface and the XACT features that
enable us to get started using the tool.
6 Streaming of XACT-generated content is, as of December 4, 2018, not yet implemented.
![Page 40: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/40.jpg)
40
Note: XACT only works for the Windows and Xbox platforms. You must use the SoundEffect and
MediaPlayer classes for any other platform. XACT is also old software (last maintained in 2012) that is
somewhat cumbersome and unwieldy to use. Because it is powerful and free, there is active work by
MonoGame developers to fully support XACT. All of these considerations might lead one to conclude that, at
least for the present, XACT is not worth the time to learn. It would hard to argue convincingly with that
viewpoint. I will introduce the use of XACT here, and I recommend that you spend enough time to understand
the basics. However, if you need to prioritize other work, feel free to do so.
We use XACT to create sound banks and wave banks, compiled into an XAP file, which the game can then use
through the content manager. In this section, we will learn the basics of how to create audio content with XACT
and use it in a program.
Follow these steps to create a new XACT project:
1. Start XACT by choosing Start ► All Programs ► Microsoft XNA Game Studio 4.0 ► Tools ►
Microsoft Cross- Platform Audio Creation Tool 3 (XACT3).
2. In the XACT main window, choose File ► New Project. The New Project Path dialog box displays.
Because we use the XACT project in our game’s content pipeline, it is often easy to use the content
folder of the game to store the XACT project file. Browse to the project’s content folder for your Lab3
project, and then enter “Lab3Sounds”) as the name for your XACT project (make sure it ends with the
.xap extension). Click the Save button.
3. On the left side of the window, Lab3Sounds now appears as a root node, with many types of child nodes
below it. The starting elements of an XACT project are wave files that must be loaded from source
recordings. In the following example, we will load a single wave file and then play back the sound using
MonoGame. First, we need a wave bank to load the source wave files into. A wave bank is a collection
of wave files that are grouped together. To add a new wave bank, click the Wave Banks menu and select
New Wave Bank. A new entry displays under Wave Banks in the XACT project tree with the default
name “Wave Bank”. We can change the default name by clicking the wave bank in the tree or by right-
clicking the wave bank and selecting Rename. A wave bank menu also displays in the right side of the
XACT workspace.
4. Next, we need to add a sound bank to the project. A sound bank is a collection of wave banks. To add a
new sound bank, click the Sound Banks menu and select New Sound Bank. Just as with the wave bank,
the new sound bank is added to the XACT project tree, but this time, the new item displays logically
under the Sound Banks tree node. The new sound bank has a default name of Sound Bank, but you can
change the name by clicking on the sound bank or by right-clicking the sound bank and selecting
Rename. The sound bank window is also added to the XACT workspace to the right.
5. The workspace might be getting cluttered now, with the wave bank and sound bank windows
overlapping. XACT provides a nice window-ordering option to organize our windows automatically.
![Page 41: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/41.jpg)
41
Click the Window menu to display the windowing options. Select Tile Horizontally from the drop-
down menu.
6. At this point, things should look like this:
7. We can now add source wave files to the project. Right-click the Wave Bank in the XACT project tree
and select Insert Wave File(s). In the open file dialog box, locate and select the sound file
“explosion.wav.”
8. Finally, we need to set up a cue. We use the cue to play back a sound or a group of sounds. The cue is
stored and accessed from the sound bank. The quickest way to add a cue is by dragging a wave from the
wave bank onto the cue section of the sound bank windows, which is in the lower left corner of the
Sound Bank window (with a heading of Cue Name).
9. After you drag the wave file and create the cue, notice there are a number of new items that display in
the sound bank window. This is because a new sound has been created. By default, it is created with the
same name as the wave. The sound is displayed in the top left of the sound bank window. After selecting
the sound entry, there is a track listing to the right of the window. Each sound can contain a number of
tracks. In this case, a single track is created that plays the wave from the wave bank. Below the sound
bank is the cue window to which you dragged the wave. A new cue is created using the same name as
the wave that was used to create it. The cue is used in the game code to control the playback of the
sounds, so a cue is required if you want to cause the sound to be played in your game. A cue is made up
![Page 42: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/42.jpg)
42
of multiple sounds from the sound bank entries in the window above it. Because the cue is created by
using the dragging shortcut, the sound of the same name is added to the cue.
10. Getting XACT to put its output where we want is something of a pain. XACT will create three files
when it builds:
Lab3Sounds.xgs
Wave Bank.xwb
Sound Bank.xsb
Somehow, we either have to tell XACT where to put these files (where MonoGame expects to find
them), or we have to manually copy the files from where XACT puts them by default to where
MonoGame wants to find them. (We could also force MonoGame to look elsewhere, but that makes our
code less portable and less readable). Let’s go with Option 1. For each of the three files, we must specify
the build path in XACT; we will start with Lab3Sounds.xgs.
a. Click on “Lab3Sounds” to highlight it in the main tree view. Now look below the main tree view
under the General Settings. The last setting is ‘Windows Global Setting,” as shown below:
b. Click on the three dots to the right of the Windows Global Setting entry and a dialog box will
appear:
c. Browse to <your project path>\Lab3_Mono\Lab3_Mono\bin\Windows\x86\Debug\Content, and click
on “Open.”
d. Now click on the single instance of “Wavebank” to highlight it in the main tree view. Now look
below the main tree view under the General Settings. The last setting is “Windows Build Path.”
e. As before, click on the three dots to the right of the Windows Build Path entry and a dialog box will
appear:
![Page 43: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/43.jpg)
43
f. Browse to <your project path>\Lab3_Mono\Lab3_Mono\bin\Windows\x86\Debug\Content, and click
on “Open.”
g. Finally, do the same steps for the single instance of “Soundbank.”
11. Select File ► Build in the XACT main menu. This causes XACT to build the project and create the
necessary files in our MonoGame project. Check to make sure that the three XACT files were created in
<your project path>\Lab3_Mono\Lab3_Mono\bin\Windows\x86\Debug\Content. (All of the xnb files
created by the Content Pipeline will also be in this directory.
12. Finally, let’s save our work. Select File ► Save Project in the XACT main menu. Don’t forget this step
or you will get weird error messages later.
Now we are ready to insert code into our game project to play this sound. To keep things simple (at least as
simple as we can make them using XACT), we will replace the sound that is made when the two sprites collide.
First, we need to add the XACT project that we just created to the content project of our game. Just like adding
other types of content, it is easy to add the XACT project. Right-click the content project and select Add, and
then click Existing Item. Then, browse to and select the Lab3Sounds.xap XACT project file in the dialog box
and click the Add button. Building the solution will create three files: Lab3Sounds.xgs is the audio engine file
and is named after the XACT project name. The Wave Bank.xwb file contains the data from the wave bank of
the same name. The Sound Bank.xsb file contains the data from the sound bank in your XACT project.
In Game1.cs, add the following code:
AudioEngine audioEngine; SoundBank soundBank; WaveBank waveBank; Cue cue;
The AudioEngine is used to perform necessary updates to the underlying audio system. The
AudioEngine.Update method is required to be called once per frame (we will do this below) to ensure the audio
playback stays up to date and is not stalled. The other three variables for the SoundBank, WaveBank, and Cue
are the code counterparts to the ones created in the XACT project. Here is a brief description of these objects.
AudioEngine: This object is the program reference to the audio services in the computer, and is used
mainly to adjust a few general settings and as a parameter to create the wave and sound banks. When
creating an AudioEngine object in your program, you need to use the name of the global settings file for
the XACT content as a parameter. This settings file name is generated when the XAP file is compiled,
and by default has the same name as the XAP file, with an .xgs extension.
WaveBank: This is a collection of wave files. To create this bank in your code, you need to pass as
parameters the AudioEngine object (which must have been previously created) and the compiled wave
bank file, which is generated when you compile your project with the default name Wave Bank.xwb.
![Page 44: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/44.jpg)
44
Although the wave bank is not explicitly used in your program, you need to create this object, because
the sound cues in the sound bank depend on the wave files in this bank.
Sound Bank: This is a collection of sound cues. You can define cues as references to the wave files
stored in the wave bank, along with properties that establish details of how to play these wave files, and
the methods that let you manage their playback. The code fragment below shows how to extend our
game to include code to create and initialize the audio components, and to start, stop, pause, and resume
this sound. The Cue class provides the methods and properties that we need to do this.
To load the files that were built by processing the XACT project through the content pipeline, add the following
lines of code to your game’s LoadContent method:
// Load files built from XACT project audioEngine = new AudioEngine("Content/Lab3Sounds.xgs"); waveBank = new WaveBank(audioEngine, "Content/Wave Bank.xwb"); soundBank = new SoundBank(audioEngine, "Content/Sound Bank.xsb");
In the Update method, insert code to play the cue when the sprites collide:
if (mySprite1.CircleCollides(mySprite2)) { mySprite1.velocity *= -1; GamePad.SetVibration(PlayerIndex.One, 1.0f, 1.0f); //soundEffect1.Play(1.0f, 0.0f, 0.0f); // Get an instance of the cue from the XACT project cue = soundBank.GetCue("Explosion"); cue.Play(); } else GamePad.SetVibration(PlayerIndex.One, 0f, 0f); // Update the audio engine audioEngine.Update(); base.Update(gameTime); }
After playing the sound, we call the AudioEngine.Update method to ensure it is called each frame.
A Cue has similar playback methods as SoundEffectInstance, such as Play, Pause, Resume, and Stop. There is
also an Apply3D method, which takes an AudioListener and AudioEmitter.
After Play is called on a Cue, it can’t be called again. We can call Pause and then Resume, but calling Play
again will cause an InvalidOperationException. To play the sound more than once, we have to create a new Cue
instance each time by calling the SoundBank.GetCue method. Each Cue returned from GetCue is its own
instance. The code above always creates a new cue instance before we play it.
If we don’t care about fine sound control, we can use
SoundBank.PlayCue(“Explosion”);
instead of
![Page 45: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/45.jpg)
45
cue = soundBank.GetCue(“Explosion”); cue.Play();
to just play the cue, similar to how fire and forget sound effects work.
Compile and run the program. You should hear explosions when the sprites collide. Now to test your skills:
change the code to make the chord.wav sound play when the moving sprite bounces off any of the four walls.
This will require modification of the Move method in clsSprite to return a bool indicating that a wall has been
hit. Then you can just wrap the call to Move with an if statement, and play the sound if the Move call returns
true. Try to figure this out for yourself, perhaps working with a colleague.
Streaming Audio: Playing Background Music7 (SKIP this section)
Some sound files are large, especially the sound files that are used to play background music. By default, the
wave files used in the XACT project are loaded into memory at runtime. This can cause problems if the wave
file is large, because the file might not fit into the memory of small devices, or it might take up more space than
what we would like. We solve this problem by streaming the desired audio, i.e., loading what we need when we
need it. Streaming is set at the wave bank level for all of the wave files contained within the wave bank.
Let’s add some streaming music to our project. Open the XACT project that you created previously. Add a new
wave bank and call it Music. Select the new wave bank in the XACT project tree. The properties of the wave
bank display in the lower left window. In the Type section, select the “Streaming” button. It should now be
highlighted a green color.
To add the music source files to the wave bank, right-click the new music wave bank and select Insert Wave
File(s). Browse to and select a music file in the dialog window and click the Open button. (You can use the
provided Music.wav file, or use your own, but you should remember that XACT is religious about Digital
Rights Management - it may not let you incorporate protected material.) Now that the source wave file is added
to our streaming wave bank, we need a cue to use for playback.
Like we did before, drag the music wave into the sound bank’s cue window to create the cue for our music
wave.
Finally, we need to change the sound that was created for the music cue to mark it as background music. Setting
a sound category to Music allows users on the Xbox to play their own music library over our game’s music.
This allows the user to hear the sound effects in our game, but to override the music to what they are playing
using the media playback provided on the Xbox. How cool is that?
To change the sound category, select the music sound entry that we just created in the sound bank (select the
music entry in the upper left quadrant of the Sound Bank, under “Sound Name”). In the gray “General” window
7 As of December 4, 2018, MonoGame does not support streaming of XACT-generated content, but it appears that there is active work on this issue. Stay tuned.
![Page 46: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/46.jpg)
46
in the lower left corner of the XACT window, select the Category drop-down and select Music. Next, click the
Infinite checkbox under the Looping section to have the sound continually play until it is stopped.
Click on the Music Wave Bank in the XACT tree view to bring up the Paths window and change the build path
to <your project path>\Lab3_Mono\Lab3_Mono\bin\Windows\x86\Debug\Content, as before. Just above where
you changed the build path, click on “Streaming,” which should then turn green.
Build and Save the XACT project, so we can add the music to our project.
When you are done, things should look like this:
Now to modify the code. We need two additional member variables to add streaming music to the previous
example. Add the following two instance variables in Game1.cs:
WaveBank streamingWaveBank; Cue musicCue;
As before, we need to load the wave bank. After the other audio loading in the game’s LoadContent method,
add the following lines of code:
// Load streaming wave bank streamingWaveBank = new WaveBank(audioEngine, "Content/Music.xwb", 0, 4); // The audio engine must be updated before the streaming cue is ready
![Page 47: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/47.jpg)
47
audioEngine.Update(); // Get cue for streaming music musicCue = soundBank.GetCue("Music"); // Start the background music musicCue.Play();
Note that we are using an overload of the WaveBank constructor that takes an additional two arguments. The first
is the offset into the wave file indicating where to start playing. In most cases, we will want to start at the
beginning, indicated by a value of 0. The last parameter is the packetsize, which determines the size of the
streaming buffer used internally to store the file as it is loaded. It is in units of DVD sectors, which are 2,048
bytes each. The minimum value must be 2. The higher the number, the larger the memory buffer, which allows
for smoother playback. You can test different values to determine the minimum value that smoothly streams the
music in your game. Four is a good default number for this value.
After creating a new streaming wave bank, you must call the AudioEngine.Update method to play the cue that
contains the streamed data from the wave bank. The last two steps are to get the Cue from the SoundBank and
to call the Play method to start the background music.
Compile and run your code. When the MonoGame developers finish implementing this functionality, your
game will have streaming background music that can be overridden by the player.
Here is the final code that demonstrates everything we have talked about in this lab. Make sure you understand
every line of this code. If you have trouble, ask questions in class or lab.
Game1.cs
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Media; namespace Lab3_Mono { /// <summary> /// This is the main type for your game. /// </summary> public class Game1 : Game { GraphicsDeviceManager graphics; SpriteBatch spriteBatch; clsSprite mySprite1; clsSprite mySprite2; // Create some sound resources // SoundEffect soundEffect1; // SoundEffect soundEffect2; // SoundEffectInstance seInstance; // Since we will loop the music, we only want to play it once // bool playMusic = false; // Song song;
![Page 48: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/48.jpg)
48
// AudioListener listener; // AudioEmitter emitter; AudioEngine audioEngine; SoundBank soundBank; WaveBank waveBank; Cue cue; WaveBank streamingWaveBank; Cue musicCue; public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; // changing the back buffer size changes the window size (in windowed mode) graphics.PreferredBackBufferWidth = 700; graphics.PreferredBackBufferHeight = 500; } /*void MediaPlayer_MediaStateChanged(object sender, System.EventArgs e) { // 0.0f is silent, 1.0f is full volume MediaPlayer.Volume -= 0.1f; MediaPlayer.Play(song); }*/ /// <summary> /// Allows the game to perform any initialization it needs to before starting to run. /// This is where it can query for any required services and load any non-graphic /// related content. Calling base.Initialize will enumerate through any components /// and initialize them as well. /// </summary> protected override void Initialize() { // TODO: Add your initialization logic here base.Initialize(); } /// <summary> /// LoadContent will be called once per game and is the place to load /// all of your content. /// </summary> protected override void LoadContent() { // Create a new SpriteBatch, which can be used to draw textures. spriteBatch = new SpriteBatch(GraphicsDevice); // Load the SoundEffect resource //soundEffect1 = Content.Load<SoundEffect>("chord"); //soundEffect2 = Content.Load<SoundEffect>("music"); // Load files built from XACT project audioEngine = new AudioEngine("Content/Lab3Sounds.xgs"); waveBank = new WaveBank(audioEngine, "Content/Wave Bank.xwb"); soundBank = new SoundBank(audioEngine, "Content/Sound Bank.xsb"); // Load streaming wave bank
![Page 49: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/49.jpg)
49
streamingWaveBank = new WaveBank(audioEngine, "Content/Music.xwb", 0, 4); // The audio engine must be updated before the streaming cue is ready audioEngine.Update(); // Get cue for streaming music musicCue = soundBank.GetCue("Music"); // Start the background music musicCue.Play(); // Change "acdc" to the file prefix of your mp3 song. // song = Content.Load<Song>("acdc"); // MediaPlayer.Play(song); // play it // Uncomment the following line will also loop the song // MediaPlayer.IsRepeating = true; // MediaPlayer.MediaStateChanged += MediaPlayer_MediaStateChanged; // Create a SoundEffect instance that can be manipulated later //seInstance = soundEffect2.CreateInstance(); //seInstance.IsLooped = true; //listener = new AudioListener(); //emitter = new AudioEmitter(); // Play the positional sound //seInstance.Apply3D(listener, emitter); //seInstance.Play(); // Load a 2D texture sprite mySprite1 = new clsSprite(Content.Load<Texture2D>("ball"), new Vector2(0f, 0f), new Vector2(64f, 64f), graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight); mySprite2 = new clsSprite(Content.Load<Texture2D>("ball"), new Vector2(218f, 118f), new Vector2(64f, 64f), graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight); // Create a SpriteBatch to render the sprite spriteBatch = new SpriteBatch(graphics.GraphicsDevice); // set the speed the sprites will move mySprite1.velocity = new Vector2(5, 5); // mySprite2.velocity = new Vector2(3, -3); } /// <summary> /// UnloadContent will be called once per game and is the place to unload /// game-specific content. /// </summary> protected override void UnloadContent() { // TODO: Unload any non ContentManager content here } /// <summary> /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// </summary> /// <param name="gameTime">Provides a snapshot of timing values.</param> protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
![Page 50: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/50.jpg)
50
Exit(); // Move the sprites mySprite1.Move(); // mySprite2.Move(); if (mySprite1.CircleCollides(mySprite2)) { mySprite1.velocity *= -1; GamePad.SetVibration(PlayerIndex.One, 1.0f, 1.0f); //soundEffect1.Play(1.0f, 0.0f, 0.0f); /*if (playMusic) { seInstance.Play(); playMusic = false; }*/ // Get an instance of the cue from the XACT project cue = soundBank.GetCue("Explosion"); cue.Play(); } else GamePad.SetVibration(PlayerIndex.One, 0f, 0f); // Change the sprite 2 position using the left thumbstick of the Xbox controller // Vector2 LeftThumb = GamePad.GetState(PlayerIndex.One).ThumbSticks.Left; // mySprite2.position += new Vector2(LeftThumb.X, -LeftThumb.Y) * 5; // Change the sprite 2 position using the keyboard KeyboardState keyboardState = Keyboard.GetState(); if (keyboardState.IsKeyDown(Keys.Up)) mySprite2.position += new Vector2(0, -5); if (keyboardState.IsKeyDown(Keys.Down)) mySprite2.position += new Vector2(0, 5); if (keyboardState.IsKeyDown(Keys.Left)) mySprite2.position += new Vector2(-5, 0); if (keyboardState.IsKeyDown(Keys.Right)) mySprite2.position += new Vector2(5, 0); // Make sprite 2 follow the mouse //if (mySprite2.position.X < Mouse.GetState().X) // mySprite2.position += new Vector2(5, 0); //if (mySprite2.position.X > Mouse.GetState().X) // mySprite2.position += new Vector2(-5, 0); //if (mySprite2.position.Y < Mouse.GetState().Y) // mySprite2.position += new Vector2(0, 5); //if (mySprite2.position.Y > Mouse.GetState().Y) // mySprite2.position += new Vector2(0, -5); // Media Player Volume /*if (Keyboard.GetState().IsKeyDown(Keys.RightControl)) // Down { if (MediaPlayer.Volume <= 0.1f) MediaPlayer.Volume = 0.0f; else MediaPlayer.Volume -= 0.1f; }
![Page 51: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/51.jpg)
51
if (Keyboard.GetState().IsKeyDown(Keys.LeftControl)) // Up { if (MediaPlayer.Volume >= 0.9f) MediaPlayer.Volume = 1.0f; else MediaPlayer.Volume += 0.1f; } // SoundEffect Volume if (Keyboard.GetState().IsKeyDown(Keys.RightShift)) // Down { if (SoundEffect.MasterVolume <= 0.1f) SoundEffect.MasterVolume = 0.0f; else SoundEffect.MasterVolume -= 0.1f; } if (Keyboard.GetState().IsKeyDown(Keys.LeftShift)) // Up { if (SoundEffect.MasterVolume >= 0.9f) SoundEffect.MasterVolume = 1.0f; else SoundEffect.MasterVolume += 0.1f; } // Move the listening position if (Keyboard.GetState().IsKeyDown(Keys.Left)) { listener.Position = new Vector3(listener.Position.X - 0.1f, listener.Position.Y, listener.Position.Z); seInstance.Apply3D(listener, emitter); } if (Keyboard.GetState().IsKeyDown(Keys.Right)) { listener.Position = new Vector3(listener.Position.X + 0.1f, listener.Position.Y, listener.Position.Z); seInstance.Apply3D(listener, emitter); } if (Keyboard.GetState().IsKeyDown(Keys.Up)) { listener.Position = new Vector3(listener.Position.X, listener.Position.Y + 0.1f, listener.Position.Z); seInstance.Apply3D(listener, emitter); } if (Keyboard.GetState().IsKeyDown(Keys.Down)) { listener.Position = new Vector3(listener.Position.X, listener.Position.Y - 0.1f, listener.Position.Z); seInstance.Apply3D(listener, emitter); }*/
![Page 52: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/52.jpg)
52
// Update the audio engine audioEngine.Update(); base.Update(gameTime); } /// <summary> /// This is called when the game should draw itself. /// </summary> /// <param name="gameTime">Provides a snapshot of timing values.</param> protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); // Draw the sprite using Alpha Blend, which uses transparency information if available spriteBatch.Begin(); mySprite1.Draw(spriteBatch); mySprite2.Draw(spriteBatch); spriteBatch.End(); base.Draw(gameTime); } } }
clsSprite.cs
using Microsoft.Xna.Framework.Graphics; // for Texture2D using Microsoft.Xna.Framework; // for Vector2 namespace Lab3_Mono { class clsSprite { public Texture2D texture { get; set; } // sprite texture, read-only property public Vector2 position { get; set; } // sprite position on screen public Vector2 size { get; set; } // sprite size in pixels public Vector2 velocity { get; set; } // sprite velocity private Vector2 screenSize { get; set; } // screen size public Vector2 center { get { return position + (size / 2); } } // sprite center public float radius { get { return size.X / 2; } } // sprite radius public clsSprite(Texture2D newTexture, Vector2 newPosition, Vector2 newSize, int ScreenWidth, int ScreenHeight) { texture = newTexture; position = newPosition; size = newSize; screenSize = new Vector2(ScreenWidth, ScreenHeight); } public void Move() { // if we´ll move out of the screen, invert velocity // checking right boundary if (position.X + size.X + velocity.X > screenSize.X)
![Page 53: IWKS 3400 Lab 31 JK Bennett - University of Colorado Denver · image with a composition of its original colors and the color tone.3 Now let’s make the necessary modifications to](https://reader036.vdocuments.site/reader036/viewer/2022070708/5eba4b9b04ad29098d67bc8a/html5/thumbnails/53.jpg)
53
velocity = new Vector2(-velocity.X, velocity.Y); // checking bottom boundary if (position.Y + size.Y + velocity.Y > screenSize.Y) velocity = new Vector2(velocity.X, -velocity.Y); // checking left boundary if (position.X + velocity.X < 0) velocity = new Vector2(-velocity.X, velocity.Y); // checking top boundary if (position.Y + velocity.Y < 0) velocity = new Vector2(velocity.X, -velocity.Y); // since we adjusted the velocity, just add it to the current position position += velocity; } public bool Collides(clsSprite otherSprite) { // check if two sprites intersect return (this.position.X + this.size.X > otherSprite.position.X && this.position.X < otherSprite.position.X + otherSprite.size.X && this.position.Y + this.size.Y > otherSprite.position.Y && this.position.Y < otherSprite.position.Y + otherSprite.size.Y); } public bool CircleCollides(clsSprite otherSprite) { // Check if two circle sprites collided return (Vector2.Distance(this.center, otherSprite.center) < this.radius + otherSprite.radius); } public void Draw(SpriteBatch spriteBatch) { spriteBatch.Draw(texture, position, Color.White); } } }
Save a copy of your work this far, e.g., by saving a copy of the project, and creating a zip file of the
project contents named something like <YourName>-Lab3-Part4.zip.