Log In  

I'm trying go back into my game Destructopillar to try add/remove some things but see I am at token limit. I've done some basic refactoring and stripping out unneeded functions that are now part of the core but it's not enough.

I was looking over the manual to see what functions are available now and was reminded about coroutines. The problem is, I never really understood how to apply coroutines. I get the concept and I think I understand the gains (like saving tokens), but just can't figure out where in the game it would benefit from coroutines, if at all.

Please check out the Destructopillar game below real quick, even just the first stage and you'll get the gist. Then please call out the places where you could see coroutines being useful.

I'm not looking for cart code analysis or digging into what's there right now. Just looking at the game on the surface...the actors, the actions, the scenes...would coroutines possibly make sense anywhere?

And the answer could really be they wouldn't help in this type of game. And that's okay...because I just don't know. I sense that coroutines could help and make things better and save me some token trouble but maybe not.

Cart #dpillar21-0 | 2021-12-27 | Code ▽ | Embed ▽ | Forks ▽ | License: CC4-BY-NC-SA
11

P#110687 2022-04-21 23:52 ( Edited 2022-04-22 00:36)

I didn't look at the cart code specifically, but here's one thing to consider. Especially if token savings is the goal, I'd look for instances where maintaining state involves some kind of sequence where each step has its own hairy details, such as a building going through multiple phases as it burns down. Without coroutines, the sequence would require state variables and conditionals that represent the sequencing, possibly spread across multiple areas of code. Would the sequencing be described more concisely as a coroutine?

Example without coroutines, possibly exaggerated token count :) (untested):

buildings = {
  {x=100, y=100, burn_timer=0, burn_phase=0}
  -- ...
}

function _update()
  for bldg in all(buildings) do
    update_building(bldg)
  end
end

function start_burn_building(bldg)
  -- burning goes through 5 phases
  -- phase 1 lasts for 300 clicks
  bldg.burn_phase=1
  bldg.burn_timer=300
end

function update_building(bldg)
  if bldg.burn_timer > 0 then
    bldg.burn_timer -= 1
  end

  if bldg.burn_phase == 1
    -- update smoke particles...

    if bldg.burn_timer == 0 then
      -- phase 2 lasts for 200 clicks
      bldg.burn_phase = 2
      bldg.burn_timer = 200
    end
  else if bldg.burn_phase == 2
    -- update smoke and small flames...

    if bldg.burn_timer == 0 then
      -- phase 3 lasts for 600 clicks
      bldg.burn_phase = 3
      bldg.burn_timer = 600
    end
  else if bldg.burn_phase == 3
    -- update large flames...
    -- ...
  else if bldg.burn_phase == 4
    -- reduced height, large flames...
    -- ...
  else if bldg.burn_phase == 5
    -- smoking ash...
    -- ...
    if bldg.burn_timer == 0 then
      -- done burning
      bldg.burn_phase = 0
    end    
  end
end

Example with coroutines (untested):

buildings = {
  {x=100, y=100, burn_coroutine=nil}
  -- ...
}

function _update()
  for bldg in all(buildings) do
    update_building(bldg)
  end
end

-- start a burn by creating the coroutine
function start_burn_building(bldg)
  bldg.burn_coroutine = cocreate(function() burn_building(bldg) end)
end

-- update a possibly-burning building by resuming the coroutine once per tick
function update_building(bldg)
  if bldg.burn_coroutine then
    result = coresume(bldg.burn_coroutine)
    if (not result) bldg.burn_coroutine = nil
  end
end

-- a coroutine to burn down a building that is resumed one per tick
function burn_building(bldg)
  for t=1,300 do
    -- update smoke particles...
    yield()
  end
  for t=1,200 do
    -- update smoke and small flames...
    yield()
  end
  for t=1,600 do
    -- update update large flames...
    yield()
  end
  -- etc.
end
P#110694 2022-04-22 00:46

Thanks for taking a swing :) I think I'm picking up what you're laying down. It helps me think about it more in context for sure.

And you're right, the current code is very much a bunch of vars tracking state and timers like your exaggerated code. It's how most of my games are made because I just didn't know any better.

So in your example with the smoke & flames...it does the particles and then hits yield() which stops and waits, but it picks up right away next trip because the coresume() is in the update loop. Sort of "animating" the growth of the fire and smoke...

Taking it a little further...there's a building and it gets hit by the player, triggering the start of burning routine. The next stage of burning with bigger flames doesn't happen until the player hits it again, which could be right away, later, or never. How does the building stay in that first stage of burning when the routine just picks up next trip after the yield() break? It seems like it would still need vars and conditions tracking the state of the building. A coroutine maybe isn't good for tracking states of an object...? Or maybe that state var checking goes IN the coroutine then?

But the use of coroutines every from your example looks a) to save a lot of tokens, and b) just looks a lot easier to manage. Anything I can do to get away from all the "if state, check timer" stuff is a win for me.

P#110697 2022-04-22 01:40

The coroutine can do more than just yielding in for loops, so your example of a building getting hit by the player to advance is a simple extension of this example. It's a common way to do RPG-style animated dialog boxes, which might pause for user input.

I can imagine this being a generally useful way to manage state machines where each state has its own update crank that needs to be turned. The outer loop only needs to know there is a crank to turn, and doesn't need to know what it does.

I like how coroutines are composable and reusable across objects: imagine multiple objects that can be set on fire and can reuse an on-fire coroutine for animations and damage. My Cutscenes and Coroutines article suggests a simple form of this, where the main coroutine actually calls other primitive functions that do the yielding.

P#110699 2022-04-22 02:56

adding to that ‘mission control’, similar to the building states, can be used to validate objectives (mostly sequential) before moving on to next mission goals.

P#110701 2022-04-22 06:45

[Please log in to post a comment]

Follow Lexaloffle:        
Generated 2022-09-28 12:59:42 | 0.007s | Q:20