Pico-8 Sprite Animation Basics


Creating sprite animation based on user input in Lua is made remarkably handy by a set of functions made available by the Pico-8 environment. I’m going to be extending these core functions with a set of reusable worker functions to establish the foundations of a game engine.


What’s covered in this article:

  1. Pico-8 programming environment structure
  2. Creating a manageable instruction set
  3. Receiving user input and manipulating game assets

This tutorial’s goal is help those of us who are new to Lua/Pico-8 to get a working prototype up and running. I’m basing this modal on my experiences developing a simple adventure game.

As I previously mentioned, the Pico-8 virtual console is a very limited environment. A big bonus to that capability (or lack of) is that scope creep has to be kept to a minimum, there by increasing the chances of completing your project!

The Pico-8 stores all it’s game data in one of two file formats, these files represents a “Cart” or “Cartridge”, just like on a real console.

  1. .p8 – The native format that is most useful if you wish to employ an external code editor.
  2. .png – Yup, just like the image format. This format makes distributing carts a breeze.

Pico-8 programming environment structure

At it’s core, the Pico-8 requires three functions to execute your game:

_init()

As it’s name implies, this function is executed upon cartridge load. It’s usage could be used to enforce a specific set of routines or functions first or to initialize the game environment.

_update()

Update acts like the main() function in the C language. It’s a constant loop operating at 30fps. Inside this function we place any code that needs to constantly listen to events such user input or collision between elements.

_draw()

Draw acts like the sibling to Update, it’s also called at 30fps but is executed after Update. As it’s name suggests, I will use this function to animate elements to the screen.

Pico-8 Code Editor

Creating a manageable instruction set

It is completely possible to build a game based on local variables and instructions nested within the mentioned core functions. However it won’t take long before your code becomes an interweaved, tangled mess. This is where objects and some concepts of the functional programming paradigm enter to tame your code.

To start, I’m going to setup three basic objects that will store information about our sprite, the space the sprite will interact with and lastly an object to house future game level data.

First, I’m setting up an object for the main game sprite to hold important values using dot notation. These values will be used to modify features throughout the game.

actor = {} -- Initialize the sprite object
actor.x = 0 -- Sprites x position
actor.y = 0 -- Sprites y position
actor.sprt = 0 -- Sprite starting frame
actor.tmr = 1 -- Internal timer for managing animation
actor.flp = false -- Used for flipping the sprite

Likewise, I’m setting here the foundations for the space the sprite will interact with.

apartm = {} -- Initialize the level
apartm.x0 = 10 -- X starting position of the space the sprite will be in
apartm.y0 = 10 -- Y starting position of the space the sprite will be in
apartm.x1 = 100 -- Determines the width (100 - 10 = 90)
apartm.y1 = 40 -- Determines the height of the space (40 - 10 = 30)

Finally this object will act as a global variable for the game levels and to direct functions execute the appropriate code block.

current_lvl = {} -- Holder for the level counter
current_lvl.number = 0 -- Initialize the level at 0

I’ve found that creating a worker function for level control a useful way to debug and navigate through different levels while developing. To start the game, I’m passing “1” to the lvl_change() function through the special _init() function. Later on through the development lifecycle I can use this feature to skip through the game.

function lvl_change(ln)
    current_lvl.number = ln
    if current_lvl.number==1 then -- Code for only level 1
        actor.x = ((apartm.x0 + apartm.x1) /2) - 8 -- Start sprite in the center of the level area
        actor.y = apartm.y1 - 15 -- Put sprite at the base of the level
    end
end

function _init()
    cls() -- Clear the screen
    lvl_change(1) -- Start the game at level 1
end

Receiving user input and manipulating game assets

In keeping with the standard of creating worker functions, I’m now setting up the default controller for user input to manipulate the main actor. As you’ll see when I call this function from the built game loop, it will keep the code compartmentalized and easier to maintain.

-- character move function
function move_actor(bl, br) -- Sprite user input receiver, params are the left and right boundaries 
	actor.tmr = actor.tmr+1 -- Interal timer to activate waiting animations
	if actor.tmr>=10 then -- After 1/3 of sec, jump to sprite 6
		actor.sprt = 6
	end
	if actor.tmr >= 60 then -- After 2 sec jump frame 8
		actor.sprt = 8
	end
	if actor.tmr >= 62 then -- And jump back to frame 6, 
		actor.sprt = 6
		actor.tmr = 0 -- Restart timer
	end	

	if btn(1) then -- Built in function that receives button input, in this case the right arrow
		if actor.x < br then -- If sprite is within the right boundries actor.flp = false -- Set deafult direction of sprite actor.x+=1.5 -- Progress the sprite along the x axis actor.sprt += sprite_animator(2) -- Animate the sprite by calling the sprite_animator function actor.tmr = 0 -- Reset internal timer if actor.sprt>=6 then -- Set the max number frames to animate
				actor.sprt = 0 -- Reset the frame number, creating a loop
			end
		end
	elseif btn(0) then
		if actor.x > bl then
			actor.flp = true -- Flip the direction of the sprite
			actor.x-=1.5 -- Move the sprite to the left
			actor.sprt+=sprite_animator(2)
			actor.tmr = 0

			if actor.sprt>=6 then
				actor.sprt = 0
			end
		end
	end
end

To further increase the reusability of the code base, I’ve created a dedicated sprite animator function that receives the number of frames to animate by and returns the result to the referencing function. The benefits of this practice will be felt as I start introducing other sprites that require animation later in the game.

function sprite_animator(x) -- This function receives the number of frames to animate by, increaments by the supplied amount and returns the value back calling user input function
	local y = 0 
	y += x
	return y
end

Finally, I’m nesting all of the worker functions into the main game loop and the screen rendering loop, _update and _draw. This style guide gives me an expandable game engine to progress from, it’s clear to see what is happening and it’s easy to remove functions for debugging purposes.

function draw_lvl() -- Abstracting the built in draw function to render the required level
	if current_lvl.number==1 then 
		rectfill(apartm.x0,apartm.y0,apartm.x1,apartm.y1, 6) -- Create a simple grey block to illustrate level boundries 
	end
end

function _update() -- Main game loop called at 30fps
	if current_lvl.number==1 then -- Define the boundries for level 1
		bnd_left = apartm.x0
		bnd_right = apartm.x1 - 14
	end
	
	move_actor(bnd_left, bnd_right) -- Call to user input function
end

function _draw() -- Write pixels to view at 30fps
	cls() -- Clear thew screen
	draw_lvl() -- Level rendering
	spr(actor.sprt,actor.x,actor.y,2,2,actor.flp) -- Draw the main sprite with the modified sprite properties 
end

There we have the foundations for an adventure game! If you would like to try out the code on your own Pico-8, feel free to download the working cart here.

Next up, I’ll be adding some scene elements and artwork.

Leave a Reply

Your email address will not be published. Required fields are marked *