🎮 Maps controller buttons to modes
This commit is contained in:
@@ -5,43 +5,83 @@ export type GamepadState = {
|
|||||||
gamepads: Gamepad[]
|
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 => {
|
export const gamepads = readable<GamepadState>({ available: false, gamepads: [] }, set => {
|
||||||
const update = () => {
|
const update = () => {
|
||||||
const hasGamepadAPI = 'getGamepads' in navigator
|
const pads = navigator.getGamepads?.() ?? []
|
||||||
if (!hasGamepadAPI) {
|
const list = Array.from(pads)
|
||||||
set({ available: false, gamepads: [] })
|
.map(p => p || null)
|
||||||
return
|
.filter(Boolean) as Gamepad[]
|
||||||
}
|
set({ available: 'getGamepads' in navigator, gamepads: list })
|
||||||
|
|
||||||
const gps = navigator.getGamepads?.() ?? []
|
|
||||||
const validGamepads = gps.filter(Boolean) as Gamepad[]
|
|
||||||
set({
|
|
||||||
available: true,
|
|
||||||
gamepads: validGamepads
|
|
||||||
})
|
|
||||||
raf = requestAnimationFrame(update)
|
raf = requestAnimationFrame(update)
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('gamepadconnected', update)
|
const onConnect = () => update()
|
||||||
window.addEventListener('gamepaddisconnected', update)
|
const onDisconnect = () => update()
|
||||||
let raf = requestAnimationFrame(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 () => {
|
return () => {
|
||||||
|
running = false
|
||||||
cancelAnimationFrame(raf)
|
cancelAnimationFrame(raf)
|
||||||
window.removeEventListener('gamepadconnected', update)
|
window.removeEventListener('gamepadconnected', onConnect)
|
||||||
window.removeEventListener('gamepaddisconnected', update)
|
window.removeEventListener('gamepaddisconnected', onDisconnect)
|
||||||
|
document.removeEventListener('visibilitychange', onVis)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export const gamepad = derived(gamepads, $gamepads =>
|
export const gamepad = derived(gamepads, s =>
|
||||||
$gamepads.available && $gamepads.gamepads.length > 0 ? $gamepads.gamepads[0] : null
|
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(
|
type ButtonEdge = { pressed: boolean; value: number; justPressed: boolean; justReleased: boolean }
|
||||||
gamepads,
|
const prev = new Map<number, { pressed: boolean; value: number }[]>()
|
||||||
$gamepads => $gamepads.available && $gamepads.gamepads.length > 0
|
|
||||||
)
|
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'
|
} from '$lib/stores'
|
||||||
import type { vector } from '$lib/types/models'
|
import type { vector } from '$lib/types/models'
|
||||||
import { VerticalSlider } from '$lib/components/input'
|
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'
|
import { notifications } from '$lib/components/toasts/notifications'
|
||||||
|
|
||||||
let throttle = new throttler()
|
let throttle = new throttler()
|
||||||
@@ -37,10 +42,25 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
// TODO React to button press
|
// TODO React to button press
|
||||||
// $effect(() => {
|
$effect(() => {
|
||||||
// if ($gamepadButtons.length === 0) return
|
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(() => {
|
onMount(() => {
|
||||||
left = nipplejs.create({
|
left = nipplejs.create({
|
||||||
|
|||||||
Reference in New Issue
Block a user