PICO-8: How do I shoot? Dynamic objects and tables

Shooting in Pico-8

PICO-8 is a small and sweet game engine aka fantasy console. This article explains how to add and remove objects like bullets to a game at playtime in PICO-8.

One question that I always get asked is: how do I add and remove multiple objects to a game dynamically. Stuff like projectiles, bullets or particles. No problem: I will show how to create objects on-the-fly in PICO-8.

BulletOne, BulletTwo, BulletThree, BulletFour

Probably the first idea is to make a new table for each object and saving it on a variable. We can just define a bunch of bullets and then set them when we press shoot.

local bulletOne = {
	x = 32, y = 32, 
	active = false, 
	draw = function(self)
		circfill(self.x, self.y, 7, 8)
	end
}

In our _draw callback we execute the draw() function of bulletOne: bulletOne:draw(). Making tables might be ok but having one variable for one object is very limiting. Is there a better way?

There must be a better way

Arrays can save you a lot of writing and duplicate code. Instead of using individual variables we can use a holding object.

bullets = {}
add(bullets, {
	x = 32, y = 32, 
	active = false, 
	draw = function(self)
		circfill(self.x, self.y, 7, 8)
	end
})

This way we can add as many objects to the bullets table as we like. And without creating a new variable every time.

Use PICO-8 iterators

How do we call the draw function for all the bullets in our _draw callback function? We use PICO-8 all() iterator function:

function _draw()
    for bullet in all(bullets) do
        bullet:draw()
    end
end

 

Additional: Global vs Local Variables

Global means that it can be accessed anywhere in your program. But there is also a table for the local scope. Local means that it is only accessible in the current part of your program. You can assign a variable to the local scope using the local keyword.

 

Arrays and Tables in Pico-8

2D Arrays

Screen Shot 2016-07-16 at 17.36.17

For beginners, I suggest you use concatenation to index 2D arrays. Create a new object in a 2D cell at (i,j) in a table called myArray with the following code:

myArray[i..","..j] = {}

To iterate over all objects in the myArray you can use the pairs iterator. Caution: the objects are not ordered when using pairs!

for k,v in pairs(myArray) do
	-- v is the cell object
	-- k is a string in the form of "i,j"
end

If we want to access the objects in a particular order we should use nested for loops:

for i=1, 8 do
	for j=1, 8 do
		local cell = myArray[i..","..j] 
		-- do stuff with the cell
	end
end

Objects And Container

PICO-8_5

Entities like the spaceship in this gif are objects. Containers for objects are special in Pico-8 because we have a couple of built-in functions to help us manage insertion and deletion. I strongly suggest to use add(), del() and all() for container and entity management.

Create and add an object to a table with add():

local entities = {}
local player = {
	x = 3,
	y = 3,
	sprite = 5
}
add(entities, player)

In your _update or _draw callbacks, you will most likely want to loop over all objects. You should use all() for that:

for entity in all(entities) do
	-- do stuff here
end

You can use del() to remove an object from the container even while iterating over the container:

for entity in all(entities) do
	del(entities, entity)
end

This only works with all() and del() together! This is great for games where you have dynamic objects such as bullets, effects or timed events that are added and removed dynamically.

I hope that these two hints help you to get started with the awesome Pico-8 engine. For advanced users, other methods might be more efficient. I recommend reading the Pico-8 Docs or the PIL for more information.