diff --git a/index.js b/index.js index f84068e..61fed36 100644 --- a/index.js +++ b/index.js @@ -50,6 +50,21 @@ const foregroundCollisionMaps = [ const clicableObjects = ["debug", "level1", "level2"]; +const playerAnimations = { + idle: { + left: [51], + right: [17], + top: [34], + bottom: [0], + }, + walk: { + left: [51, 52, 53, 54], + right: [17, 18, 19, 20], + top: [34, 35, 36, 37], + bottom: [0, 1, 2, 3], + }, +}; + class Game extends GameObject { constructor({ canvas }) { super(); @@ -63,11 +78,11 @@ class Game extends GameObject { const player = new Player({ speed: 1, gameObjects: [new SpriteSheet({ imageId: "character", tileHeight: 32 })], + animations: playerAnimations, x: 6 * TILE_SIZE, y: 4 * TILE_SIZE, width: TILE_SIZE, height: 2 * TILE_SIZE, - debug: true, }); const camera = new Camera({ gameObjects: [ @@ -78,7 +93,7 @@ class Game extends GameObject { ], target: player, }); - const fpsCounter = new FpsCounter({ debug: false }); + const fpsCounter = new FpsCounter(); this.gameObjects = [canvasResizer, camera, fpsCounter]; this.canvas = canvas; diff --git a/modules/game-objects/player.js b/modules/game-objects/player.js index eb95597..31d9a21 100644 --- a/modules/game-objects/player.js +++ b/modules/game-objects/player.js @@ -1,26 +1,40 @@ -import { GAME_HEIGHT, GAME_WIDTH, TILE_SIZE } from "../constants.js"; +import { TILE_SIZE } from "../constants.js"; import { GameObject } from "./game-object.js"; +export class Animation extends GameObject { + constructor({ frames = [], name, x = 0, y = 0 }) { + super({ x, y }); + this.frames = frames; + this.name = name; + } +} + export class Sprite extends GameObject { - constructor({ image, x = 0, y = 0, width = 0, height = 0 }) { + constructor({ image, x = 0, y = 0, width = 0, height = 0, index = 0 }) { super({ x, y, height, width }); + this.index = index; this.image = image; this.imageWidth = this.image.width; this.imageHeight = this.image.height; } - render(ctx, sourceX, sourceY) { + render(ctx, destinationX, destinationY) { ctx.drawImage( this.image, this.x, this.y, this.width, this.height, - sourceX, - sourceY, + destinationX, + destinationY, this.width, this.height ); + if (this.debug) { + ctx.fillStyle = "Red"; + ctx.font = "normal 8pt Pixelify Sans"; + ctx.fillText(this.index, destinationX, destinationY + 8); + } } } @@ -29,8 +43,8 @@ export class SpriteSheet extends GameObject { imageId, x = 0, y = 0, - tileWidth = 16, - tileHeight = 16, + tileWidth = TILE_SIZE, + tileHeight = TILE_SIZE, offsetX = 0, offsetY = 0, }) { @@ -42,31 +56,44 @@ export class SpriteSheet extends GameObject { this.tileHeight = tileHeight; this.offsetX = offsetX; this.offsetY = offsetY; - this.sprites = this._getSprites(); } - _getSprites() { + get sprites() { + if (this.gameObjects?.length) { + return this.gameObjects; + } const sprites = []; + let index = 0; for (let row = 0; row < this.imageHeight; row += this.tileHeight) { - for (let col = 0; col < this.imageWidth; col += this.tileHeight) { + for (let col = 0; col < this.imageWidth; col += this.tileWidth) { sprites.push( new Sprite({ image: this.image, + index, x: col + this.offsetX, y: row + this.offsetY, width: this.tileWidth, height: this.tileHeight, }) ); + index++; } } - return sprites; + + return (this.gameObjects = sprites); } } export class Player extends GameObject { - constructor({ gameObjects = [], x = 0, y = 0, speed = 1, ...args }) { - super({ x, y, gameObjects, ...args }); + constructor({ + gameObjects = [], + animations = {}, + defaultAnimation = { idle: "bottom" }, + x = 0, + y = 0, + speed = 1, + }) { + super({ x, y, gameObjects }); this.speed = speed; this.keys = [ { key: "ArrowUp", pressed: false, value: [0, -1] }, @@ -82,14 +109,26 @@ export class Player extends GameObject { this.x = x; this.y = y; }); + + // TODO: Decouple animation into animation class + this.animations = animations; + this.defaultAnimation = defaultAnimation; + this.currentAnimation = defaultAnimation; + this.currentAnimationFrame = 0; } update(delta) { + let idle = true; this.keys.forEach((item) => { if (item.pressed) { - this.moveCharacter(...item.value, delta); + this.moveCharacter(delta, ...item.value); + this.updateAnimation(delta, "walk", ...item.value); + idle = false; } }); + if (idle) { + this.updateAnimation(delta, "idle"); + } } onKeyPressed(key) { @@ -103,16 +142,48 @@ export class Player extends GameObject { } render(ctx, ...args) { + // TODO: Decouple animation into animation class const [x, y] = args; const [spriteSheet] = this.gameObjects; - const [item] = spriteSheet.sprites; - item.render(ctx, this.x - x, this.y - y); + if (this.debug) { + spriteSheet.sprites.forEach((item, index) => { + item.render(ctx, this.x - x + index * TILE_SIZE, this.y - y); + }); + } else { + const [currentAnimationKey] = Object.keys(this.currentAnimation); + const currentAnimationDirection = + this.currentAnimation[currentAnimationKey]; + const frames = + this.animations[currentAnimationKey][currentAnimationDirection]; + if (!frames) { + throw Error("No animation defined for " + this.currentAnimation); + } + const item = frames.map((frame) => spriteSheet.sprites[frame])[ + Math.floor(this.currentAnimationFrame) % frames.length + ]; + item.render(ctx, this.x - x, this.y - y); + } } - moveCharacter(dx, dy, delta) { + moveCharacter(delta, dx, dy) { const speed = Math.floor(delta * this.speed * 100); this.x = this.x + dx * speed; this.y = this.y + dy * speed; // TODO: Check for collisions and stop movement if needed } + + // TODO: Decouple animation into animation class + updateAnimation(delta, animation, dx, dy) { + if (animation === "idle") { + const [value] = Object.values(this.currentAnimation); + this.currentAnimation = { [animation]: value }; + return; + } + if (dx !== 0) { + this.currentAnimation = { [animation]: dx > 0 ? "right" : "left" }; + } else if (dy !== 0) { + this.currentAnimation = { [animation]: dy > 0 ? "bottom" : "top" }; + } + this.currentAnimationFrame += 10 * delta; + } }