Mouse input
This commit is contained in:
parent
d2003bc639
commit
2a82c13fb4
|
|
@ -1,3 +1,5 @@
|
||||||
|
import Input from "@common/input";
|
||||||
|
|
||||||
export function loadImageData(dataView: DataView, pointer: number) {
|
export function loadImageData(dataView: DataView, pointer: number) {
|
||||||
const width = dataView.getUint16(pointer + 0, true);
|
const width = dataView.getUint16(pointer + 0, true);
|
||||||
const height = dataView.getUint16(pointer + 2, true);
|
const height = dataView.getUint16(pointer + 2, true);
|
||||||
|
|
@ -25,6 +27,8 @@ export function createCanvas(width: number, height: number) {
|
||||||
document.body.style.alignItems = 'center';
|
document.body.style.alignItems = 'center';
|
||||||
document.body.append(canvas);
|
document.body.append(canvas);
|
||||||
|
|
||||||
|
Input.setMainCanvas(canvas);
|
||||||
|
|
||||||
return canvas;
|
return canvas;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import '@common/assets/fonts/vga.font.css';
|
import '@common/assets/fonts/vga.font.css';
|
||||||
import { randInt } from "@common/utils";
|
import { randInt } from "@common/utils";
|
||||||
import { createCanvas } from './canvas';
|
import { createCanvas } from './canvas';
|
||||||
import type { Rect } from '@common/geometry';
|
import type { Point, Rect } from '@common/geometry';
|
||||||
import { bresenhamCircleGen, bresenhamLineGen, type BresenhamCircleOptions, type BresenhamLineOptions } from '@common/navigation/bresenham';
|
import { bresenhamCircleGen, bresenhamLineGen, type BresenhamCircleOptions, type BresenhamLineOptions } from '@common/navigation/bresenham';
|
||||||
|
import Input from '@common/input';
|
||||||
|
|
||||||
export type ColorLike = string | number | Color;
|
export type ColorLike = string | number | Color;
|
||||||
export type Char = [string, ColorLike?, ColorLike?] | string;
|
export type Char = [string, ColorLike?, ColorLike?] | string;
|
||||||
|
|
@ -450,6 +451,14 @@ export class TextDisplay {
|
||||||
update() {
|
update() {
|
||||||
this.redrawDirty();
|
this.redrawDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getMousePosition(): Point {
|
||||||
|
const pos = Input.getMousePosition();
|
||||||
|
return {
|
||||||
|
x: Math.floor(pos.x / CHAR_W),
|
||||||
|
y: Math.floor(pos.y / CHAR_H),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function expandColors(chars: string[] | Char[][], colors: ColorLike | ColorLike[] | ColorLike[][]): ColorLike[][] {
|
function expandColors(chars: string[] | Char[][], colors: ColorLike | ColorLike[] | ColorLike[][]): ColorLike[][] {
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,11 @@ export interface EventWithPoint {
|
||||||
|
|
||||||
export const getRealPoint = (canvas: HTMLCanvasElement, e: EventWithPoint): DOMPoint => {
|
export const getRealPoint = (canvas: HTMLCanvasElement, e: EventWithPoint): DOMPoint => {
|
||||||
const matrix = new DOMMatrix();
|
const matrix = new DOMMatrix();
|
||||||
const scale = Math.min(canvas.clientWidth / canvas.width, canvas.clientHeight / canvas.height);
|
|
||||||
|
|
||||||
const realWidth = canvas.width * scale;
|
|
||||||
const realHeight = canvas.height * scale;
|
|
||||||
|
|
||||||
const offsetLeft = (canvas.clientWidth - realWidth) / 2;
|
const box = canvas.getBoundingClientRect();
|
||||||
const offsetTop = (canvas.clientHeight - realHeight) / 2;
|
const scale = Math.min(box.width / canvas.width, box.height / canvas.height);
|
||||||
|
|
||||||
matrix.translateSelf(offsetLeft, offsetTop);
|
matrix.translateSelf(box.left, box.top);
|
||||||
matrix.scaleSelf(scale);
|
matrix.scaleSelf(scale);
|
||||||
matrix.invertSelf();
|
matrix.invertSelf();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
import { getRealPoint } from "./dom";
|
||||||
|
import type { Point } from "./geometry";
|
||||||
|
|
||||||
interface IKeyState {
|
interface IKeyState {
|
||||||
state: boolean;
|
state: boolean;
|
||||||
prevState?: boolean;
|
prevState?: boolean;
|
||||||
|
|
@ -57,6 +60,10 @@ namespace Input {
|
||||||
NUM_7 = 'Digit7',
|
NUM_7 = 'Digit7',
|
||||||
NUM_8 = 'Digit8',
|
NUM_8 = 'Digit8',
|
||||||
NUM_9 = 'Digit9',
|
NUM_9 = 'Digit9',
|
||||||
|
|
||||||
|
MOUSE_LEFT = `Mouse0`,
|
||||||
|
MOUSE_MIDDLE = `Mouse1`,
|
||||||
|
MOUSE_RIGHT = `Mouse2`,
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum GamepadAxis {
|
export enum GamepadAxis {
|
||||||
|
|
@ -87,11 +94,13 @@ namespace Input {
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEAD_ZONE = 0.05;
|
const DEAD_ZONE = 0.05;
|
||||||
const KEYS: Partial<Record<KeyCode, IKeyState>> = {};
|
const KEYS: Partial<Record<string, IKeyState>> = {};
|
||||||
|
const mousePosition: Point = { x: 0, y: 0 };
|
||||||
|
let mainCanvas: HTMLCanvasElement | null = null;
|
||||||
let repeatIntervalMs = 10;
|
let repeatIntervalMs = 10;
|
||||||
let repeatDelayMs = 300;
|
let repeatDelayMs = 300;
|
||||||
|
|
||||||
const onStateChange = (keyId: KeyCode, state: boolean) => {
|
const onStateChange = (keyId: string, state: boolean) => {
|
||||||
console.debug(`[Input] ${state ? 'Pressed' : 'Released'} ${keyId}`);
|
console.debug(`[Input] ${state ? 'Pressed' : 'Released'} ${keyId}`);
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
|
|
@ -111,18 +120,39 @@ namespace Input {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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('keydown', (e) => onStateChange(e.code as KeyCode, true));
|
||||||
document.body.addEventListener('keyup', (e) => onStateChange(e.code as KeyCode, false));
|
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 isPressed = (...keys: KeyCode[]) => keys.some(key => KEYS[key]?.pressed);
|
||||||
export const isReleased = (...keys: KeyCode[]) => keys.some(key => KEYS[key]?.released);
|
export const isReleased = (...keys: KeyCode[]) => keys.some(key => KEYS[key]?.released);
|
||||||
export const isRepeated = (...keys: KeyCode[]) => keys.some(key => KEYS[key]?.repeated);
|
export const isRepeated = (...keys: KeyCode[]) => keys.some(key => KEYS[key]?.repeated);
|
||||||
export const isHeld = (...keys: KeyCode[]) => keys.some(key => KEYS[key]?.held);
|
export const isHeld = (...keys: KeyCode[]) => keys.some(key => KEYS[key]?.held);
|
||||||
|
|
||||||
export const hasPressed = () => Object.values(KEYS).some(k => k.pressed);
|
export const hasPressed = () => Object.values(KEYS).some(k => k?.pressed);
|
||||||
export const hasReleased = () => Object.values(KEYS).some(k => k.released);
|
export const hasReleased = () => Object.values(KEYS).some(k => k?.released);
|
||||||
export const hasRepeated = () => Object.values(KEYS).some(k => k.repeated);
|
export const hasRepeated = () => Object.values(KEYS).some(k => k?.repeated);
|
||||||
export const hasHeld = () => Object.values(KEYS).some(k => k.held);
|
export const hasHeld = () => Object.values(KEYS).some(k => k?.held);
|
||||||
|
|
||||||
export const getGamepad = () => navigator.getGamepads().find(g => g != null);
|
export const getGamepad = () => navigator.getGamepads().find(g => g != null);
|
||||||
|
|
||||||
|
|
@ -148,6 +178,8 @@ namespace Input {
|
||||||
+ getGamepadAxis(GamepadAxis.LY)
|
+ getGamepadAxis(GamepadAxis.LY)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const getMousePosition = (): Point => ({ ...mousePosition });
|
||||||
|
|
||||||
export const setRepeatInterval = (intervalMs: number) => {
|
export const setRepeatInterval = (intervalMs: number) => {
|
||||||
repeatIntervalMs = intervalMs;
|
repeatIntervalMs = intervalMs;
|
||||||
}
|
}
|
||||||
|
|
@ -156,8 +188,14 @@ namespace Input {
|
||||||
repeatDelayMs = delayMs;
|
repeatDelayMs = delayMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const setMainCanvas = (canvas: HTMLCanvasElement | null) => {
|
||||||
|
mainCanvas = canvas;
|
||||||
|
}
|
||||||
|
|
||||||
export function updateKeys() {
|
export function updateKeys() {
|
||||||
for (const key of Object.values(KEYS)) {
|
for (const key of Object.values(KEYS)) {
|
||||||
|
if (!key) continue;
|
||||||
|
|
||||||
key.released = false;
|
key.released = false;
|
||||||
key.pressed = false;
|
key.pressed = false;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { Component, World } from "@common/rpg/core/world";
|
import { Component, World } from "@common/rpg/core/world";
|
||||||
import { component } from "@common/rpg/utils/decorators";
|
import { component } from "@common/rpg/utils/decorators";
|
||||||
import { getPosition, Position } from "../position";
|
import { getPosition, Position } from "../position";
|
||||||
|
import type { Point } from "@common/geometry";
|
||||||
|
|
||||||
export interface ViewportData {
|
export interface ViewportData {
|
||||||
screenX: number;
|
screenX: number;
|
||||||
|
|
@ -13,7 +14,6 @@ export interface ViewportData {
|
||||||
|
|
||||||
@component
|
@component
|
||||||
export class Viewport extends Component<void> {
|
export class Viewport extends Component<void> {
|
||||||
|
|
||||||
get screenX(): number {
|
get screenX(): number {
|
||||||
return Math.round(getPosition(this.entity, 'screen')?.x ?? 0);
|
return Math.round(getPosition(this.entity, 'screen')?.x ?? 0);
|
||||||
}
|
}
|
||||||
|
|
@ -37,6 +37,20 @@ export class Viewport extends Component<void> {
|
||||||
get worldY(): number {
|
get worldY(): number {
|
||||||
return Math.round(getPosition(this.entity)?.y ?? 0);
|
return Math.round(getPosition(this.entity)?.y ?? 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
screenToWorld = ({ x, y }: Point): Point => {
|
||||||
|
return {
|
||||||
|
x: x - this.screenX + this.worldX,
|
||||||
|
y: y - this.screenY + this.worldY,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
worldToScreen = ({ x, y }: Point): Point => {
|
||||||
|
return {
|
||||||
|
x: x - this.worldX + this.screenX,
|
||||||
|
y: y - this.worldY + this.screenY,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createViewport = (world: World, viewportData: ViewportData) => {
|
export const createViewport = (world: World, viewportData: ViewportData) => {
|
||||||
|
|
@ -49,16 +63,9 @@ export const createViewport = (world: World, viewportData: ViewportData) => {
|
||||||
return viewport;
|
return viewport;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getViewport = (world: World): ViewportData | null => {
|
export const getViewport = (world: World): Viewport | null => {
|
||||||
for (const [, , viewport] of world.query(Viewport)) {
|
for (const [, , viewport] of world.query(Viewport)) {
|
||||||
return {
|
return viewport;
|
||||||
screenX: viewport.screenX,
|
|
||||||
screenY: viewport.screenY,
|
|
||||||
width: viewport.width,
|
|
||||||
height: viewport.height,
|
|
||||||
worldX: viewport.worldX,
|
|
||||||
worldY: viewport.worldY,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -17,16 +17,20 @@ export class TextDisplaySystem extends System {
|
||||||
|
|
||||||
override update(world: World) {
|
override update(world: World) {
|
||||||
const viewport = getViewport(world);
|
const viewport = getViewport(world);
|
||||||
const offset = viewport ? {
|
const offset = viewport ? viewport.screenToWorld({ x: 0, y: 0 }) : undefined;
|
||||||
x: viewport.worldX - viewport.screenX,
|
const viewportClipRect = viewport ? {
|
||||||
y: viewport.worldY - viewport.screenY,
|
x: viewport.screenX,
|
||||||
} : { x: 0, y: 0 };
|
y: viewport.screenY,
|
||||||
|
width: viewport.width,
|
||||||
|
height: viewport.height,
|
||||||
|
} : undefined;
|
||||||
|
|
||||||
const sprites = Array.from(world.query(Sprite, Position))
|
const sprites = Array.from(world.query(Sprite, Position))
|
||||||
.sort((a, b) => Number(a[2].absolute) - Number(b[2].absolute) || a[2].z - b[2].z);
|
.sort((a, b) => Number(a[2].absolute) - Number(b[2].absolute) || a[2].z - b[2].z);
|
||||||
|
|
||||||
for (const [e, sprite, pos] of sprites) {
|
for (const [e, sprite, pos] of sprites) {
|
||||||
if (e.has(Hidden)) continue;
|
if (e.has(Hidden)) continue;
|
||||||
|
|
||||||
const image = sprite.image;
|
const image = sprite.image;
|
||||||
const { x, y, absolute } = pos;
|
const { x, y, absolute } = pos;
|
||||||
|
|
||||||
|
|
@ -37,13 +41,11 @@ export class TextDisplaySystem extends System {
|
||||||
|
|
||||||
const region = data instanceof TextRegion ? data : new TextRegion(data);
|
const region = data instanceof TextRegion ? data : new TextRegion(data);
|
||||||
|
|
||||||
if (absolute) {
|
if (absolute || !offset || !viewportClipRect) {
|
||||||
this.display.setRegion(x, y, region);
|
this.display.setRegion(x, y, region);
|
||||||
} else {
|
} else {
|
||||||
const clipRect = this.display.getClipRect();
|
const clipRect = this.display.getClipRect();
|
||||||
if (viewport) {
|
this.display.setClipRect(viewportClipRect);
|
||||||
this.display.setClipRect({ x: viewport.screenX, y: viewport.screenY, width: viewport.width, height: viewport.height });
|
|
||||||
}
|
|
||||||
this.display.setRegion(x - offset.x, y - offset.y, region);
|
this.display.setRegion(x - offset.x, y - offset.y, region);
|
||||||
this.display.setClipRect(clipRect);
|
this.display.setClipRect(clipRect);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue