Following on from the other topic where I ask what I've done wrong to break my Pico-8 install....
I have this code that does a circle fill cross fade:
function invcircfill(r,c) local r2=r*r color(c) for j=0,127 do local y=64-j local x=sqrt(max(r2-y*y)) rectfill(0,j,64-x,j) rectfill(64+x,j,127,j) end end
In the DRAW function I have this, that I also pilfered:
The above bit obviously calls the function but I'm not sure how it works.
It bounces in and out on a loop.
Would someone WAY more advance than I, please explain what I should have instead of that call to make the circle either expand once or contract one.
This is the first thing that comes to mind:
-- in an update function if (circfill_r) then circfill_r += circfill_dr if (circfill_r <= 0 or circfill_r >= 90) circfill_r = nil end -- in the draw function if (circfill_r) invcircfill(circfill_r,0)
There's probably a more concise version, though. With those in the update and draw, you could then start an expansion or a contraction at any time by setting circfill_r to where it should start (either 1 or 89) and circfill_dr with the direction and speed (direction being whether it's positive or negative). You could also combine them into one block of code in the draw function, but that could cause the rate that it occurs to be inconsistent between platforms.
To explain the code you posted that loops, cos() (short for cosine) is a sinusoidal function. That means that as the value it's given goes up, the value it returns squiggles up and down with regularity. The (very nonstandard) pico-8 version of cos() goes between 1 and -1 with a distance of 1 between each return value of 1 (so cos(0)=1, cos(1)=1, cos(2)=1 etc.). The version you posted stretches that to 90 and -90, which is the closest integer value above the distance from the center of the screen to any corner (the actual distance is the square root of 2 multiplied by 64, or about 89.6). It then also takes the absolute value so that the radius goes from 90 to 0 and back instead with a kick at 0. "time()/4" makes it base the value on the amount of time the cartridge has been running, but dividing by 4 to slow it down to a good rate.
I'd probably do something like this:
function invcircfill(r,c) local r2=r*r color(c) for j=0,127 do local y=64-j local x=sqrt(max(r2-y*y)) rectfill(0,j,64-x,j) rectfill(64+x,j,127,j) end end function start_transition() tr_start = time() tr_new_state = state==1 and 2 or 1 end function _init() state = 1 end function _update() if (btnp(❎)) start_transition() end function _draw() cls(state == 1 and 5 or 1) rectfill(20,20,104,104,7) if tr_start then tr = time()-tr_start invcircfill(90*abs(cos(tr/4)),0) if (tr > 1) state = tr_new_state if (tr > 2) tr_start = nil end end
The function invcircfill(r, c) fills the screen with color c everywhere except for the circular area in the center of radius r. The iris transition consists of drawing this with the color black (0) starting with a large radius (original game state) going down to a radius of 0 (full black), then back up to the large radius (new game state).
This example uses three global variables to manage state:
- state: The current state of the game, either 1 or 2. This example just toggles between the two states.
- tr_start: The clock time of when the transition started, as returned by the PICO-8 time() function. If there is no active transition, this is nil.
- tr_new_state: The new state the game will be in after the transition begins irising back out.
The start_transition() function begins the transition by setting tr_start to the current system time, as returned by time(). It also prepares the new state value as tr_new_state. In this example, pressing the X button starts the transition by calling this function (see _update()).
In _draw(), this example draws the current game state as described by the state variable, in this case just a different background color for each state, with a white square. Then _draw() checks to see if it's in a transition by testing the tr_start variable: if it's defined, then we need to draw the iris effect on top of the game.
The radius of the iris is determined by how much time has passed since the transition started: tr = time()-tr_start, a number value in fractions of a second. The radius of the iris is this expression:
90 * abs(cos(tr / 4))
The trigonometric function cos() gives the iris an ease-in/ease-out effect, where it's slow at the start, fast in the middle, and slow at the end. cos(0) is 1, cos(0.25) is 0, and cos(0.5) is -1, following this easing pattern along the way. We get a 2-second transition from 0 to 0.5 by taking the number of seconds (tr) and dividing it by 4 (tr/4). We use abs() to flip the negative number to a positive number, so it's actually bouncing from 1 to 0 and back to 1. Finally, we scale the range 0-1 up to 0-90 by multiplying. The end result is a bounce from 90 to 0 back to 90 over two seconds.
We have to change the game state when the iris closes so the new game state is visible when it opens back up. In this example, we just test if the transition has gone on longer than 1 second and assign the new state if so. After 2 seconds, we set tr_start to nil, which ends the transition.
This example code could be cleaned up a bit, but it's close enough for PICO-8 work. ;)
Wonderful explanations, many thanks to both kimiyoribaka & dddaaannn.
I'm going to try and mish mash a combination of the two with an update happening in the background when the screen is completely black. (I'm trying to get this in there when the player dies and you go back to the beginning of the level).
I have one small problem with @kimiyoribaka code, which I found very controllable...
When the big circle comes all the way in, how do I stop the circle from immediately clearing? I'd like the screen to stay black, and it's obviously CLS to black isn't it!
Again, HUGE thanks to you both.
Oh, right. I guess it would do that. I put in the line
if (circfill_r <= 0 or circfill_r >= 90) circfill_r = nil
because I figured it'd be a way to have the movement stop while also not wasting cycle while the invcircfill() isn't being used. I guess the better way would be to use
if (circfill_r <= 0 or circfill_r >= 90) circfill_dr = 0
instead. That would prevent overflow bugs that might pop up from just letting things increment endlessly while also stopping the change at an appropriate time. That said, if you use my suggestion for what should be in the draw function, I recommend also setting circfill_r to nil after you're done with the transition so that the draw function doesn't waste cycles running the invcircfill() to no effect.
Thanks again @kimiyoribaka
I have it working perfectly now.
[Please log in to post a comment]