-- Leo's gemini proxy

-- Connecting to gem.lizsugar.me:1965...

-- Connected

-- Sending request

-- Meta line: 20 text/gemini

Reduce, Reuse, Recycle

// 2022-12-02, 3 min read, #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:


one to draw the menu, iterating through the menu items table

one to draw the selection cursor

one to move the selection cursor

and one to activate the menu item


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

-- Response ended

-- Page fetched on Mon May 20 15:24:47 2024