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 0000000..a50ceb0 Binary files /dev/null and b/resources/character.png differ 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",