CutscenesAndCoroutines

*This article was originally written by User:Dddaaannn for the Pico8Zine. It is contributed to the Wiki by the author under the CC-BY-SA license that covers all PICO-8 Wiki content.*

Want to spice up your game with animated scene between bouts of gameplay? Cutscenes are uncommon on small platforms like PICO-8. There's no room for full motion video, so a scene must be described in code or with some kind of cutscene engine. This is made much easier with a Lua feature added in PICO-8 v0.1.6: coroutines.

In this article, we'll take a look at coroutines, see how they simplify the code behind animated events, and design an intuitive way to implement cutscenes using this feature.

## Animation in a nutshell

Animation is a series of images displayed over time to give the illusion of movement or change in a scene. With full motion video, each image shows the entire scene at a moment in time, and displaying the animation just means showing the right image at the right time. In a game, especially on a small platform, a scene is better represented as a collection of objects, each with their own positions and image sequences. This uses less memory and allows each object to be defined and drawn separately. And of course in a game, each object may have interactive or dynamic behaviors that change the scene based on player input or other patterns.

Here is a trivial example of a planet orbiting a flickering sun:

```
t = 0       -- planet arc pos
dt = 1/72   -- arc change per frame
r = 32      -- orbit radius

function \_update()
 -- update planet position
 t += dt
 if (t > 1) t = 0

 -- update sun color
 sun\_color = 9+flr(rnd(2))
end

function \_draw()
 cls()

 -- draw sun
 circfill(63, 63, 7, sun\_color)

 -- draw planet
 x = cos(t) \* r + 60
 y = sin(t) \* r + 60
 circfill(x, y, 3, 8)
end

```

Simple animations like these require storing minimal state for each object. We calculate the planet's position based on its previous position, stored in the variable `t`. The sun's color is determined randomly with no relationship to the previous frame, and so maintains no state between frames at all.

A space invaders game may have more objects on the screen, but it can use similarly simple techniques for animating each object. For example, you might have a Lua table for each alien with properties for its position, movement (such as sideways velocity), and which sprite to use to draw it. As with this example, the `_update()` function updates all of the tables based on a simple set of rules, and the `_draw()` function draws the aliens based on their table values, once for each frame of animation.

## What is a cutscene?

A cutscene is an extended sequence of animated effects that takes place during a break in gameplay. It typically does not involve user interaction, except perhaps to advance or terminate the scene by pressing a button. It may involve multiple objects doing various things at various points in the sequence. At a given moment, an avatar of a character may be moving its mouth or emoting, while dialog text is displayed a few letters at a time, with looping animations in the background. The full scene consists of a sequence of these moments.

To implement a cutscene system, we must answer two important questions:

1. How do we represent the state of the cutscene at a given moment in time?
2. How do we describe the sequence of states in our code?

This is going to be more complicated than calculating each object's next state from its state in the previous frame. What we need is a mechanism that manages the general flow of events as well as executes the events themselves over multiple frames.

One way to do this is with a sequence table with one value for each state and an index variable that tracks which state we're in. Each value describes a primitive event, such as moving an object across the screen or displaying a line of dialog. A separate set of code and variables for each event type figures out what needs to be done for each frame.

```
scene\_one = {
 {'actor\_walk', 'boris', -10, 40, 30, 40},
 {'actor\_walk', 'natasha', 138, 40, 70, 40},
 {'dialog', 'boris', 'look natasha, moose and squirrel have the bag of money!'},
 {'dialog', 'natasha', 'yes! you hit moose with mallet and i will grab the bag.'},
 {'dialog', 'boris', 'first i will turn off the light switch.'},
 {'actor\_walk', 'boris', 30, 40, 40, 10},
 {'lightswitch\_toggle', 'boris'}
}

scene\_pos = 1

```

Notice that each event needs to maintain its own state information between frames, such as the position and velocity of an object on the screen. The event table itself is effectively a small and rather arcane programming language of our own design, and the cutscene engine is its interpreter.

