1
0
Fork 0
tsgames/src/common/input.ts

221 lines
6.6 KiB
TypeScript

import { getRealPoint } from "./dom";
import type { Point } from "./geometry";
interface IKeyState {
state: boolean;
prevState?: boolean;
released?: boolean;
pressed?: boolean;
repeated?: boolean;
held?: boolean;
changedAt: number;
eventAt: number;
repeatAt?: number;
}
namespace Input {
export enum KeyCode {
UP = 'ArrowUp',
DOWN = 'ArrowDown',
LEFT = 'ArrowLeft',
RIGHT = 'ArrowRight',
SPACE = 'Space',
SHIFT = 'Shift',
SHIFT_LEFT = 'ShiftLeft',
SHIFT_RIGHT = 'ShiftRight',
A = 'KeyA',
B = 'KeyB',
C = 'KeyC',
D = 'KeyD',
E = 'KeyE',
F = 'KeyF',
G = 'KeyG',
H = 'KeyH',
I = 'KeyI',
J = 'KeyJ',
K = 'KeyK',
L = 'KeyL',
M = 'KeyM',
N = 'KeyN',
O = 'KeyO',
P = 'KeyP',
Q = 'KeyQ',
R = 'KeyR',
S = 'KeyS',
T = 'KeyT',
U = 'KeyU',
V = 'KeyV',
W = 'KeyW',
X = 'KeyX',
Y = 'KeyY',
Z = 'KeyZ',
NUM_0 = 'Digit0',
NUM_1 = 'Digit1',
NUM_2 = 'Digit2',
NUM_3 = 'Digit3',
NUM_4 = 'Digit4',
NUM_5 = 'Digit5',
NUM_6 = 'Digit6',
NUM_7 = 'Digit7',
NUM_8 = 'Digit8',
NUM_9 = 'Digit9',
MOUSE_LEFT = 'Mouse0',
MOUSE_MIDDLE = 'Mouse1',
MOUSE_RIGHT = 'Mouse2',
};
export enum GamepadAxis {
LX = 0,
LY = 1,
RX = 2,
RY = 3,
}
export enum GamepadButton {
A = 0,
B = 1,
X = 2,
Y = 3,
LB = 4,
RB = 5,
LT = 6,
RT = 7,
SELECT = 8,
START = 9,
L3 = 10,
R3 = 11,
D_UP = 12,
D_DOWN = 13,
D_LEFT = 14,
D_RIGHT = 15,
HOME = 16,
}
const DEAD_ZONE = 0.05;
const KEYS: Partial<Record<string, IKeyState>> = {};
const mousePosition: Point = { x: 0, y: 0 };
let mainCanvas: HTMLCanvasElement | null = null;
let repeatIntervalMs = 10;
let repeatDelayMs = 300;
const onStateChange = (keyId: string, state: boolean) => {
console.debug(`[Input] ${state ? 'Pressed' : 'Released'} ${keyId}`);
const now = Date.now();
if (KEYS[keyId]) {
if (state !== KEYS[keyId].state) {
KEYS[keyId].changedAt = now;
}
KEYS[keyId].state = state;
KEYS[keyId].eventAt = now;
} else {
KEYS[keyId] = { state, changedAt: now, eventAt: now };
}
if (keyId === KeyCode.SHIFT_LEFT || keyId === KeyCode.SHIFT_RIGHT) {
const shiftState = Boolean(KEYS[KeyCode.SHIFT_LEFT]?.state || KEYS[KeyCode.SHIFT_RIGHT]?.state);
onStateChange(KeyCode.SHIFT, shiftState);
}
};
const onMouseChange = (e: MouseEvent) => {
if (mainCanvas) {
const point = getRealPoint(mainCanvas, e);
mousePosition.x = point.x;
mousePosition.y = point.y;
} else {
mousePosition.x = e.clientX;
mousePosition.y = e.clientY;
}
}
document.body.addEventListener('keydown', (e) => onStateChange(e.code as KeyCode, true));
document.body.addEventListener('keyup', (e) => onStateChange(e.code as KeyCode, false));
document.body.addEventListener('mousedown', (e) => {
onStateChange(`Mouse${e.button}`, true);
onMouseChange(e);
});
document.body.addEventListener('mouseup', (e) => {
onStateChange(`Mouse${e.button}`, false);
onMouseChange(e);
});
document.body.addEventListener('mousemove', (e) => onMouseChange(e));
document.body.addEventListener('contextmenu', (e) => e.preventDefault());
export const isPressed = (...keys: KeyCode[]) => keys.some(key => KEYS[key]?.pressed);
export const isReleased = (...keys: KeyCode[]) => keys.some(key => KEYS[key]?.released);
export const isRepeated = (...keys: KeyCode[]) => keys.some(key => KEYS[key]?.repeated);
export const isHeld = (...keys: KeyCode[]) => keys.some(key => KEYS[key]?.held);
export const hasPressed = () => Object.values(KEYS).some(k => k?.pressed);
export const hasReleased = () => Object.values(KEYS).some(k => k?.released);
export const hasRepeated = () => Object.values(KEYS).some(k => k?.repeated);
export const hasHeld = () => Object.values(KEYS).some(k => k?.held);
export const getGamepad = () => navigator.getGamepads().find(g => g != null);
export const getGamepadAxis = (i: GamepadAxis) => {
const value = getGamepad()?.axes[i] ?? 0;
return Math.abs(value) < DEAD_ZONE ? 0 : value;
}
export const getGamepadButton = (btn: GamepadButton) => getGamepad()?.buttons[btn]?.value ?? 0;
export const getLeft = () => isHeld(KeyCode.LEFT) ? 1 : Math.max(0, -getGamepadAxis(GamepadAxis.LX));
export const getRight = () => isHeld(KeyCode.RIGHT) ? 1 : Math.max(0, getGamepadAxis(GamepadAxis.LX));
export const getUp = () => isHeld(KeyCode.UP) ? 1 : Math.max(0, -getGamepadAxis(GamepadAxis.LY));
export const getDown = () => isHeld(KeyCode.DOWN) ? 1 : Math.max(0, getGamepadAxis(GamepadAxis.LY));
export const getHorizontal = () => (
(Number(isHeld(KeyCode.LEFT)) - Number(isHeld(KeyCode.RIGHT)))
+ getGamepadAxis(GamepadAxis.LX)
);
export const getVertical = () => (
(Number(isHeld(KeyCode.UP)) - Number(isHeld(KeyCode.DOWN)))
+ getGamepadAxis(GamepadAxis.LY)
);
export const getMousePosition = (): Point => ({ ...mousePosition });
export const setRepeatInterval = (intervalMs: number) => {
repeatIntervalMs = intervalMs;
}
export const setRepeatDelay = (delayMs: number) => {
repeatDelayMs = delayMs;
}
export const setMainCanvas = (canvas: HTMLCanvasElement | null) => {
mainCanvas = canvas;
}
export function updateKeys() {
for (const key of Object.values(KEYS)) {
if (!key) continue;
key.released = false;
key.pressed = false;
if (key.state) {
key.pressed = !key.held;
key.held = true;
} else {
key.released = key.held;
key.held = false;
}
const now = Date.now();
key.repeated = key.held && (now - (key.repeatAt ?? 0) > repeatIntervalMs) && (now - key.changedAt > repeatDelayMs);
if (key.repeated) {
key.repeatAt = now;
}
key.prevState = key.state;
}
}
}
export default Input;