Skip to content

Improve Game Scenarios #29

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jan 16, 2022
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- Upgraded to Bevy 0.6 in the back end
- `Text` rotation and scale now works! 🎉
- TODO: bevy_prototype_debug_lines hasn't had a release, and the `main` branch sorta works, but the lines now appear _under_ sprites instead of over them, which is not ideal. We _must_ have a release upstream and would _love_ a fix upstream. If there isn't an upstream release, we'll need to find another line-drawing solution before release.
- Updated (or finished) all of the game scenario descriptions.

## [3.0.0] - 2021-12-30

Expand Down
4 changes: 4 additions & 0 deletions examples/collision.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
//! To run this code, clone the rusty_engine repository and run the command:
//!
//! cargo run --release --example collision

use rusty_engine::prelude::*;

const ROTATION_SPEED: f32 = 3.0;
Expand Down
4 changes: 4 additions & 0 deletions examples/game_state.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
//! To run this code, clone the rusty_engine repository and run the command:
//!
//! cargo run --release --example game_state

use std::f32::consts::TAU;

use rusty_engine::prelude::*;
Expand Down
4 changes: 4 additions & 0 deletions examples/keyboard_events.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
//! To run this code, clone the rusty_engine repository and run the command:
//!
//! cargo run --release --example keyboard_events

use rusty_engine::prelude::*;