There's another way to describe events and maintain the event state we need. But first, we must take a closer look at functions.

## Functions and memory

We mostly think of functions as a way to group instructions into named units. When a program calls a function, those instructions are executed as if you typed them all at the place in the code where the function is called. We can call the function from multiple places in our program. We can also adjust the behavior of the function for each call by passing in arguments. When the instructions are complete, the function returns control (and possibly a result value) back to the program.

When the program calls a function, Lua allocates some memory to keep track of the function's local variables while it is running. When the function is done executing and its return value (if any) is calculated, Lua deallocates this memory, and no trace of the function call remains.

The PICO-8 game loop requires that the `_update()` and `_draw()` functions return before the frame can be drawn. Naturally, this means that any functions called by `_update()` or `_draw()` return as well. If a function wants to update the game state, that state must be stored in a global variable for it to persist between frames—or even between the calls to `_update()` and `_draw()`. A function call can't last longer than a frame.

Here is an excerpt of a simple cutscene engine that uses global variables to implement the `'actor_walk'` events from the previous example. `walk_actor()` is called once for each `_update()`, and it uses a global frame counter to measure each walk action.

```
walk\_count = 0
walk\_total = 30

actors = {
 boris={x=-8, y=-8, base\_sprite=16, cur\_sprite=16},
 natasha={x=-8, y=-8, base\_sprite=18, cur\_sprite=18}
}

function walk\_actor(event)
 local actor = actors[event[2]]
 local from\_x = event[3]
 local from\_y = event[4]
 local to\_x = event[5]
 local to\_y = event[6]

 actor.x = from\_x + (to\_x - from\_x) \* (walk\_count / walk\_total)
 actor.y = from\_y + (to\_y - from\_y) \* (walk\_count / walk\_total)
 actor.cur\_sprite = actor.base\_sprite + (walk\_count % 2)
end

function \_update()
 if scene\_one[scene\_pos][1] == 'actor\_walk' then
   walk\_actor(scene\_one[scene\_pos])

   walk\_count += 1
   if walk\_count == walk\_total then
     scene\_pos += 1
     walk\_count = 0
   end

 -- ...
 end
end

function \_draw()
 cls()
 for name,actor in pairs(actors) do
   spr(actor.cur\_sprite, actor.x, actor.y)
 end
end

```

There's plenty more to say about how functions can interact with data, such as object methods and lexical scoping. For now, it's sufficient to say that functions aren't expected to hold on to their own state: they update state outside of themselves, then get out of the way.

## Introducing coroutines

A coroutine is a function with the ability to pause itself and yield control back to the caller. While paused, all of the coroutine's local state remains in memory. The caller can do other work then resume the coroutine to pick up where it left off. The coroutine and the caller can bounce control between each other as many times as they like until the coroutine terminates.

To define a coroutine, simply define a function. In the place where you want the function to yield, call the `yield()` function.

In the main code where you want to use the coroutine, call the built-in function `cocreate()`, passing in the function as its sole argument. This returns an object that keeps track of this invocation of the coroutine.

