I have played around with only four emulators:
- FCEUX
- Snes9x
- VBA
- DeSmuME
I can only tell you what I know of these ones, as I haven't tried out the others. However, there are subtle differences that will come up if you attempt to handle the joypads in another emulator. The exact same thing you've been doing in one emulator may completely fail in another.
First, I will refer to one of four registers, "Boundary", "Before", "After", and "Gui". A
joypad.get in one register may not necessarily give the same result as
joypad.get in another register.
Some details of what exactly I mean about them:
"Boundary" - A piece of code you typically place in a loop with
emu.frameadvance().
"Before" - Created a function that's passed to
emu.registerbefore as a parameter. Typically, this is after the framecount and joypads are updated, but before the actual frame of emulation takes place. Apparently, not always true among all emulators.
"After" - A function passed to
emu.registerafter. Usually, this would be after the frame is emulated. Apparently, this isn't always true between emulators.
"Gui" - Function passed to
gui.register. This function would be called when the emulator updates the display. For some, this happens only once per frame. For others, they repeatedly call this function while paused, which is a mighty useful effect when dealing with user input.
In addition, the following functions are the focus of this post:
joypad.get - Usually takes a number representing the player whose input you wish to read. It simply reads the joypad, whatever state it may be in at the time.
joypad.peek - Usually takes a number, like with
joypad.get. However, it reads whatever keys the user wants to press, rather than the previous frame's input. The difference is subtle, but very clear if it's possible to run the lua code while the emulation is paused --
get will always return the same table, but
peek will keep changing with the user's key presses while the emulation is paused.
joypad.set - Usually takes a number representing the player, and always takes a table representing the input you want done next frame. Depending on the register you place this in, it may take place immediately, wait until next frame, or is completely ignored.
input.get - No parameters. Returns the state of the keyboard and mouse in a table. When dealing with joypad input, you usually don't want to use this, unless you're using a piece of "run-while-paused" code without
joypad.peek available. However, this does open another avenue of working with user input, and is useful for creating runtime options in more complex scripts.
I have gone into detail of each component I will talk about as I analyze each emulator I have tried. I wish for you to get a brief understanding as I do about each component so that you can read that which follows with an intuition that better approximates my own. I would prefer that you don't get lost while reading my stuff.
But now's the time I go into each emulator I have tried...
FCEUX 2.1.4
joypad.get returns the previous frame's input on "Boundary", and the current frame's input on "Before", "After", and "Gui". Reads movie input.
There is no
joypad.peek in this emulator, as of time of this writing.
joypad.set will apply the input immediately on "Boundary", and will apply it next frame in the other registers.
"Gui" has the advantage of running while emulation is paused, but I recommend having it affect another variable that "Boundary" can then read and set the joypads as appropriate.
joypad.set has four options for each button, giving complete control:
- true (
force the button to be pressed)
- false (
force the button unpressed)
- nil (
leave control of the button to the user)
- "invert" (
reverses the user's control of the button)
Critical error: With how
get and
set works, it is impossible to read the joypad on the current frame, decide what it means, then change the joypad before emulation needs it. On "Boundary", you pick up the previous frame's input, not the current frame. On "Before", you get the current frame's input, but
joypad.set() can't do a thing to the joypad now. This feels like a design issue to me.
However, the fact
joypad.set does allow for four options and the fact "Gui" is run while emulation is paused gives a great deal of control. Should
joypad.peek be implemented, I hope to see it gain perfect control.
Snes9x 1.51 v6
joypad.get gets the current frame's input in any register, even "Boundary". It will read changes made by lua, but lua won't get the chance to do so at "Boundary". Reads movie input.
There is no
joypad.peek in this emulator, as of time of this writing.
joypad.set will apply the input immediately in "Boundary" and is completely ignored in any other register. This has caused great confusion in me when I was transitioning from FCEUX to Snes9x lua, but I since learned emulators act differently.
It is not possible to have code run while emulation is paused, except with a fake pause by repeatedly loading the same savestate every frame.
joypad.set has only two options, however:
- nil (
forces the button unpressed)
-
anything not nil (
forces the button to be pressed)
A critical error in joypad manipulation, such as one seen in FCEUX and VBA, is averted, due to the fact you get the immediate input and can change it before the emulation updates. I am quite thankful for this, as there's no way to run code while emulation is paused.
There is a lua-side workaround to the limited joypad options:
Download JoypadSetSnes9x.luaLanguage: lua
function JoypadSetSnes9x(player,inputs)
local TempInput= joypad.get(player)
for btn,val in pairs(inputs) do -- This inherently skips nil
if not val then -- it's obviously set to false by user.
TempInput[btn]= false
elseif type(val) == "string" then -- Mimic FCEUX's "invert" option
TempInput[btn]= not TempInput[btn]
else -- It effectively evaluates to true otherwise.
TempInput[btn]= true
end
end
joypad.set(player, TempInput)
end
The joypad control is done quite well here. Although it lacks the four options like FCEUX, a lua-side workaround is possible thanks to the fact you can read and manipulate the joypad before the emulation gets a hold of it. I just only wish that lua can freely run while emulation is paused so that we can get perfect control.
VBA v22
joypad.get gets the previous frame's input on "After" and the current frame's input on "Boundary", "Before", and "Gui".
Movie data is not read.
There is no
joypad.peek in this emulator, as of time of this writing.
joypad.set will apply the input immediately on "After", and waits until the next frame on "Bondary", "Before", and "Gui". I fought hard trying to figure out what logic the darn emulator was using. I finally got it: "After" takes place before "Boundary", "Before", and "Gui", so any variables set in the latter three registers isn't seen by "After" until the next frame.
Like with Snes9x, there's no way to run lua code while emulation is paused, except hacking it by repeatedly loading the same state every frame.
joypad.set has exactly two options, just like in Snes9x:
- nil (
forces the button unpressed)
-
anything not nil (
forces the button to be pressed)
Critical error: It is impossible to read the current joypad to make decisions of the current situation. There are no joypad-related workarounds from lua-side. If you absolutely need to hijack joypad control from lua while still reading the user's input intelligently, do it in "After" and use
input.get instead. This completely negates the user's setup from the emulator side, but it's at least something.
Furthermore, with how the registers work, you can't mimic FCEUX's
joypad.set options from lua. There's also no way to run lua code while emulation is paused. Then there's the fact it doesn't read movie input. The whole thing combines into a horrible mess that I have a very difficult time working with.
Someone. Hear my plea. Get
joypad.get to read movie data. Fix the registers so that I can
get and immediately
set before the emulation updates. Allow code to run while paused. I don't know how, but if you developers can do it, you will have my thanks. As it stands, VBA is torturous to use in my attempts to produce a
viable multitrack script.
DeSmuME 0.9.6
joypad.get reads the previous frame's input in "Boundary" and "Before".
I haven't checked if it reads movie data. There's also a separate
stylus.get function which works similarly, except for use with the touchscreen controls.
joypad.peek works well in this emulator. It picks up whatever buttons the user is holding at the time. As well, there is
stylus.peek like above.
joypad.set immediately applies input on "Boundary" and "Before". Similarly,
stylus.set works as such, too.
Code can run while paused under "Gui". Excellent...
joypad.set has three options for each button. Almost there, but not quite:
- true (
force the button to be pressed)
- false (
force the button unpressed)
- nil (
leave control of the button to the user)
No critical errors here. Although
joypad.get and
joypad.set are incompatible together like with VBA and FCEUX,
joypad.peek gets around that problem by looking at the user's input rather than the previous frame's input.
If you want the "invert" option that exists for FCEUX, use the Snes9x code I've written up there, except replace
joypad.get with
joypad.peek and get rid of all instances of the identifier
player (
and accompanying comma, if any).
Of the four emulators I've tried, this is the only one to provide perfect joypad control through lua. I'd tweak
joypad.set to follow the four options present in FCEUX, but otherwise, I'm pretty satisfied with what's there. A great job has been done here.
Seeing as I haven't tried other emulators, this is where I stop.
Let me know of anything I missed. I want to at least ensure someone is aware that the joypad manipulation for one emulator will generally break horribly in another. Your Snes9x
joypad.get &
joypad.set manipulation will fail in FCEUX and VBA, and you need to be aware of
joypad.peek to get it to work right in DeSmuME.
Remember, these are simply my views on how each emulator works. I'm not saying my word is the absolute truth, I could be wrong in a few places. I started this thread so that any problems are more visible and hopefully help others to come up with tests of their own to see the inner mechanics of the lua in each emulator.