Log In  
Follow
NuSan

Having fun in Pico 8. Making prototypes in Unity. Working as engine programmer at Dontnod.
@[email protected]
Itch.io page

SHOW MORE


load #function_tool-2
// to load from inside Picotron

I made a useful tool to look at what parameters are expected from picotron's api
You can also see in what file it is implemented and inspect the code!

It only works for functions that are implemented in lua, when it's done in c it's not visible, and some lua functions don't have much code in them and redirect a variable amount of parameters toward a c function

I also exported the list of functions with their parameters if I could find them:

table   USERDATA
C   USERDATA:__add
C   USERDATA:__band
C   USERDATA:__bor
C   USERDATA:__bxor
C   USERDATA:__div
C   USERDATA:__index
C   USERDATA:__len
C   USERDATA:__mod
C   USERDATA:__mul
C   USERDATA:__newindex
C   USERDATA:__sub
C   USERDATA:__tostring
C   USERDATA:add
C   USERDATA:attribs
C   USERDATA:band
C   USERDATA:bor
C   USERDATA:bxor
C   USERDATA:clear
C   USERDATA:convert
C   USERDATA:copy
C   USERDATA:cross
C   USERDATA:distance
C   USERDATA:div
C   USERDATA:dot
C   USERDATA:get
C   USERDATA:height
C   USERDATA:magnitude
C   USERDATA:matmul
C   USERDATA:matmul2d
C   USERDATA:matmul3d
C   USERDATA:mod
C   USERDATA:mul
C   USERDATA:set
C   USERDATA:sort
C   USERDATA:sub
C   USERDATA:transpose
C   USERDATA:width
table   _G
string  _VERSION
C   _fetch_local
C   _fetch_remote
Lua _process_event_messages ()
C   _signal
C   _store_local
C   _update_buttons
Lua abs (a)
C   add
Lua add_line (s)
Lua all (c)
C   apply_diff
C   assert
C   atan2
C   blit
C   btn
C   btnp
C   camera
C   cd
C   chr
C   circ
C   circfill
Lua clear_key (scancode)
C   clip
C   cls
C   cocreate
C   collectgarbage
C   color
Lua coresume (c, ...)
table   coroutine
C   coroutine:close
C   coroutine:create
C   coroutine:isyieldable
C   coroutine:resume
C   coroutine:running
C   coroutine:status
C   coroutine:wrap
C   coroutine:yield
C   cos
C   costatus
C   count
Lua cp (f0, f1)
C   create_diff
Lua create_gui (head_el)
Lua create_process (prog_name, env_patch, do_debug)
Lua create_undo_stack (, ...)
C   cursor
Lua date (format)
table   debug
C   debug:debug
C   debug:gethook
C   debug:getinfo
C   debug:getlocal
C   debug:getmetatable
C   debug:getregistry
C   debug:getupvalue
C   debug:getuservalue
C   debug:setcstacklimit
C   debug:sethook
C   debug:setlocal
C   debug:setmetatable
C   debug:setupvalue
C   debug:setuservalue
C   debug:traceback
C   debug:upvalueid
C   debug:upvaluejoin
C   del
C   deli
C   dtime
Lua env ()
C   error
Lua exit (exit_code)
Lua fetch (location, ...)
Lua fetch_metadata (filename)
C   fget
C   fillp
C   flip
C   flr
Lua foreach (c, _f)
C   fset
C   fstat
C   fullpath
Lua function_info (fun)
C   get
Lua get_clipboard ()
Lua get_display ()
Lua get_draw_target ()
Lua get_spr (index)
C   getmetatable
Lua include (filename)
C   ipairs
Lua key (scancode)
Lua keyp (scancode)
C   line
C   load
C   ls
Lua map (ud, b, ...)
table   math
C   math:abs
C   math:acos
C   math:asin
C   math:atan
C   math:ceil
C   math:cos
C   math:deg
C   math:exp
C   math:floor
C   math:fmod
number  math:huge
C   math:log
C   math:max
number  math:maxinteger
C   math:min
number  math:mininteger
C   math:modf
number  math:pi
C   math:rad
C   math:random
C   math:randomseed
C   math:sin
C   math:sqrt
C   math:tan
C   math:tointeger
C   math:type
C   math:ult
Lua max (a, b)
C   memcpy
Lua memmap (addr, a, offset, len)
C   memset
Lua menuitem (m, a, b)
Lua mid (a, b, c)
Lua min (a, b)
Lua mkdir (p)
C   mount
Lua mouse ()
C   music
Lua mv (src, dest)
C   next
C   note
Lua notify (msg_str)
Lua on_event (event, f)
C   ord
C   oval
C   ovalfill
C   pack
C   pairs
C   pal
C   palt
C   pcall
C   peek
C   peek2
C   peek4
C   peek8
Lua peektext (i)
C   pget
C   pid
Lua pod (obj, flags, meta)
C   poke
C   poke2
C   poke4
C   poke8
Lua print (str, x, y, col)
Lua printh (str)
C   pset
C   pwd
Lua pwf ()
C   rawequal
C   rawget
C   rawlen
C   rawset
Lua readtext (clear_remaining)
C   rect
C   rectfill
Lua reset ()
Lua rm (f0)
C   rnd
C   select
C   send_message
C   set
Lua set_clipboard (, ...)
Lua set_draw_target (d)
Lua set_spr (index, s, flags_val)
C   setmetatable
C   sfx
Lua sgn (a)
C   sin
C   split
C   spr
C   sqrt
C   srand
C   sspr
C   stat
Lua stop (txt, ...)
Lua store (location, obj, meta)
Lua store_metadata (filename, meta)
table   string
Lua string:basename (self)
C   string:byte
C   string:char
C   string:dump
Lua string:ext (self)
C   string:find
C   string:format
C   string:gmatch
C   string:gsub
Lua string:hloc (self)
C   string:len
C   string:lower
C   string:match
C   string:pack
C   string:packsize
Lua string:path (self)
C   string:rep
C   string:reverse
C   string:sub
C   string:unpack
C   string:upper
Lua sub (str, ...)
C   t
table   table
C   table:concat
C   table:insert
C   table:move
C   table:pack
C   table:remove
C   table:unpack
Lua theme (which)
C   time
C   tline3d
C   tokenoid
Lua tonum (, ...)
C   tonumber
Lua tostr (val, as_hex)
C   tostring
C   type
Lua unmap (a)
C   unpack
C   unpod
C   userdata
table   utf8
C   utf8:char
string  utf8:charpattern
C   utf8:codepoint
C   utf8:codes
C   utf8:len
C   utf8:offset
C   vec
Lua vid (mode)
C   warn
Lua window (w, h, attribs)
Lua wrangle_working_file (save_state, load_state, untitled_filename, get_hlocation, set_hlocation)
C   yield

P#143479 2024-03-17 11:40 ( Edited 2024-03-17 12:33)

SHOW MORE

I extracted all the public names of functions and tables that are visible to Picotron, it might be useful to explore everything that is available
printh doesn't seems to work like in Pico 8 so I couldn't easily make it into a text format, so here is just a screenshot of all the globals functions (in white), tables (in dark blue) and others (in red):

And I also got the functions inside all the tables:

P#143148 2024-03-15 16:40 ( Edited 2024-03-15 16:41)

SHOW MORE

Cart #resmguse-0 | 2024-02-04 | Code ▽ | Embed ▽ | No License
1

Hi, I found some strange behaviors with sfx and music that looks like bugs
( I marked with "X" when it's behaving strangely)

bug 1: releasing loop of an sfx:

  • play a looping sfx
  • release it's loop: sfx(-2)
  • the sfx will complete it's loop and then stop emitting sound
    X but if you check with "stat" you can see that the sfx is still "playing" with no sound
    X it can still block a channel that the music will not be able to use, for ever until it's replaced by another sfx

bug 2: playing a sfx that is already playing in the music

  • play a looping music that contain a specific non-looping sfx using music()
  • play the same sfx using sfx()
  • the sfx will play over the music's channel with the same sfx
    X the music's sfx will not start again once the sfx ended
    X whatever channel was playing the sfx is now blocked forever from playing music, even when going to the next pattern
    X the stat for music still show that music is still playing even if you cannot ear it
    X a new call to music() will be the only way to make the channel working normally again

I understand that those corner cases are rarely used so I get why it's not being a major problem, but I still think it can give some very strange behavior to the sound, potentially blocking all of the music

P#141108 2024-02-04 13:38 ( Edited 2024-02-04 14:07)

SHOW MORE

Cart #smoothride-0 | 2023-09-29 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
9

One button tiny physic-based platformer made in Pico 8 with code under 1024 compressed bytes, made for #Pico1k jam 2023

Use RIGHT ARROW to accelerate when on the ground
Release RIGHT ARROW to fall faster when in the air
Press UP ARROW to start a new level
Press ENTER and select SEED in the menu to start from level 1 if you want a fixed series of levels

Number on top left is a timer for your run of the level, counted in frames (at 60fps), on top right is the sum for all the levels in your session. For example a casual run of all the seeded levels from 1 to 10 took me 14027 frames. My PB for level 1 is 1071.

Source as p8 are available on https://nusan.itch.io/smooth-ride in two versions, the "comment" version is what I coded with additional comments, so it's readable but the "minimal" version uses a tool (https://thisismypassport.github.io/shrinko8/) to makes it even tinier, it's not readable but it fits into the 1k compressed bytes target

P#135090 2023-09-29 07:19 ( Edited 2023-09-29 10:37)

SHOW MORE

Cart #floodedcaves-0 | 2022-04-04 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
139

it's flood season, dig caves to redirect an inevitable rising water until you can save everyone
Also available on itch.io for binaries https://nusan.itch.io/flooded-caves

This game has been made in 48h for game jam Ludum Dare 50

Your digging power is limited, it recharges over time. At regular interval, you can save someone by sending your rescue team

You can play the game using a keyboard (action is key C ), a gamepad or a mouse

P#109673 2022-04-04 11:40 ( Edited 2022-04-04 11:41)

SHOW MORE

Cart #damiandflo-0 | 2021-01-31 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
21

Game is also available on Itch.io: nusan.itch.io/dami-and-flo

Dami and Flo are two workers on a delivery train. At each station, they have to take input packages at the bottom and store them somewhere in the wagon so they can be easily delivered at a future train stop.

There is a one player mode or a two player local splitscreen mode. You can choose your difficulty with "Timer" option and "Items" quantity option. You can also choose to have more than one wagon, but it can be very difficult especially if you are not playing with a friend.

Between stations, take your time to sort and store all the packages in the wagon. Clear the input area as fast as possible so new packages can be inserted at the next stop. Delivery requests for the next station are displayed at the top of the screen.

You can drop off requested items in the output area, they will be picked up automatically at the next station. You can stack items on the ground up to 5 items high and your character can carry up to 10 items at a time.

Controls:

Player 1:

pick package: Z or C or N
drop package: X or V or M
move character: ARROW KEYS
Player 2:

pick package: LEFT SHIFT
drop package: TAB or W or Q or A
move character: S+F+E+D
Open main menu: ENTER to se stats or toggle the music

You can also use a game controller

P#87011 2021-01-31 14:27

SHOW MORE

Cart #snowballnusan-0 | 2019-10-06 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
17

Small game made for Ludum Dare 45 in 48h.

Roll your way through 7 levels and collect snow to make a happy snowman.

Controls:

  • left/right: move
  • up: jump
  • x/c: reset level

The Ludum Dare page is here if you want to vote: https://ldjam.com/events/ludum-dare/45/snowball

P#68594 2019-10-06 22:14 ( Edited 2019-10-06 22:42)

SHOW MORE

Cart #48406 | 2018-01-20 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
99

DeFacto is a game about building a large automated factory chain.
You must install miners to collect raw resources, transform them in furnaces, and combine them in factories.
The final goal is to send rockets full of robots and by doing so continue to explore the galaxy.
DeFacto is heavily inspired by the game Factorio.
You can also play and support the game on : nusan.itch.io/defacto

Controls :
The game can be played either with the mouse or keyboard

Keyboard :
C : Use selected tool/Interact with factories
V : Open tool selection panel/quit menu
enter : Open main menu (save, load, toggle music ...)

Mouse :
Left clic : Interact/Use selected tool
Right clic : Open tool selection panel/quit menu

To save your game, press enter and select save (button C)

To construct conveyor, inserter or bridge :
you have to keep pressing C/left clic and then enter the direction (arrow key or mouse motion) where the conveyor/inserter/bridge will put it's products
if you only press c/left clic without entering direction, it will place the conveyor, inserter or bridge along the last used orientation

Bridges can also be used to take and insert product, and are faster than inserters.

Starting tips :

  • start by opening the tool panel (V/Right clic) and choosing the miner tool
  • place the miner onto a raw resource field (C/Left clic), the miner will start filling
  • open the tool panel and select the inserter tool
  • place the inserter against the miner by pressing and holding C/left clic and then entering the direction where you want to put the products
  • open the tool panel and select the selling station
  • place the selling station at the end of the inserter
  • if the inserter is oriented correctly, it will start moving resources collect by the miner to the selling station and you will start gaining money
  • once you have a bit more money, start putting raw resources into furnaces, the resulting products will sell at higher prices
  • the next step is to use factories, choose the recipe (C/Left clic on the factory) and insert the required input products to make more advanced ones
P#48407 2018-01-20 14:00 ( Edited 2018-02-24 21:07)

SHOW MORE

Cart #46982 | 2017-12-04 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
12

"Juggle Jam" is more of a toy than a game. It's based on some mathematical theory behind juggle patterns. It has been made in 48h during LDJAM 40.
You can :

  • load premade juggling patterns
  • create your own juggling patterns using a dedicated in-game editor
  • create a random juggling pattern and let it hypnotise you

If you know how to juggle, you can try to follow the motion and the patterns.
Let me know if you have managed to reproduce some patterns with real balls.
Time spent during the jam actually juggling instead of coding : too much

Controls :

  • keys C and V and arrow keys
  • return : menu/help
    In play mode :
  • c:toggle view
  • v:go to editor mode
    In editor mode :
  • c:grab a ball or a throwing node
  • v:go to play mode

This idea was inspired by a video from Numberphile, you can check it if you want to know more about some math theory of juggling :
https://www.youtube.com/watch?v=7dwgusHjA0Y

You can find the Ludum Dare page here :
https://ldjam.com/events/ludum-dare/40/juggle-jam

P#46983 2017-12-03 21:27 ( Edited 2017-12-05 21:45)

SHOW MORE

Plan :

  1. Inception
  2. Gameplay
  3. How to render 3D meshes in Pico 8
  4. Pre-rendered magic
  5. Extracting meshes from Alone in the Dark
  6. Character animation
  7. Memory layout, packing and ram management
  8. Camera placement, tools
  9. Interactions, objects animations, sounds
  10. Optimization
  11. Triangle strips
  12. Future

Disclaimer : my english skills are a bit low and I wrote that post a litlle too fast ...

The final project can be found here

1. Inception

The idea of making a Pico 8 demake of the classic cult game Alone In The Dark started at Halloween 2015. I was searching for a mockup to do using Pico 8 color palette, and Alone in the Dark seemed a pretty good fit. After looking at it, the original is a lot more detailed than what would be possible in Pico 8. Getting from like VGA 256 colors to 128x128 16 colors would not be easy. But the mesh complexity in Alone in the Dark seemed doable in Pico 8. I had just finished a small 3D demo in Pico 8 so I knew that I could probably draw the character at 30 fps and pre-render the background.

The mockup that started everything :

The mockup was well received, but after that I was quite afraid by the quantity of work to do to make it real, so I let the project sleep. Until 3 month later, I get a twitter reply by Frederick Raynal himself, director of the original Alone in the dark. It turns out that he is quite interested in Pico 8.

This was enough to give me motivation to push the project forward.

You can play in you browser to the original Alone in the Dark here

2. Gameplay

Alone in the Dark is a good fit for Pico 8 as the original can be played with only 2 buttons beside direction keys. One button is to open an inventory, where you can choose a “stance” for your character (like “search”, “push”, “fight” …) or see your items. The second button apply your stance around you, so your character will search, or push something around or start fighting (using direction keys to choose fight animations).
For each item in the inventory, you can do a large quantity of actions, like read a letter, throw an object, put it on the floor, open boxes, drink potions … This is an heritage of the text-based adventure game I think. Nowadays everything is contextual, but back then you would have to know which precise action to do with every item and background object.
I first wanted to do all of that. But it was tricky on several level. First, the fighting needed a lot of animations, which is not easy to do. I also wanted an exploration game more than an action game. By replaying the original, I saw that you could bypass nearly every combat in the first two floors. So I decided to leave the fight out, and make the player find “peaceful” ways to progress.
The complex inventory system of Alone in the Dark was a bit much for Pico 8. With the 128x128 16 colors, there was not enough details to give you hints about what action you could do where. So I did a more “modern” system, where there is a visible prompt when you can do an action somewhere, and the action is contextual. It makes the game really simple, but it fit the format of a short exploration trip much better. With more tokens/time, I would have added some keys icons to show progress. And maybe some Lovecraft books to read ...

3. How to render 3D meshes in Pico 8

I started by using the tools from my previous 3D demo in Pico 8, Space Limit
I was originaly going to remake everything, from characters to background images. So I made in Blender a crappy looking character that vaguely resembled Edward Carnby. My custom-made exporter enable to have a color per triangle.
Here is the Blender plugin i made : plugin blender

My first hand-crafted Edward model :

I also latter modified a mesh stripping sample from coder corner to load, strip and convert meshes to character strings. More on that in the “Triangle strips” section.
My tools produce two strings : a vertex buffer and an index buffer. Each string can be copy/pasted in Pico 8 and some code will decode it and store it in arrays.
The vertex buffer encode the position (x,y,z) of each vertex of the mesh, and the index buffer describe the triangles of the mesh. It stores the index of the 3 vertices making the triangle, as well as the color of the triangle. To make it easy to use, each value is between 0 and 255 and is stored in hexadecimal, so two characters. The color is the only value that can be stored as 1 character, as there is only 16 values, thanks to Pico 8 limitations. The vertex buffer is also linked to a scaling value, to transform from 0-255 to the desired mesh size. The mesh can only have up to 256 vertices, as we store their index as 2 hexadecimal strings, but it’s usually enough.

The objects I made in Blender :

Here is a sample code to decode the “val” vertex buffer string into the “verts” array:

function avlist(verts,scale,val)

    local maxsize = 5.0*scale

    while(#val > 0) do
        local cur = sub(val,1,6)
        val = sub(val,7,#val)

        local x = flr("0x"..sub(cur,1,2))
        local y = flr("0x"..sub(cur,3,4))
        local z = flr("0x"..sub(cur,5,6))

        x = (x/256.0 - 0.5) * maxsize
        y = (y/256.0 - 0.5) * maxsize
        z = (z/256.0 - 0.5) * maxsize
        verts[#verts+1] = newver(x,y,z)
    end
End

Then when we want to draw the mesh, here are the steps :
First transform each vertex in the viewport space :

    local tverts = {}
    local tv = #verts
    for i=1,tv do
        local cur = clone(verts[i])

        local side = 1
        if(cur.z<0) side = -1

        cur = v_add(cur,v_sub(ent.loc,campos))
        local cur2 = clone(cur)
        cur2.x = dot(cur,camright)
        cur2.y = dot(cur,camup)
        cur2.z = dot(cur,camdir)

        local invz = 64.0*(1.0/max((cur2.z),0.1))
        cur2.x = (-cur2.x * invz + 63.5)
        cur2.y = (-cur2.y * invz + 63.5)

        tverts[i] = cur2
    end

The vector camright, camup and camdir are the 3 vectors of the camera space. In the final project, those vector and the campos are pre-transformed with the rotation of the mesh, making possible to turn the main character with a really small cost.
The next step is to sort the triangles. In a modern renderer, we would use a z-buffer but then you need to store it with good precision, and it can hurt your framerate. Each pixel need to be compared to the z and you can’t use a simple rectfill to draw several pixel together.
Sorting can be fast, but come with some issues.
To sort our triangles, we need to compute it’s distance to the camera (or simply the average of the 3 vertices z location). Then we can either sort them completely, which is slow, or iteratively, spreading the work over several frames. We will do a complete sort only when changing camera, and iteratively each frame so the dynamic objects keep beeing about sorted. If you rotate the character too fast, you will see triangles being wrongly sorted. We sort by swapping values in the triangle array, which is persistent between frames. We can even adjust how many passes of sorting we do per frame depending of the framerate.

        local tn = #tris
        for i=1,tn do
            local ct = tris[i]
            ct.avg = tverts[ct.v1].z + tverts[ct.v2].z + tverts[ct.v3].z
        end

        if tele_cam then
            sortalltris(tris)
        else
            sorttrisloop(tris,2)
        end

And finally we will render the triangles on the screen. To make a real 3D renderer, you should here compute the intersection of the triangle with the near clip plane. Depending of the intersection, you would draw up to 2 triangles. To save some framerate, I don’t do that, but it would be required for a generic 3D renderer. It’s only an issue when triangles goes out of the screen and behind the camera. We will have to place our camera carefully to avoid those glitchs.

    for i=1,tn do
        local ct = tris[i]
        local v1 = tverts[ct.v1]
        local v2 = tverts[ct.v2]
        local v3 = tverts[ct.v3]

        -- We check if the triangle is facing the camera or not
        local back = (v2.y-v1.y)*(v3.x-v1.x) - (v2.x-v1.x)*(v3.y-v1.y)

        local minx = min(min(v1.x,v2.x),v3.x)
        local maxx = max(max(v1.x,v2.x),v3.x)
        local miny = min(min(v1.y,v2.y),v3.y)
        local maxy = max(max(v1.y,v2.y),v3.y)
        local minz = min(min(v1.z,v2.z),v3.z)

        -- Compute the bounds of the triangle, cull it if outside the screen
        local clip = maxx < 0 or minx > 128 or maxy < 0 or miny > 128 or minz < 0.01

        if back>=0 and not clip then
            otri(flr(v1.x),flr(v1.y),flr(v2.x),flr(v2.y),flr(v3.x),flr(v3.y),ct.c)
        end
    end

The only part left is the rendering of the triangle itself. Here we separate the triangle in two, with an horizontal slice. Each line will then be rendered using rectfill.

function otri(x1,y1,x2,y2,x3,y3,c)

    if y2<y1 then
        if y3<y2 then
            y1,y3=swap(y1,y3)
            x1,x3=swap(x1,x3)
        else
            y1,y2=swap(y1,y2)
            x1,x2=swap(x1,x2)
        end
    else
        if y3<y1 then
            y1,y3=swap(y1,y3)
            x1,x3=swap(x1,x3)
        end
    end

    y1 += 0.001

    local miny = min(y2,y3)
    local maxy = max(y2,y3)

    local fx = x2
    if y2<y3 then
        fx = x3
    end

    local cl_y1 = clampy(y1)
    local cl_miny = clampy(miny)
    local cl_maxy = clampy(maxy)

    local steps = (x3-x1)/(y3-y1)
    local stepe = (x2-x1)/(y2-y1)

    local sx = steps*(cl_y1-y1)+x1
    local ex = stepe*(cl_y1-y1)+x1

    for y=cl_y1,cl_miny do
        rectfill(sx,y,ex,y,c)
        sx += steps
        ex += stepe
    end

    sx = steps*(miny-y1)+x1
    ex = stepe*(miny-y1)+x1

    local df = 1/(maxy-miny)

    local step2s = (fx-sx) * df
    local step2e = (fx-ex) * df

    local sx2 = sx + step2s*(cl_miny-miny)
    local ex2 = ex + step2e*(cl_miny-miny)

    for y=cl_miny,cl_maxy do
        rectfill(sx2,y,ex2,y,c)
        sx2 += step2s
        ex2 += step2e
    end
end

4. Pre-rendered magic

The main technical trick that Alone in the Dark use is : pre-rendered backgrounds. This made possible having complex environments while using real-time characters. It gave a “cinematic” feel to the game.
In the case of my Pico 8 demake, it’s a bit different. I could not realy store backgrounds in a large quantity inside the limited space of a Pico 8 cartridge. With some compression, it may be possible but only up to a certain point.
Instead of storing backgrounds, I can render them from 3D objects, but only when the camera change. I can store a large quantity of 3D meshes, and reuse them at will. I can also put a lot of camera angles, without using more memory.
Rendering the background only when the camera change leave all the cpu free to draw the dynamic objects (the main character for example). The only issue is that we need to store the background between frame, so we can start each frame with just the background, and draw dynamics on top of it.

Here is a sample code to do that. The function fillsorted() will add meshes in the background and dynamics arrays. The memcpy store and put back the background when needed (in two separate chunks to make it fit nicely where there is space left in memory). The variable need_paste is set to false when there is a camera change.

function _draw()
    if need_paste then
        memcpy(0x6000,0x3E00,0x0400)
        memcpy(0x6400,0x4300,0x1c00)
    else
        cls()

        fillsorted()
        foreach(background, draw_tris)
        bground = {}

        memcpy(0x3E00,0x6000,0x0400)
        memcpy(0x4300,0x6400,0x1c00)
        need_paste = true   
    End

    foreach(dynamics, draw_tris)
end

We don’t want to sort every triangles of every object between them, as it would be slow and not realy frame consistent when objects pass each other. So we sort first each object according to their location before drawing their sorted triangles. In the end I sorted objects using the 2D coordinates on the floor, as it was better than using 3D coordinates of the location.
The background mesh of each room was too big and it didn’t make sense to try to sort it with the static objects in the scene. So there is a list of background room meshes that is draw before everything, and a list of background object meshes drawn on top of it. And then the list of dynamic objects.

The most important issue with prerendering background is that objects from the background cannot pass in front of the main character. So for example the pillars in the cellar would always be behind, which break the immersion of the scene.
My solution was simply to manually select the most problematic objects for each view angle, and draw it dynamically so it’s sorted with the player. There is some tricks to it that I will describe in the “Optimization” section.
When the player is entering/exiting using a door in the second floor, I need a bit of masking so it seem like he pass inside the door. But the background is pre-rendered. So I made some half door covering, that I put in front of each door.

A half-doorway used to cover the character :

5. Extracting meshes from Alone in the Dark

The three characters I ripped from alone in the Dark :

After I tweeted a gif of my first mesh, Frederick Raynal gave me a good idea : using the meshes directly from the original game. I first searched if someone already had ripped the characters and made them available, but no luck. Frederick saved the day again and found an animation/mesh viewer a fan made for Alone in the Dark. As I got the source code of the viewer, I could try to add an export option. But it was a lot of work, so instead I used an openGL interceptor that can save textures and 3D models from “any” openGL game. That way I could directly get a .obj of all dynamic meshes from Alone in the Dark. Obviously the background where not in 3D, so I still needed to make that myself in Blender.
So I ripped Edward, Emily and a zombie mesh. After that I only needed to choose a color for each triangle, trying to mimic the original. The color must comme from the Pico 8 palette of 16 colors of course.

Here is the openGL interceptor I used :
GLIntercept
There was also an amateur open source project called “Free in the Dark” but it’s difficult to find now. Plus with that project the openGL injector kept giving me view-space meshes, which is not very practical.

6. Character animation

I decided quite early that I will not have fighting, so that leaved mainly the character walking animation. I did it procedurally, using only simple maths. I use y and z location of vertices to decide which part of the body it belong. Then using sinuses and offsets between body parts, I tried to give it a walking animation. There is also some interpolation, so the character take some frame to get back to the idle pause. I reset the animation time when the character doesn’t move, so he/she always start at the beginning of the walking loop. The code is a mess, but you can find it in the draw_tris_anim() function.
Both Edward and Emily are animated the same way, with just some slight adjustments to the limits between each body parts.

7. Memory layout, packing and ram management

Making everything fit in the limited space of a Pico 8 cartridge was a bit of a challenge. The layout of data was not completely static, as different data where needed at different times. I needed space to store vertex/index buffers. I needed space to store the backbuffer between frames. I needed space to store collisions.
First, there was the two character mesh data, which was required only in the selection screen. Then we would keep the selected character data in RAM, throw away the other, and would not need the cartridge memory anymore.
To reduce the collision footprint, I compressed it as 1-bit per cell. So each cell would only be traversable or not. After the selection screen, the collision data are put back in the “map” memory location, where the two characters data where stored. Having collision in the map memory make it easy to check and easy to change in gameplay (when moving objects, opening doors …)
The backbuffer between two frames is stored for the most part in the “user data” section of the memory, which is not stored in the cartridge anyway. The small part that doesn’t fit in the “user data” is put at the end of the “sfx” section, leaving about 32 sounds free for music/sfx.

Another issue was the RAM size. I started the project with the 0.1.5 version of Pico 8, which had only 512k of ram. With 0.1.6 it’s now 1024k of ram and it’s a lot easier. I still need to clean all meshes data between each room to save ram. The top floor fit totally in memory, but the second floor does not. Between each room, I clear all meshes (by setting their references to nil) and construct only the required ones from the cartridge data. The same bit of code also decide in what draw list each mesh go (prerendered background, prerendered sorted background, dynamics).
Some meshes are not stored in cartridge data but directly in strings inside the code. I did that because I was nearly out of cartridge space. But it’s quite risky as this will count in the “compressed size” limit, which is one of the main limitation.

8. Camera placement, tools

To make working on the game a bit easier, I made a debug mode where you could change the camera position, the location of it’s target, and the distance between them. The camera position and target position were printed on screen, so I could take notes when placing cameras. This mode rendered everything in real time, at like 3fps, but made debugging simpler.
To pack my datas in the cartridge, I used others cartridges as tools. Instead of having to figure out how memory is packed in the .p8 file, I just put the strings I want to insert in a fresh cartridge. A small code read the string and put each value in the memory at a specified address. The code then make a cstore() to save the memory in the .p8. I can then copy the cartridge data from that temporary cart to my final cart using a text editor.

    local vadd = cur_addr
    for i=1,#verbuf,2 do

        local value = "0x"..sub(verbuf, i,i+1)
        poke(vadd,value)

        vadd += 1
    end

View of the second floor in blender without dynamics objects :

9. Interactions, objects animations, sounds

To make the interaction system, I wanted to merge everything in a single system to keep token count low. I also wanted to experiment with LUA flexibility.

So here is how I declare a simple text interaction at the location x=10,y=20 over a circle range of radius 4 in the floor #1 :

newact(10,20,4,1,"a simple chair")

But I can extend the functionality if needed, here for an interaction that give you a key, specifying what text to print when you pickup the key :

ac_pianokey=give(newact(-52,-10,4,2,"nothing left"),"you find the piano key")

Here is some extreme case, where a door will only open if you have the piano key, and then remove some collisions and animate the door over a fixed period of time to open it. It will also say “open” in the prompt instead of “look” and play the sfx #14 :

ac_door3=newact(-35,2,7,2,"you use the small key", function() setcol(0,112,16,3,2) end, function() r2_door3.rot -= 0.0166 end,nil,"open",14)
ac_door3.need=ac_pianokey

To keep the door open if you come back later, I have some code in the scene construction code (“.anim” let me know if the action has been made) :

if(not ac_door3.anim) setcol(1,112,15,3,4)
r2_door3.rot = ac_door3.anim and -0.7 or -0.25

Some interaction start automatically when the player enter the range. Here is a zombie trap that will launch itself, change the music to #9 and make the sound #15

ac_zomb3=auto(newact(-23,-29,11,2,"it's a trap", function() setmusic(9) end,nil,nil,nil,15))

I also made an interaction type that just change the footstep sound when in range. Now that I think of it, I should just have specified a footstep sound per camera, it would have been much easier.
You can find the interaction code in the function doaction(action), but I would advise you to write your own because this one is messy and tweaked for my need.

10. Optimization

Here I will just show some funny tricks I used to maintain a nearly constant 30fps.
In the first camera of the game, you see a green table covering the whole bottom of the screen. The view was really heavy and it was sad that the first screen the player see was not at 30fps. So I simply removed the table, and I manually draw a gigantic green circle on the bottom, to simulate the table on this camera angle only.
I did most of my test using the mesh of Edward Carnby, as it’s the first I made/ripped. But when I ripped the mesh of Emily Hartwood, it had a lot more triangles. To try to keep it about the same, I sadly removed some triangles from Emily’s mesh. The most obvious is the hair area, which were a lot rounder than for Edward. I also simplified her high heels, but I don’t think it’s visible. Even after that, she is heavier than Edward, and you can lose some frame sometimes.
For some scenes I draw some objects only when I think the player is near them. So the object are drawn in the pre-rendered background, and can be also rendered dynamicaly on top if the player is near. For example some pillars in the cellar are drawn like that.
In the bedroom, downstair, it was quite hard to reach the 30fps. So there is a custom clip rectangle for dynamic objects, to limit what I need to draw to the area the player can go.

The clipping rectangle in the bedroom :

To keep some ram, objects in the second floor are not duplicated, but simply shared between rooms and moved. In the last room, with the double-wings stair, I even use the same altar, mirror and zombie for the two sides.

The tokens and compressed size limits were frequently reached. The compressed size is a bit less hard, because in recent versions of Pico 8, it only shows itself when you export in html or upload on the BBS. The tokens size is a hard limit, if you are above you can’t run the game. Regularly I would optimize and get back some tokens, mainly by removing old code, useless parenthesis or “inlining” function that were used only once.
Near the end, I even needed to remove the debug tools (and not even leave it commented as that still count in the compressed limit) for camera, cheats … I added some specialised functions to insert in a specific array instead of specifying the array each time to save tokens/compressed. Ternary operator are also useful (IF x THEN v=a ELSE v=b END become v= x AND a OR b)

11. Triangle strips
While I was making the game, I always thought that I would not be able to put the two characters inside the same cartridge. They are in fact the largest meshes and I hadn’t enough space to keep them and store objects for the two floors. Without even thinking about storing the zombie. So I was going to release two cartridge, one with Emily and one with Edward. But the options on the BBS does not really allow that in good conditions. I could choose one to put in front of the thread, and the other would be just below. But only the first would appear in XPlore and be playable easily. Or I could make two separate threads, but it would divide the attention, or even make it in a competition between the two characters.
So while asking to Zep for advices, he kinda suggested that it should be possible, as a CHALLENGE to pack everything together. So I needed to do it …
Then I had the nice idea of using triangle strips buffers, instead of the classic “triangle after triangle” index buffer. With triangle strips, you set a list of triangles where each new triangle reuse two vertices of the last. It makes the index buffer much shorter.
I looked online to find a library and the one on Coder Corner seemed nice and comprehensible : Strips
So I changed it to load the files that are exported by my Blender plugin. I added some code so every packing is done in one place (before that I used a crappy javascript tool I made). I could even pack all my files in one go, and print strings ready to paste in Pico 8.
As my colors are per triangle instead of per vertices, I needed to do the stripping separately for each colors. Each strip would store : the length of the strip (8 bits), the color of the strip (8 bits because using 4 bits would add a lot of trouble when decoding with peek) and then each vertices index (8 bits).
Here is my modified version (to use with visual 6) : PicoStrips.zip
This worked very well, each index buffer became about half as long, putting it at about the same size as the vertex buffer. With that optimization, I could repack everything with enough space to store the two characters, the zombie, and even enough sfx space to store songs and proper sound effects. All of that in one single cartridge.
For now, the triangles are not stored in ram using strips as I would need to rework a lot of things. Plus as I use the triangle list to sort everything, a strip version would not be faster.

12. Future
I am really glad I finished that project. It took about 6 months, and some hurdles were hard to overcome, but the result is nice. I want to thanks Frederick Raynal for the amazing game that is Alone in the Dark, and for his help in making this small project.
For now this project if finished, adding more of the original game would require several cartridge, and implementing more complex interactions (fight, throwing, shooting …)
But if some people are interested, I may make a simple 3D application as a sample.
I would polish my tools so others can use them, and make a simple tutorial.
Beside, I want to continue experimenting with Pico 8 and 3D, maybe with procedurally created worlds ...

If you have any questions or advices, feel free to post here.

P#25097 2016-07-11 13:02 ( Edited 2017-01-07 04:33)

SHOW MORE

Cart #25149 | 2016-07-12 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
181

"A suspicious suicide. A chilling curse. A malevolent power. And a wicked dark secret. This is Derceto ...
Choose between Emily Hartwood and Edward Carnby to explore this virtual adventure game inspired by the work of H.P.Lovecraft."

Controls : Arrow keys to move, 'c' or 'v' to interact
Music can be turned off in the menu ('enter' key)

I wrote a (long) post-mortem of this project : here
This is a small "demake" of Alone in the Dark. You can explore and find your way out of the top two floor of the mansion. I tried to keep it close to the original, but there is no fight (if you see an enemy you will die) and the interaction system is simpler. The 3D meshes of the characters are ripped from the original game.

I used a plugin I made to export meshes from Blender with color per triangle and I modified a mesh stripping sample from coder corner to load, strip and convert meshes to character strings. You can find my version (to use with Visual 6) here.

P#24899 2016-07-08 11:23 ( Edited 2016-07-08 15:23)

SHOW MORE

Hi, for a game I'm working on, you can choose between two characters. But I dont have enough space to store the two characters in the same cart. I was thinking about releasing two carts, each one with a character.

The issue is that a given thread in the BBS only show the most recent cart in the "splore" of Pico 8.
Do I need to make two separate threads then ? Or is there a way to show or choose what cart is shown in splore ?

I could also try a multiple cartridge thing, like with a main cart with a selection menu, and two cart for the characters. But I dont think it work in web player or in splore. You would need to download the 3 carts manualy.

Is there a better idea than two separate threads ?

P#22736 2016-06-12 06:32 ( Edited 2016-06-13 22:29)

SHOW MORE

Cart #combopool-0 | 2023-12-17 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
263

Update : I made an Android version, with special touch controls, on my Itch.io page
Update 2 : I improved collisions and fixed the "keep perfectly vertical" way of cheating.
Update 4: You can now use the mouse to point and shoot! Also accessibility option to display numbers on the balls, so you can better identify them

Here is Combo Pool, my entry to p8jam2. It's a game where you throw colored marbles against each other. If two marbles of the same color make contact, they merge and upgrade to the next color. Your lifebar diminish with the number of balls on the field. If you lifebar is empty, you enter in a sudden death mode, and your last ball must save you by removing some balls.

Thanks for your feedback!

Pro-tips :

  • dark and grey balls do more damage to your life bar, so avoid keeping to many at the same time
  • score system depend on number of rebound before merging and on balls colors
  • a usefull technique is to quickly throw two dark ball and make them merge at the first collision
  • try to avoid exploding pink balls as long as you can, you can expect making more point in the final explosion

If someone want to use the physic/collision code, here is a kinda simple and clean version with just that :

Cart #21797 | 2016-05-30 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
263

Old versions :

Cart #26895 | 2016-08-14 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
263

Cart #21659 | 2016-05-29 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
263

Cart #21561 | 2016-05-28 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
263

Cart #21513 | 2016-05-28 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
263

P#21515 2016-05-27 20:37 ( Edited 2023-12-17 19:14)

SHOW MORE

Cart #24981 | 2016-07-09 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
236

P.Craft is a crafting game. You wake up on a deserted island, and you have to survive. Gather materials and build your tools. Explore the area and find a cave. Will you find a way to escape the island ?

Update : A new version, P.Craft Deluxe Edition has been released on Itch.io :
https://nusan.itch.io/pcraft

With a saving system, a boss and a few new items to discover.
This new version use the multicartridge system in a complex way that is not yet compatible with the BBS, so I can't upload it here. You can however find the source .p8 files on the Itch.io page as well as binaries for windows, linux and mac.

Controls :
Button 1 (C/Z/N) : open inventory / cancel menu
Button 2 (V/X/M) : use equiped item / valid menu

The game is heavily inspired by Notch's Ludum Dare entry "Minicraft" witch seemed a perfect idea for pico 8

Quick start if needed :

  • Start by punching one or two trees.
  • Avoid zombies (water is you friend)
  • Open your inventory and select the workbench
  • Place the workbench on the ground
  • Use the workbench to craft a wood haxe
  • Select the wood haxe in your inventory
  • Cut trees until you can craft a sword on the workbench
  • Search the island until you find a cave opening
  • You can pickup your workbench again to move it near the cave
  • Your goal now is to craft a boat
  • Be carefull

Old version :

Cart #19679 | 2016-04-10 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
236

P#19680 2016-04-10 09:49 ( Edited 2016-04-10 13:49)

SHOW MORE

Cart #17759 | 2015-12-14 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
20

Get all rabbits and grow a good amount of grass to go to the next level.

Parts of lit areas can be turned to grass once isolated from light using your snake body.

Controls :
Arrow keys : move snake

There are rabbit holes, so lure rabbits outside by growing grass around them.
Yhen eat them, it will make the snake grow

Moles are your enemies, mainly because they have lasers. Don't let them see you so you can eat them

This game has been made in 48 hours during Ludum Dare 34
You can find a Timelapse made during the LudumDare, followed by a bit of gameplay :

P#17761 2015-12-13 19:29 ( Edited 2015-12-19 13:35)

SHOW MORE

Cart #17207 | 2015-11-29 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
28

Rain Culture :
Use your divine powers to help nature grow back.
Your main tool is a rain cannon and severall kind of mirrors to redirect water over the plants.

Controls :
C or Z to take control or release the nearest tool
Outside a tool : use arrow keys to move your character
Inside a tool : use arrow keys to rotate the tool

That jam was prety cool, even if I only worked two days on it. I tryed to add another mecanic, where fire could spread from leaves to leaves, but it was too random so I removed it.

P#17210 2015-11-29 18:07 ( Edited 2015-12-14 16:00)

SHOW MORE

Cart #spacelimit-0 | 2024-02-08 | Code ▽ | Embed ▽ | No License
69

Space limit is a little 3D demo (so no gameplay yet) inspired by Kerbal Space Program.
If it's too slow in your browser, try directly in Pico8

There is nothing in the sprite/tile data, the code is (realy) messy and triangles can only be monochrome.
For this project, I made a blender exporter (based on the .ply one) that can be found here

I also made a mesh converter to vertex/index string (one byte per location, scale limited to 2.5 around origin) to import in Pico 8. I made it in Javascript (If some people want it, I could port it in the blender exporter instead).
here is the converter from obj, and here for my custom format from blender.

Have fun !

P#16315 2015-11-06 14:51 ( Edited 2024-02-08 23:10)

SHOW MORE

The new export option is amazing, so I started messing with three.js (a webgl library) to use it with Pico 8.
And it works, so I made a simple proof of concept by drawing the pico 8 output in a sort of cathodic display style. The next step will be to import a 3D model of a computer screen or maybe an arcade cabin. So we can finaly see this great fantasy console.
Feel free to use the source code and mess with it. You can change your game just by replacing the game .js by the one exported from Pico 8.

Here is the page
Here is the source code

P#14909 2015-10-03 05:51 ( Edited 2015-10-03 15:25)

SHOW MORE

Cart #25047 | 2016-07-10 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
75

Last update : made a better lamp in front of the bike

Here is my first Pico 8 game, it's a "physic" bike game, a bit like the "Trials" series. I tried some experimental "distance field" collision and it work prety well.

  • 7 levels of increassing difficulty
  • 15 collectibles per levels
  • Time clock for speedruns
  • Deathless is possible
  • Deathless with all collectibles is possible but is insanely difficult

Controls :

up = gas
down = brake
left-right = rotate the bike
c to flip bike
v to retry

Old versions :

Cart #14970 | 2015-10-04 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
75

Cart #14712 | 2015-09-28 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
75

Cart #14705 | 2015-09-28 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
75

Cart #14660 | 2015-09-27 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
75

Cart #14641 | 2015-09-26 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
75

P#14642 2015-09-26 15:27 ( Edited 2016-07-10 17:58)

Follow Lexaloffle:          
Generated 2024-03-19 01:53:48 | 0.113s | Q:87