Hello everyone! This is my first small project for PICO-8 in the puzzle platformer genre. I hope you will like it!

Controls
Use the ⬅️ and ➡️ keys to move the character
To jump, use the button❎
In case of defeat, use the keyboard shortcut ⬇️ and 🅾️ to restart
Rules
The goal of the game is to complete all the levels and score as many points as possible.
To complete the level, you need to reach the flag hidden behind the obstacles. Collect the keys on the level to destroy the obstacles.
To score points, collect coins on the level, open chests and try to save time.
Assets of other authors


Well here's a weird little tools project I spent a few evenings on-- a way to write simple short PICO8 programs on my phone while on the go:

Sometimes I find myself with my phone in a long line, at the DMV, on a train or plane-- I can read news, doomscroll, or play games-- but wouldn't it be neat if I could create little games/animations? I've seen past brainstorms about development on mobile-- the main suggestions seemed to be to attach a bluetooth keyboard+mouse and use Education Edition, or set up a VPN + remote desktop connection to PICO8 running on a home machine, neither of which is as convenient for brief impulsive doodling. But I realized-- if I can understand the PICO8 @URL format used by Education Edition, perhaps I could generate that from code I write on my phone...
A few evenings of poking around led me to this strange hack: a spreadsheet-as-IDE/wrapper. This gives me a way to write code in an existing app on my phone with a virtual keyboard, with copy/paste, undo, cloud sync, and all those quality of life features "for free", and then use a few formulas in the spreadsheet to translate the code into a b64-encoded URL at https://pico-8-edu.com/ that I can run. The development is far inferior to the real PICO8 environment in terms of features, iteration speed, and so on, but I've found it fun creating little animations with thumb typing in 20 minutes of downtime here and there, so I figured I'd share it.




You’re Arlo, a farmer racing to save your family’s canola farm from bankruptcy. With olive oil dominating the market, canola prices have crashed. Grow, harvest, and sell canola to pay off your loan before the bank takes the farm. Can you bring Haims Ridge Farm back to life?
Controls
Move - arrow keys ➡️⬆️⬇️⬅️
Switch Tool - Z or C or N [O button]
Interact - X or V or M [X button]
Rules/Gameplay
- Grow and harvest canola, then deliver it to the silos to earn money.
- Use your earnings to buy upgrades at the barn.




It seems that widgets will not load if /appdata/system/startup.lua
does not exist.
I believe this is a new issue that appears in version 0.2.0. I cannot confirm this, but...
The relevant code is from line 203 of /system/startup.lua
.
Additionally, it seems that simply creating startup.lua
in /appdata/system
, even without any content, resolves the issue.

