Enemy waves spawning


I've been asked how the game is managing enemy spawning  and since this is a still in development part of the game, I'll try to explain it here and maybe some new ideas will come out of this.

Some of the choices are also dictated by pico-8 code limitations so are not optimal for every situations but they work for me.

I went through three different revision of this system and I already have some notes on how I want to evolve it a bit further, but so far it's as follows (for most enemies except a coupe of special cases) :

1-enemy data 

enemy data is split in 2 tables, one (e_data) with all the common properties for all enemies of the same kind (starting hp, half width, half height, shooting rate, horizontal spacing in a wave, score), another (enemies) one with the data for a single wave (enemy type, x starting position, y starting position, n. of enemies in this wave, bitmask of which enemy will shoot in this wave)

so for example

e_data = {
{1,4,4,150,16,10}, -- enemy 1, hp = 1, w/2 = 4, h/2 = 4, shooting rate = 150, one enemy every 16 pixels, score = 100
{1,8,8,110,20,20}, -- enemy 2, hp = 1, w/2 = 8, h/2 = 8, shooting rate = 110, one enemy every 20 pixels, score = 200
...
}
 
enemies = {
1,1289,12,8,0b00101101, -- wave of enemy 1, starts at world x 1289, world y 12, contains 8 enemies, 3rd,5th,6th,8th enemies will shoot
2,1342,80,3,0b100,      -- wave of enemy 2, starts at world x 1342, world y 80, contains 3 enemies, 1st enemy will shoot
2,1560,60,4,0b0100,     -- wave of enemy 2, starts at world x 1560, world y 60, contains 4 enemies, 2nd enemy will shoot
...
}

2-enemy activation

player's progress through a stage is determined in a camera_x variable and every update cycle, the enemies table is traversed and if  one of the waves starting x - the half width is < than camera_x+128 the wave gets activated by adding N. of enemies of that type to the enemies list just after the wave definition. each copy of the enemy gets a copy of the e_data fields, plus some additional initialized fields, the most important one is an index in the wave. Each enemy y coord is set to the wave y and each x is Transformed to Screen-Space Coordinates (subtracting camera_x from the wave starting position), so if we are at camera_x 1208 , the second wave in enemies will get activated and its enemies will be placed at 128, 148, 168 (3 copies, with 20 px spacing)

...
foreach(enemies[current_stage],enemy_update)
...
 
function enemy_update(e) 
  if enemy_visible(e) then
    ...
  end
end
 
function enemy_visible(e)
  local function enemy_activate(en,idx)
    local camera_x,e_data,unpack,rnd,_ENV =camera_x,e_data,unpack,rnd,en
    id,x,y,copy,who = unpack(e)
    hp,w,h,shot,dw,score =  unpack(e_data[id])
    cnt,r,hit,i=0,0,0,idx
    local ws = (2^i & who)
    tshot = ws > 1 and rnd(shot+1)\1 or ws*shot
    x-=camera_x
  end
  if not e.i and e[2]-e_data[e[1]][2]<camera_x+128 then
    enemy_activate(e,0)
    for i=1,e.copy-1 do
      t={} enemy_activate(t,i) add(enemies[current_stage],t)      
        e.x+= e.i*e.dw   
    end
  end
  if (e.i and e.x+e.w<-48) del(enemies[current_stage],e) return false
  return e.i and (e.i>0 or e.x-e.w < 128)
end


Once the enemy e has an index field it will receive an enemy_update(e) call and an enemy_draw(e) call so all enemies in the wave will move at proper distance and speed, even if they are still offscreen

If an enemy goes off-screen to the left far enough, it is removed form the table so it's not updated or drawn any longer.

 

If I disable x-movement you can see the waves spawning off-screen and waiting there, to be scrolled through

3-notes and improvements

the tricky part was to get the activation/positioning correct for the whole wave so enemies offscreen are following the first one which is already visible. Originally enemies were all processed in world coordinates while bullets and player were in screen coordinates but this had a lot of unnecessary code duplication or additional function parameters. The new conversion to screen space is easier.

Right now, when I spawn the enemies, the first enemy's fields are actually added to the wave object, so the first enemy is a special case containing both the original spawn fields and the single enemy fields and this requires some special handling but I plan to change it soon, so there will be 2 separate lists, the wave spawn list and the real enemy update/draw list. 

Get R-Type

Download NowName your own price

Leave a comment

Log in with itch.io to leave a comment.