Log In  

Cart #flippingout-0 | 2021-12-18 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
TO LOAD THIS PICO-8 CART in immediate mode, type: load #flippingout

I am having great difficulty coding the game I am working on using conventional _update() method - to keep track of all the elements, some of which need to lock the controls and flow for a tight loop, others of which open up the main system again.

For instance, I want to have a scene where you might animate something from point A to point B. Play a sound while that animation is taking place, wait until the sound is completed, then animate point B to C. I wrote some sample code. While my game has little to do with this demo, the loop constructs are very similar and just as complex.

Watching the demo, it all looks pretty simple and yes it can be if you use FOR/NEXT and FLIP().

Yet how would you write something like this using only _update() with no FLIP() whatsoever ? To get the exact same results in timing, motion, and pauses - that is beyond my understanding.

... heading out for a few hours, will definitely return to see how you yourself would code this using _update() notation.

P#103074 2021-12-18 22:56 ( Edited 2021-12-18 22:57)


This seems like it might be a good use case for coroutines?

P#103083 2021-12-19 00:35

Here's a quick shot at refactoring to use coroutines. I just jammed everything into _draw since the example cart doesn't really have a strong separation between update-ish and draw-ish code. I probably should have also used arguments to coresume instead of having functions take no args and initializing from globals. But I think this at least demonstrates the concept I was going for? Hopefully I understood the prompt. :)

Cart #flippingout_coroutines-0 | 2021-12-19 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

P#103087 2021-12-19 00:56 ( Edited 2021-12-19 02:09)

OMgosh ! You did it, @luchak.

Let me sit down now and try to understand - what it is you did here ! Wow ... The code I had before without using Coroutines or FLIP() was a total nightmare and I had to ditch it.

-- quotes change each day
    if tofx==-#qott*4-4 or btnp(4) then

I guess the first question out of my mouth is, is YIELD() the same as FLIP() ?

I'm not understanding cocreate(), costatus(), or coresume() just yet.

P#103096 2021-12-19 03:13 ( Edited 2021-12-19 03:18)

Awesome! Glad this seems to be a step in the right direction. Maybe the right high-level description here is that coroutines are basically functions that can return multiple times, and that can be resumed from the point of their last return. Except, to keep it exciting (and less ambiguous, I guess), return is spelled yield(). :)

yield() and flip() do both mean "move to the next frame" in our two versions of this cart, but that's not yield's intrinsic meaning. It happens to work similarly in my version of the cart because coresume() gets called at most once per frame, so when you yield back to the draw function, you're going to advance to the next frame. If you were calling multiple coroutines per frame or had more complex coroutine logic, it would look a lot less like flip.

Here's my shot at describing how everything works:

  • cocreate() takes a function and initializes a new coroutine that will start execution from the beginning of the function the first time you call coresume(). It returns a reference to the coroutine so that you have something to call coresume() and costatus() with. Note that cocreate() doesn't run the coroutine, it only sets it up.
  • coresume() transfers control to a coroutine. If you pass args to coresume(), those will be passed to the coroutine (as args to the coroutine function).
  • costatus() lets you determine if a coroutine is running, suspended (not running but can run), or dead. I was checking for the value 'dead' in this code because that let me know that the end of the coroutine's function had been hit - at which point I could know that the coroutine was done and take appropriate action.
  • yield() transfers control from a coroutine back to just after the spot that coresume() was called from.
P#103100 2021-12-19 03:36 ( Edited 2021-12-19 03:51)

Now I see what you are saying above and how it can be used, @luchak. That is really complex for me to follow.

You definitely did teach me something though. This new code WORKS, thanks to you.

function main() ------------->>



until forever

end --<<-----------------------

function dots()
  for i=1,8 do

function _init()

function _update()
P#103124 2021-12-19 18:38 ( Edited 2021-12-19 21:51)

In case this helps anyone at all, as I can imagine people coming here and reading the question that was typed above (duplicated below), and still wondering, even after luchak's very good explanation of coroutines.

Re: is YIELD() the same as FLIP() ?

yield() does not flip()

yield() gives control back to the process that resumed/called the coroutine.

Flip then happens in Pico-8 as normal at the end of _draw

Hopefully this example is clear enough:

function _init()

function _update()


function _draw()
 until btn()>0

 --we will only flip
 --when btn is pressed
 --and we come to here
 --and as normal
 --pico-8 flips
 --at the end of _draw

function counter()
 local a=0
  print("counter: "..a,1,1,6)

 until false

Unless a button is pressed, the coroutine co will be called repeatedly, and although it has both cls and print instructions, nothing will be flipped to the screen.

When a button is pressed, the loop that resumes the coroutine is exited, and Pico-8 will draw/flip whatever was the last value that the print statement put to the screen.

P#103178 2021-12-20 14:06 ( Edited 2021-12-20 14:09)

[Please log in to post a comment]

Follow Lexaloffle:          
Generated 2022-12-07 00:34:27 | 0.014s | Q:23