jsge
Version:
Javascript Game Engine
361 lines (303 loc) • 14.2 kB
JavaScript
import { GameStage, CONST, DrawImageObject, ISystemAudio } from "../../src/index.js";
import { utils } from "../../src/index.js";
const angle_2points = utils.angle_2points;
const OVERLAY_LAYER_KEY = "overlay";
const ANIMATION_FIREMOVE = "firemove",
ANIMATION_REACHWALL = "reachwall";
const ENEMY_DETECT_DISTANCE = 150;
export class MapPage extends GameStage {
#keyPressed = { ArrowUp: false, KeyW: false, ArrowLeft: false, KeyA: false, ArrowRight: false, KeyD: false, ArrowDown: false, KeyS: false };
#enemies = [];
#skippedRender = 0;
#spriteKey = "sprite";
tilemapKey = "dungeonGameMapTileset";
fireImagesKey = "fireImages";
defaultAudioKey = "default_audio_key";
fireballCastKey = "fireball_cast_key";
#gameOverAudioKey = "game_over";
#fireballDestroyAudioKey = "fireball_d";
#detectedByGhostAudioKey = "ghost_audio";
register() {
this.iLoader.addTileMap(this.tilemapKey, "./dungeon/map.tmj");
this.iLoader.addImage(this.fireImagesKey, "./dungeon/images/All_Fire_Bullet_Pixel_16x16_00.png");
this.iLoader.addAudio(this.fireballCastKey, "./dungeon/audio/zvuk-poleta-ognennogo-shara.mp3");
this.iLoader.addAudio(this.#fireballDestroyAudioKey, "./dungeon/audio/ognennyiy-shar-vspyihnul.mp3");
this.iLoader.addAudio(this.#detectedByGhostAudioKey, "./dungeon/audio/zvuk-prizraka-prividenie-24332.mp3");
this.iLoader.addAudio(this.defaultAudioKey, "./dungeon/audio/ustrashayuschiy-nagnetayuschiy-zvuk-kapaniya-kapel-v-pustom-zabroshennom-pomeschenii.mp3");
this.iLoader.addAudio(this.#gameOverAudioKey, "./dungeon/audio/nehvatka-vozduha-i-skoraya-konchina.mp3");
this.iLoader.addImage(this.#spriteKey, "./dungeon/tilemap.png");
this.backgroundSounds = new ISystemAudio(this.iLoader);
this.speed = 0;
this.movingInterval = null;
this.fireballs = [];
}
init() {
this.audio.registerAudio(this.fireballCastKey);
this.audio.registerAudio(this.#fireballDestroyAudioKey);
this.audio.registerAudio(this.#detectedByGhostAudioKey);
this.audio.registerAudio(this.#gameOverAudioKey);
this.backgroundSounds.registerAudio(this.defaultAudioKey);
this.backgroundSounds.volume = .5;
this.defaultAudio = this.backgroundSounds.getAudio(this.defaultAudioKey);
this.defaultAudio.loop = true;
this.gameOverAudio = this.audio.getAudio(this.#gameOverAudioKey);
}
start() {
const [w, h] = this.stageData.canvasDimensions;
this.draw.tiledLayer("background", this.tilemapKey);
this.draw.tiledLayer("walls", this.tilemapKey);
this.shadowRect = this.draw.rect(0, 0, w, h, "rgba(0, 0, 0, 0.5)");
//this.shadowRect.sortIndex = 2;
this.shadowRect.blendFunc = [WebGLRenderingContext.ONE, WebGLRenderingContext.DST_COLOR];
this.shadowRect.turnOffOffset();
this.sightView = this.draw.circle(55, 250, 150, "rgba(125, 112, 113, 1)"); //shapeMask
this.draw.tiledLayer("background", this.tilemapKey, false, this.sightView);
this.draw.tiledLayer("walls", this.tilemapKey, true, this.sightView);
this.draw.tiledLayer("decs", this.tilemapKey, false, this.sightView);
this.draw.tiledLayer("animated_decs", this.tilemapKey, false, this.sightView);
this.fireRange = this.draw.conus(55, 250, 120, "rgba(255, 0,0, 0.4)", Math.PI/8, 60);
this.fireRange.setMask(this.sightView);
const monster1 = new Ghost(455, 450, 16, 16, this.#spriteKey, 108, 1);
const monster2 = new Ghost(570, 410, 16, 16, this.#spriteKey, 108, 1);
const monster3 = new Ghost(455, 390, 16, 16, this.#spriteKey, 108, 1);
monster1.setMask(this.sightView);
monster2.setMask(this.sightView);
monster3.setMask(this.sightView);
this.addRenderObject(monster1);
this.addRenderObject(monster2);
this.addRenderObject(monster3);
this.player = this.draw.image(55, 250, 16, 16, this.#spriteKey, 84, { r: 8 }, 1);
this.player.setMask(this.sightView);
//const sightViewVertices = this.calculateCircleVertices({x:55, y:250}, [0, 0], 2*Math.PI, 100, Math.PI/12);
this.greenLight = this.draw.conus(315, 369, 100, "rgba(0,128,0,0.5", Math.PI, 20);
this.greenLight.setMask(this.sightView);
this.#enemies.push(monster1);
this.#enemies.push(monster2);
this.#enemies.push(monster3);
this.navItemBack = this.draw.text(w - 200, 30, "Main menu", "18px sans-serif", "white"),
this.navItemBack.turnOffOffset();
console.log("dungeon started");
this.registerEventListeners();
this.defaultAudio.play();
//setTimeout(() => {
// fix width height after render started, and sizes corrected
//const [w, h] = this.stageData.worldDimensions;
//this.shadowRect.width = w;
//this.shadowRect.height = h;
//},1000);
}
stop() {
this.stageData.cleanUp();
this.unregisterEventListeners();
this.defaultAudio.pause();
}
registerEventListeners() {
this.#registerMouseListeners();
this.#registerKeyboardListeners();
this.#registerSystemEvents();
}
unregisterEventListeners() {
this.#unregisterMouseListeners();
this.#unregisterKeyboardListeners();
this.#unregisterSystemEvents();
}
#registerSystemEvents() {
this.addEventListener(CONST.EVENTS.SYSTEM.RENDER.START, this.#renderAction);
}
#unregisterSystemEvents() {
this.removeEventListener(CONST.EVENTS.SYSTEM.RENDER.START, this.#renderAction);
}
#registerKeyboardListeners() {
document.addEventListener("keydown", this.#pressKeyAction);
document.addEventListener("keyup", this.#removeKeyAction);
}
#unregisterKeyboardListeners() {
document.removeEventListener("keydown", this.#pressKeyAction);
document.removeEventListener("keyup", this.#removeKeyAction);
}
#registerMouseListeners() {
document.addEventListener("mousemove", this.#mouseMoveAction);
document.addEventListener("click", this.#mouseClickAction);
}
#unregisterMouseListeners() {
document.removeEventListener("mousemove", this.#mouseMoveAction);
document.removeEventListener("click", this.#mouseClickAction);
}
#renderAction = (event) => {
if (this.#skippedRender > 8) {
const new_value = Math.random() * (40 - 20) + 20;
this.greenLight.fade_min = new_value;
this.#skippedRender = 0;
} else {
this.#skippedRender += 1;
}
const enemiesLen = this.#enemies.length;
for (let i = 0; i < enemiesLen; i++) {
const enemy = this.#enemies[i];
if (utils.countDistance(enemy, this.player) < ENEMY_DETECT_DISTANCE) {
this.audio.getAudio(this.#detectedByGhostAudioKey).play();
enemy.moveTo(this.player.x, this.player.y);
if (utils.countDistance(enemy, this.player) < 15) {
this.gameOverAudio.play();
alert("Ghosts reach you!! Game over.");
location.reload();
}
} else {
enemy.idle();
}
}
if(this.fireballs.length > 0) {
for (const fireball of this.fireballs) {
this.#fireballFly(fireball);
}
}
}
#pressKeyAction = (event) => {
const code = event.code;
let keyPressed = this.#keyPressed;
keyPressed[code] = true;
if (keyPressed["ArrowUp"] || keyPressed["KeyW"]){
this.stepMove(-(Math.PI/2));
}
if (keyPressed["ArrowLeft"] || keyPressed["KeyA"]) {
this.stepMove(Math.PI);
}
if (keyPressed["ArrowRight"] || keyPressed["KeyD"]) {
this.stepMove(0);
}
if (keyPressed["ArrowDown"] || keyPressed["KeyS"]){
this.stepMove(Math.PI/2);
}
};
stepMove(direction, force) {
const person = this.player;
const forceToUse = !force || force > 1 ? 1 : force,
movementForce = forceToUse * 1,
newCoordX = person.x + movementForce * Math.cos(direction),
newCoordY = person.y + movementForce * Math.sin(direction);
if (!this.isBoundariesCollision(newCoordX, newCoordY, person)) {
person.x = newCoordX;
person.y = newCoordY;
this.sightView.x = newCoordX;
this.sightView.y = newCoordY;
this.fireRange.x = newCoordX;
this.fireRange.y = newCoordY;
this.stageData.centerCameraPosition(newCoordX, newCoordY);
}
}
#removeKeyAction = (event) => {
const code = event.code;
this.#keyPressed[code] = false;
};
#mouseMoveAction = (e) => {
const [xOffset, yOffset] = this.stageData.worldOffset,
x = e.offsetX,
y = e.offsetY,
cursorPosX = x + xOffset,
cursorPosY = y + yOffset,
rad = angle_2points(this.player.x, this.player.y, cursorPosX, cursorPosY);
//console.log("move, x: ", e.offsetX, " y: ", e.offsetY);
//this.player.rotation = rad;
this.fireRange.rotation = rad - (Math.PI/20);
const isNav1Traversed = utils.isPointRectIntersect(e.offsetX, e.offsetY, this.navItemBack.boundariesBox);
if (isNav1Traversed) {
this.navItemBack.strokeStyle = "rgba(0, 0, 0, 0.3)";
document.getElementsByTagName("canvas")[0].style.cursor = "pointer";
} else if (this.navItemBack.strokeStyle) {
this.navItemBack.strokeStyle = undefined;
document.getElementsByTagName("canvas")[0].style.cursor = "default";
} else {
document.getElementsByTagName("canvas")[0].style.cursor = "default";
}
};
#mouseClickAction = (e) => {
const isNav1Click = utils.isPointRectIntersect(e.offsetX, e.offsetY, this.navItemBack.boundariesBox);
if (isNav1Click) {
this.iSystem.stopGameStage("dungeon");
this.iSystem.startGameStage("start");
} else {
const fireball = this.#createFireball();
this.fireballs.push(fireball);
}
}
#createFireball = () => {
const f = this.draw.image(this.player.x, this.player.y, 16, 16, this.fireImagesKey, 406, {r:4});
f.rotation = this.fireRange.rotation;
f.addAnimation(ANIMATION_FIREMOVE, [{duration:100, id:406}, {duration:100, id:407}, {duration:100, id:408}, {duration:100, id:409}, {duration:100, id:500}], true);
f.addAnimation(ANIMATION_REACHWALL, [{duration:100, id:116}, {duration:100, id:117}, {duration:100, id:118}], false);
f.emit(ANIMATION_FIREMOVE);
this.audio.getAudioCloned(this.fireballCastKey).play();
return f;
}
#fireballFly = (fireball) => {
//let distance = 0;
const speed = 1,
direction = this.fireRange.rotation + Math.PI/28;
const newCoordX = fireball.x + speed * Math.cos(direction),
newCoordY = fireball.y + speed * Math.sin(direction);
fireball.x = newCoordX;
fireball.y = newCoordY;
fireball.rotation = this.fireRange.rotation;
//console.log(newCoordX);
if (this.isBoundariesCollision(newCoordX, newCoordY, fireball)
|| this.isObjectsCollision(newCoordX, newCoordY, fireball, this.#enemies)) {
//console.log("boundaries collision happen");
this.fireballs.splice(this.fireballs.indexOf(fireball), 1);
fireball.stopRepeatedAnimation(ANIMATION_FIREMOVE);
fireball.emit(ANIMATION_REACHWALL);
setTimeout(() => {
//remove fireball
fireball.destroy();
this.audio.getAudioCloned(this.#fireballDestroyAudioKey).play();
}, 200);
}
}
calculateCircleVertices(renderObject, offset, angle, width, step) {
const [ xOffset, yOffset ] = offset,
renderObjectX = renderObject.x - xOffset,
renderObjectY = renderObject.y - yOffset,
deg = renderObject.direction || 0,
len = angle + deg;
let conusPolygonCoords = [renderObjectX, renderObjectY];
for (let r = 0; r <= len; r += step) {
let dx = Math.cos(r) * width,
dy = Math.sin(r) * width;
let x2 = renderObjectX + dx,
y2 = renderObjectY + dy,
sightCords = {
x1: renderObjectX,
y1: renderObjectY,
x2: x2,
y2: y2
},
traversalToUse = {x: sightCords.x2, y: sightCords.y2, p:1};
conusPolygonCoords.push(traversalToUse.x, traversalToUse.y);
//const verticesLen = conusPolygonCoords.length;
//if (verticesLen % 6 === 0)
//conusPolygonCoords.push(renderObjectX, renderObjectY);
}
//const excess = conusPolygonCoords.length % 6;
// fix vertices number to be a multiple of 6
//const coordsFixed = conusPolygonCoords.slice(0, conusPolygonCoords.length - excess);
return conusPolygonCoords;
}
}
class Ghost extends DrawImageObject {
#idle = true;
#moveSpeed = 0.4;
constructor(mapX, mapY, width, height, key, imageIndex, spacing) {
super(mapX, mapY, width, height, key, imageIndex, null, null, spacing);
}
moveTo = (x, y) => {
this.#idle = false;
const forceToUse = this.#moveSpeed,
direction = angle_2points(this.x, this.y, x, y),
newCoordX = this.x + forceToUse * Math.cos(direction),
newCoordY = this.y + forceToUse * Math.sin(direction);
this.x = newCoordX;
this.y = newCoordY;
}
idle = () => {
this.#idle = true;
}
}