Ever wish PICO-8 had dual analog stick support? Me too, so I wrote Pinput. Pinput is a Lua library and suite of external helper apps to provide XInput-like gamepad support to PICO-8, without requiring binary patching. The library and helpers use the GPIO area of cartridge RAM to communicate, making Pinput a sort of virtual peripheral plugged into an imaginary expansion port. (The P8T Twitter client uses GPIO the same way.)
You can try it now if you have a gamepad that works with your browser. (You may want to test it first with this gamepad test page.)
Demo video:
What do you get?
- Up to 8 players
- Per player:
- 2 analog thumbsticks
- 2 analog triggers
- 4-way digital D-pad
- 4 digital action buttons (ABXY)
- 2 digital bumper/shoulder buttons
- 2 digital thumbstick buttons
- 2 or 3 digital menu buttons (Start and Back, plus Guide/Xbox/PS button on macOS and web)
Pinput currently works on:
- macOS: with up to 8 gamepads supported by Apple's Game Controller API (newer Xbox One, DualShock 4, DualSense, MFi)
- Windows: with up to 4 gamepads supported by XInput (360, Xbox One)
- Web: depends on your OS and browser's support for the web gamepad API, but expect USB/BT HID gamepads to work, and on Windows, probably XInput too
Coming soon:
- rumble/vibration support
- battery level/charging status for wireless controllers (macOS and Windows only)
- Linux
How does it work?
When you initialize the Pinput library in a cartridge, it writes a magic UUID to the first 16 bytes of the GPIO area. The external helpers look for this magic UUID in PICO-8's memory to identify the GPIO area. On macOS, the helper uses Mach virtual memory APIs to map GPIO into the helper's address space. On Windows, the helper uses ReadProcessMemory/WriteProcessMemory from the Windows memory API. Cartridges exported in web format expose GPIO as an array of integers in JavaScript, so the web version just needs a little extra JavaScript. Once the helper knows where GPIO is, it can read and write gamepad data there for the cartridge to use.
All platforms have the same interface between a cartridge and the helper, so you can run the same Pinput-enabled cartridge on macOS, Windows, or your browser.
Why?
I wanted to do a Geometry Wars demake but still have twinstick support. 😁
good work but the external program is a killer for standalone release :/
one quality of life improvement would be to merge with regular btn functions for standard axis/buttons - that would make the game compatible with both pinput and regular input schemes.
note: the pinput lua code could use a bit less tokens (like jscript libs, a minified version would be handy!)
@freds72 good notes!
I definitely want to support standalone executables someday, but patching binaries on 4 supported architectures (Windows 32-bit x86, macOS 64-bit x86, Linux 64-bit x86, and Linux 32-bit ARM) is probably an order of magnitude more work than manipulating cartridge memory from a separate program.
I like the idea of wrapping BTN()/BTNP() to read from Pinput for O/X buttons and the D-pad. Might add that to the next release as another chunk of Lua.
And yeah, pinput.lua is very verbose right now. I'll see about minifying it — not everyone is going to need the button constants or the player index checks, for example, and a minified version could trade those convenience features for reduced token count.
Would it be possible to make it compatible with Directinput as well ? I want to make a pico8 game that is compatible with Atari VCS Paddle.
@hijongpark The next version should be able to talk to DirectInput devices as well. I'm in the process of rewriting Pinput in Rust + SDL so that I can reuse the same code base everywhere, and SDL can handle DirectInput-compatible devices on every platform (it's the same library PICO-8 itself uses). As of tonight, it's working on macOS and partially on Linux, but I haven't set up a Rust build on Windows yet.
That said, Pinput currently expects a gamepad-like controller. I'm not familiar with the Atari VCS Paddle, but if SDL doesn't recognize it as a gamepad, Pinput might have to be further customized for it to work. How does that twist stick show up to other games?
in windows controller settings and joy2key, it is recognized as dial and slider1 and moves between -1 and 1 just like any axes.
It's potentially doable. My original vision for Pinput is to match the capabilities of XInput and the macOS, web, and SDL game controller APIs inspired by XInput: controllers are gamepad-shaped, and have a certain number of axes, buttons, and triggers. Supporting arbitrary game controllers is out of scope for baseline Pinput, especially if I don't own that hardware and can't test with it.
However, I'd be happy to help you create a custom fork of Pinput for your game, because that sounds neat. You'd need to look for that specific controller, use SDL's general joystick API instead of the gamepad-specific API it's currently using, and map that onto Pinput's axes and buttons. Since the VCS Paddle has fewer than a gamepad, it should be trivial.
I'm currently looking into a bug with DirectInput devices on Windows, but I hope to have builds published for the cross-platform Rust + SDL version of Pinput by this weekend.
It took a little longer than I expected, but I've just released version 0.1.2 of Pinput, which includes the first iteration of the Rust + SDL version, and supports Linux in addition to macOS, Windows, and exported web cartridges. This version also adds rumble effects.
@hijongpark Just remembered an SDL feature that could get your Atari VCS Paddle working with Pinput, without any code changes. Try this:
- Download the Rust + SDL version of Pinput for Windows: https://github.com/VyrCossont/Pinput/releases/download/v0.1.2/pinput-rust-windows-x86_64-0.1.2.zip
- Download an SDL gamepad mapping tool from the list at https://github.com/gabomdq/SDL_GameControllerDB such as https://www.generalarcade.com/gamepadtool/
- Connect your paddle.
- Use the mapping tool to map the paddle's axes and buttons to gamepad axes and buttons. I'd map the regular X and Y to left stick X and Y, the twist axis to right stick X, and the buttons to A and B. Skip the rest.
- You'll now have a mapping string that looks something like
<some hex digits>,Atari VCS Paddle,a:b0,b:b1,leftx:a0,lefty:a1,rightx:a2,
- Set the SDL_GAMECONTROLLERCONFIG environment variable to this string, either in the same console you'll run pinput.exe from, or globally.
- Pinput should now recognize your paddle as a gamepad.
[Please log in to post a comment]