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.
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. :)
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 print(qott,tofx,121,11) tofx=tofx-.5 if tofx==-#qott*4-4 or btnp(4) then sfx(-1) sfx(14,0) mode=3 end |
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.
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.
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() ------------->> cls() c=0 repeat color(6) print(c) c=c+1 dots() _flip() until forever end --<<----------------------- function dots() for i=1,8 do pset(rnd(128),rnd(128),rnd(16)) _flip() end end function _init() _flip=yield _a=cocreate(main) end function _update() coresume(_a) end |
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() co=cocreate(counter) end function _update() end function _draw() repeat if(costatus(co)=="suspended")assert(coresume(co)) 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 end function counter() local a=0 repeat a+=1 cls() print("counter: "..a,1,1,6) yield() until false end |
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.
[Please log in to post a comment]