Love

OUYA: Pico-8's Fantasy Console

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!

The Pico-8 Console

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

Cute!

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

It's LÖVE Time! No, dev, no!

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

Lovely!

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

OUYA

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

Someone mail me this

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.

Vi8

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.

About Seppi

Howdy; my name's Seppi, and I'm an indie game developer. I am an active member of the LÖVE community. I've been making games for years now, and I'm always interested in helping prospective indie developers out.

One of the paradoxes I've learned over the years, especially as a software developer, is the more I know, the more I realize I don't know. I quote;

Only a fool would take anything posted here as fact.

Questions, comments or insults? Feel free to leave in the comments section, or contact me; you can hit me up on twitter @josefnpat

Subscribe to RSS - Love