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 ) { 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); }; } }