💾 Archived View for gem.lizsugar.me › 2022 › 12 › 02 › modularity.gmi captured on 2023-12-28 at 15:23:58. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2023-09-08)

➡️ Next capture (2024-03-21)

-=-=-=-=-=-=-

Reduce, Reuse, Recycle

2022-12-02 | #programming #pico-8 #game design

One of the things I'm trying to do is make things reusable. I'm lazy, and I don't want to keep redoing the same things over and over. And I don't want _Lab Trouble_ (working title) to be my only game, so I recently redesigned the main menu with this in mind and took advantage of a really cool aspect of the way lua handles functions.

So I've got a table that I'm using to hold menu draw coordinates, currently selected menu item, and another table of tables for the menu items. Each menu item is made up of a string to put on screen, a function to call when activated, and optionally a variable to show some value (e.g. game difficulty). Then there are four functions:

The activation one is where the magic happens. Lua is really cool about how it handles functions.

Functions in Lua are first-class values with proper lexical scoping. What does it mean for functions to be "first-class values"? It means that, in Lua, a function is a value with the same rights as conventional values like numbers and strings.

https://www.lua.org/pil/6.html

To be perfectly honest, I do not fully understand that on an academic level. But I do understand that it allows me to use functions like variables. I understand that it allows me to arbitrarily call functions referenced in a table element.

If I've got a table (lua tables are 1-indexed):

my_table = {
  [1] = hurt_player,
  [2] = heal_player
}

I can then later in my code use the following code to call the functions:

local my_func = my_table[1]
my_func()

And pow, the player gets hurt.

You put this together and you get my menu:

main_menu = {
  x = 32, y = 60,
  selected_item = 1,

  -- menu structure:
  --   simple menu item:
  --    display string, function to call
  --
  --   menu item that changes a value:
  --    display string, table containing a `change` function, value
  options = {
    { "\x97   start game", start_gameplay },
    { "\x97   help", display_help },
    { "\x8b\x91 difficulty: ", game.difficulty, game.difficulty.value },
    { "\x8b\x91 level: ", levels, current_level },
  },

  draw_menu = function(self)
    print(game.title .. "\n" .. game.credit, self.x + 8, self.y - 30)

    cursor(self.x, self.y)
    for i in all(self.options) do
      local line = i[1]
      if (i[3]) line = line .. tostring(i[3])
      print(line)
    end
  end,

  draw_cursor = function(self)
    local offset = self.selected_item - 1
    cursor(self.x - 8, self.y + (6 * offset))
    print("\x8f")
  end,

  cursor_move = function(self, direction)
    if direction == 0 then
      if (self.selected_item == 1) then
        self.selected_item = #self.options
      elseif (self.selected_item > 1) then
        self.selected_item -= 1
      end
    elseif direction == 1 then
      if (self.selected_item < #self.options) then
        self.selected_item += 1
      elseif (self.selected_item == #self.options) then
        self.selected_item = 1
      end
    end
  end,

  activate_item = function(self, direction)
    local menu_func = self.options[self.selected_item][2]
    local menu_value = self.options[self.selected_item][3]
    if (not direction and not menu_value) then
      menu_func()
    elseif (direction and menu_value) then
      self.options[self.selected_item][3] = menu_func.change(menu_func, direction)
    end
  end
}

Then add some top level functions to handle interaction and display of the menu:

function menu_loop()
  if (btnp(0)) main_menu:activate_item(0) -- left
  if (btnp(1)) main_menu:activate_item(1) -- right
  if (btnp(2)) main_menu:cursor_move(0) -- up
  if (btnp(3)) main_menu:cursor_move(1) -- down
  if (btnp(5)) main_menu:activate_item() -- x (controller a, kb: x)
end

function draw_main_menu()
  main_menu:draw_menu()
  main_menu:draw_cursor()
end

Call those in your `_update()` and `_draw()` functions (in pico-8, anyway) respectively, and blam, instant menu!

Now, I'm sure this is _nothing_ new to other people, but I'm really enjoying learning how to do all of this at this level. I should be able to take this menu to any other pico-8 game I make, just modify the menu items as needed, and just go to town. It should be fully usable for submenus in any context as well.

It is currently very simple, allowing only for a single, vertically oriented menu, and it does not offer scrolling. There are updates to be done that I will add as the need arises in later projects.

I've been making a lot of other progress too and I feel like I'm getting closer to releasing this game! I'm really excited and hope I can make it to that finish line.

---

View this page on web