Skip to main content Link Search Menu Expand Document (external link)

Theme: auto

Asteroids and Shooting

Now we’ll add asteroids moving around and the ability to shoot lasers at them.

Asteroid Basics

Create a new actor subclass called Asteroid. In the constructor, create the following components:

  • A SpriteComponent using the "Assets/Asteroid.png" texture
  • A MoveComponent with a forward speed that you like
  • A WrapComponent

We want to create asteroids with random positions and orientations so that they are different each time. To help with this, we have provided a basic random library in Random.h/cpp. To initialize this library, at the start of Game::Initialize, add a call to Random::Init().

All the Random functions are static, so you don’t need to create an instance of Random to use it.

Then, back in the Asteroid constructor, initialize the rotation and position to random values:

  • Set the rotation to a Random::GetFloatRange between 0.0f and Math::TwoPi.
  • Set the position to a Random::GetVector. This takes in a min and max vector. In this case case, you want min/max to correspond to the coordinates for the top left corner and bottom right corners of the window, respectively.

Now in Game::LoadData, create 10 asteroids (make a loop for this).

You should now have 10 asteroids flying around the screen, looking roughly like this:

Laser

Make a new subclass of Actor called Laser. It needs:

  • A SpriteComponent using "Assets/Laser.png"
  • A MoveComponent with a forward speed of something you like (should be faster than the ship by a decent amount)
  • A WrapComponent

Creating Actors from Another Actor

We want to add code to Ship::HandleInput that says when you press the space bar, create a Laser with the same position/rotation as the ship.

There’s a problem here, though. In Game::ProcessInput we are looping over the actor vector. If, while we are looping over it, we call CreateActor we are going to add another actor to the vector. However, this could crash because it’s not safe to add to a vector while looping over it. The same thing could happen if we create an actor in HandleUpdate.

To fix this, add a second std::vector<Actor*> to game for the “pending create” actors. These are actors that have been newly created.

Then, change CreateActor so it adds the new actor to “pending create” actors instead of just the regular actor vector.

Then, in Game::UpdateGame, right before you loop over the actor vector, loop over “pending create” actors and add all of them to the regular actor vector. Then clear pending actors. This effectively will “move” all the actors from the pending actors to the regular actors.

Let’s Make Lasers

Now in Ship::HandleInput create a Laser actor when the spacebar is pressed. Set the position of the laser to the ship’s position, and the rotation of the laser to the ship’s rotation.

Now if you hold down the space bar, you should be able to create a ridiculous number of lasers:

Laser Limiting

We want to make it so that you can only shoot one laser per second. To do this:

  • Add a float variable to Ship to track the “cooldown” of the laser, and initialize it to 0.0f
  • When you’re deciding whether to fire the laser, only fire the laser if the the cooldown is <= 0.0f. If you do fire a laser, set the cooldown to 1.0f
  • In Ship, add an override of HandleUpdate and reduce the cooldown by delta time
  • Thus, when you fire a laser, you won’t be able to fire another one for a second

You should only be able to fire one laser per second:

Adding Support for Destroying Actors

We don’t want every actor to live forever. For example, we want a laser to get destroyed after either it hits an asteroid or 1.5 seconds elapse.

Like with creating actors, we have to be careful not mess with the actor vector while looping over it.

First, in Game:

  • Add a std::vector<Actor*> to Game for actors that are “pending destroy”
  • Add a public function called AddPendingDestroy that takes in an Actor* and adds it to the “pending destroy” vector

Then, in Actor:

  • Add a bool for if the actor “is active.” Initialized it to true and add a getter/setter for this:
    • The getter should be named IsActive
    • The setter should be named SetIsActive
  • Change both Update and Input so they do nothing if “is active” is false
  • Add a public Destroy function which:
    • Sets “is active” to false
    • Calls AddPendingDestroy function you added to Game to add this actor to the “pending destroy” vector

Back in Game:

  • Add a private function called DestroyActor which takes in an Actor* and:
    • Removes the actor from the regular actors vector (you can use std::erase)
    • Calls delete on the pointer
  • Then, at the end of UpdateGame, loop over the “pending destroy” vector, calling DestroyActor on each actor in it. Then clear the “pending destroy vector”

Ok that seems a bit convoluted, so let’s take a step back to understand:

  1. When you want to destroy an actor in your logic, simply call Destroy() on it. That’s it.
  2. How’s it work?
    1. When you call Destroy, it sets “is active” to false, which makes sure it won’t update/receive input anymore
    2. It also adds it to “pending destroy” actors in game
    3. Then when update game reaches the end, the “pending destroy” actors are removed from the regular actor vector and deleted

Laser Lifetime

Now make it so that lasers die after being alive for 1.5 seconds.

You’ll need to add a member variable to track the lifetime and an override of HandleUpdate. And when it’s time to destroy itself, call Destroy.

Now test it out! You should get lasers disappearing now.

Lasers Colliding with Asteroids

Now we’ll make it so that lasers can destroy asteroids. We haven’t talked about more complex collision yet, so we’ll keep it simple for now.

First, we need a way to track all the asteroids in the game world. So, add a private std::vector<class Asteroid*> to Game, and corresponding AddAsteroid/RemoveAsteroid functions. Then, make the Asteroid constructor and destructor call the appropriate functions.

You also need to make a GetAsteroids() function that returns the asteroid vector by const reference, like:

const std::vector<class Asteroid*>& GetAsteroids() const;

It’s very important that we use the reference here, as it avoids making a very expensive copy.

Then in Laser::HandleUpdate, you’ll need to loop over the asteroid vector. Use Vector2::Distance to calculate the distance between the position of each asteroid and the position of the laser. If this distance is <= 50, then call Destroy on both the itself and the asteroid, and break out of the loop.

You should now be able to shoot at and destroy asteroids, and it’ll play something like the reference version.

Once you’ve pushed your code, you should review the grading specifications to confirm you’ve satisfied them.