I based this post on an article by nerdyteachers
NB: to make the code easier to read, the examples shown here contain spaces, which is not recommended in Pico 8 to save space.
How to write a class~ish
We will start with a simple class:
function monster(x, y, sp) m={x=x,y=y,sp=sp or 0} function m.update(s) s.x+=1 end function m.draw(s) spr(s.s,s.x,s.y) end return m end |
The word class is a bit of a misnomer, because in reality monster
is just a function.
This function constructs the class instance, using a table
.
Attributs
Inside the table
You will write the attributes of your class:
function monster(x,y) p={x=x,y=x} ... |
Note that the function's parameters act as constructors, allowing arguments to be passed to the class.
Methods
There are two ways to write a method with a :
or with a .
.
The first way is the more usual in Lua, because in this way you can omit the parameter reserved for the class instance.
The parameter self
automatically exists in your function without declaration.
The other way ask you to declare, in first place, a parameter reserved for the class instance.
In short, function m:draw()
can be written like that function m.draw(self)
.
We will prefer the second way because it's make economize tokens using this solution.
For instance, the method draw will look like that
function m.draw(s) spr(s.sp,s.x,s.y) end |
Using the class
To create an instance of the class, you just need to use the function mo=monster(64,64,0)
And to call a method is also simple mo:update
Note that using :
as a link between the instance and the method, is a way of avoiding re-passing the instance to the method. Simply put, writing mo:draw()
is a way to avoid writing mo.draw(mo)
Here is an example of code using the class monster
function _init() mo=monster(64, 64, 0) end function _update() mo:update() end function _draw() cls() mo:draw() end |
Inheritance~ish
Here is the spider class that inherits from monster
function spider(x, y) spi=monster(x,y,0) return spi end |
It's yet really basic, but it works.
New attribute
If you want to add a new attribute you can proceed like it was a table
, (because it's a table
)
function spider(x, y) spi=monster(x,y,0) spi.hp=50 return spi end |
New method
To add another method, you just do as you did before.
function spider(x, y) spi=monster(x,y,0) spi.hp=50 function spi.damage(s,d) s.hp-=d end return spi end |
Overwrite a method
The most "complex" task is the overwriting.
Before, you need to understand that a function is store in memory, in the same way that a variable.
For instance, if I want, I can store print
in a variable a and use a
as print
.
a = print function _draw() a("This is a test.", 5, 10) end |
It works because when you write a=print
, a
contains a reference to the print
function.
Now that we are aware of that, we can proceed to overwriting.
If I want to add a behavior to my spider
on update
, in addition to the existing code already inside the update
method from monster
, I will proceed like this:
... spi.supspiupdate=spi.update function spi.update(s) spi.supspiupdate(s) s.y +=1 end ... |
First you store the update method from the ancestor inside a variable here spi.supspiupdate
This variable need to be unique inside your class (including parent and child).
Then you can overwrite your function.
You can use the variable with the reference of the ancestor method, inside the new method.
Note that we pass directly the instance to the function (s
passed as argument to s.supspiupdate
).
Like this, you can have almost every feature expected by object-oriented programming.
function monster(x, y, sp) m={x=x,y=y,sp=sp or 0} function m.update(s) s.x+=1 end function m.draw(s) spr(s.s,s.x,s.y) end return m end function spider(x, y) spi=monster(x,y,0) spi.hp=50 function spi.damage(s,d) spi.hp-=d end spi.supspiupdate=spi.update function spi.update(s) spi.supspiupdate(s) s.y +=1 end return spi end |
function _init() sp=spider(64, 64) end function _update() sp:update() end function _draw() cls() sp:draw() end |
So i was trying to make an ASCII style procedural runner with a death wall that constantly approaches and enemies to dodge and spike traps to avoid. Im kinda stuck and am getting an error on line 29. Any ideas?
-- ASCII RUN
-- Constants
player = {x=20, y=64, vy=0, on_ground=false, sprite='@'}
ground_y = 96
gravity = 0.3
jump_power = -2.5
death_wall_x = 0
speed = 1.5
score = 0
level = {}
define_tiles = {
[' '] = {solid=false}, -- Empty space
['#'] = {solid=true}, -- Ground
['^'] = {solid=true, deadly=true}, -- Spikes
['E'] = {solid=true, deadly=true} -- Enemy
}
-- Generate procedural level chunks
function generate_chunk()
local chunk = {}
for i=1,16 do
local tile = ' '
if i > 12 then
tile = '#' -- Ground
elseif i == 12 and math.random(1, 5) == 1 then
tile = '^' -- Spikes
elseif i == 11 and math.random(1, 10) == 1 then
tile = 'E' -- Enemy
end
add(chunk, tile)
end
return chunk
end
function init_level()
for i=1,32 do
add(level, generate_chunk())
end
end
init_level()
function update_player()
-- Gravity
player.vy += gravity
player.y += player.vy
-- Collision with ground
if player.y > ground_y then
player.y = ground_y
player.vy = 0
player.on_ground = true
else
player.on_ground = false
end
-- Jumping
if btnp(4) and player.on_ground then
player.vy = jump_power
end
end
function update_level()
-- Scroll the level left
death_wall_x += speed
score += 1
-- Remove old chunks and add new ones
if #level > 0 and death_wall_x % 8 == 0 then
deli(level, 1)
add(level, generate_chunk())
end
end
function check_collision()
local px = 2 -- Player's x position in array (adjusted for indexing)
local py = flr((player.y - 64) / 8) + 1
if level


Sweep life's troubles away!

Controls
Move with the directions/arrows
Sweep left and right with the buttons (sweep directions can be flipped in the pause menu)
Hold both buttons to auto sweep quickly
How to play
Sweep the various troubles into groups of 3-5 before sweeping them off the right side to score points.
Game over when the time runs out, a group of 6 forms, or 42 troubles are on the board.
Made with help from
ThaCuber's vector.p8
Gruber's Pico-8 Tunes Vol. 2
Updates
Mar 23, 2025


an interactive drawing tool to create and explore unpredictably chaotic black-and-white graphics