I recently discovered this small, yet interesting game engine, called Pico-8. The concept is simple; it's a fantasy console that pays homage to an older era of video game development, taking cue from classic systems like the Commodore 64, BBC Micro, ZX Spectrum and more.
Yet as of now it only runs in browsers and as desktop applications!
So why isn't there a console for Pico8? Source: Art by @bitmOO
More about Pico8
Pico8 enforces limitations that can create hostile reactions, such as:
- I only have 128x128 spritesheet to work with! What?
- How in gods name is this Lua? It's missing some of the most important functions!
- Where did my floating point precision go?
- Why do I only have sixteen colors? I'm running a 24-bit color monitor!
- How do I play music and sound effects at the same time?
Overcoming these barriers does seem a bit odd, considering the smartphones we have in our pockets are multitudes faster than the computers used in the older apollo space missions, but it does provide for some great entertainment (or torture some might conclude.)
As an inquisitve programmer, I documented Pico-8's version of Lua and went into what one might consider way too much detail. Doing this did give me an understanding of what is and what isn't available to the developer.
To put this post into context of the OUYA, I personally contributed a lot of the documentation for the Love-android-sdl2 project by Martin Felis. A simple byproduct of writing much of the documentation is that now I understand how to deploy games written for the LÖVE Framework on android devices, one of them being the OUYA.
What's the Gameplan?
Putting one and one together (or perhaps one and one and one and one), I decided that I was going to port a small game I wrote for Pico8 to the OUYA using;
- Pico8 to ensure that the codebase was Pico8 compatible.
- PicoLove, a pico8 interpreter by fstf, so that I could package the game as a LÖVE game.
- LÖVE 0.9.2 to ensure that the game was being interpreted correctly by PicoLove.
- Love-Android-SDL2 to deploy the game to an APK that could be installed on an Android device.
- An OUYA for a testing the game in a real environment.
So all this information boils down to the ultimate question;
Can I use the OUYA as the "hardware" component for the Pico8 engine?
Pico8
The Pico8 Engine in action. Source: Lexaloffle
Developing this game wasn't all that painful. With a strong familiarity with Lua 5.2 and a good idea how to get around the pitfalls of the Pico8 Lua varient, I was able to create an interesting and colorful top down space shooter for two players.
I had some issues at first, considering that the Pico8 editor is abysmal (one can easily argue that this is on purpose.) My understanding was that while I didn't have a problem with the machine's limitations, there was no way I was going to give up my workflow just for the sake of nostalgia. Many "old school" game developers (and newer ones as well) use a much faster and more powerful machine in tandem with the actual piece of hardware they were deploying to.
I was able to figure out Pico8's *.p8
data format easily enough, and wrote some lower level unix-like utility scripts for working with them. Uncreatively I named them Pico8Utils.
I found myself modeling a lot of LÖVE's API, such as;
I wrote a function similar to love.graphics.printf
, which allowed me to center text when drawing it:
function printf(string,x,y,width,alignment)
if alignment == "center" then
print(string,x+(width-4*#string)/2,y)
elseif alignment == "left" then
print(string,x,y)
elseif alignment == "right" then
print(string,x+width-4*#string,y)
else
printh("invalid alignment")
end
end
Using objects to make the game stateful. For example;
states = {
main_menu = { draw = function() end, update = function() end }
game = { draw = function() end, update = function() end }
}
current_state = states.main_menu
function _update()
current_state.update(1/30)
end
function _draw()
current_state.draw()
end
Writing helper functions to clarify user input controls.
_btnstr_data = {}
_btnstr_data.l = 0
_btnstr_data.r = 1
_btnstr_data.u = 2
_btnstr_data.d = 3
_btnstr_data.a = 4
_btnstr_data.b = 5
function btnstr(s,p)
return btn(_btnstr_data[s],p or 0)
end
function btnpstr(s,p)
return btnp(_btnstr_data[s],p or 0)
end
While there is a lot of talk on the Pico8 forums about the implied token and character limit, this game was not large enough for me to worry about size optimization.
PicoLove & LÖVE 0.9.2
Common misconceptions of the author. Source: A meme edited by the author
This step led me to believe that the remainder of the project would be easy. The only notable thing done for this stage was that I replaced the nogame.p8
file with the game itself. After making a few controller tweaks, I had a working .love
package that would run against the LÖVE framework.
Love-Android-SDL2
The Love-Android-SDL Logo Source: love-android-sdl2
Building and packaging the *.apk
with the *.love
file was not so hard, it was jut a matter of following along with my documentation. I was able to produce a package and loaded it on my device with adb install -r *.apk
.
Then bad things started happening;
Consistent crashes when the OpenAL bindings were called. PicoLove uses LuaJIT's ffi
bindings to access the OpenAL shared object to generate sounds. Unfortunately it couldn't find it on Android. A little time with arecord
and a dirty hack later, everything was working:
__pico_sfx = {}
for i = 1,10 do
__pico_sfx[i] = love.audio.newSource("sfx/"..i..".ogg")
end
function sfx(n)
if __pico_sfx[n] then
__pico_sfx[n]:stop()
__pico_sfx[n]:play()
end
end
Shaders were written in a way that they were not OpenGLES compatible. I had to start writing hacky workarounds to re-implement them so the palleting wouldn't be offloaded to the GPU. After writing a color table and setting the appropriate colors, I then disabled shaders in a very lazy way;
love.graphics.newShader = function()
return {send = function() end}
end
love.graphics.setShader = function() end
This stuff was pretty hacky, I admit, but I had a vision in mind and was trying to keep the scope of the project down.
There is a lot of work to be done on PicoLove if it is going to run on Android with the entire Pico8 API. I will admit I didn't use much of the Pico8 API in my game, so porting the majority of what I did do was easy, but many functions have yet to be updated and tested on Android.
OUYA Specific configurations
This is an OUYA, in case you haven't seen one. Source: Wikipedia
PicoLove showed some interest in deploying to Android, but lacked some of the callbacks required for joystick controllers. For the OUYA, the most important part was joystick integration. Modeling love.mousepressed
and love.mousereleased
functions after the PicoLove API, I was able to create love.joystickpressed
and love.joystickreleased
variants.
It took a little fiddling and usage of adb logcat
to figure out the joystick mapping, but I was able to get something that worked.
__joystick_table = {
14,--DLEFT
15,--DRIGHT
12,--DUP
13,--DDOWN
1,--[O]UYA (A LOCATION)
2,--OUY[A] (B LOCATION),
}
function love.joystickpressed(joystick,button)
local count
for ccount,checkjoystick in pairs(love.joystick.getJoysticks()) do
local js_id = joystick:getID()
local cjs_id = checkjoystick:getID()
if cjs_id == js_id then
count = ccount-1
end
end
if count then
for imap,map in pairs(__joystick_table) do
if map == button then
log("pressed "..button.." for player "..count)
__pico_keypressed[count][imap-1] = -1
break
end
end
end
end
function love.joystickreleased(joystick,button)
local count
for ccount,checkjoystick in pairs(love.joystick.getJoysticks()) do
local js_id = joystick:getID()
local cjs_id = checkjoystick:getID()
if cjs_id == js_id then
count = ccount-1
end
end
if count then
for imap,map in pairs(__joystick_table) do
if map == button then
log("released"..button.." for player "..count)
__pico_keypressed[count][imap-1] = nil
break
end
end
end
end
Preparing the game for OUYA specifically wasn't all that complicated considering the majority of the implementation was joystick callbacks. I imagine that a touchscreen would have made for a more complicated interface.
Retrospect and the future
This could totally be a thing. Why not? Source: @bitmOO
There were some obvious issues in this gigantic process, but I feel like with a little more polish at every stage and there could be a real "marketplace" running Pico8 games on the OUYA or other Android microconsoles.
The big takeaway here for me is that it is possible and works well. This could give way to an "app" on the OUYA, GameStick or FireTV that could play any Pico8 compatible game. Perhaps when PicoLove matures to a stable point, one could coordinate with lexaloffle to create a parsable API that would allow this "app" to load content from the site dynamically. Perhaps a gamestation in a gamestation?
While there is much discussion about the OUYA being a contender in the microconsole market, perhaps the Pico8 community could embrace the limitations of the OUYA by supporting it with a fantasy console founded on technical limitations.
Game over, man. Source: itch.io
If you are interested in playing Vi8, the game mentioned in this article, it is free for the web/desktop version and only $1 for the OUYA release.