multiplayer start
This commit is contained in:
parent
7aaf62bdc5
commit
c4c25f554c
|
@ -18,7 +18,7 @@ const resize = () =>
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
editorRef.value.mount(wrapperRef.value);
|
editorRef.value.mount(wrapperRef.value);
|
||||||
// TODO: for dev
|
// TODO: for dev
|
||||||
editorRef.value.loadLevel('https://lunasqu.ee/freeblox/test-level.json');
|
// editorRef.value.loadLevel('https://lunasqu.ee/freeblox/test-level.json');
|
||||||
window.addEventListener('resize', resize);
|
window.addEventListener('resize', resize);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,15 @@ import {
|
||||||
instanceCharacterObject,
|
instanceCharacterObject,
|
||||||
getCharacterController,
|
getCharacterController,
|
||||||
Humanoid,
|
Humanoid,
|
||||||
|
GameSocket,
|
||||||
|
EventEmitter,
|
||||||
|
Renderer,
|
||||||
|
randomUUID,
|
||||||
} from '@freeblox/engine';
|
} from '@freeblox/engine';
|
||||||
|
|
||||||
import { Vector3 } from 'three';
|
import { Quaternion, Vector3 } from 'three';
|
||||||
import { ThirdPersonCamera } from './camera';
|
import { ThirdPersonCamera } from './camera';
|
||||||
|
import { GameEvents, SpawnEvent } from '../types/events';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gameplay manager.
|
* Gameplay manager.
|
||||||
|
@ -32,10 +37,22 @@ export class GameplayComponent extends EngineComponent {
|
||||||
|
|
||||||
private move = new Vector3();
|
private move = new Vector3();
|
||||||
private look = 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 {
|
override initialize(): void {
|
||||||
this.world = this.renderer.scene.getObjectByName('World') as World;
|
this.world = this.renderer.scene.getObjectByName('World') as World;
|
||||||
this.cleanUpEvents = this.bindEvents();
|
this.cleanUpEvents = this.bindEvents();
|
||||||
|
this.server.track(this.world);
|
||||||
|
this.server.connect('ws://localhost:8256', this.uuid, 'testing');
|
||||||
}
|
}
|
||||||
|
|
||||||
override update(delta: number): void {
|
override update(delta: number): void {
|
||||||
|
@ -55,9 +72,14 @@ export class GameplayComponent extends EngineComponent {
|
||||||
this.character?.localToWorld(this.look);
|
this.character?.localToWorld(this.look);
|
||||||
|
|
||||||
this.character?.setVelocity(this.move);
|
this.character?.setVelocity(this.move);
|
||||||
|
const look = this.move.clone().normalize();
|
||||||
if (this.move.length()) {
|
if (this.move.length()) {
|
||||||
this.character?.setLook(this.move.clone().normalize());
|
this.character?.setLook(look);
|
||||||
}
|
}
|
||||||
|
this.events.emit('sendPlayer', {
|
||||||
|
velocity: this.move,
|
||||||
|
lookAt: look,
|
||||||
|
});
|
||||||
if (this.jump) {
|
if (this.jump) {
|
||||||
this.jump = false;
|
this.jump = false;
|
||||||
this.character?.jump();
|
this.character?.jump();
|
||||||
|
@ -68,12 +90,22 @@ export class GameplayComponent extends EngineComponent {
|
||||||
this.cleanUpEvents?.();
|
this.cleanUpEvents?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async loadCharacter(name: string, uuid?: string) {
|
public async loadCharacter(
|
||||||
const char = await instanceCharacterObject(name);
|
name: string,
|
||||||
if (uuid) char.uuid = uuid;
|
pos?: Vector3,
|
||||||
|
rot?: Quaternion,
|
||||||
|
uuid?: string
|
||||||
|
) {
|
||||||
|
const char = await instanceCharacterObject(name, pos, uuid);
|
||||||
const ctrl = getCharacterController(char);
|
const ctrl = getCharacterController(char);
|
||||||
this.world.add(char);
|
this.world.add(char);
|
||||||
this.characters.push(ctrl);
|
this.characters.push(ctrl);
|
||||||
|
|
||||||
|
if (rot) char.quaternion.copy(rot);
|
||||||
|
|
||||||
|
this.events.emit('sceneJoin', char);
|
||||||
|
|
||||||
|
if (uuid !== this.uuid) return;
|
||||||
this.character = ctrl;
|
this.character = ctrl;
|
||||||
|
|
||||||
this.controls = new ThirdPersonCamera(
|
this.controls = new ThirdPersonCamera(
|
||||||
|
@ -82,7 +114,6 @@ export class GameplayComponent extends EngineComponent {
|
||||||
this.renderer.renderer.domElement
|
this.renderer.renderer.domElement
|
||||||
);
|
);
|
||||||
this.controls.initialize();
|
this.controls.initialize();
|
||||||
this.events.emit('sceneJoin', char);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bindEvents() {
|
private bindEvents() {
|
||||||
|
@ -122,8 +153,13 @@ export class GameplayComponent extends EngineComponent {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const worldLoadedEvent = () => {
|
const createCharacterEvent = (event: SpawnEvent) => {
|
||||||
this.loadCharacter('Diamond');
|
this.loadCharacter(
|
||||||
|
event.playerName,
|
||||||
|
event.position,
|
||||||
|
event.rotation,
|
||||||
|
event.playerId
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const physicsLoadedEvent = () => {
|
const physicsLoadedEvent = () => {
|
||||||
|
@ -132,12 +168,12 @@ export class GameplayComponent extends EngineComponent {
|
||||||
|
|
||||||
window.addEventListener('keydown', keyDownEvent);
|
window.addEventListener('keydown', keyDownEvent);
|
||||||
window.addEventListener('keyup', keyUpEvent);
|
window.addEventListener('keyup', keyUpEvent);
|
||||||
this.events.addListener('loadComplete', worldLoadedEvent);
|
this.events.addListener('spawnCharacter', createCharacterEvent);
|
||||||
this.events.addListener('physicsComplete', physicsLoadedEvent);
|
this.events.addListener('physicsComplete', physicsLoadedEvent);
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('keydown', keyDownEvent);
|
window.removeEventListener('keydown', keyDownEvent);
|
||||||
window.removeEventListener('keyup', keyUpEvent);
|
window.removeEventListener('keyup', keyUpEvent);
|
||||||
this.events.removeEventListener('loadComplete', worldLoadedEvent);
|
this.events.removeEventListener('spawnCharacter', createCharacterEvent);
|
||||||
this.events.removeEventListener('physicsComplete', physicsLoadedEvent);
|
this.events.removeEventListener('physicsComplete', physicsLoadedEvent);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { EngineEvents } from '@freeblox/engine';
|
import { EngineEvents } from '@freeblox/engine';
|
||||||
|
import { Vector3 } from 'three';
|
||||||
|
|
||||||
export type Events = {};
|
export type Events = {};
|
||||||
|
|
||||||
|
|
|
@ -159,6 +159,7 @@ const explorerOperation = (
|
||||||
};
|
};
|
||||||
|
|
||||||
register('initialized', () => createSceneMap());
|
register('initialized', () => createSceneMap());
|
||||||
|
register('loadComplete', () => createSceneMap());
|
||||||
register('sceneJoin', () => createSceneMap());
|
register('sceneJoin', () => createSceneMap());
|
||||||
register('sceneLeave', () => createSceneMap());
|
register('sceneLeave', () => createSceneMap());
|
||||||
register('selected', (event) => updateSelectionMap(event));
|
register('selected', (event) => updateSelectionMap(event));
|
||||||
|
|
|
@ -10,6 +10,12 @@
|
||||||
"dev": "tsc --watch",
|
"dev": "tsc --watch",
|
||||||
"prepare": "npm run build"
|
"prepare": "npm run build"
|
||||||
},
|
},
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"game",
|
"game",
|
||||||
"engine",
|
"engine",
|
||||||
|
@ -19,11 +25,15 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/three": "^0.152.1",
|
"@types/three": "^0.152.1",
|
||||||
|
"@types/uuid": "^9.0.2",
|
||||||
"typescript": "^5.0.4"
|
"typescript": "^5.0.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dimforge/rapier3d": "^0.11.2",
|
"@dimforge/rapier3d": "^0.11.2",
|
||||||
|
"buffer": "^6.0.3",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"three": "^0.153.0"
|
"smart-buffer": "^4.2.0",
|
||||||
|
"three": "^0.153.0",
|
||||||
|
"uuid": "^9.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -161,8 +161,18 @@ export class LevelComponent extends EngineComponent {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const instanceEvent = (event: InstanceEvent) =>
|
const instanceEvent = (event: InstanceEvent) => {
|
||||||
this.createObject(event.type, event.parent);
|
const parent =
|
||||||
|
typeof event.parent === 'string'
|
||||||
|
? this.world.getObjectByProperty('uuid', event.parent)
|
||||||
|
: event.parent;
|
||||||
|
const object = this.createObject(event.type, parent, !!event.data);
|
||||||
|
|
||||||
|
if (event.data && object) {
|
||||||
|
object.deserialize(event.data);
|
||||||
|
this.events.emit('sceneJoin', object);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const resetEvent = () => {
|
const resetEvent = () => {
|
||||||
this.world.clear();
|
this.world.clear();
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { Humanoid } from '../gameobjects/humanoid.object';
|
||||||
import { PhysicsObject } from '../gameobjects/physics.object';
|
import { PhysicsObject } from '../gameobjects/physics.object';
|
||||||
import { GameObject } from '../types/game-object';
|
import { GameObject } from '../types/game-object';
|
||||||
import type Rapier from '@dimforge/rapier3d';
|
import type Rapier from '@dimforge/rapier3d';
|
||||||
|
import { ServerTransformEvent } from '..';
|
||||||
|
|
||||||
export class PhysicsWorldComponent extends EngineComponent {
|
export class PhysicsWorldComponent extends EngineComponent {
|
||||||
public name = PhysicsWorldComponent.name;
|
public name = PhysicsWorldComponent.name;
|
||||||
|
@ -58,14 +59,28 @@ export class PhysicsWorldComponent extends EngineComponent {
|
||||||
this.removePhysics(object);
|
this.removePhysics(object);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const serverTransformEvent = (event: ServerTransformEvent) => {
|
||||||
|
const object = this.trackedObjects.find(
|
||||||
|
(obj) => event.object === obj.uuid
|
||||||
|
) as PhysicsObject;
|
||||||
|
if (!object) return;
|
||||||
|
object.rigidBody?.setTranslation(event.position, false);
|
||||||
|
object.rigidBody?.setRotation(event.quaternion, false);
|
||||||
|
event.velocity && object.rigidBody?.setLinvel(event.velocity, false);
|
||||||
|
event.angularVelocity &&
|
||||||
|
object.rigidBody?.setAngvel(event.angularVelocity, false);
|
||||||
|
};
|
||||||
|
|
||||||
this.events.addListener('loadComplete', worldLoadEvent);
|
this.events.addListener('loadComplete', worldLoadEvent);
|
||||||
this.events.addListener('sceneJoin', sceneJoinEvent);
|
this.events.addListener('sceneJoin', sceneJoinEvent);
|
||||||
this.events.addListener('sceneLeave', sceneLeaveEvent);
|
this.events.addListener('sceneLeave', sceneLeaveEvent);
|
||||||
|
this.events.addListener('serverTransform', serverTransformEvent);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
this.events.removeEventListener('loadComplete', worldLoadEvent);
|
this.events.removeEventListener('loadComplete', worldLoadEvent);
|
||||||
this.events.removeEventListener('sceneJoin', sceneJoinEvent);
|
this.events.removeEventListener('sceneJoin', sceneJoinEvent);
|
||||||
this.events.removeEventListener('sceneLeave', sceneLeaveEvent);
|
this.events.removeEventListener('sceneLeave', sceneLeaveEvent);
|
||||||
|
this.events.removeEventListener('serverTransform', serverTransformEvent);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ export class PhysicsObject extends PhysicalObject implements PhysicsTicking {
|
||||||
isTickingObject = true;
|
isTickingObject = true;
|
||||||
|
|
||||||
protected collider?: Rapier.Collider;
|
protected collider?: Rapier.Collider;
|
||||||
protected rigidBody?: Rapier.RigidBody;
|
public rigidBody?: Rapier.RigidBody;
|
||||||
protected physicsWorldRef?: Rapier.World;
|
protected physicsWorldRef?: Rapier.World;
|
||||||
|
|
||||||
@EditorProperty({ type: Boolean })
|
@EditorProperty({ type: Boolean })
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
|
import { Buffer as BufferPolyfill } from 'buffer';
|
||||||
|
declare var Buffer: typeof BufferPolyfill;
|
||||||
|
globalThis.Buffer = BufferPolyfill;
|
||||||
|
|
||||||
export * from './core';
|
export * from './core';
|
||||||
|
export * from './net';
|
||||||
export * from './utils';
|
export * from './utils';
|
||||||
export * from './types';
|
export * from './types';
|
||||||
export * from './components';
|
export * from './components';
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
export enum ChatType {
|
||||||
|
MESSAGE = 0,
|
||||||
|
COMMAND,
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
export enum ErrorType {
|
||||||
|
AUTH_FAIL = 0,
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
export * from './packet-type.enum';
|
||||||
|
export * from './chat-type.enum';
|
||||||
|
export * from './error-type.enum';
|
||||||
|
export * from './packet';
|
||||||
|
export * from './socket';
|
|
@ -0,0 +1,19 @@
|
||||||
|
export enum PacketType {
|
||||||
|
AUTH = 0,
|
||||||
|
KEEPALIVE,
|
||||||
|
STREAM_START,
|
||||||
|
STREAM_ASSET,
|
||||||
|
STREAM_OBJECT,
|
||||||
|
STREAM_DESTROY,
|
||||||
|
STREAM_EVENT,
|
||||||
|
STREAM_FINISH,
|
||||||
|
STREAM_TRANSFORM,
|
||||||
|
STREAM_CHAT,
|
||||||
|
PLAYER_LIST,
|
||||||
|
PLAYER_JOIN,
|
||||||
|
PLAYER_QUIT,
|
||||||
|
PLAYER_CHARACTER,
|
||||||
|
PLAYER_MOVEMENT,
|
||||||
|
PLAYER_CHAT,
|
||||||
|
ERROR,
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
import { PacketType } from './packet-type.enum';
|
||||||
|
import { SmartBuffer } from 'smart-buffer';
|
||||||
|
import { Instancable } from '../types/instancable';
|
||||||
|
import { Quaternion, Vector3 } from 'three';
|
||||||
|
|
||||||
|
export class Packet {
|
||||||
|
private buffer = new SmartBuffer();
|
||||||
|
constructor(public packet?: PacketType) {}
|
||||||
|
|
||||||
|
write(data: any, type: Instancable<any> | string) {
|
||||||
|
switch (type) {
|
||||||
|
case 'string':
|
||||||
|
case String:
|
||||||
|
this.buffer.writeStringNT(data);
|
||||||
|
break;
|
||||||
|
case 'bool':
|
||||||
|
case Boolean:
|
||||||
|
this.buffer.writeUInt8(data ? 1 : 0);
|
||||||
|
break;
|
||||||
|
case 'float':
|
||||||
|
case Number:
|
||||||
|
this.buffer.writeFloatLE(data);
|
||||||
|
break;
|
||||||
|
case 'uint8':
|
||||||
|
this.buffer.writeUInt8(data);
|
||||||
|
break;
|
||||||
|
case 'int32':
|
||||||
|
this.buffer.writeInt32LE(data);
|
||||||
|
break;
|
||||||
|
case 'uint32':
|
||||||
|
this.buffer.writeUInt32LE(data);
|
||||||
|
break;
|
||||||
|
case 'vec3':
|
||||||
|
this.buffer.writeFloatLE(data.x);
|
||||||
|
this.buffer.writeFloatLE(data.y);
|
||||||
|
this.buffer.writeFloatLE(data.z);
|
||||||
|
break;
|
||||||
|
case 'quat':
|
||||||
|
this.buffer.writeFloatLE(data.x);
|
||||||
|
this.buffer.writeFloatLE(data.y);
|
||||||
|
this.buffer.writeFloatLE(data.z);
|
||||||
|
this.buffer.writeFloatLE(data.w);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
read<T = string>(type: Instancable<any> | string) {
|
||||||
|
switch (type) {
|
||||||
|
case 'string':
|
||||||
|
case String:
|
||||||
|
return this.buffer.readStringNT() as T;
|
||||||
|
case 'bool':
|
||||||
|
case Boolean:
|
||||||
|
return this.buffer.readUInt8() as T;
|
||||||
|
case 'float':
|
||||||
|
case Number:
|
||||||
|
return this.buffer.readFloatLE() as T;
|
||||||
|
case 'uint8':
|
||||||
|
return this.buffer.readUInt8() as T;
|
||||||
|
case 'int32':
|
||||||
|
return this.buffer.readInt32LE() as T;
|
||||||
|
case 'uint32':
|
||||||
|
return this.buffer.readUInt32LE() as T;
|
||||||
|
case 'vec3':
|
||||||
|
return new Vector3(
|
||||||
|
this.buffer.readFloatLE(),
|
||||||
|
this.buffer.readFloatLE(),
|
||||||
|
this.buffer.readFloatLE()
|
||||||
|
) as T;
|
||||||
|
case 'quat':
|
||||||
|
return new Quaternion(
|
||||||
|
this.buffer.readFloatLE(),
|
||||||
|
this.buffer.readFloatLE(),
|
||||||
|
this.buffer.readFloatLE(),
|
||||||
|
this.buffer.readFloatLE()
|
||||||
|
) as T;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeHeader() {
|
||||||
|
if (this.packet === undefined) return;
|
||||||
|
this.buffer.insertUInt8(this.packet, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
toBuffer() {
|
||||||
|
this.writeHeader();
|
||||||
|
return this.buffer.toBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
static from(buffer: Buffer) {
|
||||||
|
const packet = new Packet();
|
||||||
|
packet.buffer = SmartBuffer.fromBuffer(buffer);
|
||||||
|
packet.packet = packet.buffer.readUInt8();
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,155 @@
|
||||||
|
import { Quaternion, Vector3 } from 'three';
|
||||||
|
import { Packet, PacketType } from '.';
|
||||||
|
import { World } from '../gameobjects/world.object';
|
||||||
|
import { Disposable } from '../types/disposable';
|
||||||
|
import { EngineEvents, PlayerEvent } from '../types/events';
|
||||||
|
import { EventEmitter } from '../utils/events';
|
||||||
|
|
||||||
|
export class GameSocket implements Disposable {
|
||||||
|
private ws?: WebSocket;
|
||||||
|
private host?: string;
|
||||||
|
private token!: string;
|
||||||
|
private playerId!: string;
|
||||||
|
private world!: World;
|
||||||
|
private cleanUpEvents?: () => void;
|
||||||
|
constructor(private events: EventEmitter<EngineEvents>) {}
|
||||||
|
|
||||||
|
track(world: World) {
|
||||||
|
this.world = world;
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(host: string, id: string, token: string) {
|
||||||
|
this.host = host;
|
||||||
|
this.playerId = id;
|
||||||
|
this.token = token;
|
||||||
|
this.ws = new WebSocket(host);
|
||||||
|
this.ws.binaryType = 'arraybuffer';
|
||||||
|
this.bindSocket(this.ws!);
|
||||||
|
console.log('connected to', this.host);
|
||||||
|
this.cleanUpEvents = this.bindEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect() {
|
||||||
|
this.ws?.close();
|
||||||
|
this.events.emit('serverDisconnect');
|
||||||
|
console.log('disconnected from', this.host);
|
||||||
|
this.cleanUpEvents?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
this.cleanUpEvents?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
private onOpen() {
|
||||||
|
const packet = new Packet(PacketType.AUTH)
|
||||||
|
.write(this.playerId, String)
|
||||||
|
.write(this.token, String)
|
||||||
|
.write(0, 'uint8')
|
||||||
|
.toBuffer();
|
||||||
|
this.ws?.send(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handlePacket(incoming: Packet) {
|
||||||
|
switch (incoming.packet) {
|
||||||
|
case PacketType.ERROR: {
|
||||||
|
const error = incoming.read(String);
|
||||||
|
alert(error);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PacketType.STREAM_START:
|
||||||
|
console.log('starting world loading');
|
||||||
|
break;
|
||||||
|
case PacketType.STREAM_OBJECT: {
|
||||||
|
const objectUUID = incoming.read(String);
|
||||||
|
const parentUUID = incoming.read(String);
|
||||||
|
const objectType = incoming.read(String);
|
||||||
|
const objectData = JSON.parse(incoming.read(String) as string);
|
||||||
|
this.events.emit('instance', {
|
||||||
|
type: objectType as string,
|
||||||
|
parent: parentUUID,
|
||||||
|
data: objectData,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PacketType.STREAM_TRANSFORM: {
|
||||||
|
const objectUUID = incoming.read(String)!;
|
||||||
|
const objectPos = incoming.read<Vector3>('vec3')!;
|
||||||
|
const objectQuat = incoming.read<Quaternion>('quat')!;
|
||||||
|
const objectLinvel = incoming.read<Vector3>('vec3')!;
|
||||||
|
const objectAngvel = incoming.read<Vector3>('vec3')!;
|
||||||
|
this.events.emit('serverTransform', {
|
||||||
|
object: objectUUID,
|
||||||
|
position: objectPos,
|
||||||
|
quaternion: objectQuat,
|
||||||
|
velocity: objectLinvel,
|
||||||
|
angularVelocity: objectAngvel,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PacketType.STREAM_FINISH: {
|
||||||
|
this.events.emit('loadComplete');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PacketType.PLAYER_LIST: {
|
||||||
|
const playerCount = incoming.read<number>('uint32')!;
|
||||||
|
const players = Array.from({ length: playerCount }, () => null).map(
|
||||||
|
() => incoming.read(String)?.split(':')
|
||||||
|
);
|
||||||
|
console.log('player list', players);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PacketType.PLAYER_JOIN: {
|
||||||
|
const playerId = incoming.read(String)!;
|
||||||
|
const playerName = incoming.read(String)!;
|
||||||
|
console.log('player joined', playerId, playerName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PacketType.PLAYER_CHARACTER: {
|
||||||
|
const playerId = incoming.read(String)!;
|
||||||
|
const playerName = incoming.read(String)!;
|
||||||
|
const position = incoming.read<Vector3>('vec3')!;
|
||||||
|
const rotation = incoming.read<Quaternion>('quat')!;
|
||||||
|
this.events.emit('spawnCharacter', {
|
||||||
|
playerId,
|
||||||
|
playerName,
|
||||||
|
position,
|
||||||
|
rotation,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bindSocket(ws: WebSocket) {
|
||||||
|
ws.addEventListener('message', (event) => {
|
||||||
|
const packet = Packet.from(Buffer.from(event.data));
|
||||||
|
this.handlePacket(packet);
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.addEventListener('close', (event) => {
|
||||||
|
this.disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.addEventListener('error', (event) => {});
|
||||||
|
|
||||||
|
ws.addEventListener('open', (event) => {
|
||||||
|
this.onOpen();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private bindEvents() {
|
||||||
|
const sendPlayerEvent = (event: PlayerEvent) => {
|
||||||
|
if (this.ws?.readyState !== WebSocket.OPEN) return;
|
||||||
|
this.ws.send(
|
||||||
|
new Packet(PacketType.PLAYER_MOVEMENT)
|
||||||
|
.write(event.velocity, 'vec3')
|
||||||
|
.write(event.lookAt, 'vec3')
|
||||||
|
.toBuffer()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.events.on('sendPlayer', sendPlayerEvent);
|
||||||
|
return () => {
|
||||||
|
this.events.removeEventListener('sendPlayer', sendPlayerEvent);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,9 @@ import {
|
||||||
Object3D,
|
Object3D,
|
||||||
ColorRepresentation,
|
ColorRepresentation,
|
||||||
Vector3,
|
Vector3,
|
||||||
|
Quaternion,
|
||||||
} from 'three';
|
} from 'three';
|
||||||
|
import { SerializedObject } from './game-object';
|
||||||
|
|
||||||
export interface MousePositionEvent {
|
export interface MousePositionEvent {
|
||||||
position: Vector2;
|
position: Vector2;
|
||||||
|
@ -63,7 +65,28 @@ export interface ReparentEvent {
|
||||||
|
|
||||||
export interface InstanceEvent {
|
export interface InstanceEvent {
|
||||||
type: string;
|
type: string;
|
||||||
parent?: Object3D;
|
parent?: Object3D | string;
|
||||||
|
data?: SerializedObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServerTransformEvent {
|
||||||
|
object: string;
|
||||||
|
position: Vector3;
|
||||||
|
quaternion: Quaternion;
|
||||||
|
velocity?: Vector3;
|
||||||
|
angularVelocity?: Vector3;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlayerEvent {
|
||||||
|
velocity: Vector3;
|
||||||
|
lookAt: Vector3;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SpawnEvent {
|
||||||
|
playerId: string;
|
||||||
|
playerName: string;
|
||||||
|
position: Vector3;
|
||||||
|
rotation: Quaternion;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EngineEvents = {
|
export type EngineEvents = {
|
||||||
|
@ -84,4 +107,10 @@ export type EngineEvents = {
|
||||||
physicsComplete: () => void;
|
physicsComplete: () => void;
|
||||||
initialized: () => void;
|
initialized: () => void;
|
||||||
reset: () => void;
|
reset: () => void;
|
||||||
|
|
||||||
|
spawnCharacter: (event: SpawnEvent) => void;
|
||||||
|
sendPlayer: (event: PlayerEvent) => void;
|
||||||
|
serverConnect: () => void;
|
||||||
|
serverTransform: (obj: ServerTransformEvent) => void;
|
||||||
|
serverDisconnect: () => void;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { AnimationClip, Bone, Object3D, SkinnedMesh } from 'three';
|
import { AnimationClip, Bone, Object3D, SkinnedMesh, Vector3 } from 'three';
|
||||||
import { assetManager } from '../assets';
|
import { assetManager } from '../assets';
|
||||||
import { Group } from '../gameobjects/group.object';
|
import { Group } from '../gameobjects/group.object';
|
||||||
import { MeshPart } from '../gameobjects/mesh.object';
|
import { MeshPart } from '../gameobjects/mesh.object';
|
||||||
|
@ -36,7 +36,11 @@ export const loadBaseCharacter = async () => {
|
||||||
return cachedMeta;
|
return cachedMeta;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const instanceCharacterObject = async (name: string) => {
|
export const instanceCharacterObject = async (
|
||||||
|
name: string,
|
||||||
|
pos = new Vector3(0, 1, 0),
|
||||||
|
uuid?: string
|
||||||
|
) => {
|
||||||
const base = await loadBaseCharacter();
|
const base = await loadBaseCharacter();
|
||||||
|
|
||||||
const cloned = SkeletonUtils.clone(base.root!);
|
const cloned = SkeletonUtils.clone(base.root!);
|
||||||
|
@ -79,8 +83,9 @@ export const instanceCharacterObject = async (name: string) => {
|
||||||
const controller = new Humanoid();
|
const controller = new Humanoid();
|
||||||
controller.position.set(0, 4.75, 0);
|
controller.position.set(0, 4.75, 0);
|
||||||
controller.archivable = false;
|
controller.archivable = false;
|
||||||
|
if (uuid) controller.uuid = uuid;
|
||||||
baseObject.add(controller);
|
baseObject.add(controller);
|
||||||
baseObject.position.set(0, 1, 0);
|
baseObject.position.copy(pos);
|
||||||
|
|
||||||
return baseObject;
|
return baseObject;
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,3 +2,4 @@ export * from './debounce';
|
||||||
export * from './events';
|
export * from './events';
|
||||||
export * from './read-metadata';
|
export * from './read-metadata';
|
||||||
export * from './character';
|
export * from './character';
|
||||||
|
export * from './random';
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
export const randomUUID = () => v4();
|
|
@ -169,16 +169,28 @@ importers:
|
||||||
'@dimforge/rapier3d':
|
'@dimforge/rapier3d':
|
||||||
specifier: ^0.11.2
|
specifier: ^0.11.2
|
||||||
version: 0.11.2
|
version: 0.11.2
|
||||||
|
buffer:
|
||||||
|
specifier: ^6.0.3
|
||||||
|
version: 6.0.3
|
||||||
reflect-metadata:
|
reflect-metadata:
|
||||||
specifier: ^0.1.13
|
specifier: ^0.1.13
|
||||||
version: 0.1.13
|
version: 0.1.13
|
||||||
|
smart-buffer:
|
||||||
|
specifier: ^4.2.0
|
||||||
|
version: 4.2.0
|
||||||
three:
|
three:
|
||||||
specifier: ^0.153.0
|
specifier: ^0.153.0
|
||||||
version: 0.153.0
|
version: 0.153.0
|
||||||
|
uuid:
|
||||||
|
specifier: ^9.0.0
|
||||||
|
version: 9.0.0
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@types/three':
|
'@types/three':
|
||||||
specifier: ^0.152.1
|
specifier: ^0.152.1
|
||||||
version: 0.152.1
|
version: 0.152.1
|
||||||
|
'@types/uuid':
|
||||||
|
specifier: ^9.0.2
|
||||||
|
version: 9.0.2
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.0.4
|
specifier: ^5.0.4
|
||||||
version: 5.0.4
|
version: 5.0.4
|
||||||
|
@ -1633,6 +1645,10 @@ packages:
|
||||||
lil-gui: 0.17.0
|
lil-gui: 0.17.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/uuid@9.0.2:
|
||||||
|
resolution: {integrity: sha512-kNnC1GFBLuhImSnV7w4njQkUiJi0ZXUycu1rUaouPqiKlXkh77JKgdRnTAp1x5eBwcIwbtI+3otwzuIDEuDoxQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/webxr@0.5.2:
|
/@types/webxr@0.5.2:
|
||||||
resolution: {integrity: sha512-szL74BnIcok9m7QwYtVmQ+EdIKwbjPANudfuvDrAF8Cljg9MKUlIoc1w5tjj9PMpeSH3U1Xnx//czQybJ0EfSw==}
|
resolution: {integrity: sha512-szL74BnIcok9m7QwYtVmQ+EdIKwbjPANudfuvDrAF8Cljg9MKUlIoc1w5tjj9PMpeSH3U1Xnx//czQybJ0EfSw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -2233,7 +2249,6 @@ packages:
|
||||||
|
|
||||||
/base64-js@1.5.1:
|
/base64-js@1.5.1:
|
||||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/big-integer@1.6.51:
|
/big-integer@1.6.51:
|
||||||
resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==}
|
resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==}
|
||||||
|
@ -2353,6 +2368,13 @@ packages:
|
||||||
ieee754: 1.2.1
|
ieee754: 1.2.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/buffer@6.0.3:
|
||||||
|
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
|
||||||
|
dependencies:
|
||||||
|
base64-js: 1.5.1
|
||||||
|
ieee754: 1.2.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/builtin-modules@3.3.0:
|
/builtin-modules@3.3.0:
|
||||||
resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==}
|
resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
@ -3814,7 +3836,6 @@ packages:
|
||||||
|
|
||||||
/ieee754@1.2.1:
|
/ieee754@1.2.1:
|
||||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/ignore-walk@6.0.3:
|
/ignore-walk@6.0.3:
|
||||||
resolution: {integrity: sha512-C7FfFoTA+bI10qfeydT8aZbvr91vAEU+2W5BZUlzPec47oNb07SsOfwYrtxuvOYdUApPP/Qlh4DtAO51Ekk2QA==}
|
resolution: {integrity: sha512-C7FfFoTA+bI10qfeydT8aZbvr91vAEU+2W5BZUlzPec47oNb07SsOfwYrtxuvOYdUApPP/Qlh4DtAO51Ekk2QA==}
|
||||||
|
@ -6104,7 +6125,6 @@ packages:
|
||||||
/smart-buffer@4.2.0:
|
/smart-buffer@4.2.0:
|
||||||
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
|
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
|
||||||
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
|
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/smob@1.4.0:
|
/smob@1.4.0:
|
||||||
resolution: {integrity: sha512-MqR3fVulhjWuRNSMydnTlweu38UhQ0HXM4buStD/S3mc/BzX3CuM9OmhyQpmtYCvoYdl5ris6TI0ZqH355Ymqg==}
|
resolution: {integrity: sha512-MqR3fVulhjWuRNSMydnTlweu38UhQ0HXM4buStD/S3mc/BzX3CuM9OmhyQpmtYCvoYdl5ris6TI0ZqH355Ymqg==}
|
||||||
|
|
Loading…
Reference in New Issue