Author Topic: Programming Game Levels?  (Read 459 times)

Lochlan

  • Sr. Member
  • ****
  • Posts: 408
Programming Game Levels?
« on: August 03, 2013, 02:44:33 PM »
I'm writing an STG engine in HuC/Assembly and I'm nearly finished with the engine part of it.  I'm very far along--I can do stuff like generate enemies, handle player/enemy/bullet collisions, handle updating my stacks of objects in the game loop, etc.--but there is something holding me back.  It's (maybe) not so much a question of programming as it is code structure:

How the hell do I program a level?

Since this is an STG, basically I need to create a series of enemies that are generated on a timer, that's going to solve my problem.  But I'm wondering how to develop this programmatically.  Do I just have a level1(), level2(), etc. series of functions with their own game loops, each calling add_enemy() functions on a timer?  Do I figure out how to incorporate some sort of scripting language, writing scripts for the levels and letting the engine interpret them?  This is not the first STG game engine I've built, but is the first one I've bothered to try and make real levels for.

I looked a bit on stack overflow and found some ok answers (if not exciting ones), but I'm wondering what you other PCE devs do.

Thanks a lot for any responses!
I'm not sorry about this, as I'm not sorry about ANY attack by the goverrats.

Lochlan

  • Sr. Member
  • ****
  • Posts: 408
Re: Programming Game Levels?
« Reply #1 on: August 03, 2013, 03:04:33 PM »
YAML?
I'm not sorry about this, as I'm not sorry about ANY attack by the goverrats.

TheOldMan

  • Hero Member
  • *****
  • Posts: 958
Re: Programming Game Levels?
« Reply #2 on: August 03, 2013, 03:35:51 PM »
There are lots of way to do it, depending on how the other code is set up.
We usually do something like this:

game_over = 0;
level = 1;
while( ! game_over)
{
   init_level();
   game_over =  run_level();
   if( !game_over )
   {
     next_level();
   }
   else
   {
      run_game_ending();
   }
}

If you parameterized your levels so they all act the same, init_level() just has to copy the appropriate values from a table, and next_level() just cleans up and bumps level.
Then run_level() can use the parameters to decide which enemies to release and when.

Arkhan

  • Hero Member
  • *****
  • Posts: 14142
  • Fuck Elmer.
    • Incessant Negativity Software
Re: Programming Game Levels?
« Reply #3 on: August 03, 2013, 08:16:12 PM »
Atlantean basically does what is described above.

It doesn't use YAML or any other goofy modern nonsense.

If you have something more elaborate where each level has different types of enemies and layouts, you'll need to do some organizing and planning so that you can have a way to read those sort of things out based off of the level you're on.


Does this engine support vertical/horizontal scrolling ?  Or is it single screen?
[Fri 19:34]<nectarsis> been wanting to try that one for awhile now Ope
[Fri 19:33]<Opethian> l;ol huge dong

I'm a max level Forum Warrior.  I'm immortal.
If you're not ready to defend your claims, don't post em.

Lochlan

  • Sr. Member
  • ****
  • Posts: 408
Re: Programming Game Levels?
« Reply #4 on: August 03, 2013, 08:40:02 PM »
Thank you both for your replies.

If you parameterized your levels so they all act the same, init_level() just has to copy the appropriate values from a table, and next_level() just cleans up and bumps level.
Then run_level() can use the parameters to decide which enemies to release and when.

That's a very interesting approach.  It sounds like you're suggesting letting the engine do the heavy lifting and providing an array of enemies/timings to it, or something along those lines.  Such a design would definitely allow for easy scripting of levels, which sounds like a desirable feature as I move forward with new levels.  Would you mind sharing what your table and its entries look like (at least roughly)?  I assume it's an array with elements that contain enemy type and the time they are generated?

If you have something more elaborate where each level has different types of enemies and layouts, you'll need to do some organizing and planning so that you can have a way to read those sort of things out based off of the level you're on.

Right now I just have one basic enemy that floats by stupidly and waits to get shot, but the plan is to have multiple enemy types with individual patterns.  Can you elaborate on your comment about having "a way to read those sort of things out based off of the level you're on?"  It sounds like TheOldMan's enemy "table" would be appropriate here, since you could load a new table per level.

