Cars are floating past. Shoot them down!
You are at a carnival booth. Cars float across the back of the booth. The player uses their marble gun to shoot down as many of the cars as possible before the time runs out.
This scenario can be extended to 2 players.
car-shoot-demo.mp4
- Follow the instructions in the Common Setup section of the scenarios readme to set up the skeleton of the project.
- Define a
GameState
struct with the following fields:marble_labels
- a vector of strings. These will be labels for our marble sprites.cars_left
- an integer tracking how many cars are left to spawnspawn_timer
- a timer indicating when it's time to spawn another car
- Create an instance of your
GameState
struct togame.run()
with the following values:marble_labels
should be this vector of strings:vec!["marble1".into(), "marble2".into(), "marble3".into()]
- we'll pop these off to use as labels for marble sprites, and then push them back on the vector when the marble sprites get destroyed. That way, we can only have three marbles in-flight at any time.cars_left
to a reasonable number such as25
. This will be the number of cars which drive by.spawn_timer
should be a non-repeatingTimer
set to0.0
seconds so that it goes off immediately upon startup.
- Pass your game state variable to
game.run()
In your // game setup goes here
section of main
...
- (Optional) Set the window title to be
Car Shoot
- (Optional) Play some music. We recommend the music preset
MusicPreset::Classy8Bit
at a volume of0.1
. - Create a player sprite. The player will be represented by a rectangle that represents the barrel of the marble gun. We'll use
SpritePreset::RacingBarrierRed
- We'll pretend the player is standing off the bottom of the screen, and only the barrel of their gun is visible. Let's place the sprite accordingly--set the sprite's:
rotation
toUP
so it is pointing towards the top of the screenscale
to0.5
so it's about the right sizetranslation.y
to-325.0
so it goes off the bottom of the screen a littlelayer
to10.0
so it will be on top of the marble which will be at a lower layer
- Create a
Text
with the label"cars left"
that displays how many cars are left to spawn, with:- the
value
offormat!("Cars left: {}", game_state.cars_left);
- the
translation
ofVec2::new(540.0, -320.0);
to put it in the bottom right corner of the screen.
- the
In your game_logic(...)
function...
- Have the "gun barrel" follow the mouse on the X axis by set the
translation.x
of the player sprite to thex
value of the mouse location.- Get a mutable reference to the player sprite
- Get the mouse location via the mouse state's
location
method - Make a variable
player_x
and set it to the player's currenttranslation.x
so we can use it later on:let player_x = player.translation.x;
- If the left mouse button was just pressed, then:
- If there is a label string left in the
game_state.marble_labels
vector, then:- Using the label value, create a new marble sprite using
SpritePreset::RollingBallBlue
- Make sure the label has been removed from the
game-state.marble_labels
vector. This way, we can only have as many marbles on thes screen as there are labels to remove from the vector. (We'll add the label back to the vector when we've finished with the marble). - Set the marble sprite's:
translation.x
to the player's x location that we put in ourplayer_x
variable.translation.y
to-275.0
, which will put the marble under the end of the gun.layer
to5.0
, which will put it underneath the gun spritecollision
totrue
so that we can detect collisions between the marble and the cars.
- Play a sound effect to indicate the firing of the marble. We suggest the sound effect preset
SfxPreset::Impact2
at a volume of0.4
.
- Using the label value, create a new marble sprite using
- If there is a label string left in the
- Move the marbles upwards (in the positive Y direction)
- Define a
MARBLE_SPEED
constant (probably out in the module level) for how fast your marble should move and set it to thef32
value of600.0
. - Loop through all the marble sprites (the sprites whose labels start with
"marble"
), for each of them:- increment the marble sprite's
translation.y
byMARBLE_SPEED * engine.delta_f32
- increment the marble sprite's
- Define a
- Clean up sprites that have moved off the top or the right side of the screen.
- We can't modify a hash map of sprites while we're looping through its values, so let's create an empty vector of strings and fill it with labels of sprites that we want to delete. Once we're done examining the hash map, we can loop through the vector of labels and remove those hash map entries.
- Create a new vector
labels_to_delete
- For every sprite value in the hash map:
- check to see if either the
translation.y > 400.0
or thetranslation.x > 750.0
. If either of those conditions are true, push a clone of the label onto thelabels_to_delete
vector.
- check to see if either the
- For every label in
labels_to_delete
:- Remove the sprite entry. The hash map's
remove
method takes an immutable reference to the key type, so if you are looping through the label strings by value, you may need to add a&
in front of your label variable:engine.sprites.remove(&label)
- If the label starts with
marble
, then push the label onto thegame_state.marble_labels
vector. That way we'll be able to shoot another marble.
- Remove the sprite entry. The hash map's
- Spawn a car if the
game_state.spawn_timer
just finished! So tick the spawn timer and check to see if it just finished -- if it did, then:- Set
game_state.spawn_timer
to a newTimer
with a random value between0.1
and1.25
- Add the
rand
crate as a dependency in yourCargo.toml
- Add
use rand::prelude::*;
to the top of yourmain.rs
file - Use
thread_rng().gen_range(0.1..1.25)
to obtain a randomf32
value between0.1
and1.25
- Create a non-repeating
Timer
and assign it as the value togame_state.spawn_timer
- Add the
- If there are any cars left (check the value of
game_state.cars_left
), then:- Decrement
game_state.cars_left
by one - Retrieve a mutable reference to the
Text
we labeled"cars left"
- Set the
value
toformat!("Cars left: {}", game_state.cars_left)
- Set the
- Create a label for the current car that starts with
car
:format!("car{}", game_state.cars_left)
(remember, a label starting withcar
is what the movement code is looking for). - Create a vector of
SpritePreset
s of cars to randomly select from:let car_choices = vec![SpritePreset::RacingCarBlack, SpritePreset::RacingCarBlue, SpritePreset::RacingCarGreen, SpritePreset::RacingCarRed, SpritePreset::RacingCarYellow];
- Make a random sprite preset choice:
car_choices.iter().choose(&mut thread_rng()).unwrap().clone()
- Actually create the sprite with the label and sprite preset selected above. Set the sprite's:
translation.x
to-740.0
translation.y
to a random value from-100.0
to325.0
--thread_rng().gen_range(-100.0..325.0)
collision
totrue
so that the car will collide with marbles
- Decrement
- Set
- Move cars right across the screen (in the positive X direction). The logic for this section is very similar to the previous section that moved marbles.
- Define a
CAR_SPEED
constant and set it to250.0
- Loop through all the car sprites (the sprites whose labels start with
"car"
), for each of them:- increment the car sprite's
translation.x
byCAR_SPEED * engine.delta_f32
- increment the car sprite's
- Define a
- Now it's time to handle the collisions! For each
CollisionEvent
inengine.collision_events
:- We only care about the start of collisions, not the ending of them, so if
event.state.is_end()
, thencontinue
the loop. - Similarly, if one of the event pair's labels doesn't start with
"marble"
, then it's either two marbles or two cars colliding with each other, which we don't care about. So if!event.pair.one_starts_with("marble")
, thencontinue
the loop. - At this point we know that one of the pair is a marble and the other is a car, and they both need to be removed. So using the labels in the
event.pair
tuple, delete both sprites. - Now that a marble has been "destroyed", we are allowed to shoot it from the gun again, so grab whichever label of the
event.pair
tuple that starts with"marble"
and push a clone of it back onto thegame_state.marble_labels
vector. - Play a sound effect for successfully hitting a car with a marble. Use
SfxPreset::Confirmation1
with a volume of0.2
- We only care about the start of collisions, not the ending of them, so if
You made it to the end of the main scenario! You should have a playable game prototype by this point.
- Keep track of points, display the points in a corner of the screen
- Make it so that after the game ends, you can press a key and start a new game
- Keep track of the high score across games and display it when the game ends
- Don't allow cars to spawn on top of other cars
- Powerups! Powerups float across like cars and activate when hit.
- Spread-fire
- Rapid-fire
- Explosion - clear the screen
- Make the movement of the cars more interesting - have them drive in curvy motions
- Smart black cars - black cars sometimes slow down or speed up so a shot will miss them
- Armored cars - Green cars take two marbles to take down
- Add support for a second player, with separate scores for each player. You'll need to figure out some way for the second player to control their marble gun...