From 9d635eadb88349c751153966367a74e339f9abdc Mon Sep 17 00:00:00 2001 From: Juan Sebastian Montoya Date: Fri, 20 Sep 2024 00:46:42 -0500 Subject: [PATCH 1/6] feat(#15): add basic character and update camera to follow it --- index.html | 4 + index.js | 33 ++++++- modules/game-objects/camera.js | 51 ++++------- modules/game-objects/map-management.js | 2 +- modules/game-objects/player.js | 118 +++++++++++++++++++++++++ resources/character.png | Bin 0 -> 10952 bytes resources/overworld.json | 51 ++++++++--- 7 files changed, 214 insertions(+), 45 deletions(-) create mode 100644 modules/game-objects/player.js create mode 100644 resources/character.png diff --git a/index.html b/index.html index d8caa7e..01c925e 100644 --- a/index.html +++ b/index.html @@ -19,6 +19,10 @@ id="overworld" src="resources/overworld.png" /> +
diff --git a/index.js b/index.js index f392821..f84068e 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ -import { GAME_HEIGHT, GAME_WIDTH } from "./modules/constants.js"; +import { GAME_HEIGHT, GAME_WIDTH, TILE_SIZE } from "./modules/constants.js"; import { Camera, CanvasResizer, @@ -6,6 +6,7 @@ import { GameObject, MapManagement, } from "./modules/game-objects/index.js"; +import { Player, SpriteSheet } from "./modules/game-objects/player.js"; const backgroundMaps = [ { @@ -29,6 +30,24 @@ const foregroundMaps = [ { name: "ocean", imageId: "overworld", elementId: "level2", layer: 2 }, ]; +const foregroundCollisionMaps = [ + { + name: "overworld", + imageId: "overworld", + elementId: "level1", + layer: 2, + selected: true, + collision: true, + }, + { + name: "ocean", + imageId: "overworld", + elementId: "level2", + layer: 2, + collision: true, + }, +]; + const clicableObjects = ["debug", "level1", "level2"]; class Game extends GameObject { @@ -41,11 +60,23 @@ class Game extends GameObject { height: GAME_HEIGHT, percentage: 0.9, }); + const player = new Player({ + speed: 1, + gameObjects: [new SpriteSheet({ imageId: "character", tileHeight: 32 })], + x: 6 * TILE_SIZE, + y: 4 * TILE_SIZE, + width: TILE_SIZE, + height: 2 * TILE_SIZE, + debug: true, + }); const camera = new Camera({ gameObjects: [ new MapManagement({ maps: backgroundMaps }), new MapManagement({ maps: foregroundMaps }), + player, + new MapManagement({ maps: foregroundCollisionMaps }), ], + target: player, }); const fpsCounter = new FpsCounter({ debug: false }); this.gameObjects = [canvasResizer, camera, fpsCounter]; diff --git a/modules/game-objects/camera.js b/modules/game-objects/camera.js index b2816c6..3aa8e2c 100644 --- a/modules/game-objects/camera.js +++ b/modules/game-objects/camera.js @@ -9,41 +9,23 @@ export class Camera extends GameObject { width = GAME_WIDTH, height = GAME_HEIGHT, speed = 1, + target = { x: 0, y: 0, width: 0, height: 0 }, // A camera that follows a target }) { super({ x, y, gameObjects, width, height }); this.speed = speed; - this.keys = [ - { key: "ArrowUp", pressed: false, value: [0, -1] }, - { key: "ArrowDown", pressed: false, value: [0, 1] }, - { key: "ArrowLeft", pressed: false, value: [-1, 0] }, - { key: "ArrowRight", pressed: false, value: [1, 0] }, - ]; - this.availableKeys = this.keys.reduce( - (acc, item) => ({ ...acc, [item.key]: item }), - {} - ); - this.eventEmitter.on("changeLevel", () => { + this.target = target; + this.eventEmitter.on("levelChanged", () => { this.x = x; this.y = y; }); + this.eventEmitter.on("targetChanged", (target) => { + this.target = target; + }); } update(delta) { - this.keys.forEach((item) => { - if (item.pressed) { - this.moveCamera(...item.value, delta); - } - }); - } - - onKeyPressed(key) { - if (!this.availableKeys[key]) return; - this.availableKeys[key].pressed = true; - } - - onKeyReleased(key) { - if (!this.availableKeys[key]) return; - this.availableKeys[key].pressed = false; + super.update(delta); + this.followTarget(); } render(ctx) { @@ -52,16 +34,19 @@ export class Camera extends GameObject { ); } - moveCamera(dx, dy, delta) { + followTarget() { + const { x, y, width: tWidth, height: tHeight } = this.target; + const centerX = x + tWidth / 2; + const centerY = y + tHeight / 2; const [item] = this.gameObjects; const { height, width } = item.selected ?? item; - this.x = Math.min( - Math.max(this.x + dx * Math.floor(delta * this.speed * 100), 0), - width * TILE_SIZE - this.width + this.x = Math.max( + 0, + Math.min(centerX - this.width / 2, width * TILE_SIZE - this.width) ); - this.y = Math.min( - Math.max(this.y + dy * Math.floor(delta * this.speed * 100), 0), - height * TILE_SIZE - this.height + this.y = Math.max( + 0, + Math.min(centerY - this.height / 2, height * TILE_SIZE - this.height) ); } } diff --git a/modules/game-objects/map-management.js b/modules/game-objects/map-management.js index a6dd3f7..88c174e 100644 --- a/modules/game-objects/map-management.js +++ b/modules/game-objects/map-management.js @@ -27,6 +27,6 @@ export class MapManagement extends GameObject { } const map = this.gameObjects.find((item) => item.elementId === elementId); this.selected = map.name; - this.eventEmitter.emit("changeLevel"); + this.eventEmitter.emit("levelChanged"); } } diff --git a/modules/game-objects/player.js b/modules/game-objects/player.js new file mode 100644 index 0000000..eb95597 --- /dev/null +++ b/modules/game-objects/player.js @@ -0,0 +1,118 @@ +import { GAME_HEIGHT, GAME_WIDTH, TILE_SIZE } from "../constants.js"; +import { GameObject } from "./game-object.js"; + +export class Sprite extends GameObject { + constructor({ image, x = 0, y = 0, width = 0, height = 0 }) { + super({ x, y, height, width }); + this.image = image; + this.imageWidth = this.image.width; + this.imageHeight = this.image.height; + } + + render(ctx, sourceX, sourceY) { + ctx.drawImage( + this.image, + this.x, + this.y, + this.width, + this.height, + sourceX, + sourceY, + this.width, + this.height + ); + } +} + +export class SpriteSheet extends GameObject { + constructor({ + imageId, + x = 0, + y = 0, + tileWidth = 16, + tileHeight = 16, + offsetX = 0, + offsetY = 0, + }) { + super({ x, y }); + this.image = document.getElementById(imageId); + this.imageWidth = this.image.width; + this.imageHeight = this.image.height; + this.tileWidth = tileWidth; + this.tileHeight = tileHeight; + this.offsetX = offsetX; + this.offsetY = offsetY; + this.sprites = this._getSprites(); + } + + _getSprites() { + const sprites = []; + for (let row = 0; row < this.imageHeight; row += this.tileHeight) { + for (let col = 0; col < this.imageWidth; col += this.tileHeight) { + sprites.push( + new Sprite({ + image: this.image, + x: col + this.offsetX, + y: row + this.offsetY, + width: this.tileWidth, + height: this.tileHeight, + }) + ); + } + } + return sprites; + } +} + +export class Player extends GameObject { + constructor({ gameObjects = [], x = 0, y = 0, speed = 1, ...args }) { + super({ x, y, gameObjects, ...args }); + this.speed = speed; + this.keys = [ + { key: "ArrowUp", pressed: false, value: [0, -1] }, + { key: "ArrowDown", pressed: false, value: [0, 1] }, + { key: "ArrowLeft", pressed: false, value: [-1, 0] }, + { key: "ArrowRight", pressed: false, value: [1, 0] }, + ]; + this.availableKeys = this.keys.reduce( + (acc, item) => ({ ...acc, [item.key]: item }), + {} + ); + this.eventEmitter.on("levelChanged", (...args) => { + this.x = x; + this.y = y; + }); + } + + update(delta) { + this.keys.forEach((item) => { + if (item.pressed) { + this.moveCharacter(...item.value, delta); + } + }); + } + + onKeyPressed(key) { + if (!this.availableKeys[key]) return; + this.availableKeys[key].pressed = true; + } + + onKeyReleased(key) { + if (!this.availableKeys[key]) return; + this.availableKeys[key].pressed = false; + } + + render(ctx, ...args) { + const [x, y] = args; + const [spriteSheet] = this.gameObjects; + const [item] = spriteSheet.sprites; + item.render(ctx, this.x - x, this.y - y); + } + + moveCharacter(dx, dy, delta) { + 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 + } +} diff --git a/resources/character.png b/resources/character.png new file mode 100644 index 0000000000000000000000000000000000000000..a50ceb040f5ad2821d1b4976c19ef2a849dd8fb3 GIT binary patch literal 10952 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 rwt*N%c~A(*HU9rpoH4&1lPRx!qQo<^V literal 0 HcmV?d00001 diff --git a/resources/overworld.json b/resources/overworld.json index 10984dd..a936f24 100644 --- a/resources/overworld.json +++ b/resources/overworld.json @@ -33,17 +33,48 @@ "x":0, "y":0 }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 605, 607, 606, 607, 606, 607, 606, 607, 606, 607, 606, 607, 606, 607, 606, 607, 607, 607, 607, 607, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 367, 528, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 407, 568, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 13, 9, 10, 11, 0, 0, 0, + 446, 446, 446, 446, 446, 446, 446, 446, 446, 447, 568, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 568, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 214, 215, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 568, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 568, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":20, + "id":2, + "name":"Tile Layer 2", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":30, + "x":0, + "y":0 + }, { "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 485, 486, 486, 486, 486, 486, 486, 486, 486, 486, 486, 486, 486, 486, 486, 486, 486, 486, 486, 486, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 525, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 565, 566, 566, 566, 566, 566, 566, 566, 566, 566, 566, 566, 566, 566, 566, 566, 566, 566, 449, 566, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 605, 607, 606, 607, 606, 607, 606, 607, 606, 607, 606, 607, 606, 607, 606, 607, 607, 607, 607, 607, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 367, 528, 0, 0, 0, 0, 0, 0, 0, 0, 0, 522, 523, 523, 523, 523, 523, 523, 523, 523, 524, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 407, 568, 0, 0, 0, 0, 0, 0, 0, 0, 0, 562, 0, 12, 13, 9, 10, 11, 0, 0, 564, - 446, 446, 446, 446, 446, 446, 446, 446, 446, 447, 568, 0, 0, 0, 0, 0, 0, 0, 0, 0, 562, 0, 47, 53, 49, 50, 51, 0, 0, 564, - 486, 486, 486, 486, 486, 486, 486, 486, 486, 487, 568, 0, 0, 0, 0, 0, 0, 0, 0, 0, 562, 0, 87, 88, 89, 90, 91, 214, 215, 564, - 526, 526, 526, 526, 526, 526, 526, 526, 526, 527, 568, 0, 450, 451, 0, 0, 0, 450, 451, 0, 562, 0, 127, 128, 129, 130, 131, 254, 255, 564, - 566, 566, 566, 566, 566, 566, 566, 566, 566, 567, 568, 0, 490, 491, 0, 0, 0, 490, 491, 0, 562, 0, 167, 168, 169, 170, 171, 294, 295, 564, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 522, 523, 523, 523, 523, 523, 523, 523, 523, 524, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 562, 0, 0, 0, 0, 0, 0, 0, 0, 564, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 562, 0, 47, 53, 49, 50, 51, 0, 0, 564, + 486, 486, 486, 486, 486, 486, 486, 486, 486, 487, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 562, 0, 87, 88, 89, 90, 91, 0, 0, 564, + 526, 526, 526, 526, 526, 526, 526, 526, 526, 527, 0, 0, 450, 451, 0, 0, 0, 450, 451, 0, 562, 0, 127, 128, 129, 130, 131, 254, 255, 564, + 566, 566, 566, 566, 566, 566, 566, 566, 566, 567, 0, 0, 490, 491, 0, 0, 0, 490, 491, 0, 562, 0, 167, 168, 169, 170, 171, 294, 295, 564, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 562, 0, 0, 0, 0, 0, 0, 0, 0, 564, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 602, 603, 603, 642, 0, 641, 603, 603, 603, 604, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -55,8 +86,8 @@ 0, 0, 0, 0, 0, 0, 0, 0, 85, 86, 0, 0, 0, 0, 0, 85, 86, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "height":20, - "id":2, - "name":"Tile Layer 2", + "id":6, + "name":"Tile Layer 3", "opacity":1, "type":"tilelayer", "visible":true, @@ -64,7 +95,7 @@ "x":0, "y":0 }], - "nextlayerid":3, + "nextlayerid":8, "nextobjectid":1, "orientation":"orthogonal", "renderorder":"right-down", From ad3226de525704361c666ab2a85bc42e3974cdd7 Mon Sep 17 00:00:00 2001 From: Juan Sebastian Montoya Date: Fri, 20 Sep 2024 08:33:02 -0500 Subject: [PATCH 2/6] feat(#15): add new font --- index.css | 7 +++++++ index.html | 13 +++++++++++++ 2 files changed, 20 insertions(+) diff --git a/index.css b/index.css index 3f1fcd8..04a6df7 100644 --- a/index.css +++ b/index.css @@ -19,3 +19,10 @@ body { #resources { display: none; } + +.pixelify-sans-regular { + font-family: "Pixelify Sans", sans-serif; + font-optical-sizing: auto; + font-weight: 600; + font-style: normal; +} \ No newline at end of file diff --git a/index.html b/index.html index 01c925e..8699c7d 100644 --- a/index.html +++ b/index.html @@ -7,6 +7,19 @@ content="width=device-width, initial-scale=1.0" /> Game Engine + + + Date: Fri, 20 Sep 2024 08:34:45 -0500 Subject: [PATCH 3/6] feat(#15): use new font in fps counter --- modules/game-objects/fps-counter.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/game-objects/fps-counter.js b/modules/game-objects/fps-counter.js index 3d4b266..e56bdbb 100644 --- a/modules/game-objects/fps-counter.js +++ b/modules/game-objects/fps-counter.js @@ -1,8 +1,8 @@ import { GameObject } from "./game-object.js"; export class FpsCounter extends GameObject { - constructor({ debug = false }) { - super({ debug }); + constructor() { + super(); this.fps = 0; } @@ -15,7 +15,7 @@ export class FpsCounter extends GameObject { return; } ctx.fillStyle = "Red"; - ctx.font = "normal 12pt Arial"; + ctx.font = "normal 12pt Pixelify Sans"; ctx.fillText(this.fps + " fps", 5, 15); } } From 9403de104272a84f3de196aace30755f366b26b9 Mon Sep 17 00:00:00 2001 From: Juan Sebastian Montoya Date: Fri, 20 Sep 2024 08:35:05 -0500 Subject: [PATCH 4/6] feat(#15): add basic animations implementation --- index.js | 19 +++++- modules/game-objects/player.js | 105 +++++++++++++++++++++++++++------ 2 files changed, 105 insertions(+), 19 deletions(-) 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; + } } From e2cd0ee490b662e331c22867cebc8f825d9a8534 Mon Sep 17 00:00:00 2001 From: Juan Sebastian Montoya Date: Fri, 20 Sep 2024 11:40:18 -0500 Subject: [PATCH 5/6] feat(#15): fix centering player issue --- modules/game-objects/player.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/game-objects/player.js b/modules/game-objects/player.js index 31d9a21..1d2c2d4 100644 --- a/modules/game-objects/player.js +++ b/modules/game-objects/player.js @@ -92,8 +92,10 @@ export class Player extends GameObject { x = 0, y = 0, speed = 1, + width = TILE_SIZE, + height = TILE_SIZE, }) { - super({ x, y, gameObjects }); + super({ x, y, gameObjects, width, height }); this.speed = speed; this.keys = [ { key: "ArrowUp", pressed: false, value: [0, -1] }, From ea9d912e2f935a3fc302abcd9997c04a4096c2b4 Mon Sep 17 00:00:00 2001 From: Juan Sebastian Montoya Date: Fri, 20 Sep 2024 15:55:32 -0500 Subject: [PATCH 6/6] feat(#15): separate sprite from player --- index.js | 3 +- modules/game-objects/animation.js | 7 +++ modules/game-objects/index.js | 3 + modules/game-objects/player.js | 83 ---------------------------- modules/game-objects/sprite-sheet.js | 49 ++++++++++++++++ modules/game-objects/sprite.js | 30 ++++++++++ 6 files changed, 91 insertions(+), 84 deletions(-) create mode 100644 modules/game-objects/animation.js create mode 100644 modules/game-objects/sprite-sheet.js create mode 100644 modules/game-objects/sprite.js diff --git a/index.js b/index.js index 61fed36..f3776e6 100644 --- a/index.js +++ b/index.js @@ -5,8 +5,9 @@ import { FpsCounter, GameObject, MapManagement, + Player, + SpriteSheet, } from "./modules/game-objects/index.js"; -import { Player, SpriteSheet } from "./modules/game-objects/player.js"; const backgroundMaps = [ { diff --git a/modules/game-objects/animation.js b/modules/game-objects/animation.js new file mode 100644 index 0000000..d9ac5f1 --- /dev/null +++ b/modules/game-objects/animation.js @@ -0,0 +1,7 @@ +export class Animation extends GameObject { + constructor({ frames = [], name, x = 0, y = 0 }) { + super({ x, y }); + this.frames = frames; + this.name = name; + } +} diff --git a/modules/game-objects/index.js b/modules/game-objects/index.js index fbeecb0..bae02d1 100644 --- a/modules/game-objects/index.js +++ b/modules/game-objects/index.js @@ -4,3 +4,6 @@ export { FpsCounter } from "./fps-counter.js"; export { GameObject } from "./game-object.js"; export { MapManagement } from "./map-management.js"; export { Map } from "./map.js"; +export { Player } from "./player.js"; +export { Sprite } from "./sprite.js"; +export { SpriteSheet } from "./sprite-sheet.js"; diff --git a/modules/game-objects/player.js b/modules/game-objects/player.js index 1d2c2d4..2af7011 100644 --- a/modules/game-objects/player.js +++ b/modules/game-objects/player.js @@ -1,89 +1,6 @@ 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, 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, destinationX, destinationY) { - ctx.drawImage( - this.image, - this.x, - this.y, - this.width, - this.height, - 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); - } - } -} - -export class SpriteSheet extends GameObject { - constructor({ - imageId, - x = 0, - y = 0, - tileWidth = TILE_SIZE, - tileHeight = TILE_SIZE, - offsetX = 0, - offsetY = 0, - }) { - super({ x, y }); - this.image = document.getElementById(imageId); - this.imageWidth = this.image.width; - this.imageHeight = this.image.height; - this.tileWidth = tileWidth; - this.tileHeight = tileHeight; - this.offsetX = offsetX; - this.offsetY = offsetY; - } - - 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.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 (this.gameObjects = sprites); - } -} - export class Player extends GameObject { constructor({ gameObjects = [], diff --git a/modules/game-objects/sprite-sheet.js b/modules/game-objects/sprite-sheet.js new file mode 100644 index 0000000..747f847 --- /dev/null +++ b/modules/game-objects/sprite-sheet.js @@ -0,0 +1,49 @@ +import { TILE_SIZE } from "../constants.js"; +import { GameObject } from "./game-object.js"; +import { Sprite } from "./sprite.js"; + +export class SpriteSheet extends GameObject { + constructor({ + imageId, + x = 0, + y = 0, + tileWidth = TILE_SIZE, + tileHeight = TILE_SIZE, + offsetX = 0, + offsetY = 0, + }) { + super({ x, y }); + this.image = document.getElementById(imageId); + this.imageWidth = this.image.width; + this.imageHeight = this.image.height; + this.tileWidth = tileWidth; + this.tileHeight = tileHeight; + this.offsetX = offsetX; + this.offsetY = offsetY; + } + + 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.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 (this.gameObjects = sprites); + } +} diff --git a/modules/game-objects/sprite.js b/modules/game-objects/sprite.js new file mode 100644 index 0000000..06cd55a --- /dev/null +++ b/modules/game-objects/sprite.js @@ -0,0 +1,30 @@ +import { GameObject } from "./game-object.js"; + +export class Sprite extends GameObject { + 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, destinationX, destinationY) { + ctx.drawImage( + this.image, + this.x, + this.y, + this.width, + this.height, + 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); + } + } +}