I have been struggling a bit also with the concept of creating some data structure to hold enemy movement/attack patterns, is that what you're referring to?

Does this engine support vertical/horizontal scrolling ?  Or is it single screen?

It's a horizontal scroller, none of that ancient single-screen business :P
« Last Edit: August 03, 2013, 10:19:26 PM by Lochlan »
I'm not sorry about this, as I'm not sorry about ANY attack by the goverrats.

Lochlan

  • Sr. Member
  • ****
  • Posts: 408
Re: Programming Game Levels?
« Reply #5 on: August 03, 2013, 10:49:16 PM »
It doesn't use YAML or any other goofy modern nonsense.

I think I was a bit misdirected about my YAML comment, it was just based on something I found on stack overflow.  I said something about "letting the engine interpret [scripts]" but I guess it would be absurd to let the engine literally parse scripts, any scripts would probably have to compile into something.  Maybe it's just unnecessary work to do something like this, my idea was that it could be useful to write scripts instead of fiddling with code when designing levels (since level design will probably be a huge amount of trial and error).  But there's no reason I'd have to do that.  It would definitely be useful for a JRPG or an adventure game, but STGs are pretty straightforward.
I'm not sorry about this, as I'm not sorry about ANY attack by the goverrats.

TheOldMan

  • Hero Member
  • *****
  • Posts: 958
Re: Programming Game Levels?
« Reply #6 on: August 04, 2013, 02:49:04 AM »
The engine should already be doing a lot of the 'heavy lifting', from your description. My tables won't help you, for the simple reason that they aren't for a shooter :)

At the very least, you need to know the enemy type and when it spawns. You will probably also want to know how many to spawn, how fast they move, how long to delay between shots, which pattern to use....and a few other things.