To invoke or resume the coroutine, call `coresume()` with the coroutine object. You can pass additional arguments to `coresume()` and they will be passed as arguments to the function on first invocation. On subsequent invocations, the additional arguments are passed to the coroutine function as the return value from the call to the `yield()` function. (You can ignore them if you don't need them.)

The `costatus()` method returns the status of the coroutine. It takes the coroutine object as its argument and returns a string, either `'suspended'` or `'dead'`. A coroutine dies when its function returns instead of yields.

The following example moves a ball across the screen when you press button 5. The ball stops at the end of its path.

```
b = {x=4, y=4}
cor = nil

function anim(ball)
 for i = 4,124,4 do
   ball.x = i
   ball.y = i
   yield()
 end
end

function \_update()
 if btnp(5) then
   cor = cocreate(anim)
 end
 if cor and costatus(cor) != 'dead' then
   coresume(cor, b)
 else
   cor = nil
 end
end

function \_draw()
 cls()
 circfill(b.x, b.y, 4, 7)
end

```

When you press button 5, `_update()` creates a new coroutine based on the `anim()` function and stores it in a global variable. As long as the coroutine is active, `_update()` resumes it once per frame. The coroutine updates the position of the ball by one step, then yields. After the last update, `anim()`'s for loop exits, and the function returns, killing the coroutine.

Notice that the state of the ball animation is maintained in the paused coroutine, in the local variable `i`. It also keeps its own reference the ball location object, passed to it by the initial call to `coresume()`. Even though this is also stored in the global variable `b` so that `_update()` and `_draw()` can share it, the coroutine doesn't need to access the global, because it keeps its own local reference (`ball`).

One more thing about coroutines that will prove useful: if a coroutine calls another function, that function can call `yield()`, and the entire coroutine will yield at that point. Yielding preserves the entire call stack and all local variables for functions in progress. This lets you write functions that yield that are used by coroutines but are not necessarily coroutines themselves. We will use this later.

## Caveat: coroutines hide runtime errors

A word of caution before we proceed. As of Pico-8 v0.1.10, if a runtime error occurs during a coroutine, the coroutine exits but execution continues normally from the call to `coresume()` instead of halting the program. This is problematic because some kinds of coding errors, such as incorrect `nil` values, manifest as runtime errors. If your coroutine code has such an error, you won't see the usual error report, and instead will see weird behavior.

Take care when writing code used inside coroutines. If possible, test the code outside of the coroutine before relying on it.

## Building a cutscene engine

Our coroutine-based cutscene engine has one master coroutine used by `_update()` and `_draw()` to indicate that a scene is taking place. This is stored in a global. If this is `nil`, then there is no cutscene in progress.

```
scene\_update\_cor = nil

```

The coroutine updates a data structure describing everything that needs to be drawn to the screen. `_update()` drives the master coroutine, and `_draw()` renders the draw list. Each `drawobj` object can have its sprite number, tile width and height, position, and color palette modified during a cutscene.

```
-- a list of drawobj
scene\_draw\_list = nil

-- initial values for drawobj properties
drawobj = {
 n = 0,      -- the sprite number (upper left)
 w = 1,      -- the width in sprites
 h = 1,      -- the height in sprites
 x = 0,      -- the x coord
 y = 0,      -- the y coord
 pals = {}   -- a list of lists of arguments to pal() to use before drawing
}

-- creates a new drawobj
function drawobj:new(o)
 o = o or {}
 setmetatable(o, self)
 self.\_\_index = self
 return o
end

-- draws the drawobj (called by \_draw() when in scene mode)
function drawobj:draw()
 foreach(pals, pal)
 spr(self.n, self.x, self.y, self.w, self.h)
 pal()
end

```

For this demo, the game loop starts a cutscene, then allows the player to restart it by pressing a button. Normally this is where the main game would go.

```
function \_init()
 start\_scene(sc\_opening)
 cur\_level = 0
end

function \_update()
 if scene\_update\_cor then
   if costatus(scene\_update\_cor) != 'dead' then
     coresume(scene\_update\_cor)
   else
     scene\_update\_cor = nil
   end
 else
   if btnp(5) then
     start\_scene(sc\_opening)
   end
 end
end

function \_draw()
 cls()
 if scene\_update\_cor then
   for i=1,#scene\_draw\_list do
     scene\_draw\_list[i]:draw()
   end
 else
   print('press x to replay', 0, 0, 7)
 end
end

```

The `start_scene()` function is where things get interesting. It takes as an argument a function (such as `sc_opening`) that defines a cutscene. `start_scene()` creates a new coroutine for this function and assigns it to the `scene_update_cor` global. It also clears the draw list.

```
function start\_scene(f)
 scene\_update\_cor = cocreate(f)
 scene\_draw\_list = {}
end

```

You can now see what a scene function does: it creates new `drawobj` objects, puts them in the `scene_draw_list`, manipulates their parameters, and yields once per frame. When the scene is over, it just returns.

Remember that I mentioned that a coroutine can call other functions that yield? This allows us to write a library of animation primitives, then call them from the scene function.

```
-- waits t frames
function sch\_delay(t)
 for c=1,t do
   yield()
 end
end

-- places a drawobj at a location, adding it to the scene if needed
function sch\_place\_obj(obj, x, y)
 del(scene\_draw\_list, obj)
 obj.x = x
 obj.y = y
 add(scene\_draw\_list, obj)
end

-- moves a drawobj linearly to a new location over t frames
-- obj must be placed first
function sch\_move\_obj\_lin(obj, destx, desty, t)
 local incx = (destx - obj.x) / t
 local incy = (desty - obj.y) / t
 for i=1,t do
   obj.x += incx
   obj.y += incy
   yield()
 end
end

-- removes a drawobj from the scene, if it is present
function sch\_remove\_obj(obj)
 del(scene\_draw\_list, obj)
end

function sc\_opening()
 o1 = drawobj:new({n=1})
 sch\_place\_obj(o1, 0, 0)
 sch\_move\_obj\_lin(o1, 120, 120, 60)
 sch\_move\_obj\_lin(o1, 0, 0, 60)
 sch\_delay(20)
 sch\_remove\_obj(o1, 0, 0)
end

```

The scene function now looks like a movie script! Even though the actual control flow returns to `_update()` once per frame, we can use simple Lua function calls to describe every event in the scene, in sequence.

These helper functions can get much more sophisticated. They can loop animations, move objects along paths or with easing, play sounds or start music. A helper function could do animated dialog, and could even listen for a button press to display text faster ala RPG-style dialog.

## Multi-track animations

That scene function is nice and all, but it can only animate one object per helper call. What if we want multiple objects going at the same time, possibly on different scripts running simultaneously? Coroutines to the rescue!

```
function do\_scene(tracks)
 local cors = {}
 for tfunc in all(tracks) do
   add(cors, cocreate(tfunc))
 end

 while #cors > 0 do
   for t in all(cors) do
     if costatus(t) != 'dead' then
       coresume(t)
     else
       del(cors, t)
     end
   end
   yield()
 end
end

function sc\_opening()
 o1 = drawobj:new({n=1})
 o2 = drawobj:new({n=2})
 do\_scene(
   {
     function()
       sch\_place\_obj(o1, 0, 0)
       sch\_move\_obj\_lin(o1, 120, 120, 60)
       sch\_move\_obj\_lin(o1, 0, 0, 60)
       sch\_delay(20)
       sch\_remove\_obj(o1, 0, 0)
     end,
     function()
       sch\_place\_obj(o2, 120, 0)
       sch\_move\_obj\_lin(o2, 0, 120, 90)
       sch\_move\_obj\_lin(o2, 120, 0, 90)
       sch\_delay(60)
     end
   })
end

```

The `do_scene()` driver takes a list of inner scene functions, one per "track," then executes them in tandem. For each frame of the master scene, it updates each track by one frame. It accomplishes this using an inner list of coroutines based on the track functions. Each track is allowed to finish on its own schedule. The scene is over when all tracks have completed.

As we've seen, coroutines let us think about control flow in a new way that works especially well for animated sequences like cutscenes. They are similarly useful in gameplay situations for one-off multi-frame animation effects such as explosions and particle effects, or as a driver for background action. Give coroutines a try on your next project!

— Dan

P.S. Check out geckojsc's RPG dialog demo, which uses coroutines in a similar way: http://www.lexaloffle.com/bbs/?tid=3833

*Copyright © 2015 Dan Sanderson. This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.*