iwks 3400 lab 31 jk bennett - university of colorado denver · image with a composition of its...

53
1 IWKS 3400 Lab 3 1 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. Its probably a good idea to read through the entire lab before starting. This write-up is long, but dont 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ãos 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. Its 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 players speed, mountains far awayfrom 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.

Upload: others

Post on 12-May-2020

1 views

Category:

Documents


0 download

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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.