Unfortunately, HuC doesn't have a good way to use structured data, so it's up to you to decide how to manage all that information. Generally, I prefer multi-level arrays (since that's about all we have) where something from one array tells me which info to use from other arrays. For example, if I'm handling an enemy of type 2, I can use that to decide which array to use to set the enemy up. And since init_level() has copied that information from another table, the values can change without altering the code.

From your description, it looks like that's a bit in the future, though. First you need to decide how to handle multiple enemy types, and how you want them to behave. Then you can worry about designing different levels.

[Unfortunately, HuC doesn't really support pointers to functions, either. It would be so much simpler that way...]

Lochlan

  • Sr. Member
  • ****
  • Posts: 408
Re: Programming Game Levels?
« Reply #7 on: August 04, 2013, 07:59:14 AM »
The engine should already be doing a lot of the 'heavy lifting', from your description. My tables won't help you, for the simple reason that they aren't for a shooter

I guess what I meant by "heavy lifting" was feeding it an array of level data or something as opposed to having a game loop with hard-coded enemy spawn points/times.  That sounds awful, but I was thinking like if(frame=foo){add_enemy_1(arg1, arg2, arg3);} or something.  Which totally seems cumbersome.  I was aware that this approach seemed awful, but I don't have a lot of experience with this sort of thing so I wanted to get a flavor for what the devs here do.  And now I have, so this thread has been very useful for me.

Generally, I prefer multi-level arrays (since that's about all we have) where something from one array tells me which info to use from other arrays. For example, if I'm handling an enemy of type 2, I can use that to decide which array to use to set the enemy up. And since init_level() has copied that information from another table, the values can change without altering the code.

Thanks, that's very helpful.  I guess I will figure out how to structure an "enemy pattern" array or whatever.

From your description, it looks like that's a bit in the future, though. First you need to decide how to handle multiple enemy types, and how you want them to behave. Then you can worry about designing different levels.

These things are related.  Whatever programmatic structure will hold the "queue" of enemies to be spawned at a given time/place will also somehow reference the pattern.

I could just make update_enemy_1(), update_enemy_2(), etc. functions but that sounds cumbersome and seems like a lot of code duplication.  Part of my desire to learn how you guys structure your levels has to do with my interest in figuring out how this kind of information is stored.  It sounds like an "enemy pattern" array of some kind, referenced by my level array is the way to go.

Thanks for your reply.
« Last Edit: August 04, 2013, 08:02:42 AM by Lochlan »
I'm not sorry about this, as I'm not sorry about ANY attack by the goverrats.

Arkhan

  • Hero Member
  • *****
  • Posts: 14142
  • Fuck Elmer.
    • Incessant Negativity Software
Re: Programming Game Levels?
« Reply #8 on: August 04, 2013, 12:53:37 PM »
Well, for a shooter, you'd want to avoid scripts and parsing alot of data, because it will slow things down.

You'll probably want to just have enemy types, all of which follow some sort of preset movement pattern.

So, enemy 1 could move in a sine wave, enemy 2 could be some zig zag thing.

and then you'd have the same sort of thing for the behavior of the enemies.    With a shooter, I imagine they don't really have much "intelligence", and just do some predetermined movement and shooting pattern until they are offscreen, or they are dead.

Once you have things like that, all you would really need to be doing is have some sort of spawn-processing function that, as the  level progresses, knows what to spit out.  You can use tile cues as you scroll through the map as triggers for enemies, or just use a timer.


[Fri 19:34]<nectarsis> been wanting to try that one for awhile now Ope
[Fri 19:33]<Opethian> l;ol huge dong

I'm a max level Forum Warrior.  I'm immortal.
If you're not ready to defend your claims, don't post em.

TheOldMan

  • Hero Member
  • *****
  • Posts: 958
Re: Programming Game Levels?
« Reply #9 on: August 04, 2013, 02:12:39 PM »
Quote
That sounds awful, but I was thinking like if(frame=foo){add_enemy_1(arg1, arg2, arg3);} or something.
...?

enemy_delay[enemy_type]--;
if( enemy_delay[enemy_type] <= 0 )
{
   if( spawn_count[enemy_type] > 0 )
   {
      add_enemy( enemy_type, arg2, arg3);
      spawn_count[enemy_type]--;
   }
   enemy_delay = enemy_time[ enemy_type ];
}
processed once per type.
Then the # to spawn, and delay between spawns would be global arrays, loaded with diferent values in init_level().

As Arkhan says, each enemy could have a specific pattern they follow. Or, you could have several patterns and use them randomly for the enemies. At some point, you have to update the enemys position; it's a simple matter to call one-of-N functions (one for each enemy type), giving it the enemy number to work on, to do that.

...
[ if you're conversent in assembler....

   lda  enemy_type
   asl  a
   tax
   lda  enemy_no
   jmp   [move_tab],x

I don't guarantee thats correct, but it gives you an idea of how to do it. The trick is making sure any information needed for updates/whatever is available indexed by enemy_no. And getting the correct functions mapped in.
]
....

The enemy queue should only hold the enemy index; everything else should use the index to look up other information. To update the enemies, then, you loop through the que, calling the appropriate update function based on the enemy type. Same thing applies elsewhere, as well.

Note that then you can change enemy_time[] and spawn_count[] on a level-by level basis. For that matter, you could change the move_tab table (which contains the adresses of the functions to call for the enemy type), too.....

Prime

  • Newbie
  • *
  • Posts: 8
Re: Programming Game Levels?
« Reply #10 on: August 08, 2013, 02:18:31 AM »
I structure my finite state machines like so(below)you can structure your ai alot more advanced than that though but should give some ideas for you.
EDIT:I've misread your post but since i already typed the code i'm leaving it there :)
So what you want is a spawn object script and that was explained already sorry for the irrelevant post.


do_objects:
 lda obj_id,x
 beq do_objectsexit            //object is nonactive goto next object
 jsr do_aistruct
 lda obj_state,x
 sec
 sbc #1
 tay
 lda (fsm_ptr),y
 sta movement_ptr
 iny
 lda (fsm_ptr),y
 sta movement_ptr
 jmp (movement_ptr)




do_aistruct:
 txa
 pha
 lda obj_id,x
 sec
 sbc #1
 tax
 lda objstruct_tbl,x
 sta fsm_ptr
 lda objstruct_tbl+1,x
 sta fsm_ptr+1
 pla
 tax
 rts
//i constructed these names quickly
objstruct_tbl:
 .word cannonship
 .word cannonship2
 .word metal
 .word metal2
 ect....


cannon:
 .word cannon_routine1
 .word cannon_routine2
 .word cannon_routine3
 .word cannon_routine4
 .word cannon_routine5
cannonship2:
 ect..
« Last Edit: August 08, 2013, 02:38:33 AM by Prime »

Bonknuts

  • Hero Member
  • *****
  • Posts: 3292
Re: Programming Game Levels?
« Reply #11 on: August 09, 2013, 03:40:03 AM »
I think scripting levels for forced scrolling shooters/shmups is a must IMO. Although compile it to a binary format first to save on processing time. I use macros and equates to directly write the simplified scripts for such levels. No custom script compiler needed. Here's a quick example:

Code: [Select]
Stage01:

_FuncLoff
_FuncLrst
_BG_OFF
_SPR_OFF
_SCROLL_OFF
_COL_OFF
_WAIT_FRM $0001

_MAP_PTR tile_map
_COL_PTR tile_col

_MAP_X 0, 29, tile_map
_COL_X 0, 29, tile_col

.DB MAP_Y,00,00
_MAP_SPD $0800
_MAP_RE
_COL_RE
_PAL_PTR tile_pal
_PAL_XFER 0, 255

_PAL_PTR status_pal
_PAL_XFER 160, 32

_VRAM_PTR $0780
_VXFER_PTR status_map
_VDC_XFER status_map

_VRAM_PTR $0800
_VXFER_PTR status_tile
_VDC_XFER status_tile


_VRAM_PTR $ab0
_VXFER_PTR tile_data
_VDC_XFER SIZEOF(tile_data)

_VRAM_PTR $6900
_VXFER_PTR ship_spr
_VDC_XFER $780

_PAL_PTR ship_pal
_PAL_XFER 256, 32



_BG_ON
_SPR_ON
;.DB PLYR_OFF
_COL_ON
_SCROLL_ON

_MAPaccel 80, 0, 3, -1, $0080

_WAIT_SEC 80
;_WAIT_SEC 340
;_MAPaccelREMOVE
;_MAP_X 50, 58, tile_map
;_MAP_RE
;_MAP_SPD $003f
;_WAIT_SEC 10
;_MAP_X 150, 58, tile_map
;_MAP_RE
;_MAP_SPD $0200
;_WAIT_SEC 10
;_MAP_X 250, 58, tile_map
;_MAP_RE
;_MAP_SPD $0100
;_WAIT_SEC 10
;_MAP_X 300, 58, tile_map
;_MAP_RE
;_MAP_SPD $008
;_WAIT_SEC 10
;_MAP_X 350, 58, tile_map
;_MAP_RE


;_STOP
_SCROLL_OFF

 Enemies are done the same way. They are pretty much just timed events. I treat certain 'command' codes (which you don't see here, but you would if you saw the binary or hex version of this) as non terminating. That is to say, you can string multiple commands for that instance/time slice/etc together and add a terminator at the end. Other commands are self terminating, etc. It's pretty much just like a command-string music engine (think MML style sound engines instead of traditional trackers). Though mine (above) is a little more complicated in that it will push certain function pointers onto a function stack to be processed once a frame. The functions can remove themselves once they expire, or high priority functions can force remove them if need be (like resetting a stage point upon a continue, or mid stage change/transition, etc). I like the idea of a function stack that gets parsed once a frame. The primary function list itself is just a simple 256 byte table. The cpu quickly parses if a value is TRUE or FALSE (no terminator). Upon TRUE, it uses that place in the stack as an index into a 24bit pointer (bank:address) table, and JSRs to the function. Simply removing a function from the list is to change the value to FALSE. No re-ordering or removing a pointer or such.

 Anyway, the macros end up creating ".DB" or data defines, with operand data as well. I agree with what others have said, have a set of predetermined paths that can be applied to enemy types.