Week3 - Input and Movement
Week3.png

Start Project: Week3Start.zip

Finished Project: Week3Finish.zip

Notes

  • I mentioned this last week as well. I send out a reminder email each week on Tuesday for the workshop on Wednesday. If you would like to be on the email list, send me an email (jared(dot)robertson(at)aggiemail(dot)usu(dot)edu) and let me know you want to be on the SIGXNA email list.
  • Although not necessary, I would recommend going through the week 1 and week 2 descriptions if you haven't or weren't able to attend the workshops.
  • We started with Start Project above. The project contains a complete game except that there is no user input, no collision detection, and the main ship doesn't have any movement methods implemented yet. That's what we'll be doing this week.
  • Also, the game has background music and sound effects in it. However, we didn't cover how to do it and probably won't have time to cover it this semester but you can find some good audio tutorials here.

The first thing to do is get the keyboard input.
At the top of Game1.cs there is a comment that says "// Previous keyboard state is used to prevent lots of repeating". Right below that put the following two lines:

KeyboardState keyboardState = Keyboard.GetState();
KeyboardState previousKeyboardState = Keyboard.GetState();

This creates two keyboard objects. Next thing we need to do is make sure keyboardState and previousKeyboardState give us the right values. At the top of Game1.Update() where it says "// Get the previous keyboard state and update the current one" add the following lines:
previousKeyboardState = keyboardState;
keyboardState = Keyboard.GetState();

Here we update previousKeyboardState to be keyboardState and then keyboardState to be the current keyboard state.

Now that we have a working keyboard, we'll get hyperspace working because it's easy. In the middle of Game1.cs there is a comment that says // If the player presses enter, teleport somewhere. Right below that add this:

if (keyboardState.IsKeyDown(Keys.Enter))
{
    spaceship.Hyperspace();
    soundHyperspaceActivation.Play();
}

Try running the code and pressing Enter. Do you notice a problem with it? We want the spaceship to teleport just once, but it's actually teleporting every update that Enter is pressed! Instead of calling spaceship.Hyperspace() every time Enter is pressed, we only want to do it every time it is pressed for the first time. This is where previousKeyboardState comes in. If we are looking for when a key is first pressed, we know that in previousKeyboardState is couldn't have been pressed. Let's change the line that says
if (keyboardState.IsKeyDown(Keys.Enter))

to
if (keyboardState.IsKeyDown(Keys.Enter) && previousKeyboardState.IsKeyUp(Keys.Enter))

Now every time you press Enter it will teleport you only once.

The next thing we should do is have the spaceship fire its gun. The logic is exactly the same as the hyperspace and goes right above it where it says "// If the player presses space, fire the gun":

if(keyboardState.IsKeyDown(Keys.Space) && previousKeyboardState.IsKeyUp(Keys.Space))
{
    spaceship.Fire();
    soundLaser.Play();
}

Now you can shoot, but you can't move around, so let's get that taken care of next.

Right above where the shoot code is, there's a comment that says "// If the player presses left/right, turn that way":

if (keyboardState.IsKeyDown(Keys.Left))
{
    spaceship.TurnLeft();
}
if (keyboardState.IsKeyDown(Keys.Right))
{
    spaceship.TurnRight();
}

This simply tells spaceship to turn in the direction is should. We have the input, but if we go to GameObject.TurnLeft() we'll see that it isn't implemented yet. That's what we'll do now.
In GameObject there are many properties. The two we want to pay attention to are RotationalSpeed and Rotation. RotationalSpeed was initialized in the constructor to 0.1f and represents the speed at which the spaceship rotates. Rotation is the current rotation when the spaceship is drawn (you can see that in the GameObject.Draw() method). We want Rotation to change by RotationalSpeed every time the method is called. So add this line to GameObject.TurnLeft():
Rotation -= RotationalSpeed;

And this to GameObject.TurnRight():
Rotation += RotationalSpeed;

Now the ship is able to rotate, fire, and hyperspace.

