forked from sent/waves
445 lines
15 KiB
JavaScript
445 lines
15 KiB
JavaScript
/**
|
|
* @file car.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 Animator from '../framework/three.js/animator.class.js';
|
|
|
|
import Utility from '../game/utility.class.js';
|
|
|
|
import { SkeletonUtils } from '../lib/three.js/SkeletonUtils.js';
|
|
import * as THREE from '../lib/three.js/three.module.js';
|
|
|
|
export default class Car extends Entity {
|
|
constructor(context, scene, options = Object.create(null)) {
|
|
options.updatePriority = 4;
|
|
|
|
super(context, scene, options);
|
|
|
|
const tScene = scene.getThreeScene();
|
|
|
|
const gltf = ModelManager.getModel('characters');
|
|
|
|
const animations = Animator.filterAnimations(gltf.animations, [
|
|
'DriverCalm',
|
|
'DriverScreaming',
|
|
'DriverDead',
|
|
], null, null, true, false, true);
|
|
|
|
this._rig = SkeletonUtils.clone(gltf.scene.getObjectByName('Man'));
|
|
|
|
this._rig.getObjectByName('CyclistMesh').removeFromParent();
|
|
this._rig.getObjectByName('CyclistBloodyMesh').removeFromParent();
|
|
|
|
this._animator = new Animator(this._rig, animations);
|
|
|
|
this._group = new THREE.Group();
|
|
this._group.name = 'Car';
|
|
this._group.add(this._rig);
|
|
this._group.add(gltf.scene.getObjectByName('Car'));
|
|
this._group.add(gltf.scene.getObjectByName('CarMonster'));
|
|
tScene.add(this._group);
|
|
|
|
this._group.traverse(obj => {
|
|
obj.matrixAutoUpdate = true;
|
|
obj.frustumCulled = false;
|
|
obj.material = Utility.replaceMaterial(obj.material);
|
|
});
|
|
|
|
this._player = scene.findEntityOfType('Player');
|
|
this._road = scene.findEntityOfType('Road');
|
|
this._overlay = scene.findEntityOfType('Overlay');
|
|
|
|
this._sceneName = null;
|
|
this._sceneSeconds = null;
|
|
|
|
this._stopping = null;
|
|
this._playerFallSeconds = null;
|
|
this._soundPlayed = null;
|
|
this._secondsUntilClonk = null;
|
|
|
|
this._roadPosition = new THREE.Vector2();
|
|
|
|
this._lightPoint = null;
|
|
this._lightSpot = null;
|
|
|
|
const listener = this._player.getListener();
|
|
|
|
this._soundCar = new THREE.PositionalAudio(listener);
|
|
this._soundCar.name = 'SoundCar';
|
|
this._soundCar.matrixAutoUpdate = true;
|
|
this._soundCar.setBuffer(AudioSystem.getSoundBuffer('effect__clonk'));
|
|
this._group.add(this._soundCar);
|
|
}
|
|
|
|
reset(context) {
|
|
this._animator.stopActions();
|
|
|
|
this._sceneName = null;
|
|
|
|
this._stopping = false;
|
|
this._playerFallSeconds = 0;
|
|
this._soundPlayed = false;
|
|
this._secondsUntilClonk = null;
|
|
|
|
this._roadPosition.set(0, 0);
|
|
|
|
if (this._lightPoint !== null) {
|
|
this._lightPoint.pool();
|
|
this._lightPoint = null;
|
|
}
|
|
|
|
if (this._lightSpot !== null) {
|
|
this._lightSpot.pool();
|
|
this._lightSpot = null;
|
|
}
|
|
|
|
this._group.visible = false;
|
|
|
|
this._rig.getObjectByName('DriverCalmMesh').visible = false;
|
|
this._rig.getObjectByName('DriverScreamingMesh').visible = false;
|
|
this._rig.getObjectByName('DriverDeadMesh').visible = false;
|
|
|
|
this._group.getObjectByName('Car').visible = false;
|
|
this._group.getObjectByName('CarMonster').visible = false;
|
|
}
|
|
|
|
update(context) {
|
|
super.update(context);
|
|
|
|
if (this._sceneName === null) {
|
|
return;
|
|
}
|
|
|
|
const dt = context.time.elapsedSeconds;
|
|
|
|
const firstUpdate = this._sceneSeconds === 0;
|
|
|
|
this._sceneSeconds += dt;
|
|
|
|
const distanceMin = this._road.getRoadDistanceMin();
|
|
const distanceMax = this._road.getRoadDistanceMax();
|
|
const type = this._road.getRoadType(this._roadPosition.y);
|
|
const laneCenter = type.laneCenter;
|
|
const laneOffset = type.laneOffset;
|
|
const playerRoadPosition = this._road.getPlayerRoadPosition();
|
|
const playerBiking = this._player.getBiking();
|
|
const pdx = this._roadPosition.x - playerRoadPosition.x;
|
|
const pdd = this._road.getRoadDistanceBetween(playerRoadPosition.y, this._roadPosition.y);
|
|
const chaseVelocity = pdd / 20;
|
|
|
|
let targetX = THREE.MathUtils.clamp(
|
|
this._roadPosition.x,
|
|
playerRoadPosition.x - 0.5,
|
|
playerRoadPosition.x + 0.5);
|
|
let t;
|
|
let againstPlayer = false;
|
|
|
|
switch (this._sceneName) {
|
|
case 'drive_by_calm':
|
|
case 'drive_by_screaming':
|
|
case 'drive_by_dead':
|
|
againstPlayer = true;
|
|
|
|
targetX = -laneOffset + 0.2;
|
|
|
|
this._roadPosition.y -= 0.15 * dt;
|
|
|
|
this._lightSpot.distance = Math.min(13, this._lightSpot.distance + dt * 5);
|
|
|
|
if (pdd > 2 && pdd < 9) {
|
|
this._player.slowDown(1);
|
|
}
|
|
|
|
if (pdd < 15 && !this._soundPlayed) {
|
|
this._soundPlayed = true;
|
|
|
|
AudioSystem.play('effect__car_drive_by');
|
|
|
|
if (this._sceneName === 'drive_by_dead') {
|
|
AudioSystem.play('ambiance__creepy_violin', 1, true, 'violin');
|
|
context.time.setTimeout(() => {
|
|
AudioSystem.stop('violin', 5);
|
|
}, 4000);
|
|
}
|
|
}
|
|
|
|
if (playerBiking && Math.abs(pdx) < 1.3 && Math.abs(pdd) < 2.5) {
|
|
this._player.playScene(context, 'crash');
|
|
}
|
|
|
|
if (this._sceneName === 'drive_by_screaming') {
|
|
this._secondsUntilClonk -= dt;
|
|
|
|
if (this._secondsUntilClonk <= 0) {
|
|
this._secondsUntilClonk += 0.875;
|
|
|
|
this._soundCar.play();
|
|
}
|
|
}
|
|
|
|
if (this._roadPosition.y <= distanceMin) {
|
|
this.reset(context);
|
|
|
|
return;
|
|
}
|
|
|
|
break;
|
|
|
|
case 'chase':
|
|
t = Math.min(1, this._sceneSeconds / 4);
|
|
|
|
this._roadPosition.y = Math.min(this._roadPosition.y - chaseVelocity * dt, playerRoadPosition.y - 0.5);
|
|
|
|
this._lightSpot.distance = 0.01 + t * 20;
|
|
|
|
if (this._sceneSeconds > 4 &&
|
|
this._player.getLookAngleTowards(this._group) < Math.PI * 0.5 || this._sceneSeconds > 15) {
|
|
AudioSystem.stop('ambiance', 0.5);
|
|
|
|
context.time.setTimeout(() => {
|
|
AudioSystem.play('effect__car_reveal');
|
|
}, 2000);
|
|
|
|
this._player.lookAt(this._group, 8, false);
|
|
|
|
this._overlay.playScene(context, 'letterbox', 7);
|
|
|
|
this._overlay.setContrast(0.5, 0.55, 0.55, 20);
|
|
|
|
this._sceneName = 'chase_reveal';
|
|
this._sceneSeconds = 0;
|
|
}
|
|
|
|
break;
|
|
|
|
case 'chase_reveal':
|
|
t = Math.min(1, Math.max(0, (this._sceneSeconds - 2) / 6));
|
|
|
|
this._lightPoint.distance = 1 + t * 0.5;
|
|
this._lightPoint.intensity = 0.5 + t;
|
|
|
|
this._lightSpot.distance = 15;
|
|
this._lightSpot.position.z = 2.5 - 3 * t;
|
|
|
|
this._roadPosition.y = THREE.MathUtils.clamp(
|
|
this._roadPosition.y - chaseVelocity * dt,
|
|
playerRoadPosition.y - 1,
|
|
playerRoadPosition.y - 0.1);
|
|
|
|
if (this._sceneSeconds > 8) {
|
|
AudioSystem.play('ambiance__car_chase', 1, true, 'ambiance');
|
|
|
|
this._roadPosition.y = playerRoadPosition.y - 0.5;
|
|
|
|
this._sceneName = 'chase_started';
|
|
this._sceneSeconds = 0;
|
|
}
|
|
|
|
break;
|
|
|
|
case 'chase_started':
|
|
t = Math.min(1, this._sceneSeconds / 2);
|
|
|
|
this._lightSpot.distance = Math.max(this._lightSpot.distance, 0.1 + t * 15);
|
|
|
|
if (!this._stopping) {
|
|
if (this._player.getFallen()) {
|
|
this._playerFallSeconds += dt;
|
|
this._roadPosition.y = Math.max(this._roadPosition.y - dt, playerRoadPosition.y - 0.5 + this._playerFallSeconds * 0.1);
|
|
} else {
|
|
this._roadPosition.y = THREE.MathUtils.clamp(
|
|
this._roadPosition.y - chaseVelocity * dt,
|
|
playerRoadPosition.y - 0.5,
|
|
playerRoadPosition.y);
|
|
}
|
|
}
|
|
|
|
if (playerBiking && Math.abs(pdd) < 2.5) {
|
|
AudioSystem.play('effect__car_reveal');
|
|
|
|
this._player.playScene(context, 'eaten_by_car');
|
|
|
|
this._sceneName = 'chase_ended';
|
|
this._sceneSeconds = 0;
|
|
}
|
|
|
|
break;
|
|
|
|
case 'chase_ended':
|
|
t = Math.min(1, this._sceneSeconds / 6);
|
|
|
|
this._roadPosition.y = playerRoadPosition.y - 0.3 + t * t * 0.25;
|
|
|
|
break;
|
|
|
|
case 'chicken_race':
|
|
againstPlayer = true;
|
|
|
|
t = Math.min(1, this._sceneSeconds / 2);
|
|
|
|
this._lightSpot.distance = 0.1 + t * 20;
|
|
|
|
if (pdd < 6) {
|
|
this._roadPosition.y -= 0.2 * dt;
|
|
|
|
this._player.pause(1);
|
|
} else {
|
|
this._roadPosition.y -= 0.6 * dt;
|
|
}
|
|
|
|
if (playerRoadPosition.y > this._roadPosition.y - 0.12) {
|
|
AudioSystem.stop('ambiance');
|
|
AudioSystem.play('effect__bike_crash');
|
|
AudioSystem.play('effect__game_over_scream');
|
|
AudioSystem.play('effect__game_over_splatter');
|
|
|
|
this._player.pause(5);
|
|
|
|
this._overlay.playScene(context, 'fade_out_red_splatter', 5, () => {
|
|
this.getScene().findEntityOfType('Director').resetInTuonela(context);
|
|
|
|
this._player.pause(2);
|
|
|
|
this._overlay.playScene(context, 'fade_in_black', 10);
|
|
});
|
|
|
|
this._sceneName = 'collided';
|
|
this._sceneSeconds = 0;
|
|
}
|
|
|
|
break;
|
|
|
|
case 'collided':
|
|
againstPlayer = true;
|
|
this._roadPosition.y = playerRoadPosition.y + 0.12;
|
|
|
|
break;
|
|
}
|
|
|
|
const steerT = firstUpdate ? 1 : 1 - 0.1 ** dt;
|
|
const lastX = this._roadPosition.x;
|
|
|
|
if (this._sceneName !== 'chase_ended') {
|
|
const laneWidth = laneOffset === 0 ? 0.5 : Math.abs(laneOffset) + 1;
|
|
|
|
targetX = THREE.MathUtils.clamp(targetX, laneCenter - laneWidth, laneCenter + laneWidth);
|
|
}
|
|
|
|
this._roadPosition.set(
|
|
this._roadPosition.x * (1 - steerT) + targetX * steerT,
|
|
THREE.MathUtils.clamp(this._roadPosition.y, distanceMin, distanceMax));
|
|
|
|
const dx = dt === 0 ? 0 : (this._roadPosition.x - lastX) / dt;
|
|
|
|
this._road.roadToWorldPosition(this._roadPosition, this._group.position);
|
|
|
|
this._group.position.x += Math.random() * 0.015;
|
|
this._group.position.y += Math.random() * 0.015;
|
|
this._group.position.z += Math.random() * 0.015;
|
|
|
|
this._group.rotation.set(0, 0, 0);
|
|
this._group.rotateY(-this._road.getRoadAngle(this._roadPosition.y) - dx * 0.1
|
|
+ Math.sin(context.time.totalSeconds * 2) * 0.03 + againstPlayer * Math.PI);
|
|
this._group.rotateX(THREE.MathUtils.clamp(-this._road.getForwardSlopeAngle(this._group), -0.5, 0.5));
|
|
|
|
this._animator.update(dt);
|
|
}
|
|
|
|
playScene(context, sceneName) {
|
|
this.reset(context);
|
|
|
|
const environment = this.getScene().findEntityOfType('Environment');
|
|
const isChase = sceneName === 'chase' || sceneName === 'chase_started' || sceneName === 'chicken_race';
|
|
|
|
this._sceneName = sceneName;
|
|
this._sceneSeconds = 0;
|
|
|
|
this._lightPoint = environment.getPointLight(0xfcf095, 1.5, 1, 0.8, 0, 1.5, 0.2);
|
|
this._lightSpot = environment.getSpotLight(0xf1a134, 4, 10, 0.4, 0.5, 0.2, 0, 0.5, 2.4, 0, isChase ? 0.5 : -0.5, 5);
|
|
|
|
this._group.add(this._lightPoint);
|
|
this._group.add(this._lightSpot);
|
|
this._group.add(this._lightSpot.target);
|
|
|
|
this._group.visible = true;
|
|
|
|
switch (sceneName) {
|
|
case 'drive_by_calm':
|
|
this._rig.getObjectByName('DriverCalmMesh').visible = true;
|
|
this._group.getObjectByName('Car').visible = true;
|
|
|
|
this._animator.playAction('DriverCalm', Infinity);
|
|
|
|
this._roadPosition.set(0, this._road.getRoadDistanceMax());
|
|
|
|
break;
|
|
|
|
case 'drive_by_screaming':
|
|
this._rig.getObjectByName('DriverScreamingMesh').visible = true;
|
|
this._group.getObjectByName('Car').visible = true;
|
|
|
|
this._animator.playAction('DriverScreaming', Infinity);
|
|
|
|
this._roadPosition.set(0, this._road.getRoadDistanceMax());
|
|
|
|
this._secondsUntilClonk = 0.28;
|
|
|
|
break;
|
|
|
|
case 'drive_by_dead':
|
|
this._rig.getObjectByName('DriverDeadMesh').visible = true;
|
|
this._group.getObjectByName('Car').visible = true;
|
|
|
|
this._animator.playAction('DriverDead', Infinity);
|
|
|
|
this._roadPosition.set(0, this._road.getRoadDistanceMax());
|
|
|
|
break;
|
|
|
|
case 'chicken_race':
|
|
case 'chase':
|
|
case 'chase_started':
|
|
AudioSystem.play(this._sceneName === 'chase_started' ? 'effect__car_reveal' : 'effect__car_drive_by');
|
|
|
|
this._rig.getObjectByName('DriverDeadMesh').visible = true;
|
|
this._group.getObjectByName('CarMonster').visible = true;
|
|
|
|
this._animator.playAction(sceneName === 'chase' ? 'DriverDead' : 'DriverCalm', Infinity);
|
|
|
|
this._lightPoint.color.setHex(0xff6666);
|
|
this._lightPoint.intensity = sceneName === 'chase' ? 0.2 : 1;
|
|
this._lightPoint.distance = sceneName === 'chase' ? 1 : 3;
|
|
|
|
this._lightSpot.color.setHex(0xff6666);
|
|
this._lightSpot.distance = 0.1;
|
|
this._lightSpot.penumbra = 0.4;
|
|
this._lightSpot.position.y = 1.5;
|
|
this._lightSpot.position.z = 2.5;
|
|
this._lightSpot.target.y = 0.5;
|
|
|
|
this._roadPosition.set(0, this._road.getRoadDistanceMin());
|
|
|
|
if (sceneName === 'chase_started') {
|
|
this._player.lookAt(this._group, 3, false);
|
|
} else if (sceneName === 'chicken_race') {
|
|
this._roadPosition.set(0, this._road.getRoadDistanceMax() - 1);
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
throw new Error(`Car scene "${sceneName}" does not exist`);
|
|
}
|
|
}
|
|
|
|
stop() {
|
|
this._stopping = true;
|
|
}
|
|
}
|
|
|
|
Car.p_register();
|