CyberEgg 2077
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

380 lines
10 KiB

// game-app.js
// Copyright (C) 2022 Rob Colbert @[email protected]
// License: Apache-2.0
'use strict';
const DTP_COMPONENT_NAME = 'game-app';
import '../less/style.less';
import {
NiceColor,
NiceEasing,
NiceGame,
NiceInputTools,
NiceMath,
NiceVector2d,
} from 'dtp-nice-game';
import GameBackground from './lib/game-background.js';
import GamePlayer from './lib/game-player.js';
import GameEggSimulator from './lib/game-egg-simulator.js';
import GameEnemies from './lib/game-enemies.js';
import GameEnemyFormation from './lib/game-enemy-formation.js';
import { GameVoiceChannels } from './lib/game-voice-channels.js';
export default class GameApp extends NiceGame {
constructor ( ) {
super(DTP_COMPONENT_NAME);
this.mode = 'starting';
this.initMenuOverlays();
document.addEventListener('ngsdk:game-boot', this.onGameBoot.bind(this));
this.eggs = [ ];
this.playfield = {
width: 960,
height: 540,
margin: 120,
colors: {
margin: {
fill: new NiceColor(0, 0, 0, 0.8),
stroke: new NiceColor(32,32,64, 1.0),
},
},
};
this.voiceChannels = new GameVoiceChannels(this);
}
async run ( ) {
await this.startGameEngine(
this.onGameUpdate.bind(this),
this.onGameRender.bind(this),
);
}
async onGameBoot (event) {
NiceInputTools.cancelEvent(event);
this.gameDisplay.appendChild(this.overlays.mainMenu);
/*
* Initialize the inputs this game needs
*/
this.input.addKey('moveLeft', 'ArrowLeft');
this.input.addKey('moveRight', 'ArrowRight');
this.input.addKey('throwEgg', 'Space');
this.input.addButton('moveLeft', '#btn-move-left');
this.input.addButton('moveRight', '#btn-move-right');
this.input.addButton('throwEgg', '#btn-throw-egg');
this.input.addGamepadButton('throwEgg', 0);
this.input.addGamepadButton('moveLeft', 14);
this.input.addGamepadButton('moveRight', 15);
this.startMusicStream('/dist/assets/audio/music/blueberries');
await this.loadGameAssets();
this.mode = 'menu';
this.background.setMode('story');
}
async onStartGame (event) {
NiceInputTools.cancelEvent(event);
await this.startNewGame();
}
async startMusicStream (url) {
await this.audio.setMusicStream(url);
this.audio.playMusicStream();
this.audio.musicVolume = 0.4;
}
async startNewGame ( ) {
this.mode = 'loading';
this.gameDisplay.removeChild(this.overlays.mainMenu);
this.gameDisplay.appendChild(this.overlays.gameControls);
this.startMusicStream('/dist/assets/audio/music/idra');
await this.loadAudio();
this.player.moveSpeed = 2;
this.eggs.clear();
this.oldWantsThrowEgg = false;
this.mode = 'game';
this.level = 1;
this.startLevel();
this.background.setMode('play');
}
startLevel ( ) {
const now = performance.now();
this.spawnRandomEnemy();
this.nextSpawnInterval = 1800 * 2;
this.nextSpawnTime = now + this.nextSpawnInterval;
this.formations = [ ];
}
onGameUpdate (elapsed, now) {
this.background.update(elapsed, now);
switch (this.mode) {
case 'loading':
break;
case 'menu':
return this.updateMenu(elapsed, now);
case 'game':
return this.updateGame(elapsed, now);
}
}
onGameRender (ctx) {
switch (this.mode) {
case 'loading':
break;
case 'menu':
return this.renderMenu(ctx);
case 'game':
return this.renderGame(ctx);
}
}
updateMenu (/* elapsed, now */) {
if (this.input.isInputPressed('system:start')) {
this.startNewGame();
}
}
renderMenu (ctx) {
this.background.render(ctx);
this.player.render(ctx);
}
updateGame (elapsed, now) {
this.player.update(elapsed, now);
/*
* Use this.oldWantsThrowEgg to gate button presses and only throw the
* egg when the button is first pressed (not every frame while pressed)
*/
const wantsThrowEgg = this.input.isInputPressed('throwEgg');
if (wantsThrowEgg && !this.oldWantsThrowEgg) {
this.throwEgg();
}
this.oldWantsThrowEgg = wantsThrowEgg;
this.eggs.update(elapsed, now);
this.enemies.update(elapsed, now);
/*
* Spawn an enemy from the groyper army at scheduled intervals.
*/
if (now >= this.nextSpawnTime) {
this.nextSpawnTime += this.nextSpawnInterval;
this.spawnRandomEnemy();
}
/*
* Process any current formations
*/
for (const formation of this.formations) {
formation.update();
}
}
spawnRandomEnemy ( ) {
switch (NiceMath.randomRangeInt(1, 4)) {
case 1:
this.enemies.spawnBeardson();
break;
case 2:
this.enemies.spawnGroyper();
break;
case 3:
this.enemies.spawnTorba();
break;
case 4:
this.enemies.spawnFuentesFagFace();
break;
}
}
renderGame (ctx) {
this.background.render(ctx);
this.enemies.render(ctx);
for (const formation of this.formations) {
formation.render(ctx);
}
this.renderPlayfield(ctx);
this.eggs.render(ctx);
this.player.render(ctx);
}
renderPlayfield (ctx) {
ctx.fillStyle = this.playfield.colors.margin.fill.toCssString();
ctx.fillRect(0, 0, this.playfield.margin, this.playfield.height);
ctx.fillRect(this.playfield.width - this.playfield.margin, 0, this.playfield.margin, this.playfield.height);
ctx.beginPath();
ctx.moveTo(this.playfield.margin, 0);
ctx.lineTo(this.playfield.margin, this.playfield.height);
ctx.moveTo(this.playfield.width - this.playfield.margin, 0);
ctx.lineTo(this.playfield.width - this.playfield.margin, this.playfield.height);
ctx.strokeStyle = this.playfield.colors.margin.stroke.toCssString();
ctx.lineWidth = 3.0;
ctx.stroke();
}
throwEgg ( ) {
const moveSpeed = 4 + (Math.random() * 2);
this.log.info('throwEgg', 'throwing egg');
this.eggs.throwEgg(new NiceVector2d(
this.player.position.x,
this.player.position.y - 48,
), moveSpeed);
this.audio.playSound('throw-egg-001');
}
spawnFormation ( ) {
const formation = new GameEnemyFormation(this, new NiceVector2d(480, 20));
formation.addTier(3);
formation.addTier(7);
}
flashBorder (color) {
this
.createTween(
this.playfield.colors.margin.stroke,
{
r: color.r,
g: color.g,
b: color.b,
a: color.a,
},
{
r: this.playfield.colors.margin.stroke.r,
g: this.playfield.colors.margin.stroke.g,
b: this.playfield.colors.margin.stroke.b,
a: this.playfield.colors.margin.stroke.a,
},
NiceEasing.Bounce.InOut,
)
.duration(1000)
.run()
; // end createTween
}
getRandomPlayfieldX (padding = 0) {
const fieldWidth = this.playfield.width - (this.playfield.margin * 2) - (padding * 2);
return this.playfield.margin + padding + (Math.random() * fieldWidth);
}
initMenuOverlays ( ) {
/*
* Attach to each expected menu overlay
*/
this.addOverlay('debugDisplay', '.menu-overlay#debug-display');
this.addOverlay('mainMenu', '.menu-overlay#main-menu');
this.addOverlay('gameControls', '.menu-overlay#game-controls');
document.addEventListener('ngsdk:gamepad-connected', this.onGamepadConnected.bind(this));
this.startButton = this.overlays.mainMenu.querySelector('button#start-button');
this.startButton.onclick = this.onStartGame.bind(this);
this.gameDisplay.removeChild(this.overlays.mainMenu);
this.overlays.mainMenu.removeAttribute('hidden');
this.gameDisplay.removeChild(this.overlays.gameControls);
this.overlays.gameControls.removeAttribute('hidden');
this.overlays.systemMenu.removeAttribute('hidden');
}
async onGamepadConnected (event) {
const { gamepad } = event.detail;
this.log.info('onGamepadConnected', 'gamepad connected', { gamepad });
const gamepadFeedback = this.overlays.systemMenu.querySelector('span#gamepad-feedback');
gamepadFeedback.textContent = 'CONNECTED';
}
async loadGameAssets ( ) {
const jobs = [ ];
this.background = new GameBackground(this);
jobs.push(this.background.load());
this.player = new GamePlayer(this);
jobs.push(this.player.load());
this.enemies = new GameEnemies(this);
jobs.push(this.enemies.load());
this.eggs = new GameEggSimulator(this);
jobs.push(this.eggs.load());
jobs.push(this.loadImage('groyper', '/dist/assets/img/groyper.png', 64, 64));
jobs.push(this.loadImage('groyper.large', '/dist/assets/img/groyper.large.png', 180, 180));
jobs.push(this.loadImage('beardson', '/dist/assets/img/beardson.png', 38, 64));
jobs.push(this.loadImage('beardson.large', '/dist/assets/img/beardson.large.png', 108, 180));
jobs.push(this.loadImage('fuentes-fagface', '/dist/assets/img/fuentes-fagface.png', 49, 64));
jobs.push(this.loadImage('fuentes-puppy', '/dist/assets/img/fuentes-puppy-mode.png', 64, 64));
jobs.push(this.loadImage('fuentes-puppy.large', '/dist/assets/img/fuentes-puppy-mode.large.png', 180, 180));
jobs.push(this.loadImage('torba', '/dist/assets/img/torba.png', 52, 64));
jobs.push(this.loadImage('torba.large', '/dist/assets/img/torba.large.png', 146, 180));
await Promise.all(jobs);
}
async loadAudio ( ) {
const jobs = [ ];
jobs.push(this.audio.loadSound('enemy-spawn', '/dist/assets/audio/sfx/spawn.ogg'));
jobs.push(this.audio.loadSound('throw-egg-001', '/dist/assets/audio/sfx/throw-egg-001.wav'));
jobs.push(this.audio.loadSound('impact-001', '/dist/assets/audio/sfx/impact-001.wav'));
jobs.push(this.audio.loadSound('impact-002', '/dist/assets/audio/sfx/impact-002.wav'));
jobs.push(this.audio.loadSound('impact-003', '/dist/assets/audio/sfx/impact-003.wav'));
jobs.push(this.audio.loadSound('impact-004', '/dist/assets/audio/sfx/impact-004.wav'));
jobs.push(this.audio.loadSound('impact-005', '/dist/assets/audio/sfx/impact-005.wav'));
jobs.push(this.audio.loadSound('impact-006', '/dist/assets/audio/sfx/impact-006.wav'));
jobs.push(this.audio.loadSound('impact-007', '/dist/assets/audio/sfx/impact-007.wav'));
jobs.push(this.audio.loadSound('beardson-touchdown', '/dist/assets/audio/sfx/pew-pew-pew.wav'));
return Promise.all(jobs);
}
}
window.addEventListener('load', async ( ) => {
window.game = new GameApp();
window.game.run();
});