The last thing we will want to do with user input and movement is get the ship to give thrust. This is more complicated but still doable. Start by adding the user input to Game1.Update() right below this comment"// If the player presses up, give it thrust":

if (keyboardState.IsKeyDown(Keys.Up))
{
    spaceship.Thrust();
    UpdateEngineSound(true); // Make the engine sound turn on
}
else
{
    UpdateEngineSound(false); // Make the engine sound turn off
}

Next go to GameObject.Thrust(). In this method there are two comments. The first one says to calculate the acceleration. Instead of just giving our ship some velocity, we want it to work as if it had engines and was free-floating in space. Every time we give it thrust, then, it accelerates in the direction it's facing. The acceleration from the thrust changes the velocity which in turn changes the position. We'll get to all this in a minute, but first we'll find the acceleration. We're going to use some trigonometry now, but don't be intimidated - I'll try to explain everything. Add these lines of code right below that comment:
Acceleration.X = (float)Math.Sin(Rotation);
Acceleration.Y = -(float)Math.Cos(Rotation);
Acceleration *= Speed;

If you remember, Rotation is a float that represents the angle of the spaceship. We need to find out the X and Y components of the rotation to find out which way to accelerate. In the math classes we took in school, the cosine of the angle is the Y component and the sine of the angle is the X component and have a combined magnitude of 1. This only works when a 0 angle is facing to the right. In our example, the ship is facing up, so we need to shift it accordingly. So if you were wondering why the Sin and Cos were "switched", that's why. You could switch the Sin and Cos back and remove the "-" sign from Acceleration.Y and it would fly around perfectly fine to the right.
Anyway, the next thing we do is multiply Acceleration by Speed, which is a poorly chosen word that really means acceleration magnitude, or how much it accelerates each update.
We have the acceleration and still need to calculate velocity and position. These ones are much easier, however. Right below where you calculated the acceleration, put in this line of code to calculate Velocity:
Velocity += Acceleration;

That was easy. Calculating Position is just as easy, but goes in GameObject.Update() instead. Right at the top of that method it says "// Calculate Position based on Velocity":
Position += Velocity;

That's it! We should be able to fly around now. Go ahead and give it a try.

There's still one problem with the thrust, giving it too much will cause it to get going very, very fast. That's the next thing we need fix. Back in GameObject.Thrust() below the comment that says "// Prevent from going faster than MaxSpeed", add the following lines:

if (Velocity.Length() > MaxSpeed)
{
    Velocity.Normalize();
    Velocity *= MaxSpeed;
}

If we remember from physics, velocity is both a direction and a magnitude. We can find the magnitude of Velocity by using a method called Length. If this magnitude is too much - greater than our predefined MaxSpeed, we need to slow it down. To do this, we normalize the velocity vector, which gives is a magnitude/length 1, and then multiply it by MaxSpeed to have it go as fast as possible.

That's everything for input and movement. The last thing we will cover is collision detection.
Back in Game1.cs there is a method called CollisionDetection that is blank. We need to check each laser with each enemy on the screen to see if they are close enough to collide. Add the following to Game1.CollisionDetection()

foreach (Laser laser in spaceship.Lasers)
{
    foreach (Probe probe in probes)
    {
    }
}

If you aren't familiar with C# or Java, we are using two foreach loops, which are similar to a for loop except that we don't have to worry about going out of bounds or anything. Next, add the following lines inside of the two foreach loops:
// Get the distance between the laser and probe
float distance = (laser.Position - probe.Position).Length();
 
// Check for collision
if (distance < laser.Radius + probe.Radius)
{
    laser.TTL = 0;
    probe.Die();
    points += 3;
}

First thing we do is get the distance between the laser and enemy (probe). Their positions are vectors so we take their difference and find the length. Next, if this distance is less than the size their combined radii, we know there is a collision. In the case of a collision, we set the laser's TTL (Time to live) to 0, killing it, tell the probe to die, and increment the player's points by 3.

That's it! Next week we'll be adding explosion effects to the probes when they are destroyed.

back to SIGXNA

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License