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) }
Top comments (0)