8.5 KiB
This file contains text that used to be in the cartridge itself, but I'm getting increasingly anxious about cartridge space so I'm moving it out ot a separate file.
main loop sequence
- level_frame
- events
- merge new_events into events
- update bg intangibles
- move ships (player first)
- move bullets (player first)
- calculate collisions
- pship on eship
- ebullet on pship
- pbullet on eship
- update fg intangibles
- check for end of level
draw order
bottom to top:
- intangibles_bg
- player bullets
- player ships
- enemy ships
- enemy bullets
- intangibles_fg
notes
intangibles_fg move()s after all collisions and other moves are processed. if an intangible is added to the list as a result of a collision or move, it will itself be move()d before it is drawn.
data-driven items
guns and bullets both allow the most common behaviors to be expressed with data alone. ships only need a movement algorithm expressed.
guns
- t - metatable for bullet type. fired once in the bullet's default direction per shot.
- enemy - if true, fired bullets are flagged as enemy bullets.
- icon - sprite index of an 8x8 sprite to display in the hud when the player has this gun. default is 20, a generic crosshair bullseye thing.
- cooldown - min frames between shots.
- ammo, maxammo - permitted number of shots. 0 is empty and unfireable. maxammo = 0 will cause a divide by zero so don't do that. if nil, ammo is infinite.
default guns manage ammo and cooldown in shoot, then call actually_shoot to create the projectile. override only actually_shoot to change projectile logic while keeping cooldown and ammo logic.
bullets
- dx, dy - movement per frame. player bullets use -dy instead.
- enemyspd - multiplier for dx and dy on enemy bullets. default is 0.5, making enemy shots much easier to dodge
- damage - damage per hit; used by ships
- sprite - sprite index.
- x_off, y_off - renamed for the next two vars. may revert
- center_off_x - the horizontal centerpoint of the bullet, for positioning when firing. assume a pixel's coordinates refer to the upper left corner of the pixel; the center of a 2-width bullet with an upper left corner at 0 is 1, not 0.5.
- top_off_y, bottom_off_y - also for positioning when firing. positive distance from top or bottom edge to image. top_off_y will usually be 0, bottom_off_y will not be when bullets are smaller than the sprite box.
- width, height - measured in full sprites (8x8 boxes), not pixels. used for drawing.
bullets despawn when above or below the screen (player or enemy bullets, respectively).
by default, bullets despawn when they hit something. override hitship to change this.
ships
ships move by calculating momentum, then offsetting their position by that momentum, then clamping their position to the screen (horizontally only for ships that autoscroll). ships that autoscroll (slip==true) then slide down by scrollspeed. fractional coordinates are ok. after movement, ships lose momentum (ship.drag along each axis). abs(momentum) can't exceed ship.maxspeed.
ships gain momentum by acting like a player pushing buttons. the player ship actually reads buttons for this.
act -- returns new acceleration:
dx, dy, shoot_spec, shoot_main.
dx and dy are change in momentum
in px/frame. this is controls
only -- friction is handled in
ship:move (drag
value).
ships hitting another ship take 1 damage per frame of overlap. ships hitting a bullet check bullet.damage to find out how much damage they take. damage is applied to shields, then hp. damaged ships flash briefly - blue (12) if all damage was shielded, white (7) if hp was damaged. a ship that then has 0 or less hp calls self:die() and tells the main game loop to remove it.
shieldcooldown is the interval between restoring shield points. shieldpenalty is the delay before restoring points after any damage, reset to this value on every damaging hit (whether it is absorbed by the shield or not) -- shield behaves like halo and other shooters in its heritage, where it recovers if you avoid damage for a while. not that there is any safe cover in this kind of game.
ships do not repair hp on their own. negative-damage bullets are treated as 0, but a bullet can choose to repair the ship it hits in its own hitship method, or otherwise edit it (changing weapons, refilling weapon ammo). powerups are therefore a kind of bullet.
levels
a level is a table mapping effective frame number to functions. when a level starts, it sets lframe ("level frame") and distance to 0.
every frame, level_frame increments lframe by 0x0.0001. then if the level is not frozen, it increments distance by 1.0 and runs the function in the level table for exactly that frame number (if any). distance is therefore "nonfrozen frames", and is used to trigger level progress. lframe always increments. ships are encouraged to use lframe to control animation and movement, and may use distance to react to level progress separately from overall time. remember to multiply lframe-related stuff by 0x0001.
a special sentinel value, eol, marks the end of the level. (the level engine doesn't know when it's out of events, so without eol, the level will simply have no events forever.) when it finds eol, level_frame throws away the current level and tells the main loop that it might be done. the main loop agrees the level is over and the player has won when the level has reached eol and there are no more enemy ships, enemy bullets, or background events remaining. player ships, player bullets, and intangibles are not counted.
level freezing
the level is frozen when the global value freeze > 0. generally, something intending to block level progress (a miniboss, a minigame, etc.) increments freeze and prepares some means of decrementing it when it no longer wants to block level progress.
most commonly, we want to block until some specific ship or group of ships has died. for these ships, override ship:die to decrement freeze. make sure to set ship.dead in any new ship:die method so anything else looking at it can recognize the ship as dead.
for anything else, you probably want an event to figure out when to unfreeze.
levels start at 1
distance is initialized to 0 but gets incremented before the first time the engine looks for events. therefore, the first frame of the level executes level[1]. since levelframe executes before anything else, level[1] sets up the first frame drawn in the level. the player does not see a blank world before level[1] runs. level[1] can therefore be used to reconfigure the player ship, set up backgrounds, start music, kick off some kind of fade-in animation, etc.
events
the global list "events" stores 0-argument functions which are called every frame. if they return true, they are removed from the list and not run again; if they return false, they stay and will be called in later frames. the level does not end while the events table is nonempty.
events are most commonly used to set up something for later (for example, blip uses an event to remove the fx_pallete from the flashing ship when the blip expires), but can also be used to implement a "level within a level" that does something complicated until it's done. if you froze the level when creating the event, remember to thaw it (freeze -= 1) on all paths that return true.
to do complex stuff in events, use a closure or a metatable that specifies __call.
to avoid editing the events list while it is being iterated, events that create new events must add those events to new_events rather than events. new_events is only valid during the "event execution" stage, so events created at any other time must go directly on events without using new_events.
intangibles
the intangibles_fg and intangibles_bg lists contain items with :move and :draw. like ships and bullets, they move during _update60 and draw during _draw. they are not checked for collisions.
intangibles_bg moves/draws before anything else moves or draws. intangibles_fg moves/draws last. this controls whether your intangible object draws in front of or behind other stuff. you probably want intangibles_bg for decorative elements and intangibles_fg for explosions, score popups, etc.
there's no scrolling background engine but intangibles_bg could be used to create one, including using the map (otherwise unused in this engine) for the purpose.
intangibles do not prevent the level from ending. like bullets and ships, if :move returns true, they are dropped.