Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Timing of enemy arrival in shmup

This thread is locked; no one can reply to it. rss feed Print
Timing of enemy arrival in shmup
_jagged
Member #17,124
September 2019

Hello everyone,
I've started making a 2d shmup in the style of rtype. This is my first game and first time using an api (I've been coding for about 4 months). So far everything is going well and I'm having a blast. My 'engine' cycles through the gamestates (start screen, in game, game over) and I have explosion animations, collision, audio etc all working nicely.

But... I'm not sure what is an efficient way to time when my waves of enemies should appear onscreen. Unlike my functions for collision I don't know how to use a function for spawning enemies unless they are all spawned at the same time. In a game like Rtype the enemies typically appear onscreen one by one with maybe 0.25 - 0.5 second delay.

Currently I have a counter int the redraw block:

#SelectExpand
1if (redraw && al_is_event_queue_empty(event_queue)) 2{ 3 redraw = 0; 4 switch (gamestate) 5 { 6 case START_SCREEN: 7 al_draw_textf(font, al_map_rgb(255, 0, 255), SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, 8 ALLEGRO_ALIGN_CENTER, "C-Type"); 9 al_draw_textf(font, al_map_rgb(255, 0, 255), SCREEN_WIDTH / 2, (SCREEN_HEIGHT / 10 2) + (SCREEN_HEIGHT / 8), 11 ALLEGRO_ALIGN_CENTER, "press spacebar to begin"); 12 break; 13 case IN_GAME: 14 enemy_timer++;

And then in the event timer block I do this:

#SelectExpand
1if (enemy_timer == 240) 2init_enemy(&enemy_grunts[0], 1920, 300); 3 4if (enemy_timer == 270) 5init_enemy(&enemy_grunts[1], 1920, 300); 6 7if (enemy_timer == 300) 8init_enemy(&enemy_grunts[2], 1920, 300);

Which is obvious tiresome. Can I make this happen for ten enemies by using a function?
Maybe with an allegro timer dedicated for enemies?

Cheers!

Audric
Member #907
January 2001

For a continuously scrolling game, I would use the "level offset" (in pixels) as a time mark, instead of a number of (milli)seconds. This makes it easier for you to match the level graphics.

First version would be to hard-code in an array of structs the "program" of your level:

T_Schedule level1_schedule[] = {
/* At T= 0 pixels, a squadron of three) */
{0, ENEMY_TYPE_BLUE_SHIP, 23, 200}, /* 23 pixels off the right edge, 200 pixels from 
bottom */
{0, ENEMY_TYPE_BLUE_SHIP, 23, 250},
{0, ENEMY_TYPE_BLUE_SHIP, 23, 300},

/* At T = 40 pixels, a single bonus ship */
{0, ENEMY_TYPE_BONUS, 45, 100},

};

Be sure to write everything in chronological order.
Your code can then start at the beginning of the schedule. Every time the level moves, check if the current item (that has not been "executed" yet) has a T <= the current T.
If it has, do the spawning that is specified in the current entry, move one step forward, and repeat the check (because you often have several spawns at the same T, as I have in my example at T=0)

Once you have such system working, you can consider externalizing the data in a file (raw text, xml, whatever is easiest for you to edit). Your code can then execute the loading once, when your program starts, and fill the exact same array of structure - the "schedule player" code is then unchanged!
This is more work for the same result, but then you no longer have to recompile every time you add/change/fix the enemy spawns. If that's something you will do a lot (many levels, lots of tweaking, careful placement) it will save you lots of time.

MikiZX
Member #17,092
June 2019

What Audric says is possibly the best and straight forward way of having this up and running while keeping control over what is happening during the level.

My approach to this problem in a small shmup I did was to just do a modulo divide (integer reminder of a division) of the enemy_timer with different numbers (similar to what you are already doing) - so every 50 increases of the timer I would spawn one wave of weak enemies, every 73 increases a different type of wave, every 300 a stronger wave, and reset the timer every 10000 increases (sort of level complete thingy) etc. which as a system will lack character but is still playable and enjoyable.
So, something like this:

if (enemy_timer%50==0)
  init_enemy(&enemy_grunts[0], 1920, 300);
  
  if (enemy_timer%73==0)
  init_enemy(&enemy_grunts[1], 1920, 300);
  
  if (enemy_timer%300==0)
  init_enemy(&enemy_grunts[2], 1920, 300);

As this is your first project, the following suggestion might be too much work and complex (so if you fancy the idea, you might want to keep this only for the version 2 of your game) - If you will want to extend your game to have decorative graphics like R-Type has (top and bottom of the screen) then as Audric suggests you might want to externalize your map data. And, possibly adapt to using a map editor such as 'Tiled' where one layer of the map could dictate the decorations to be drawn on the screen and another layer of the same map be used to tell your game when to trigger which type of enemy or introduce power-ups. Now, this would require quite a bit of planning, extra work and adapting your graphics to certain sizes (likely), creating tilesets for Tiled, etc. but it might be rewarding in the long run.

One last thing, I am likely wrong as I cannot have all the insight in your code but, looking at it, I believe you would need to move the enemy_timer++; out of the redraw part and into the timer section of your code? (so the enemy_timer variable increases at regular intervals as opposed to increasing at different pace depending on if the game is running on slower computer or a faster one - unless this is the design you wish to have, naturally).

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

This is a perfect use case for an event based system. You can have events that cause enemies to spawn and make enemies appear based on an event that you can send whenever you want.

_jagged
Member #17,124
September 2019

Thank you all for your responses. But, I must admit... I'm a bit confused. What Audric has said makes a lot of sense; I should be spawning these enemies based on the position of the background/environment. But my first and only level is a repeating background and parallax foreground (not really sure how to explain this) that repeats. MikiZx, wouldn't your approach be less efficient than what I have now due to the modulus? I agree with your skepticism of the enemy_timer placement, but I was under the impression that this kind of game would have no problem emptying the event queue with ANY current hardware.

Edgar, could you elaborate, please?

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Using an event system is quite similar to what you have.

Basically, you monitor game time, and spawn events or enemies based on conditions. The way you're doing it now is fine, and others have made good suggestions. I would go with their solutions for now.

MikiZX
Member #17,092
June 2019

Actually regarding modulus suggestion you are right, it is inferior to Audric's and Edgar's suggestion (since using modulus result takes away almost all control from you in telling the game when to spawn the enemies). The only advantage I can think of for using modulus is that it is quick.

Also, if you do not actually have a map but only repeating graphics then 'Tiled' suggestion I made is definitely an overkill.

For your case I believe Audric's and Edgar's suggestions are much better fit.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Modulus works fine for the background offset, just keep the actual distance traversed and modulo that to get the offset. Then use the actual distance for spawning enemies.

Go to: