Log In  

Hello friends! I'm attempting to implement replay functionality in PICO-8. (e.g. re-watching your last race in a racing game) I started by changing my update function, so that instead of invoking game actions directly, they are added to a command queue. Imagine something like this:

function move_player(x, y)
    -- move ZIG for great justice
end

function update_game()
    if btnp(➡️) then
        add_cmd(move_player, {1, 0})
    end
        -- other input handling here

    process_commands()
end

An entry in the queue is a function, parameters (passed as a table), and the frame that the command was added (e.g. 23). At the end of the update function I execute everything in the queue that is scheduled for the current frame. process_commands uses an index to track where the next command is, and increments the index whenever it executes one. To replay a game, all I have to do is reset my game state and command queue the index to 1, and run my game engine against the already-populated command queue. So far so good, easy enough to implement and it's working fine.

The next thing I thought was how I could use this to run a demo mode for the game. I play the game, I somehow persist the command queue, and use it to drive the demo mode. So now I need to persist this command queue outside of the Lua runtime. My first thought was looking into serialization of data in Lua, but if I understand Lua correctly I'd be serializing the same functions over and over, and it looks like I don't have access to string.dump anyways.

My best idea now is creating a table that keys the string value of my function names on the various game action functions, and add some code to add_cmd to actually build a string of Lua code that will re-create my table. Seems kinda derpy but it would work, and does require me to change the name table if I change or add game actions.

Another alternative would be to record the raw input data for every frame and re-write my input handling code so that exactly where the input data is coming from is abstracted. Then my data is just a bunch of booleans that are easily serializable, but I hate this because now I'm tracking way more data than I care about, and it makes handling actual user input while the replay is running less straightforward.

How would you approach implementing replay functionality in PICO-8?

I'm a lifelong programmer but very new to game development, feel free to criticize / correct as you see fit, you won't hurt my feelings! I love learning.

P#137023 2023-11-06 18:46

1

Hi!

I actually had to code a similar thing for a job interview, a few years ago.

I would make sure my code was deterministic (which mainly means saving/setting a seed if your code contains calls to rnd() ).

Then I would go with the basic solution of recording inputs every frame and playing them back. It worked like a charm for the interview. It has the advantage of relying on a very simple data structure (a bitfield, or an array of bools), and no maintenance on the related code is required as the definition of an input is not something that can change in pico-8.

But you're right, as the cart space is tight, saving all inputs every frame is expensive (even at one byte per frame, at 60 fps you would only store 1024/60~=17seconds of gameplay in a KiB). I guess I would circumvent that by only saving input events instead (button press and release).

Also for the serialization required for your demo mode, I would simply copy the data somewhere in sprite, map or sfx memory (depending on where I have room) using poke(), and then use cstore() to save it to the cart, so that I can read it easily from cart memory when entering demo mode.

P#137044 2023-11-06 22:33

@MarechalBanane Thank you for your reply! I did already implement a seed system so the game engine is deterministic.

Reflecting on this a bit more, I think you're right that capturing inputs is the most straightforward, at least in the context of PICO-8 and wanting to persist the data somewhere. I really like my solution, but persisting Lua function references is just awkward. (still not entirely clear to me if Lua functions are technically references to anonymous objects, or if they are immutable values, or something else)

I suppose with the input recording method, if the user was permitted to customize their controls you would either need to save the mapping used to go along with the data, or save the mapped input data instead of raw data.

I haven't yet had the occasion to store raw data in the graphics / sound region of the cart like you described, it sounds like an interesting exercise and something that's worth familiarizing myself with, even if I determine it's not necessary in this context.

Thanks again!

P#137047 2023-11-06 23:51 ( Edited 2023-11-06 23:53)

You're welcome!

Yeah clearly saving mapped input data is better. That's what I meant by raw, as it's what you provide to the pico-8 api (btn and btnp), but your naming is more accurate.

Just curious, where did you plan on serializing the data if not to empty cart space?

P#137063 2023-11-07 07:38

[Please log in to post a comment]