Compare commits

...

3 Commits

Author SHA1 Message Date
Chris Wanstrath
0c4c6fe58c gamepad support 2025-09-29 14:56:47 -07:00
Chris Wanstrath
815a589b23 prepare for gamepads 2025-09-29 14:56:40 -07:00
Chris Wanstrath
711c0e25ac update README 2025-09-29 13:49:14 -07:00
5 changed files with 78 additions and 5 deletions

View File

@ -52,7 +52,10 @@ https://wakamaifondue.com/
## Pluto Goals: Phase 2
- [ ] pico8-style games
- [ ] public tunnel for your NOSE webapps
- [ ] self updating
- [ ] public tunnel lives through reboots
- [ ] self updating NOSE server
- [ ] `pub/` static hosting in webapps
- [ ] game/bin/www cartridges
- [ ] upload files to projects
- [ ] pico8-style games

View File

@ -193,7 +193,7 @@ export function update(_delta: number, input: InputState) {
paused = !paused
if (paused) return
if (input.justPressed.has(" ")) {
if (input.justPressed.has(" ") || input.justPressed.has("z")) {
rotateShape()
}

View File

@ -49,8 +49,8 @@ export async function handleGameStart(msg: Message) {
canvas = createCanvas()
canvas.focus()
setStatus(msgId, "ok")
canvas.addEventListener("keydown", handleKeydown)
canvas.addEventListener("keyup", handleKeyup)
window.addEventListener("keydown", handleKeydown)
window.addEventListener("keyup", handleKeyup)
window.addEventListener("resize", resizeCanvas)
resizeCanvas()
gameLoop(new GameContext(canvas.getContext("2d")!), game)
@ -137,6 +137,10 @@ function gameLoop(ctx: GameContext, game: Game) {
function endGame() {
running = false
window.removeEventListener("keydown", handleKeydown)
window.removeEventListener("keyup", handleKeyup)
window.removeEventListener("resize", resizeCanvas)
if (oldMode === "tall") browserCommands.mode?.()
canvas.classList.remove("active")

64
app/src/js/gamepad.ts Normal file
View File

@ -0,0 +1,64 @@
////
// Support for gamepads.
// Maps incoming gamepad button presses to keyboard buttons.
//
const BUTTONS = [
"A", "B", "X", "Y",
"LB", "RB", "LT", "RT",
"Select", "Start",
"LStick", "RStick",
"DPad Up", "DPad Down", "DPad Left", "DPad Right"
]
const BUTTONS_TO_KEYS: Record<number, string> = {
0: "z", // A → Z
1: "x", // B → X
2: "c", // X → C
3: "v", // Y → V
12: "ArrowUp",
13: "ArrowDown",
14: "ArrowLeft",
15: "ArrowRight",
9: "Enter", // Start
8: "Escape" // Select
}
const activePads = new Set<number>()
const prevState: Record<number, boolean[]> = {}
export function initGamepad() {
window.addEventListener("gamepadconnected", e => {
console.log("Gamepad connected:", e.gamepad)
activePads.add(e.gamepad.index)
requestAnimationFrame(handleGamepad)
})
window.addEventListener("gamepaddisconnected", e => {
console.log("Gamepad disconnected:", e.gamepad)
activePads.delete(e.gamepad.index)
})
}
function handleGamepad() {
const pads = navigator.getGamepads()
for (const gp of pads) {
if (!gp) continue
if (!prevState[gp.index]) prevState[gp.index] = gp.buttons.map(() => false)
gp.buttons.forEach((btn, i) => {
const was = prevState[gp.index]![i]
const is = btn.pressed
if (was !== is) {
const key = BUTTONS_TO_KEYS[i]
if (key) {
const type = is ? "keydown" : "keyup"
window.dispatchEvent(new KeyboardEvent(type, { code: key, key }))
}
prevState[gp.index]![i] = is
}
})
}
requestAnimationFrame(handleGamepad)
}

View File

@ -2,6 +2,7 @@ import { initCompletion } from "./completion.js"
import { initCursor } from "./cursor.js"
import { initEditor } from "./editor.js"
import { initFocus } from "./focus.js"
import { initGamepad } from "./gamepad.js"
import { initHistory } from "./history.js"
import { initHyperlink } from "./hyperlink.js"
import { initInput } from "./input.js"
@ -13,6 +14,7 @@ initCompletion()
initCursor()
initFocus()
initEditor()
initGamepad()
initHistory()
initHyperlink()
initInput()