waves/public/assets/g/burgerandfrights/js/entities/spirits.class.js
2025-04-09 17:11:14 -05:00

273 lines
8.5 KiB
JavaScript

/**
* @file spirits.class.js
* @version 1.0.0
*/
import AudioSystem from '../framework/audiosystem.class.js';
import Entity from '../framework/entity.class.js';
import ModelManager from '../framework/modelmanager.class.js';
import Utility from '../game/utility.class.js';
import * as THREE from '../lib/three.js/three.module.js';
export default class Spirits extends Entity {
constructor(context, scene, options = Object.create(null)) {
options.updatePriority = 8;
super(context, scene, options);
const tScene = scene.getThreeScene();
const gltf = ModelManager.getModel('characters');
this._spirits = [];
const spiritOriginal = gltf.scene.getObjectByName('Spirit');
spiritOriginal.matrixAutoUpdate = true;
spiritOriginal.material = Utility.replaceMaterial(spiritOriginal.material);
for (let i = 0; i < 4; i++) {
const spirit = spiritOriginal.clone();
spirit.name = `Spirit${i}`;
tScene.add(spirit);
this._spirits.push({
mesh: spirit,
roadPosition: new THREE.Vector2(),
height: null,
angle: null,
lookSeconds: null,
counter: null,
});
}
this._player = scene.findEntityOfType('Player');
this._road = scene.findEntityOfType('Road');
this._overlay = scene.findEntityOfType('Overlay');
this._sceneName = null;
this._stopping = null;
}
reset(context) {
this._sceneName = null;
this._stopping = false;
this._spirits.forEach(spirit => {
spirit.mesh.visible = false;
spirit.height = 0;
spirit.angle = 0;
spirit.lookSeconds = 0;
spirit.counter = 0;
});
}
update(context) {
super.update(context);
if (this._sceneName === null) {
return;
}
const dt = context.time.elapsedSeconds;
this._sceneSeconds += dt;
const playerRoadPosition = this._road.getPlayerRoadPosition();
let reset = false;
switch (this._sceneName) {
case 'behind_player':
const spirit = this._spirits[0];
const angleToPlayer = this._player.getLookAngleTowards(spirit.mesh);
spirit.height = 0.7 + Math.sin(context.time.totalSeconds) * 0.2;
spirit.roadPosition.set(
spirit.counter < 5 ? 0 : playerRoadPosition.x,
playerRoadPosition.y - (spirit.counter < 5 ? 1.3 : 0.1));
if (spirit.counter % 2 === 0) {
if (angleToPlayer > Math.PI * 0.6) {
spirit.lookSeconds += dt;
if (spirit.lookSeconds > 1) {
spirit.lookSeconds = 0;
spirit.mesh.visible = true;
spirit.counter++;
}
} else {
spirit.lookSeconds = 0;
}
} else if (angleToPlayer < Math.PI * 0.4 || spirit.lookSeconds > 0 && spirit.counter === 5) {
if (spirit.counter < 5) {
spirit.counter++;
} else {
if (spirit.lookSeconds === 0) {
AudioSystem.play('effect__stinger_1');
}
spirit.lookSeconds += dt;
this._player.shakeCamera(0.3, 0.05);
if (spirit.lookSeconds > 0.6) {
this.reset(context);
this._overlay.playScene(context, 'fade_in_noise', 0.5);
return;
}
}
} else {
spirit.lookSeconds = 0;
}
break;
case 'approach_at_speed':
const distanceMax = this._road.getRoadDistanceMax();
const playerSpeed = this._player.getSpeed();
this._spirits.forEach((spirit, i) => {
if (playerSpeed > 0.5 && !this._stopping) {
spirit.counter = Math.min(5, spirit.counter + dt);
} else {
spirit.counter = Math.max(0, spirit.counter - dt);
}
spirit.height = -5 + spirit.counter + 0.7 + Math.sin(context.time.totalSeconds + i) * 0.5;
spirit.roadPosition.x = i < 2 ? -5.5 : 5.5;
if (spirit.roadPosition.y < playerRoadPosition.y - 1) {
spirit.roadPosition.y = Math.min(playerRoadPosition.y + 1 + Math.random() * 2, distanceMax);
}
spirit.mesh.visible = true;
});
break;
case 'surround_player':
let playSound = false;
this._spirits.forEach((spirit, i) => {
const angle = Math.PI * 0.5 * i + context.time.totalSeconds;
const distance = 1 + spirit.lookSeconds * 2;
spirit.angle = angle;
spirit.roadPosition.set(
playerRoadPosition.x + Math.sin(angle) * distance,
playerRoadPosition.y + Math.cos(angle) * distance * 0.1);
spirit.height = 1 + Math.cos(spirit.lookSeconds) * 0.4;
if (!spirit.mesh.visible) {
spirit.mesh.visible = this._player.getLookRotation().y < -0.95;
} else if (this._player.getLookRotation().y > -0.7 || spirit.lookSeconds > 0) {
playSound = spirit.lookSeconds === 0;
spirit.lookSeconds += dt;
this._player.shakeCamera(0.3, 0.05);
if (spirit.lookSeconds > 1.5) {
reset = true;
}
}
});
if (reset) {
this.reset(context);
this._overlay.playScene(context, 'fade_in_noise', 0.5);
}
if (playSound) {
AudioSystem.play('effect__stinger_1');
}
break;
case 'follow_end':
this._spirits.forEach((spirit, i) => {
const n = Math.abs(i - 1.5);
spirit.roadPosition.set(-2 + i * 1.33, playerRoadPosition.y + 0.3);
if (spirit.roadPosition.y > spirit.counter + 8) {
spirit.height += dt * 3;
reset = spirit.height > 20;
} else {
spirit.height = Math.max(3 - n, 10 - this._sceneSeconds * 2 + n * 2)
+ Math.sin(context.time.totalSeconds + i) * 0.4;
}
});
if (reset) {
this.reset(context);
}
break;
}
this._spirits.forEach(spirit => {
if (spirit.parent === null) {
return;
}
this._road.roadToWorldPosition(spirit.roadPosition, spirit.mesh.position);
spirit.mesh.position.y += spirit.height;
spirit.mesh.rotation.set(0,
-spirit.angle - this._road.getRoadAngle(spirit.roadPosition.y) + Math.PI, 0);
});
}
playScene(context, sceneName) {
this.reset(context);
const tScene = this.getScene().getThreeScene();
this._sceneName = sceneName;
this._sceneSeconds = 0;
switch (sceneName) {
case 'behind_player':
this._spirits[0].angle = Math.PI;
break;
case 'approach_at_speed':
this._spirits.forEach((spirit, i) => {
spirit.angle = i < 2 ? -Math.PI * 0.5 : Math.PI * 0.5;
});
break;
case 'surround_player':
break;
case 'follow_end':
this._spirits.forEach(spirit => {
spirit.mesh.visible = true;
spirit.counter = this._road.getPlayerRoadPosition().y;
});
break;
default:
throw new Error(`Spirits scene "${sceneName}" does not exist`);
}
}
stop() {
this._stopping = true;
}
}
Spirits.p_register();