fn main() {
Expand Down
4 changes: 4 additions & 0 deletions examples/keyboard_state.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
//! To run this code, clone the rusty_engine repository and run the command:
//!
//! cargo run --release --example keyboard_state

use std::f32::consts::PI;

use rusty_engine::prelude::*;
Expand Down
4 changes: 4 additions & 0 deletions examples/layer.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
//! To run this code, clone the rusty_engine repository and run the command:
//!
//! cargo run --release --example layer

use rusty_engine::prelude::*;

fn main() {
Expand Down
4 changes: 4 additions & 0 deletions examples/level_creator.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
//! To run this code, clone the rusty_engine repository and run the command:
//!
//! cargo run --release --example level_creator

use rusty_engine::prelude::*;

struct GameState {
Expand Down
4 changes: 4 additions & 0 deletions examples/mouse_events.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
//! To run this code, clone the rusty_engine repository and run the command:
//!
//! cargo run --release --example mouse_events

use rusty_engine::prelude::*;

const ORIGIN_LOCATION: (f32, f32) = (0.0, -200.0);
Expand Down
4 changes: 4 additions & 0 deletions examples/mouse_state.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
//! To run this code, clone the rusty_engine repository and run the command:
//!
//! cargo run --release --example mouse_state

use rusty_engine::prelude::*;

const ORIGIN_LOCATION: (f32, f32) = (0.0, -200.0);
Expand Down
4 changes: 4 additions & 0 deletions examples/music.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
//! To run this code, clone the rusty_engine repository and run the command:
//!
//! cargo run --release --example music

//! This is an example of playing a music preset. For playing your own music file, please see the
//! `sound` example.

Expand Down
4 changes: 4 additions & 0 deletions examples/music_sampler.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
//! To run this code, clone the rusty_engine repository and run the command:
//!
//! cargo run --release --example music_sampler

use rusty_engine::prelude::*;

struct GameState {
Expand Down
75 changes: 33 additions & 42 deletions examples/scenarios/car_shoot.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,34 @@
//! To run this code, clone the rusty_engine repository and run the command:
//!
//! cargo run --release --example car_shoot

use rand::prelude::*;
use rusty_engine::prelude::*;
use SpritePreset::*; // The SpritePreset enum was imported from rusty_engine::prelude

#[derive(Default)]
struct GameState {
marbles_left: Vec<String>,
cars_left: Vec<i32>,
cars_left: i32,
spawn_timer: Timer,
}

fn main() {
let mut game = Game::new();

// Set the title of the window to be Car Shooter
game.window_settings(WindowDescriptor {
title: "Car Shoot".into(),
..Default::default()
});

// Create the player
let player = game.add_sprite("player", RacingBarrierRed);
player.rotation = UP;
player.scale = 0.5;
player.translation.y = -325.0;
player.layer = 10.0;

// Set the Window Settings
game.window_settings(WindowDescriptor {
title: "Car Shooter".into(),
..Default::default()
});

// Start the music
game.audio_manager.play_music(MusicPreset::Classy8Bit, 0.1);

Expand All @@ -33,14 +37,10 @@ fn main() {
// Marbles left. We'll use these strings as labels for sprites. If they are present in the
// vector, then they are available to be shot out of the marble gun. If they are not present,
// then they are currently in play.
for i in 0..3 {
game_state.marbles_left.push(format!("marble{}", i));
}
game_state.marbles_left = vec!["marble1".into(), "marble2".into(), "marble3".into()];

// Cars left in level - each integer represents a car that will be spawned
for i in 0..25 {
game_state.cars_left.push(i);
}
game_state.cars_left = 25;
let cars_left = game.add_text("cars left", "Cars left: 25");
cars_left.translation = Vec2::new(540.0, -320.0);

Expand All @@ -53,24 +53,16 @@ const CAR_SPEED: f32 = 300.0;

fn game_logic(engine_state: &mut EngineState, game_state: &mut GameState) -> bool {
// Handle marble gun movement
for event in engine_state.mouse_location_events.drain(..) {
let player = engine_state.sprites.get_mut("player").unwrap();
player.translation.x = event.position.x;
let player = engine_state.sprites.get_mut("player").unwrap();
if let Some(location) = engine_state.mouse_state.location() {
player.translation.x = location.x;
}
let player_x = player.translation.x;

// Shoot marbles!
for event in engine_state.mouse_button_events.clone() {
if !matches!(event.state, ElementState::Pressed) {
continue;
}
if engine_state.mouse_state.just_pressed(MouseButton::Left) {
// Create the marble
if let Some(label) = game_state.marbles_left.pop() {
let player_x = engine_state
.sprites
.get_mut("player")
.unwrap()
.translation
.x;
let marble = engine_state.add_sprite(label, RollingBallBlue);
marble.translation.y = -275.0;
marble.translation.x = player_x;
Expand All @@ -89,9 +81,18 @@ fn game_logic(engine_state: &mut EngineState, game_state: &mut GameState) -> boo
marble.translation.y += MARBLE_SPEED * engine_state.delta_f32;
}

// Move cars across the screen
for car in engine_state
.sprites
.values_mut()
.filter(|car| car.label.starts_with("car"))
{
car.translation.x += CAR_SPEED * engine_state.delta_f32;
}

// Clean up sprites that have gone off the screen
let mut labels_to_delete = vec![];
for sprite in engine_state.sprites.values_mut() {
for sprite in engine_state.sprites.values() {
if sprite.translation.y > 400.0 || sprite.translation.x > 750.0 {
labels_to_delete.push(sprite.label.clone());
}
Expand All @@ -103,15 +104,6 @@ fn game_logic(engine_state: &mut EngineState, game_state: &mut GameState) -> boo
}
}

// Move cars across the screen
for car in engine_state
.sprites
.values_mut()
.filter(|car| car.label.starts_with("car"))
{
car.translation.x += CAR_SPEED * engine_state.delta_f32;
}

// Spawn cars
if game_state
.spawn_timer
Expand All @@ -121,10 +113,11 @@ fn game_logic(engine_state: &mut EngineState, game_state: &mut GameState) -> boo
// Reset the timer to a new value
game_state.spawn_timer = Timer::from_seconds(thread_rng().gen_range(0.1..1.25), false);
// Get the next car
if let Some(i) = game_state.cars_left.pop() {
let cars_left = engine_state.texts.get_mut("cars left").unwrap();
cars_left.value = format!("Cars left: {}", i);
let label = format!("car{}", i);
if game_state.cars_left > 0 {
game_state.cars_left -= 1;
let label = format!("car{}", game_state.cars_left);
let cars_left_text = engine_state.texts.get_mut("cars left").unwrap();
cars_left_text.value = format!("Cars left: {}", game_state.cars_left);
let car_choices = vec![
RacingCarBlack,
RacingCarBlue,
Expand Down Expand Up @@ -152,8 +145,6 @@ fn game_logic(engine_state: &mut EngineState, game_state: &mut GameState) -> boo
continue;
}
if !event.pair.one_starts_with("marble") {
// it's two cars spawning on top of each other, take one out
engine_state.sprites.remove(&event.pair.0);
continue;
}
engine_state.sprites.remove(&event.pair.0);
Expand Down
4 changes: 4 additions & 0 deletions examples/scenarios/extreme_drivers_ed.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
//! To run this code, clone the rusty_engine repository and run the command:
//!
//! cargo run --release --example extreme_drivers_ed

use rusty_engine::prelude::*;

struct GameState {
Expand Down
4 changes: 4 additions & 0 deletions examples/scenarios/road_race.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
//! To run this code, clone the rusty_engine repository and run the command:
//!
//! cargo run --release --example road_race

use rand::prelude::*;
use rusty_engine::prelude::*;
use SpritePreset::*; // The SpritePreset enum was imported from rusty_engine::prelude
Expand Down
4 changes: 4 additions & 0 deletions examples/sfx.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
//! To run this code, clone the rusty_engine repository and run the command:
//!
//! cargo run --release --example sfx

//! This is an example of playing a sound effect preset. For playing your own sound effect file,
//! please see the `sound` example.

Expand Down
4 changes: 4 additions & 0 deletions examples/sfx_sampler.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
//! To run this code, clone the rusty_engine repository and run the command:
//!
//! cargo run --release --example sfx_sampler

use rusty_engine::prelude::*;

#[derive(Default)]
Expand Down
4 changes: 4 additions & 0 deletions examples/sound.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
//! To run this code, clone the rusty_engine repository and run the command:
//!
//! cargo run --release --example sound

//! This is an example of playing sound by path. For playing music or sound effect presets, please
//! see the `music` or `sfx` examples.

Expand Down
5 changes: 4 additions & 1 deletion examples/sprite.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
//
//! To run this code, clone the rusty_engine repository and run the command:
//!
//! cargo run --release --example sprite

use rusty_engine::prelude::*;

fn main() {
Expand Down
4 changes: 4 additions & 0 deletions examples/text.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
//! To run this code, clone the rusty_engine repository and run the command:
//!
//! cargo run --release --example text

use rusty_engine::prelude::*;

struct GameState {
Expand Down
4 changes: 4 additions & 0 deletions examples/transform.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
//! To run this code, clone the rusty_engine repository and run the command:
//!
//! cargo run --release --example transform

use rusty_engine::prelude::*;

fn main() {
Expand Down
4 changes: 4 additions & 0 deletions examples/window.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
//! To run this code, clone the rusty_engine repository and run the command:
//!
//! cargo run --release --example window

use rusty_engine::prelude::*;

fn main() {
Expand Down
5 changes: 3 additions & 2 deletions scenarios/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,13 @@ Legend:
| Easy | You will be told each step, and each section includes the code that you should have ended up with, and there is a complete reference project. |
| Medium | You will be told each step, but won't be shown all the code. There might be a reference project. |
| Hard | You will be told what to accomplish, and maybe be given a couple pointers. There is probably no reference project. |
| Insane | You'll need to implement some game engine features yourself |

## Scenarios

- (Easy) [Road Race](https://github.com/CleanCut/rusty_engine/tree/main/scenarios/road_race.md)
- (Medium) [Car Shoot](https://github.com/CleanCut/rusty_engine/tree/main/scenarios/car_shoot.md)
- (Medium) [Driver's Ed](https://github.com/CleanCut/rusty_engine/tree/main/scenarios/car_shoot.md)
- (Medium) [Driver's Ed](https://github.com/CleanCut/rusty_engine/tree/main/scenarios/extreme_drivers_ed.md)
- (Hard) [Cannon Practice](https://github.com/CleanCut/rusty_engine/tree/main/scenarios/cannon_practice.md)
- (Hard) [Space Invaders](https://github.com/CleanCut/rusty_engine/tree/main/scenarios/space_invaders.md)
- (Hard) [Labrinth](https://github.com/CleanCut/rusty_engine/tree/main/scenarios/labrinth.md)
- (Insane) [Labrinth](https://github.com/CleanCut/rusty_engine/tree/main/scenarios/labrinth.md) - Rusty Engine doesn't yet provide all the features needed to implement this scenario.
58 changes: 54 additions & 4 deletions scenarios/cannon_practice.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,66 @@ In this scenario you will create a cannon that sits on the bottom left side of t

## Common Setup

1. Follow the instructions in the [Common Setup](https://github.com/CleanCut/rusty_engine/tree/main/scenarios#common-setup) section of the scenarios readme to set up the skeleton of the project.
1. Follow the instructions in the [Common Setup](https://github.com/CleanCut/rusty_engine/tree/main/scenarios#common-setup-do-this-first) section of the scenarios readme to set up the skeleton of the project.

## Game State & Constants

1. Define a game state struct with fields for:
- The firing magnitude of the cannon (an `f32`)
- The rotation of the cannon (an `f32`)
- The current velocity of the cannon ball (a `Vec2`)
1. Define a constant for acceleration due to gravity. The unit will be pixels per second per second.
1. Decide on a sprite to use as a cannon ball
1. Decide on a sprite to use as the cannon
1. Decide on a sprite to use for the goal that you are trying to hit
1. Decide on a sprite (or sprites) to use for obstacles that you should avoid hitting

## Game Initialization

In your `// setup goes here` section of `main()`...

1.
1. Create the initial game state struct with good starting values
1. Create and place the cannon, obstacles, and goal sprites. You may use the `level_creator` example to do this, if you wish.
- Place the cannon on the lower left side of the screen
- Use a field from the game state to set the rotation of the cannon
- Place a single obstacle in lower middle of the screen (so you have to fire over it)
- Place the goal on the lower right side of the screen
1. Create the text for displaying the firing magnitude of the cannon, place it in the top left corner of the screen.
1. If you want music, start it now.

## Gameplay Logic

In your `game_logic(...)` function...
In your [`game_logic(...)` function](https://cleancut.github.io/rusty_engine/25-game-logic-function.html)...

1. Decide which keyboard/mouse input will control the rotation of the cannon, and implement rotating the cannon.
- Constrain the min/max angle of rotation to angles in the first quadrant (from straight up to straight right) with [the `.clamp` method](https://doc.rust-lang.org/std/primitive.f32.html#method.clamp) and the [`UP` and `RIGHT` constants](https://docs.rs/rusty_engine/latest/rusty_engine/#constants).
1. Decide which keyboard/mouse input will fire the cannon. Implement it so that pressing (whatever you chose) creates a cannon ball sprite, but only if one does not exist. Place it at the same coordinates as the cannon, but at a layer lower than the cannon so the cannon obscures it until it is out from underneath it.
- Play a sound when the cannon is fired.
1. Set the initial velocity `Vec2` for the cannon ball. This is fairly straightforward math:
```rust
let initial_cannonball_velocity = Vec2::new(
game_state.firing_magnitude * cannon.rotation.cos(),
game_state.firing_magnitude * cannon.rotation.sin(),
);
```

1. Move the cannon ball sprite by the amount stored in the game state's velocity field multiplied by `engine_state.delta_f32` each frame. At this point, you should be able to run the game, rotate the cannon, fire the cannon, and see the cannon move across the screen in a straight line.
1. Implement the gravity logic. Each frame, subtract (_gravity constant_ * `engine_state.delta_f32`) from the "Y" value of the cannon ball's velocity.
1. Decide which keyboard/mouse input will change the firing magnitude of the cannon, and implement it.
- Constrain the firing magnitude between `0.0` and some semi-reasonable value.
- Every time the firing magnitude changes, change the value of the `Text` that is displaying it in the top left corner of the screen. Don't change the value of the `Text` if the firing magnitude didn't change.
1. Detect collisions between the cannon ball and obstacles. The cannon ball should be destroyed if it hits an obstacle.
1. For collisions to be detected between two sprites, the `.collision` field of _both_ sprites must be set to true. Set this field on the cannon ball, the obstacles, and the goal.
1. Play a sound when the cannon ball hits an obstacle
1. Detect collisions between the cannon ball and the goal. The game is won if the cannon ball hits the goal.
1. Play a sound when the cannon ball hits the goal.


## Challenge

1.
- Introduce constant wind that varies between shots
- Make the obstactle move, rotate, or scale dynamically to make it so you have to time your shot correctly as well
- Replace the `Text` displaying the magnitude of your starting velocity with a visual slider (literally slide a barrier from the edge of the screen to some pre-defined point)
- Make destructible obstactles that reduce the cannon ball's velocity by half in the X direction
- Allow the cannon to move a small distance in the +/- X direction
- Add scorekeeping and alter the layout of the obstacles each time the cannon hits the goal
Loading