Adding gamepad support to the title screen

Started by alexgleason, October 14, 2018, 04:24:20 PM

Previous topic - Next topic
October 14, 2018, 04:24:20 PM Last Edit: October 14, 2018, 04:28:11 PM by alexgleason
Hey all, just wanted to share my progress with this. I ended up writing a script that gives default configs to any menu object:

scripts/enable_commands.lua
Code ( lua) Select

-- ♡ Copying is an act of love. Please copy and share.
--
-- Use this script to pass in a game or menu object and have it set up
-- default keyboard and joystick controls so you can use `on_command_pressed`
-- as expected.
--
-- Usage:
--    local enable_commands = require("scripts/enable_commands")
--    enable_commands.enable(my_menu)
--
-- Then simply define `function my_menu:on_command_pressed(command)`

local enable_commands = {}


local function on_key_pressed(self, key)
  if self.on_command_pressed == nil then return true end
  if key == "space" then self:on_command_pressed("action")
  elseif key == "c" then self:on_command_pressed("attack")
  elseif key == "d" then self:on_command_pressed("pause")
  elseif key == "up" then self:on_command_pressed("up")
  elseif key == "down" then self:on_command_pressed("down")
  elseif key == "left" then self:on_command_pressed("left")
  elseif key == "right" then self:on_command_pressed("right")
  elseif key == "x" then self:on_command_pressed("item_1")
  elseif key == "v" then self:on_command_pressed("item_2") end
  return true
end

local function on_key_released(self, key)
  if self.on_command_released == nil then return true end
  if key == "space" then self:on_command_released("action")
  elseif key == "c" then self:on_command_released("attack")
  elseif key == "d" then self:on_command_released("pause")
  elseif key == "up" then self:on_command_released("up")
  elseif key == "down" then self:on_command_released("down")
  elseif key == "left" then self:on_command_released("left")
  elseif key == "right" then self:on_command_released("right")
  elseif key == "x" then self:on_command_released("item_1")
  elseif key == "v" then self:on_command_released("item_2") end
  return true
end

local function on_joypad_button_pressed(self, button)
  if self.on_command_pressed == nil then return true end
  if button == 0 then self:on_command_pressed("action")
  elseif button == 1 then self:on_command_pressed("attack")
  elseif button == 4 then self:on_command_pressed("pause")
  elseif button == 2 then self:on_command_pressed("item_1")
  elseif button == 3 then self:on_command_pressed("item_2") end
  return true
end

local function on_joypad_button_released(self, button)
  if self.on_command_released == nil then return true end
  if button == 0 then self:on_command_released("action")
  elseif button == 1 then self:on_command_released("attack")
  elseif button == 4 then self:on_command_released("pause")
  elseif button == 2 then self:on_command_released("item_1")
  elseif button == 3 then self:on_command_released("item_2") end
  return true
end

local function on_joypad_axis_moved(self, axis, state)
  if axis == 0 then
    if state == 1 and self.on_command_pressed then self:on_command_pressed("right")
    elseif state == -1 and self.on_command_pressed then self:on_command_pressed("left")
    elseif state == 0 and self.on_command_released then
      -- FIXME: Only release the last command?
      self:on_command_released("right")
      self:on_command_released("left")
    end
  end
  if axis == 1 then
    if state == 1 and self.on_command_pressed then self:on_command_pressed("down")
    elseif state == -1 and self.on_command_pressed then self:on_command_pressed("up")
    elseif state == 0 and self.on_command_released then
      -- FIXME: Only release the last command?
      self:on_command_released("down")
      self:on_command_released("up")
    end
  end
  return true
end

-- Enable on an item
function enable_commands.enable(item)
  item.on_key_pressed = on_key_pressed
  item.on_key_released = on_key_released
  item.on_joypad_button_pressed = on_joypad_button_pressed
  item.on_joypad_button_released = on_joypad_button_released
  item.on_joypad_axis_moved = on_joypad_axis_moved
end

return enable_commands


Then just run your menu through this, either in main.lua or in your menu script itself, like so:

Code ( lua) Select

local enable_commands = require("scripts/enable_commands")

-- Ability to use game commands on these menus
enable_commands.enable(title_screen)

function title_screen:on_command_pressed(command)
  -- Handle commands here! No need to handle keys or joypad inputs.
end


It's a horribly verbose script! But it works. Normally the save file stores the controller config, so it creates a chicken and egg problem where you must create a save file before you can configure a gamepad, but need to go through the file select menu first. This partially works around that by assigning the Solarus default keyboard and joystick inputs to normal gamepad actions like "attack" and "action" etc. for a particular menu (the title screen).

What I think I'll actually end up doing is load the game before the title screen, and maybe let the player configure their input device here. My goal is to make the game playable on a platform like RetroPie where no keyboard exists. But this works for now.

As a side note, I think having 3 separate save files is overkill for most Solarus games. Unlike the days of Super Nintendo, most people have their own personal devices that can run Solarus, whether that's a laptop, desktop, Android phone/tablet, or Nintendo Switch. The exception might be a RetroPie device, but even then these often have hundreds of games and different people will probably be playing a different game during a different time. So, I'm going to focus on handling 1 save file at a time which will make things much easier.
RIP Aaron Swartz

October 14, 2018, 06:56:08 PM #1 Last Edit: October 14, 2018, 06:57:58 PM by alexgleason
This is a FAR simpler solution. If you're willing to just accept that your game has only one save file, you can start it up first thing, then you'll be free to use on_command_pressed in your menus. Nothing else but this is needed in your main.lua:

Code ( lua) Select

function sol.main:on_started()
  local game = sol.game.load("save1.dat")
  game:start() -- This is where the magic happens

  sol.menu.start(game, solarus_logo) -- We're setting the menus to use the "game" we started above

  function solarus_logo:on_finished()
    sol.menu.start(game, title_screen) -- The title screen uses internal logic to reload the game once it's ready
  end

end
RIP Aaron Swartz

"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."