Patrick O'Dacre
Posted on January 5, 2023
In this guide we'll go through the following steps:
- Create && Fire Drone Lasers
- Show Entity hitboxes
Create && Fire Drone Lasers
We'll create a fixed number of lasers for the drones to use. Much like with our own laser, a drone laser won't fire unless it is available -- health == 0
.
We need to implement two separate timers -- one master timer, and another for each individual drone. These two timers help us control the number of drone lasers firing at the player.
We'll add a ready
field to the Entity
struct to control the laser fire-rate of individual drones, and the drone_laser_cooldown
field to the Game
struct as our master timer.
NUM_OF_DRONE_LASERS :: 5
Game :: struct
{
/// Other stuff ^^^
/// new
drone_laser_tex: ^SDL.Texture,
drone_lasers: [NUM_OF_DRONE_LASERS]Entity,
drone_laser_cooldown : f64,
}
Entity :: struct
{
source: SDL.Rect,
dest: SDL.Rect,
dx: f64,
dy: f64,
health: int,
ready: f64,
}
create_entities :: proc()
{
/// create drones ^^
// drone lasers
game.drone_laser_tex = SDL_Image.LoadTexture(game.renderer, "assets/drone_laser_1.png")
assert(game.drone_laser_tex != nil, SDL.GetErrorString())
drone_laser_w : i32
drone_laser_h : i32
SDL.QueryTexture(game.drone_laser_tex, nil, nil, &drone_laser_w, &drone_laser_h)
for _, idx in 1..=NUM_OF_DRONE_LASERS
{
game.drone_lasers[idx] = Entity{
dest = SDL.Rect{
x = -100,
y = -100,
w = drone_laser_w / 8,
h = drone_laser_h / 6,
},
dx = DRONE_LASER_SPEED,
dy = DRONE_LASER_SPEED,
health = 0,
}
}
///
}
The drone lasers aren't shaped quite like I want, so I skew the image by changing the w
and h
a bit to get it as close to round as I can.
Fire Lasers
We'll allow drones to fire when within certain boundaries and the cooldown timer has expired:
if drone.dest.x > 30 &&
drone.dest.x < (WINDOW_WIDTH - 30) &&
drone.ready <= 0 &&
game.drone_laser_cooldown < 0
{
// fire
}
We check this each time we render a drone.
If the drone's individual timer has expired, ie: drone.ready < 0
then we'll look for the first available drone laser to "fire":
// find a drone laser:
fire_drone_laser : for laser, idx in &game.drone_lasers
{
// find the first one available
if laser.health == 0
{
// fire
}
}
Drones need to shoot towards our player, so we'll have to calculate a dx
and dy
value that will lead the laser from the position of the drone to the current position of the player:
// fire from the drone's position
laser.dest.x = drone.dest.x
laser.dest.y = drone.dest.y
laser.health = 1
new_dx, new_dy := calc_slope(
laser.dest.x,
laser.dest.y,
game.player.dest.x,
game.player.dest.y,
)
if !game.is_paused
{
laser.dx = new_dx * get_delta_motion(drone.dx + 150)
laser.dy = new_dy * get_delta_motion(drone.dx + 150)
}
// reset the cooldown to prevent firing too rapidly
drone.ready = DRONE_LASER_COOLDOWN_TIMER_SINGLE
game.drone_laser_cooldown = DRONE_LASER_COOLDOWN_TIMER_ALL
Of course, we're also careful to reset our timers.
Now, each time this active laser is rendered, it will move according to the calculated dx
and dy
:
// Render Drone Lasers -- check collisions -> render
for laser, idx in &game.drone_lasers
{
if laser.health == 0 do continue
// check collision based on previous frame's rendered position
// check player health to make sure drone lasers don't explode
// while we're rendering our stage_reset() scenes after a player
// has already died.
if game.player.health > 0
{
hit := collision(
game.player.dest.x,
game.player.dest.y,
game.player.dest.w,
game.player.dest.h,
laser.dest.x,
laser.dest.y,
laser.dest.w,
laser.dest.h,
)
if hit
{
laser.health = 0
if !game.is_invincible
{
explode_player(&game.player)
}
}
}
if !game.is_paused
{
laser.dest.x += i32(laser.dx)
laser.dest.y += i32(laser.dy)
}
// reset laser if it's offscreen
// checking x and y b/c these drone
// lasers go in different directions
if laser.dest.x <= 0 ||
laser.dest.x >= WINDOW_WIDTH ||
laser.dest.y <= 0 ||
laser.dest.y >= WINDOW_HEIGHT
{
laser.health = 0
}
if laser.health > 0
{
when HITBOXES_VISIBLE do render_hitbox(&laser.dest)
SDL.RenderCopy(game.renderer, game.drone_laser_tex, &laser.source, &laser.dest)
}
}
Show Entity hitboxes
To help with visualizing how our collisions are working, let's add a little utility to outline our collision areas.
We'll add a flag that will be checked at compile time:
HITBOXES_VISIBLE :: false
Next, we'll add this helper function to render a red rectangle at a given destination SDL.Rect
:
render_hitbox :: proc(dest: ^SDL.Rect)
{
r := SDL.Rect{ dest.x, dest.y, dest.w, dest.h }
SDL.SetRenderDrawColor(game.renderer, 255, 0, 0, 255)
SDL.RenderDrawRect(game.renderer, &r)
}
Now we can pass in any entity SDL.Rect
to get an outline of their collision boundaries:
if laser.health > 0
{
when HITBOXES_VISIBLE do render_hitbox(&laser.dest)
SDL.RenderCopy(game.renderer, game.laser_tex, nil, &laser.dest)
}
Posted on January 5, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.