Log In  

update 2024: the 0.2.6 update improves things! search the update post for menuitem(0x301 for details. (My original menuitem post remains below, unchanged)


PICO-8 has fancy menuitems but there are some gotchas and bugs to be aware of.

Here's an example of what I do by default; the rest of this post will explain how the code works and why I do things this way:

Cart #menuitems-1 | 2023-08-21 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
6

L/R pitfall

Imagine you want to add a "mute" button to your game's menu. Can you spot the issue with this code?

function _init()
  menuitem(1,"mute",function()
    ismuted=not ismuted
    -- ... play/pause the music
  end)
  -- ... other game init
end

The issue: left/right can be used to change the menuitem, and left/right always leaves the menu open afterward (this may be a bug; it's unclear) so there's no indication to the player that they accidentally muted the game! It's a minor issue... but it's easy to accidentally hit left/right when navigating the menu on a gamepad, which makes this worse.

An easy fix: don't respond to L/R (menuitem(1,"mute",function(bb) if bb>=16 then ... end end)). This is what I'd recommend for games that are short on tokens but willing to spare a few to fix this.

Responsive menu

If you'd like a more responsive menu, you can do this:

function _init()
  menuitem(1,ismuted and "music: off" or "music: on",function()
    ismuted=not ismuted
    -- ... play/pause the music
    -- update the label:
    menuitem(nil,ismuted and "music: off" or "music: on") -- feels like we could save some tokens...
    return true
  end)
  -- ... other game init
end

Now the option shows its status, and updates its label immediately when you change it!

Note the return true -- this keeps the menu open. Without this, if the player presses left (toggling the setting) and then presses enter (trying to close the menu but instead toggling the setting again), they'll wonder why their change isn't sticking. By leaving the menu open, we show them that they actually re-toggled the setting, and they need to choose "continue" instead to resume the game.

Code organization idiom

The repeated ismuted and "music: off" or "music: on" is suspicious -- can we save some tokens? Yes, by setting up each menuitem() again at the end of the callback, just to refresh the labels:

function _init()
  domenuitems() -- setup all menuitem()s
  -- ... other game init
end
function domenuitems()
  menuitem(1,ismuted and "music: off" or "music: on",function()
    ismuted=not ismuted
    -- ... play/pause the music
    domenuitems() -- refresh the label
    return true
  end)
  -- menuitem(2,...
  -- menuitem(3,...
end

I do this for code-organizing reasons -- it's nice to have all the menuitem code in one place -- but it can also save a few tokens, depending on your setup.

Context-dependent menuitems

Some menuitems should only be show in certain situations -- e.g. don't show "back to title" if we're already on the title screen. You could break each menuitem into its own little setup function (like zep's original LOAD #CUSTOM_MENU cart does -- check it out, it's nice!) or do what I do: menuitem(1,condition and "label",...). This will only show the menuitem when condition is truthy. I call domenuitems() anytime the menu needs changing (e.g. at the end of init_title_screen()) and it all works out.

Recommendations / Summary

I put all my menuitem() calls inside a custom domenuitems() function. Every callback runs domenuitems() at the end, to refresh the labels.

There are three types of menu options I commonly use: commands, toggles, and selectors.

  • For commands (run some code, e.g. return to the title screen), I wrap the callback code in a if bb>=16 then ... end block, to prevent L/R from triggering the command
  • For toggles (e.g. mute/unmute the game), my callback always has return true, leaving the menu open afterward and avoiding the UX issue described above
  • For selectors (choose a number from a range, e.g. change levels), my callback has return bb<16, leaving the menu open only if L/R were used to change the selection. (this is technically unnecessary due to a longstanding bug(?) in PICO-8's menu handling)

Read the code of the example cart at the top of this post for more info!

P#133332 2023-08-21 01:02 ( Edited 2024-03-14 08:32)


[Please log in to post a comment]