diff --git a/GameObject.js b/GameObject.js index 83ff46d..abc9adb 100644 --- a/GameObject.js +++ b/GameObject.js @@ -13,6 +13,8 @@ class GameObject { this.behaviorLoop = config.behaviorLoop || []; this.behaviorLoopIndex = 0; + + this.talking = config.talking || []; } mount(map) { diff --git a/KeyPressListener.js b/KeyPressListener.js new file mode 100644 index 0000000..cb24677 --- /dev/null +++ b/KeyPressListener.js @@ -0,0 +1,26 @@ +class KeyPressListener { + constructor(keyCode, callback) { + let keySafe = true; + this.keydownFunction = function(event) { + if (event.code === keyCode) { + if (keySafe) { + keySafe = false; + callback(); + } + } + }; + this.keyupFunction = function(event) { + if (event.code === keyCode) { + keySafe = true; + } + }; + + document.addEventListener("keydown", this.keydownFunction); + document.addEventListener("keyup", this.keyupFunction); + } + + unbind() { + document.removeEventListener("keydown", this.keydownFunction); + document.removeEventListener("keyup", this.keyupFunction); + } +} \ No newline at end of file diff --git a/Overworld.js b/Overworld.js index 489d21a..727df6e 100644 --- a/Overworld.js +++ b/Overworld.js @@ -37,9 +37,33 @@ class Overworld { step(); } - init() { - this.map = new OverworldMap(window.OverworldMaps.DemoRoom); + bindActionInput() { + new KeyPressListener("Enter", () => { + // check if there is a person to talk to + this.map.checkForActionCutscene(); + }) + } + + bindHeroPositionCheck() { + document.addEventListener("PersonWalkingComplete", e => { + if (e.detail.whoId === "hero") { + // heros position changed + this.map.checkForFootstepCutscene() + } + }) + } + + startMap(mapConfig) { + this.map = new OverworldMap(mapConfig); + this.map.overworld = this; this.map.mountObjects(); + } + + init() { + this.startMap(window.OverworldMaps.DemoRoom); + + this.bindActionInput(); + this.bindHeroPositionCheck(); this.directionInput = new DirectionInput(); this.directionInput.init(); @@ -51,6 +75,8 @@ class Overworld { { who: "hero", type: "walk", direction: "down" }, { who: "hero", type: "walk", direction: "down" }, { who: "npc1", type: "walk", direction: "right" }, + { who: "hero", type: "stand", direction: "left", time: 200 }, + { type: "textMessage", text: "Hi!" }, ]) } } \ No newline at end of file diff --git a/OverworldEvent.js b/OverworldEvent.js index 7e98057..9580e43 100644 --- a/OverworldEvent.js +++ b/OverworldEvent.js @@ -46,6 +46,24 @@ class OverworldEvent { document.addEventListener("PersonWalkingComplete", completeHandler) } + textMessage(resolve) { + if (this.event.faceHero) { + const obj = this.map.gameObjects[this.event.faceHero]; + obj.direction = utils.oppositeDirection(this.map.gameObjects["hero"].direction); + } + + const message = new TextMessage({ + text: this.event.text, + onComplete: () => resolve() + }) + message.init( document.querySelector(".game-container") ) + } + + changeMap(resolve) { + this.map.overworld.startMap(window.OverworldMaps[this.event.map]); + resolve(); + } + init() { return new Promise(resolve => { this[this.event.type](resolve) diff --git a/OverworldMap.js b/OverworldMap.js index 1b7a84a..a19d37c 100644 --- a/OverworldMap.js +++ b/OverworldMap.js @@ -1,6 +1,8 @@ class OverworldMap { constructor(config) { + this.overworld = null; this.gameObjects = config.gameObjects; + this.cutsceneSpaces = config.cutsceneSpaces || {}; this.walls = config.walls || {}; this.lowerImage = new Image(); @@ -52,6 +54,25 @@ class OverworldMap { Object.values(this.gameObjects).forEach(object => object.doBehaviorEvent(this)) } + checkForActionCutscene() { + const hero = this.gameObjects["hero"]; + const nextCoords = utils.nextPosition(hero.x, hero.y, hero.direction); + const match = Object.values(this.gameObjects).find(object => { + return `${object.x},${object.y}` === `${nextCoords.x},${nextCoords.y}` + }); + if (!this.isCutscenePlaying && match && match.talking.length) { + this.startCutscene(match.talking[0].events); + } + } + + checkForFootstepCutscene() { + const hero = this.gameObjects["hero"]; + const match = this.cutsceneSpaces[ `${hero.x},${hero.y}`]; + if (!this.isCutscenePlaying && match) { + this.startCutscene(match[0].events); + } + } + addWall(x, y) { this.walls[`${x},${y}`] = true; } @@ -85,17 +106,31 @@ window.OverworldMaps = { { type: "stand", direction: "right", time: 300 }, { type: "stand", direction: "down", time: 400 }, { type: "stand", direction: "up", time: 700 }, + ], + talking: [ + { + events: [ + { type: "textMessage", text: "Schau dich ruhig um.", faceHero: "npc1" }, + { type: "textMessage", text: "Vielleicht findest du etwas interessantes ..." }, + ] + } ] }), npc2: new Person({ - x: utils.withGrid(8), - y: utils.withGrid(9), + x: utils.withGrid(2), + y: utils.withGrid(5), behaviorLoop: [ - { type: "walk", direction: "left" }, - { type: "stand", direction: "up", time: 800 }, - { type: "walk", direction: "up" }, - { type: "walk", direction: "right" }, - { type: "walk", direction: "down" }, + { type: "stand", direction: "right", time: 300 }, + // { type: "stand", direction: "down", time: 300 }, + ], + talking: [ + { + events: [ + { type: "textMessage", text: "Dieser Raum darf nicht betreten werden.", faceHero: "npc2" }, + { type: "textMessage", text: "Geh weg!" }, + { who: "hero", type: "walk", direction: "right" }, + ] + } ] }) }, @@ -141,20 +176,51 @@ window.OverworldMaps = { [utils.asGridCoord(8,1)] : true, [utils.asGridCoord(7,1)] : true, [utils.asGridCoord(6,1)] : true, + }, + cutsceneSpaces: { + [utils.asGridCoord(1,6)]: [ + { + events: [ + { who: "npc2", type: "walk", direction: "down" }, + { who: "npc2", type: "stand", direction: "left" }, + { who: "hero", type: "stand", direction: "right", time: 300 }, + { type: "textMessage", text: "Du kannst hier nicht rein!" }, + { who: "npc2", type: "walk", direction: "up" }, + { who: "npc2", type: "stand", direction: "right" }, + { who: "hero", type: "walk", direction: "right" }, + { who: "hero", type: "walk", direction: "right" }, + ] + } + ], + [utils.asGridCoord(11,5)]: [ + { + events: [ + { type: "changeMap", map: "Kitchen" } + ] + } + ], } }, Kitchen: { lowerSrc: "/images/maps/room-builder.png", upperSrc: "/images/maps/room-builder.png", gameObjects: { - hero: new GameObject({ - x: 2, - y: 3, + hero: new Person({ + isPlayerControlled: true, + x: utils.withGrid(2), + y: utils.withGrid(3), }), - npc1: new GameObject({ - x: 3, - y: 6, - src: "/images/characters/people/hero-run.png" + npc1: new Person({ + x: utils.withGrid(3), + y: utils.withGrid(6), + // src: "/images/characters/people/hero-run.png", + talking: [ + { + events: [ + { type: "textMessage", text: "You made it!", faceHero: "npc1" }, + ] + } + ] }) } } diff --git a/TextMessage.js b/TextMessage.js new file mode 100644 index 0000000..2a90064 --- /dev/null +++ b/TextMessage.js @@ -0,0 +1,36 @@ +class TextMessage { + constructor({ text, onComplete }) { + this.text = text; + this.onComplete = onComplete; + this.element = null; + } + + createElement() { + this.element = document.createElement("div"); + this.element.classList.add("TextMessage"); + + this.element.innerHTML = (` +

${this.text}

+ + `); + + this.element.querySelector("button").addEventListener("click", () => { + this.done(); + }); + + this.actionListener = new KeyPressListener("Enter", () => { + this.actionListener.unbind(); + this.done(); + }) + } + + done() { + this.element.remove(); + this.onComplete(); + } + + init(container) { + this.createElement(); + container.appendChild(this.element); + } +} \ No newline at end of file diff --git a/index.html b/index.html index 313bb4b..813ae26 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,8 @@ - + + witchday @@ -19,6 +20,8 @@ + + \ No newline at end of file diff --git a/styles/TextMessage.css b/styles/TextMessage.css new file mode 100644 index 0000000..fc77e07 --- /dev/null +++ b/styles/TextMessage.css @@ -0,0 +1,32 @@ +.TextMessage { + position: absolute; + left: 0; + right: 0; + bottom: 0; + height: 36px; + font-size: 12px; + padding: 4px; + background: var(--menu-background); + border-top: 1px solid var(--menu-border-color); + color: var(--menu-font-color); +} + +.TextMessage_p { + margin: 0; + font-size: 12px; +} + +.TextMessage_button { + margin: 0; + font-size: 12px; + padding: 0; + -webkit-appearance: none; + background: none; + border: 0; + font-family: inherit; + cursor: pointer; + + position: absolute; + right: 2px; + bottom: 0; +} \ No newline at end of file diff --git a/style.css b/styles/global.css similarity index 65% rename from style.css rename to styles/global.css index 9ca09f1..d6775e8 100644 --- a/style.css +++ b/styles/global.css @@ -1,3 +1,13 @@ +:root { + --border-color: #000; + --dialog-background: #fff; + + --menu-background: #fff; + --menu-border-color: #aaa; + --menu-font-color: #000; + --menu-selected-background: #ddd; +} + * { box-sizing: border-box; } diff --git a/utils.js b/utils.js index d7ba581..098c4fc 100644 --- a/utils.js +++ b/utils.js @@ -22,6 +22,13 @@ const utils = { return {x, y}; }, + oppositeDirection(direction) { + if (direction === "left") { return "right" } + if (direction === "right") { return "left" } + if (direction === "up") { return "down" } + return "up"; + }, + emitEvent(name, detail) { const event = new CustomEvent(name, { detail