diff --git a/GameObject.js b/GameObject.js index 16cc04e..e3d7d87 100644 --- a/GameObject.js +++ b/GameObject.js @@ -1,5 +1,6 @@ class GameObject { constructor(config) { + this.id = null; this.isMounted = false; this.x = config.x || 0; this.y = config.y || 0; @@ -9,14 +10,42 @@ class GameObject { src: config.src || "/images/characters/people/hero.png", }); + + this.behaviorLoop = config.behaviorLoop || []; + this.behaviorLoopIndex = 0; } mount(map) { this.isMounted = true; map.addWall(this.x, this.y); + + // when behavior kick off after delay + setTimeout(() => { + this.doBehaviorEvent(map); + }, 10); } update() { } + + async doBehaviorEvent(map) { + // stop if cutscene is playing + if (map.isCutscenePlaying || this.behaviorLoop.length === 0) { + return; + } + + let eventConfig = this.behaviorLoop[this.behaviorLoopIndex]; + eventConfig.who = this.id; + + const eventHandler = new OverworldEvent({ map, event: eventConfig }); + await eventHandler.init(); + + this.behaviorLoopIndex += 1; + if (this.behaviorLoopIndex === this.behaviorLoop.length) { + this.behaviorLoopIndex = 0; + } + + this.doBehaviorEvent(map); + } } \ No newline at end of file diff --git a/Overworld.js b/Overworld.js index 2a253d9..489d21a 100644 --- a/Overworld.js +++ b/Overworld.js @@ -22,7 +22,9 @@ class Overworld { this.map.drawLowerImage(this.ctx, cameraPerson); - Object.values(this.map.gameObjects).forEach(object => { + Object.values(this.map.gameObjects).sort((a,b) => { + return a.y - b.y; + }).forEach(object => { object.sprite.draw(this.ctx, cameraPerson); }) @@ -43,5 +45,12 @@ class Overworld { this.directionInput.init(); this.startGameLoop(); + + this.map.startCutscene([ + { who: "hero", type: "walk", direction: "down" }, + { who: "hero", type: "walk", direction: "down" }, + { who: "hero", type: "walk", direction: "down" }, + { who: "npc1", type: "walk", direction: "right" }, + ]) } } \ No newline at end of file diff --git a/OverworldEvent.js b/OverworldEvent.js new file mode 100644 index 0000000..7e98057 --- /dev/null +++ b/OverworldEvent.js @@ -0,0 +1,54 @@ +class OverworldEvent { + constructor({map, event}) { + this.map = map; + this.event = event; + } + + stand(resolve) { + const who = this.map.gameObjects[ this.event.who ]; + who.startBehavior({ + map: this.map + }, { + type: "stand", + direction: this.event.direction, + time: this.event.time + }) + + // handler to complete when correct person is done walking + const completeHandler = e => { + if (e.detail.whoId === this.event.who) { + document.removeEventListener("PersonStandComplete", completeHandler); + resolve(); + } + } + + document.addEventListener("PersonStandComplete", completeHandler) + } + + walk(resolve) { + const who = this.map.gameObjects[ this.event.who ]; + who.startBehavior({ + map: this.map + }, { + type: "walk", + direction: this.event.direction, + retry: true + }) + + // handler to complete when correct person is done walking + const completeHandler = e => { + if (e.detail.whoId === this.event.who) { + document.removeEventListener("PersonWalkingComplete", completeHandler); + resolve(); + } + } + + document.addEventListener("PersonWalkingComplete", completeHandler) + } + + init() { + return new Promise(resolve => { + this[this.event.type](resolve) + }) + } +} \ No newline at end of file diff --git a/OverworldMap.js b/OverworldMap.js index 7c1ccb3..21a873b 100644 --- a/OverworldMap.js +++ b/OverworldMap.js @@ -8,6 +8,8 @@ class OverworldMap { this.upperImage = new Image(); this.upperImage.src = config.upperSrc; + + this.isCutscenePlaying = false; } drawLowerImage(ctx, cameraPerson) { @@ -24,13 +26,29 @@ class OverworldMap { } mountObjects() { - Object.values(this.gameObjects).forEach(o => { - // todoL determine if this object should actually mount + Object.keys(this.gameObjects).forEach(key => { + let object = this.gameObjects[key]; + object.id = key; - o.mount(this); + + object.mount(this); }) } + async startCutscene(events) { + this.isCutscenePlaying = true; + + for (let i = 0; i < events.length; i++) { + const eventHandler = new OverworldEvent({ + event: events[i], + map: this, + }) + await eventHandler.init(); + } + + this.isCutscenePlaying = false; + } + addWall(x, y) { this.walls[`${x},${y}`] = true; } @@ -56,10 +74,27 @@ window.OverworldMaps = { x: utils.withGrid(8), y: utils.withGrid(2), }), - // npc1: new Person({ - // x: utils.withGrid(6), - // y: utils.withGrid(7), - // }) + npc1: new Person({ + x: utils.withGrid(6), + y: utils.withGrid(5), + behaviorLoop: [ + { type: "stand", direction: "down", time: 800 }, + { type: "stand", direction: "right", time: 300 }, + { type: "stand", direction: "down", time: 400 }, + { type: "stand", direction: "up", time: 700 }, + ] + }), + npc2: new Person({ + x: utils.withGrid(8), + y: utils.withGrid(9), + behaviorLoop: [ + { type: "walk", direction: "left" }, + { type: "stand", direction: "up", time: 800 }, + { type: "walk", direction: "up" }, + { type: "walk", direction: "right" }, + { type: "walk", direction: "down" }, + ] + }) }, walls: { // "16,16": true diff --git a/Person.js b/Person.js index b1b55a5..de86efd 100644 --- a/Person.js +++ b/Person.js @@ -21,7 +21,7 @@ class Person extends GameObject { // more cases for starting to walk come here // case: keyboard ready and arrow pressed - if (this.isPlayerControlled && state.arrow) { + if (state.map.isCutscenePlaying == false && this.isPlayerControlled && state.arrow) { this.startBehavior(state, { type: "walk", direction: state.arrow @@ -37,12 +37,24 @@ class Person extends GameObject { if (behavior.type === "walk") { // stop if space is not free if (state.map.isSpaceTaken(this.x, this.y, this.direction)) { + behavior.retry && setTimeout(() => { + this.startBehavior(state, behavior); + }, 10); return; } // ready to walk state.map.moveWall(this.x, this.y, this.direction); this.movementProgressRemaining = 16; + this.updateSprite(state); + } + + if (behavior.type = "stand") { + setTimeout(() => { + utils.emitEvent("PersonStandComplete", { + whoId: this.id + }) + }, behavior.time); } } @@ -50,6 +62,12 @@ class Person extends GameObject { const [property, change] = this.directionUpdate[this.direction]; this[property] += change; this.movementProgressRemaining -= 1; + + if (this.movementProgressRemaining === 0) { + utils.emitEvent("PersonWalkingComplete", { + whoId: this.id + }) + } } updateSprite() { diff --git a/index.html b/index.html index e629de6..313bb4b 100644 --- a/index.html +++ b/index.html @@ -18,6 +18,7 @@ + \ No newline at end of file diff --git a/utils.js b/utils.js index 24568b1..d7ba581 100644 --- a/utils.js +++ b/utils.js @@ -21,5 +21,12 @@ const utils = { } return {x, y}; + }, + emitEvent(name, detail) { + const event = new CustomEvent(name, { + detail + }); + document.dispatchEvent(event); + } } \ No newline at end of file