Update - 10/03/2019
space_shooter_rs is alive and well!
Update - 09/02/2019
Big News!
A few months after continued development of this game I was contacted by someone representing the Amethyst Engine regarding making this game an official showcase game for Amethyst. I gladly accepted! This means that this game will now be relocated to Amethyst's GitHub page. I still have just as much control over the future of the game, the only difference is that it is now officially part of the Amethyst organization. I hope to have more people from the Amethyst community contribute to it.
Changes
- Collisions now happen between the player and enemies allowing for an alternate way to destroy enemies
- Barrel rolls grant blast immunity
- Hauler ally added, grants defense when reaching the bottom of the arena
- Health bar added, depleted when spaceship takes damage
- Defense bar added, depleted when enemies reach bottom of the arena
- Roll bar added to indicate readiness of barrel roll
- Health and defense wrenches added and sometimes drop from enemies when destroyed
- 1x and 5x currency rocks added and sometimes drop from enemies when destroyed
- Sound effects added
- Phase system added
Original Post - 06/03/2019
Amethyst
From its website, Amethyst is described as a "data-driven game engine written in Rust". The engine uses an ECS (entity, component, system) for game logic and architecture. I am still relatively new to Amethyst, but I understand from my experience that ECS is used in Amethyst as a way to architect a program by separating code into components, entities, and systems so that the program can be abstractly parallelized. In other words, through using ECS and Amethyst, your game can automatically take advantage of multiple CPU cores. There are other game engine options for Rust, but Amethyst appears to be the most popular and ambitious of the options. To see other options for game development in Rust I recommend checking out arewegameyet.com.
ECS
ECS is core to Amethyst. There is no getting around using ECS when developing a game with Amethyst. At first. this was difficult for me. I previously had been more used to game engines like Pygame and LÖVE. However, after a couple weeks of using the engine, I found that ECS makes the process of game programming not only more efficient, but also in many ways easier. For example, ECS makes it much easier to quickly understand other people's code. It is very easy to determine at any given point whether the code you are looking at is a system, component or entity and subsequently figure out exactly what that the code is doing.
Components and Entities
Components are the building blocks of entities and entities are the building blocks of the game.
An example of an entity is the player-controlled spaceship in my space shooter game.
When defining an entity you often
use .with()
to indicate that you are creating the entity "with" that component. Below is
the example of me doing this in my space shooter game. In the code, you can see that I'm defining
an entity representing my spaceship player to be put in the game. The entity contains a
SpriteRender
component, a custom Spaceship
component that I defined, a Transform
component, and a Transparent
component.
let mut local_transform = Transform::default();
local_transform.set_xyz(GAME_WIDTH / 2.0, GAME_HEIGHT / 6.0, 0.9);
let sprite_render = SpriteRender {
sprite_sheet: sprite_sheet_handle.clone(),
sprite_number: 0,
};
world
.create_entity()
.with(sprite_render)
.with(Spaceship {
width: SPACESHIP_WIDTH,
height: SPACESHIP_HEIGHT,
hitbox_width: SPACESHIP_HITBOX_WIDTH,
hitbox_height: SPACESHIP_HITBOX_HEIGHT,
max_speed: SPACESHIP_MAX_SPEED,
current_velocity_x: 0.0,
current_velocity_y: 0.0,
acceleration_x: SPACESHIP_ACCELERATION_X,
deceleration_x: SPACESHIP_DECELERATION_X,
acceleration_y: SPACESHIP_ACCELERATION_Y,
deceleration_y: SPACESHIP_DECELERATION_Y,
fire_speed: SPACESHIP_STARTING_FIRE_SPEED,
fire_reset_timer: 0.0,
damage: SPACESHIP_STARTING_DAMAGE,
barrel_cooldown: SPACESHIP_BARREL_COOLDOWN,
barrel_reset_timer: 0.0,
barrel_speed: SPACESHIP_BARREL_SPEED,
barrel_action_right: false,
barrel_action_left: false,
barrel_duration: SPACESHIP_BARREL_DURATION,
barrel_action_timer: SPACESHIP_BARREL_DURATION,
barrel_damage: 0.0,
pos_x: local_transform.translation().x,
pos_y: local_transform.translation().y,
})
.with(local_transform)
.with(Transparent)
.build();
The above example includes a component that is custom, the Spaceship
, and components that
are included with Amethyst such as SpriteRender
, Transform
, and Transparent
. Below is the
code where I define my custom Spaceship
component. It contains a lot of different data that
is used in different systems to control the game and it implements Component
. It is also
implemented with storage which I'll get into in the Systems section.
pub struct Spaceship {
pub width: f32,
pub height: f32,
pub hitbox_width: f32,
pub hitbox_height: f32,
pub current_velocity_x: f32,
pub current_velocity_y: f32,
pub max_speed: f32,
pub acceleration_x: f32,
pub deceleration_x: f32,
pub acceleration_y: f32,
pub deceleration_y: f32,
pub fire_speed: f32,
pub fire_reset_timer: f32,
pub damage: f32,
pub barrel_cooldown: f32,
pub barrel_reset_timer: f32,
pub barrel_speed: f32,
pub barrel_action_left: bool,
pub barrel_action_right: bool,
pub barrel_duration: f32,
pub barrel_action_timer: f32,
pub barrel_damage: f32,
pub pos_x: f32,
pub pos_y: f32,
}
impl Component for Spaceship {
type Storage = DenseVecStorage<Self>;
}
Systems
Systems are where the logic of the game happens.
Above, where the Spaceship
component is defined, it is implemented with
type Storage = DenseVecStorage<Self>;
. Creating a system consists mainly of writing and
reading to storages associated with entities and components. This is done by creating a type out of all of the SystemData
needed for the system. Then, as needed, data can be iterated through, read,
and modified to create game logic. In the example below, you can see that I also get user input in this system.
pub struct SpaceshipSystem;
impl<'s> System<'s> for SpaceshipSystem {
type SystemData = (
Entities<'s>,
WriteStorage<'s, Transform>,
WriteStorage<'s, Spaceship>,
WriteStorage<'s, Enemy>,
Read<'s, InputHandler<String, String>>,
Read<'s, Time>,
ReadExpect<'s, SpriteResource>,
ReadExpect<'s, LazyUpdate>,
);
fn run(&mut self, (entities, mut transforms, mut spaceships, mut enemies, input, time, sprite_resource, lazy_update): Self::SystemData) {
let mut shoot = input.action_is_down("shoot").unwrap();
let mut barrel_left = input.action_is_down("barrel_left").unwrap();
let mut barrel_right= input.action_is_down("barrel_right").unwrap();
for (spaceship, transform) in (&mut spaceships, &mut transforms).join() {
spaceship.pos_x = transform.translation().x;
spaceship.pos_y= transform.translation().y;
//firing cooldown
if spaceship.fire_reset_timer > 0.0 {
spaceship.fire_reset_timer -= time.delta_seconds();
shoot = false;
}
//barrel roll input cooldown
if spaceship.barrel_reset_timer > 0.0 && !spaceship.barrel_action_left && !spaceship.barrel_action_right {
spaceship.barrel_reset_timer -= time.delta_seconds();
barrel_left = false;
barrel_right = false;
}
//barrel roll action cooldown
if spaceship.barrel_action_left || spaceship.barrel_action_right {
//if currently barrel rolling can't initiate another barrel roll
barrel_left = false;
barrel_right = false;
//countdown to end of barrel roll if time left else set velocity to the appropriate max speed, stop the action, and reset cooldown
if spaceship.barrel_action_timer > 0.0 {
spaceship.barrel_action_timer -= time.delta_seconds();
}else {
if spaceship.barrel_action_left {
spaceship.current_velocity_x = -1.0 * spaceship.max_speed;
}
if spaceship.barrel_action_right {
spaceship.current_velocity_x = spaceship.max_speed;
}
spaceship.barrel_action_left = false;
spaceship.barrel_action_right = false;
spaceship.barrel_reset_timer = spaceship.barrel_cooldown;
for enemy in (&mut enemies).join() {
enemy.barrel_damaged = false;
}
}
}
if shoot && !spaceship.barrel_action_left && !spaceship.barrel_action_right {
let fire_position = Vector3::new(
transform.translation()[0], transform.translation()[1] + spaceship.height / 2.0, 0.1,
);
fire_blast(&entities, &sprite_resource, 3, fire_position, spaceship.damage, spaceship.current_velocity_x, spaceship.current_velocity_y, &lazy_update);
spaceship.fire_reset_timer = spaceship.fire_speed;
}
if barrel_left {
spaceship.barrel_action_left = true;
spaceship.barrel_action_timer = spaceship.barrel_duration;
}
if barrel_right {
spaceship.barrel_action_right = true;
spaceship.barrel_action_timer = spaceship.barrel_duration;
}
}
}
}
Just like when defining entities, systems are added to a dispatcher by using
.with()
as well. Below is the code for the dispatcher used in the main game state.
impl Default for SpaceShooter {
fn default() -> Self {
SpaceShooter {
dispatcher: DispatcherBuilder::new()
.with(systems::SpaceshipSystem, "spaceship_system", &[])
.with(systems::BlastSystem, "blast_system", &[])
.with(systems::EnemySystem, "enemy_system", &[])
.with(systems::SpawnerSystem, "spawner_system", &[])
.with(systems::PlayerHitSystem, "player_hit_system", &[])
.with(systems::ExplosionSystem, "explosion_system", &[])
.with(systems::ItemSystem, "item_system", &[])
.with(systems::BarrelRollSystem, "barrel_roll_system", &[])
.with(systems::SpaceshipMovementSystem, "spaceship_movement_system", &[])
.with(systems::ItemSpawnSystem, "item_spawn_system", &[])
.build(),
}
}
}
Space Shooter Game
This game began as a way for me to learn Rust and Amethyst and has turned into a project that I plan on continuing. You can view the readme file on the Github page. On it, I have defined mechanics, visual, audio, and gameplay objectives that I plan to implement into the game.
Overall I plan on making this into a game inspired like games such as Raiden and The Binding of Isaac. Raiden is an arcade game where an aircraft is controlled to shoot down enemies and score points. The Binding of Isaac is a game with randomly generated levels, with rooms, enemies, items, and bosses. I have already implemented a pool that items can be added to for upgrading the player's ship and I have implemented a pool that enemies can be pulled from to be randomly spawned.
Below is gameplay of my space shooter game at its current state.
Comments (0)
Page 1 of 0
You need to be logged in to comment