freeblox/packages/client/src/game/core/gameplay.ts

199 lines
5.0 KiB
TypeScript

import {
EngineComponent,
World,
instanceCharacterObject,
getCharacterController,
Humanoid,
GameSocket,
EventEmitter,
Renderer,
randomUUID,
SpawnEvent,
} from '@freeblox/engine';
import { Quaternion, Vector3 } from 'three';
import { ThirdPersonCamera } from './camera';
import { GameEvents } from '../types/events';
/**
* Gameplay manager.
*/
export class GameplayComponent extends EngineComponent {
public name = GameplayComponent.name;
public world!: World;
public cleanUpEvents?: () => void;
public controls!: ThirdPersonCamera;
public character!: Humanoid;
public movementSpeed = 16;
public movement = {
forward: 0,
backward: 0,
left: 0,
right: 0,
};
public jump = false;
private prevVelocity = new Vector3();
private prevJump = false;
private move = new Vector3();
private look = new Vector3();
private server = new GameSocket(this.events);
public uuid = randomUUID();
constructor(
protected renderer: Renderer,
protected events: EventEmitter<GameEvents>
) {
super(renderer, events);
}
override initialize(): void {
this.world = this.renderer.scene.getObjectByName('World') as World;
this.cleanUpEvents = this.bindEvents();
this.server.track(this.world);
this.server.connect('ws://localhost:8256', this.uuid, 'testing');
// this.loadCharacter('test', undefined, undefined, this.uuid);
}
override update(delta: number): void {
this.controls?.update(delta);
this.move.set(0, 0, 0);
this.look.setFromMatrixColumn(this.renderer.camera.matrix, 0);
this.look.multiplyScalar(this.movement.forward + -this.movement.backward);
this.look.crossVectors(this.renderer.camera.up, this.look);
this.move.add(this.look);
this.look.setFromMatrixColumn(this.renderer.camera.matrix, 0);
this.look.multiplyScalar(this.movement.right + -this.movement.left);
this.move.add(this.look);
this.look.copy(this.move).normalize();
this.character?.localToWorld(this.look);
const look = this.move.clone().normalize();
let jump = this.jump;
if (!this.move.equals(this.prevVelocity) || jump !== this.prevJump) {
this.events.emit('sendPlayer', {
playerId: this.uuid,
velocity: this.move,
lookAt: look,
jump,
});
}
this.character?.setVelocity(this.move);
if (this.move.length()) {
this.character?.setLook(look);
}
if (this.jump) {
this.jump = false;
this.character?.jump();
}
this.prevVelocity.copy(this.move);
this.prevJump = jump;
}
override dispose(): void {
this.cleanUpEvents?.();
this.server.dispose();
}
public async loadCharacter(
name: string,
pos?: Vector3,
rot?: Quaternion,
uuid?: string,
objectUUID?: string
) {
const char = await instanceCharacterObject(name, pos);
const ctrl = getCharacterController(char);
if (uuid) ctrl.uuid = uuid;
if (objectUUID) char.uuid = objectUUID;
this.world.add(char);
if (rot) char.applyQuaternion(rot);
this.events.emit('sceneJoin', char);
if (uuid !== this.uuid) return;
this.character = ctrl;
this.controls = new ThirdPersonCamera(
this.renderer.camera,
char,
this.renderer.renderer.domElement
);
this.controls.initialize();
}
private bindEvents() {
const keyDownEvent = (event: KeyboardEvent) => {
switch (event.code) {
case 'KeyW':
this.movement.forward = this.movementSpeed;
break;
case 'KeyS':
this.movement.backward = this.movementSpeed;
break;
case 'KeyA':
this.movement.left = this.movementSpeed;
break;
case 'KeyD':
this.movement.right = this.movementSpeed;
break;
case 'Space':
this.jump = true;
break;
}
};
const keyUpEvent = (event: KeyboardEvent) => {
switch (event.code) {
case 'KeyW':
this.movement.forward = 0;
break;
case 'KeyS':
this.movement.backward = 0;
break;
case 'KeyA':
this.movement.left = 0;
break;
case 'KeyD':
this.movement.right = 0;
break;
}
};
const createCharacterEvent = (event: SpawnEvent) => {
this.loadCharacter(
event.playerName,
event.position,
event.rotation,
event.playerId,
event.objectUUID
);
};
const physicsLoadedEvent = () => {
this.events.emit('initialized');
};
window.addEventListener('keydown', keyDownEvent);
window.addEventListener('keyup', keyUpEvent);
this.events.addListener('spawnCharacter', createCharacterEvent);
this.events.addListener('physicsComplete', physicsLoadedEvent);
return () => {
window.removeEventListener('keydown', keyDownEvent);
window.removeEventListener('keyup', keyUpEvent);
this.events.removeEventListener('spawnCharacter', createCharacterEvent);
this.events.removeEventListener('physicsComplete', physicsLoadedEvent);
};
}
}