From d08e0105cbc59c6cc804f04aaf1e4e625a13960c Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Fri, 1 Mar 2024 19:45:33 -0700 Subject: [PATCH] eyes follow cursor --- src/engine/TheAbstractionEngine.ts | 38 +++++++++++++- src/engine/components/ComponentNames.ts | 1 + src/engine/components/Control.ts | 11 +++++ src/engine/components/FacingDirection.ts | 10 +++- src/engine/components/Sprite.ts | 6 +++ src/engine/components/index.ts | 1 + src/engine/config/constants.ts | 43 ++++++++++++++++ src/engine/entities/Player.ts | 7 ++- src/engine/interfaces/Direction.ts | 12 +++++ src/engine/systems/FacingDirection.ts | 63 ++++++++++++++++++++++++ src/engine/systems/Input.ts | 63 ++++++++++++++++++++++++ src/engine/systems/SystemNames.ts | 1 + src/engine/systems/index.ts | 2 + 13 files changed, 252 insertions(+), 6 deletions(-) create mode 100644 src/engine/components/Control.ts create mode 100644 src/engine/systems/FacingDirection.ts create mode 100644 src/engine/systems/Input.ts diff --git a/src/engine/TheAbstractionEngine.ts b/src/engine/TheAbstractionEngine.ts index e720293..76ca7e9 100644 --- a/src/engine/TheAbstractionEngine.ts +++ b/src/engine/TheAbstractionEngine.ts @@ -1,7 +1,7 @@ import { Game } from "."; import { loadAssets } from "./config"; import { Player } from "./entities"; -import { Render } from "./systems"; +import { FacingDirection, Input, Render } from "./systems"; export class TheAbstractionEngine { private game: Game; @@ -17,7 +17,14 @@ export class TheAbstractionEngine { public async init() { await loadAssets(); - [new Render(this.ctx)].forEach((system) => this.game.addSystem(system)); + const inputSystem = new Input(); + this.addWindowEventListenersToInputSystem(inputSystem); + + const facingDirectionSystem = new FacingDirection(inputSystem); + + [new Render(this.ctx), inputSystem, facingDirectionSystem].forEach( + (system) => this.game.addSystem(system), + ); const player = new Player(); this.game.addEntity(player); @@ -39,4 +46,31 @@ export class TheAbstractionEngine { this.animationFrameId = null; } } + + private addWindowEventListenersToInputSystem(input: Input) { + window.addEventListener("keydown", (e) => { + if (!e.repeat) { + input.keyPressed(e.key.toLowerCase()); + } + }); + + window.addEventListener("keyup", (e) => + input.keyReleased(e.key.toLowerCase()), + ); + + window.addEventListener("blur", () => input.clearKeys()); + + window.addEventListener("mousemove", (e) => { + const canvas = this.ctx.canvas; + const rect = canvas.getBoundingClientRect(); + + const scaleX = canvas.width / rect.width; + const scaleY = canvas.height / rect.height; + + const x = (e.clientX - rect.left) * scaleX; + const y = (e.clientY - rect.top) * scaleY; + + input.setMousePosition({ x, y }); + }); + } } diff --git a/src/engine/components/ComponentNames.ts b/src/engine/components/ComponentNames.ts index 0f1200a..bfeb101 100644 --- a/src/engine/components/ComponentNames.ts +++ b/src/engine/components/ComponentNames.ts @@ -3,4 +3,5 @@ export namespace ComponentNames { export const FacingDirection = "FacingDirection"; export const GridPosition = "GridPosition"; export const BoundingBox = "BoundingBox"; + export const Control = "Control"; } diff --git a/src/engine/components/Control.ts b/src/engine/components/Control.ts new file mode 100644 index 0000000..48983b2 --- /dev/null +++ b/src/engine/components/Control.ts @@ -0,0 +1,11 @@ +import { Component, ComponentNames } from "."; + +export class Control extends Component { + public isControllable: boolean = true; + + constructor(isControllable = true) { + super(ComponentNames.Control); + + this.isControllable = isControllable; + } +} diff --git a/src/engine/components/FacingDirection.ts b/src/engine/components/FacingDirection.ts index a449d21..7108366 100644 --- a/src/engine/components/FacingDirection.ts +++ b/src/engine/components/FacingDirection.ts @@ -1,12 +1,18 @@ import { Component, ComponentNames, Sprite } from "."; -import { type Direction } from "../interfaces"; +import { Direction } from "../interfaces"; export class FacingDirection extends Component { public readonly directionSprites: Map; + public currentDirection: Direction; - constructor() { + constructor(currentDirection: Direction = Direction.NONE) { super(ComponentNames.FacingDirection); + this.currentDirection = currentDirection; this.directionSprites = new Map(); } + + public setDirection(direction: Direction) { + this.currentDirection = direction; + } } diff --git a/src/engine/components/Sprite.ts b/src/engine/components/Sprite.ts index 6a66a5c..82d7011 100644 --- a/src/engine/components/Sprite.ts +++ b/src/engine/components/Sprite.ts @@ -1,5 +1,6 @@ import { Component, ComponentNames } from "."; import type { Dimension2D, DrawArgs, Coord2D } from "../interfaces"; +import { clamp } from "../utils"; export class Sprite extends Component { private sheet: HTMLImageElement; @@ -31,6 +32,11 @@ export class Sprite extends Component { this.currentFrame = 0; } + public fillTimingsFromSprite(sprite: Sprite) { + this.msSinceLastFrame = clamp(sprite.msSinceLastFrame, 0, this.msPerFrame); + this.currentFrame = clamp(sprite.currentFrame, 0, this.numFrames - 1); + } + public update(dt: number) { this.msSinceLastFrame += dt; if (this.msSinceLastFrame >= this.msPerFrame) { diff --git a/src/engine/components/index.ts b/src/engine/components/index.ts index 30fe50a..d3a32ad 100644 --- a/src/engine/components/index.ts +++ b/src/engine/components/index.ts @@ -4,3 +4,4 @@ export * from "./Sprite"; export * from "./FacingDirection"; export * from "./GridPosition"; export * from "./BoundingBox"; +export * from "./Control"; diff --git a/src/engine/config/constants.ts b/src/engine/config/constants.ts index a00a141..c2cbc76 100644 --- a/src/engine/config/constants.ts +++ b/src/engine/config/constants.ts @@ -1,3 +1,46 @@ +export enum Action { + MOVE_LEFT, + MOVE_RIGHT, + MOVE_UP, + MOVE_DOWN, + RESET, + INTERACT, +} + +export namespace KeyConstants { + export const KeyActions: Record = { + a: Action.MOVE_LEFT, + arrowleft: Action.MOVE_LEFT, + + d: Action.MOVE_RIGHT, + arrowright: Action.MOVE_RIGHT, + + w: Action.MOVE_UP, + arrowup: Action.MOVE_UP, + + s: Action.MOVE_DOWN, + arrowdown: Action.MOVE_DOWN, + + " ": Action.INTERACT, + enter: Action.INTERACT, + }; + + // value -> [key] from KeyActions + export const ActionKeys: Map = Object.keys( + KeyActions, + ).reduce((acc: Map, key) => { + const action = KeyActions[key.toLowerCase()]; + + if (acc.has(action)) { + acc.get(action)!.push(key); + return acc; + } + + acc.set(action, [key]); + return acc; + }, new Map()); +} + export namespace Miscellaneous { export const WIDTH = 800; export const HEIGHT = 800; diff --git a/src/engine/entities/Player.ts b/src/engine/entities/Player.ts index f25730c..2f616c4 100644 --- a/src/engine/entities/Player.ts +++ b/src/engine/entities/Player.ts @@ -5,6 +5,7 @@ import { Sprite, GridPosition, BoundingBox, + Control, } from "../components"; import { Direction } from "../interfaces/"; @@ -19,14 +20,16 @@ export class Player extends Entity { this.addComponent( new BoundingBox( { - x: 0, - y: 0, + x: 200, + y: 200, }, { width: Player.spriteSpec.width, height: Player.spriteSpec.height }, 0, ), ); + this.addComponent(new Control()); + this.addComponent(new GridPosition(0, 0)); this.addFacingDirectionComponents(); } diff --git a/src/engine/interfaces/Direction.ts b/src/engine/interfaces/Direction.ts index c2e2c1e..6f19367 100644 --- a/src/engine/interfaces/Direction.ts +++ b/src/engine/interfaces/Direction.ts @@ -5,3 +5,15 @@ export enum Direction { RIGHT = "RIGHT", NONE = "NONE", } + +export const angleToDirection = (angle: number): Direction => { + if (angle >= -Math.PI / 4 && angle < Math.PI / 4) { + return Direction.RIGHT; + } else if (angle >= Math.PI / 4 && angle < (3 * Math.PI) / 4) { + return Direction.DOWN; + } else if (angle >= (3 * Math.PI) / 4 || angle < -(3 * Math.PI) / 4) { + return Direction.LEFT; + } else { + return Direction.UP; + } +}; diff --git a/src/engine/systems/FacingDirection.ts b/src/engine/systems/FacingDirection.ts new file mode 100644 index 0000000..f831bf6 --- /dev/null +++ b/src/engine/systems/FacingDirection.ts @@ -0,0 +1,63 @@ +import { + ComponentNames, + FacingDirection as FacingDirectionComponent, + BoundingBox, + Sprite, +} from "../components"; +import { Game } from "../Game"; +import { System, SystemNames, Input } from "."; +import { Direction, angleToDirection } from "../interfaces"; + +export class FacingDirection extends System { + private input: Input; + + constructor(input: Input) { + super(SystemNames.FacingDirection); + + this.input = input; + } + + public update(_dt: number, game: Game) { + const mousePosition = this.input.getMousePosition(); + const mouseBoundingBox = new BoundingBox(mousePosition, { + width: 0, + height: 0, + }); + + game.forEachEntityWithComponent( + ComponentNames.FacingDirection, + (entity) => { + if (!entity.hasComponent(ComponentNames.BoundingBox)) { + return; + } + + const boundingBox = entity.getComponent( + ComponentNames.BoundingBox, + )!; + const facingDirection = entity.getComponent( + ComponentNames.FacingDirection, + ); + + const { center } = boundingBox; + const angle = Math.atan2( + mousePosition.y - center.y, + mousePosition.x - center.x, + ); + + const mouseInBoundingBox = + boundingBox.isCollidingWith(mouseBoundingBox); + const direction = mouseInBoundingBox + ? Direction.NONE + : angleToDirection(angle); + + facingDirection.setDirection(direction); + + const oldSprite = entity.getComponent(ComponentNames.Sprite); + const sprite = facingDirection.directionSprites.get(direction)!; + sprite.fillTimingsFromSprite(oldSprite); + + entity.addComponent(sprite); + }, + ); + } +} diff --git a/src/engine/systems/Input.ts b/src/engine/systems/Input.ts new file mode 100644 index 0000000..9b88378 --- /dev/null +++ b/src/engine/systems/Input.ts @@ -0,0 +1,63 @@ +import { SystemNames, System } from "."; +import { Game } from ".."; +import { ComponentNames } from "../components"; +import { Control } from "../components/Control"; +import { Action, KeyConstants } from "../config"; +import { Entity } from "../entities"; +import { Coord2D } from "../interfaces"; + +export class Input extends System { + private keys: Set; + private mousePosition: Coord2D; + + constructor() { + super(SystemNames.Input); + + this.keys = new Set(); + this.mousePosition = { x: 0, y: 0 }; + } + + public clearKeys() { + this.keys.clear(); + } + + public keyPressed(key: string) { + this.keys.add(key); + } + + public keyReleased(key: string) { + this.keys.delete(key); + } + + public update(_dt: number, game: Game) { + game.forEachEntityWithComponent(ComponentNames.Control, (entity) => + this.handleInput(entity), + ); + } + + public handleInput(entity: Entity) { + const controlComponent = entity.getComponent( + ComponentNames.Control, + ); + if (!controlComponent.isControllable) return; + + if (this.hasSomeKey(KeyConstants.ActionKeys.get(Action.INTERACT))) { + console.log("interact"); + } + } + + private hasSomeKey(keys?: string[]): boolean { + if (keys) { + return keys.some((key) => this.keys.has(key)); + } + return false; + } + + public setMousePosition(mousePosition: Coord2D) { + this.mousePosition = mousePosition; + } + + public getMousePosition(): Coord2D { + return this.mousePosition; + } +} diff --git a/src/engine/systems/SystemNames.ts b/src/engine/systems/SystemNames.ts index 41207a4..1ed9894 100644 --- a/src/engine/systems/SystemNames.ts +++ b/src/engine/systems/SystemNames.ts @@ -4,4 +4,5 @@ export namespace SystemNames { export const Input = "Input"; export const Collision = "Collision"; export const WallBounds = "WallBounds"; + export const FacingDirection = "FacingDirection"; } diff --git a/src/engine/systems/index.ts b/src/engine/systems/index.ts index bb87060..31c98ac 100644 --- a/src/engine/systems/index.ts +++ b/src/engine/systems/index.ts @@ -1,3 +1,5 @@ export * from "./SystemNames"; export * from "./System"; export * from "./Render"; +export * from "./Input"; +export * from "./FacingDirection";