diff --git a/src/components/Player/constants.ts b/src/components/Player/constants.ts new file mode 100644 index 0000000..9150c3d --- /dev/null +++ b/src/components/Player/constants.ts @@ -0,0 +1,14 @@ +export const WIDTH = 32; +export const HEIGHT = 48; +export const TILE_X = 0; +export const TILE_Y = 8; +export const ANIMATION_LENGTH = 3; +export const SPEED = 4; + +export const Input = { + Interact: [" ", "Enter"], + Up: ["w", "ArrowUp"], + Left: ["a", "ArrowLeft"], + Down: ["s", "ArrowDown"], + Right: ["d", "ArrowRight"], +}; diff --git a/src/components/Player/index.tsx b/src/components/Player/index.tsx index 4545e01..7df6769 100644 --- a/src/components/Player/index.tsx +++ b/src/components/Player/index.tsx @@ -1,14 +1,11 @@ import { useEffect, useRef, FC, useContext } from "react"; -import { GAME_STATES, TILE_SETS, TILE_SIZE } from "../../constants"; +import { GAME_STATES, TILE_SETS } from "../../constants"; import { GlobalContext } from "../../contexts"; +import { Vector } from "../../utils"; +import { ANIMATION_LENGTH, HEIGHT, Input, WIDTH } from "./constants"; +import { drawFrame, getInputVector, move } from "./utils"; import "./style.css"; -const WIDTH = 32; -const HEIGHT = 48; -const TILE_X = 0; -const TILE_Y = 8; -const ANIMATION_LENGTH = 3; - type PlayerProps = { top: number; left: number; @@ -18,8 +15,6 @@ type PlayerProps = { /* * TODO: - * - 2D Vectors for movement direction - * - util function for tile set, tiles and animation * - move object specific interactions outside of Player * - move player controls to global context * - use input loop to remove keydown delay @@ -41,90 +36,27 @@ const Player: FC = ({ onInteract, onCollision, top, left }) => { "coin-canvas" ) as HTMLCanvasElement | null; - if (!canvasRef.current) { + const ctx = canvasRef.current?.getContext("2d"); + if (!canvasRef.current || !ctx) { return; } canvasRef.current.style.top = canvasRef.current.style.top || `${top}px`; canvasRef.current.style.left = canvasRef.current.style.left || `${left}px`; - const ctx = canvasRef.current.getContext("2d"); - - if (!ctx) { - return; - } const tileSet = new Image(); tileSet.src = TILE_SETS.Player; tileSet.onload = () => { let keyPressed = false; - let direction = "down"; + let direction = Vector.Down; let currentFrame = 0; - ctx.drawImage( - tileSet, - TILE_X, - TILE_Y, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); + + drawFrame(ctx, tileSet, direction, currentFrame); window.onkeyup = () => { currentFrame = 0; keyPressed = false; - ctx.clearRect(0, 0, WIDTH, HEIGHT); - - if (direction === "up") { - ctx.drawImage( - tileSet, - TILE_X, - TILE_Y + TILE_SIZE * 4, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } else if (direction === "left") { - ctx.drawImage( - tileSet, - TILE_X, - TILE_Y + TILE_SIZE * 6, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } else if (direction === "down") { - ctx.drawImage( - tileSet, - TILE_X, - TILE_Y, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } else if (direction === "right") { - ctx.drawImage( - tileSet, - TILE_X, - TILE_Y + TILE_SIZE * 2, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } + drawFrame(ctx, tileSet, direction, currentFrame); }; window.onkeydown = (event) => { @@ -164,30 +96,13 @@ const Player: FC = ({ onInteract, onCollision, top, left }) => { coinCanvas.remove(); } - if (event.key === " " || event.key === "Enter") { + if (Input.Interact.includes(event.key)) { onInteract((wasOpen) => !wasOpen); - } else if (event.key === "w" || event.key === "ArrowUp") { - direction = "up"; - canvasRef.current.style.top = `${ - parseInt(canvasRef.current.style.top || "0") - 4 - }px`; - } else if (event.key === "s" || event.key === "ArrowDown") { - direction = "down"; - canvasRef.current.style.top = `${ - parseInt(canvasRef.current.style.top || "0") + 4 - }px`; - } else if (event.key === "a" || event.key === "ArrowLeft") { - direction = "left"; - canvasRef.current.style.left = `${ - parseInt(canvasRef.current.style.left || "0") - 4 - }px`; - } else if (event.key === "d" || event.key === "ArrowRight") { - direction = "right"; - canvasRef.current.style.left = `${ - parseInt(canvasRef.current.style.left || "0") + 4 - }px`; } + direction = getInputVector(event.key); + move(direction, canvasRef.current); + if ( fireCanvas && !invulnerable && @@ -249,60 +164,7 @@ const Player: FC = ({ onInteract, onCollision, top, left }) => { if (!keyPressed) { keyPressed = true; - ctx.clearRect(0, 0, WIDTH, HEIGHT); - - if (direction === "up") { - ctx.drawImage( - tileSet, - TILE_X + WIDTH * currentFrame, - TILE_Y + TILE_SIZE * 4, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } - if (direction === "left") { - ctx.drawImage( - tileSet, - TILE_X + WIDTH * currentFrame, - TILE_Y + TILE_SIZE * 6, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } - if (direction === "down") { - ctx.drawImage( - tileSet, - TILE_X + WIDTH * currentFrame, - TILE_Y, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } - if (direction === "right") { - ctx.drawImage( - tileSet, - TILE_X + WIDTH * currentFrame, - TILE_Y + TILE_SIZE * 2, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } + drawFrame(ctx, tileSet, direction, currentFrame); setTimeout(() => { keyPressed = false; diff --git a/src/components/Player/utils.ts b/src/components/Player/utils.ts new file mode 100644 index 0000000..da30b6f --- /dev/null +++ b/src/components/Player/utils.ts @@ -0,0 +1,55 @@ +import { Vector } from "../../utils"; +import { TILE_SIZE } from "../../constants"; +import { Input, WIDTH, HEIGHT, TILE_X, TILE_Y, SPEED } from "./constants"; + +export const getSpritePos = (direction: Vector, currentFrame: number) => { + let yMultiplier = 0; + + if (direction.eq(Vector.Up)) { + yMultiplier = 4; + } else if (direction.eq(Vector.Left)) { + yMultiplier = 6; + } else if (direction.eq(Vector.Right)) { + yMultiplier = 2; + } + + return { + tileX: TILE_X + WIDTH * currentFrame, + tileY: TILE_Y + TILE_SIZE * yMultiplier, + }; +}; + +export const drawFrame = ( + ctx: CanvasRenderingContext2D, + tileSet: CanvasImageSource, + direction: Vector, + currentFrame: number +) => { + ctx.clearRect(0, 0, WIDTH, HEIGHT); + const { tileX, tileY } = getSpritePos(direction, currentFrame); + + ctx.drawImage(tileSet, tileX, tileY, WIDTH, HEIGHT, 0, 0, WIDTH, HEIGHT); +}; + +export const getInputVector = (key: string) => { + if (Input.Up.includes(key)) { + return Vector.Up; + } else if (Input.Down.includes(key)) { + return Vector.Down; + } else if (Input.Left.includes(key)) { + return Vector.Left; + } else if (Input.Right.includes(key)) { + return Vector.Right; + } + + return Vector.Zero; +}; + +export const move = (direction: Vector, canvas: HTMLCanvasElement) => { + if (direction.eq(Vector.Zero)) { + return; + } + const velocity = direction.mul(SPEED); + canvas.style.top = `${parseInt(canvas.style.top || "0") - velocity.x}px`; + canvas.style.left = `${parseInt(canvas.style.left || "0") + velocity.y}px`; +}; diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..4ad8809 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1 @@ +export * from "./vector"; diff --git a/src/utils/vector.ts b/src/utils/vector.ts new file mode 100644 index 0000000..8a0b15b --- /dev/null +++ b/src/utils/vector.ts @@ -0,0 +1,37 @@ +export class Vector { + public x: number; + public y: number; + + constructor(x: number, y: number) { + this.x = x; + this.y = y; + } + + public mul(value: number) { + return new Vector(this.x * value, this.y * value); + } + + public eq({ x, y }: Vector): boolean { + return this.x === x && this.y === y; + } + + public static get Zero() { + return new Vector(0, 0); + } + + public static get Up() { + return new Vector(1, 0); + } + + public static get Down() { + return new Vector(-1, 0); + } + + public static get Left() { + return new Vector(0, -1); + } + + public static get Right() { + return new Vector(0, 1); + } +}