🎮 Maps controller buttons to modes
This commit is contained in:
@@ -5,43 +5,83 @@ export type GamepadState = {
|
||||
gamepads: Gamepad[]
|
||||
}
|
||||
|
||||
const DEADZONE = 0.15
|
||||
const dz = (x: number) => {
|
||||
const a = Math.abs(x)
|
||||
if (a < DEADZONE) return 0
|
||||
return ((a - DEADZONE) / (1 - DEADZONE)) * Math.sign(x)
|
||||
}
|
||||
|
||||
let raf = 0
|
||||
let running = false
|
||||
|
||||
export const gamepads = readable<GamepadState>({ available: false, gamepads: [] }, set => {
|
||||
const update = () => {
|
||||
const hasGamepadAPI = 'getGamepads' in navigator
|
||||
if (!hasGamepadAPI) {
|
||||
set({ available: false, gamepads: [] })
|
||||
return
|
||||
}
|
||||
|
||||
const gps = navigator.getGamepads?.() ?? []
|
||||
const validGamepads = gps.filter(Boolean) as Gamepad[]
|
||||
set({
|
||||
available: true,
|
||||
gamepads: validGamepads
|
||||
})
|
||||
const pads = navigator.getGamepads?.() ?? []
|
||||
const list = Array.from(pads)
|
||||
.map(p => p || null)
|
||||
.filter(Boolean) as Gamepad[]
|
||||
set({ available: 'getGamepads' in navigator, gamepads: list })
|
||||
raf = requestAnimationFrame(update)
|
||||
}
|
||||
|
||||
window.addEventListener('gamepadconnected', update)
|
||||
window.addEventListener('gamepaddisconnected', update)
|
||||
let raf = requestAnimationFrame(update)
|
||||
const onConnect = () => update()
|
||||
const onDisconnect = () => update()
|
||||
const onVis = () => {
|
||||
if (document.hidden) {
|
||||
running = false
|
||||
cancelAnimationFrame(raf)
|
||||
} else if (!running) {
|
||||
running = true
|
||||
raf = requestAnimationFrame(update)
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('gamepadconnected', onConnect)
|
||||
window.addEventListener('gamepaddisconnected', onDisconnect)
|
||||
document.addEventListener('visibilitychange', onVis)
|
||||
|
||||
running = true
|
||||
raf = requestAnimationFrame(update)
|
||||
|
||||
return () => {
|
||||
running = false
|
||||
cancelAnimationFrame(raf)
|
||||
window.removeEventListener('gamepadconnected', update)
|
||||
window.removeEventListener('gamepaddisconnected', update)
|
||||
window.removeEventListener('gamepadconnected', onConnect)
|
||||
window.removeEventListener('gamepaddisconnected', onDisconnect)
|
||||
document.removeEventListener('visibilitychange', onVis)
|
||||
}
|
||||
})
|
||||
|
||||
export const gamepad = derived(gamepads, $gamepads =>
|
||||
$gamepads.available && $gamepads.gamepads.length > 0 ? $gamepads.gamepads[0] : null
|
||||
export const gamepad = derived(gamepads, s =>
|
||||
s.available && s.gamepads.length ? s.gamepads[0] : null
|
||||
)
|
||||
|
||||
export const gamepadAxes = derived(gamepad, $gamepad => $gamepad?.axes ?? [0, 0, 0, 0])
|
||||
export const hasGamepad = derived(gamepads, s => s.available && s.gamepads.length > 0)
|
||||
|
||||
export const gamepadButtons = derived(gamepad, $gamepad => $gamepad?.buttons ?? [])
|
||||
export const gamepadAxes = derived(gamepad, g => (g ? g.axes.map(dz) : [0, 0, 0, 0]))
|
||||
|
||||
export const hasGamepad = derived(
|
||||
gamepads,
|
||||
$gamepads => $gamepads.available && $gamepads.gamepads.length > 0
|
||||
)
|
||||
type ButtonEdge = { pressed: boolean; value: number; justPressed: boolean; justReleased: boolean }
|
||||
const prev = new Map<number, { pressed: boolean; value: number }[]>()
|
||||
|
||||
export const gamepadButtons = derived(gamepad, g => g?.buttons ?? [])
|
||||
|
||||
export const gamepadButtonsEdges = derived(gamepad, g => {
|
||||
if (!g) return [] as ButtonEdge[]
|
||||
const p = prev.get(g.index) || []
|
||||
const out = g.buttons.map((b, i): ButtonEdge => {
|
||||
const pr = p[i] || { pressed: false, value: 0 }
|
||||
const pressed = !!b.pressed || b.value > 0.5
|
||||
return {
|
||||
pressed,
|
||||
value: b.value,
|
||||
justPressed: pressed && !pr.pressed,
|
||||
justReleased: !pressed && pr.pressed
|
||||
}
|
||||
})
|
||||
prev.set(
|
||||
g.index,
|
||||
out.map(x => ({ pressed: x.pressed, value: x.value }))
|
||||
)
|
||||
return out
|
||||
})
|
||||
|
||||
@@ -15,7 +15,12 @@
|
||||
} from '$lib/stores'
|
||||
import type { vector } from '$lib/types/models'
|
||||
import { VerticalSlider } from '$lib/components/input'
|
||||
import { gamepadAxes, hasGamepad } from '$lib/stores/gamepad'
|
||||
import {
|
||||
gamepadAxes,
|
||||
gamepadButtons,
|
||||
gamepadButtonsEdges,
|
||||
hasGamepad
|
||||
} from '$lib/stores/gamepad'
|
||||
import { notifications } from '$lib/components/toasts/notifications'
|
||||
|
||||
let throttle = new throttler()
|
||||
@@ -37,10 +42,25 @@
|
||||
})
|
||||
|
||||
// TODO React to button press
|
||||
// $effect(() => {
|
||||
// if ($gamepadButtons.length === 0) return
|
||||
//
|
||||
// })
|
||||
$effect(() => {
|
||||
if (!$hasGamepad) return
|
||||
const b = $gamepadButtonsEdges
|
||||
if (!b.length) return
|
||||
if (b[0]?.justPressed) mode.set(5)
|
||||
if (b[1]?.justPressed) mode.set(4)
|
||||
if (b[2]?.justPressed) mode.set(3)
|
||||
if (b[3]?.justPressed) mode.set(0)
|
||||
if (b[12]?.justPressed)
|
||||
input.update(inputData => {
|
||||
inputData['height'] = Math.min(inputData.height + 0.1, 1)
|
||||
return inputData
|
||||
})
|
||||
if (b[13]?.justPressed)
|
||||
input.update(inputData => {
|
||||
inputData['height'] = Math.min(inputData.height - 0.1, 1)
|
||||
return inputData
|
||||
})
|
||||
})
|
||||
|
||||
onMount(() => {
|
||||
left = nipplejs.create({
|
||||
|
||||
Reference in New Issue
Block a user