multiplayer start
This commit is contained in:
parent
7aaf62bdc5
commit
c4c25f554c
|
@ -18,7 +18,7 @@ const resize = () =>
|
|||
onMounted(() => {
|
||||
editorRef.value.mount(wrapperRef.value);
|
||||
// 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);
|
||||
});
|
||||
|
||||
|
|
|
@ -4,10 +4,15 @@ import {
|
|||
instanceCharacterObject,
|
||||
getCharacterController,
|
||||
Humanoid,
|
||||
GameSocket,
|
||||
EventEmitter,
|
||||
Renderer,
|
||||
randomUUID,
|
||||
} from '@freeblox/engine';
|
||||
|
||||
import { Vector3 } from 'three';
|
||||
import { Quaternion, Vector3 } from 'three';
|
||||
import { ThirdPersonCamera } from './camera';
|
||||
import { GameEvents, SpawnEvent } from '../types/events';
|
||||
|
||||
/**
|
||||
* Gameplay manager.
|
||||
|
@ -32,10 +37,22 @@ export class GameplayComponent extends EngineComponent {
|
|||
|
||||
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');
|
||||
}
|
||||
|
||||
override update(delta: number): void {
|
||||
|
@ -55,9 +72,14 @@ export class GameplayComponent extends EngineComponent {
|
|||
this.character?.localToWorld(this.look);
|
||||
|
||||
this.character?.setVelocity(this.move);
|
||||
const look = this.move.clone().normalize();
|
||||
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) {
|
||||
this.jump = false;
|
||||
this.character?.jump();
|
||||
|
@ -68,12 +90,22 @@ export class GameplayComponent extends EngineComponent {
|
|||
this.cleanUpEvents?.();
|
||||
}
|
||||
|
||||
public async loadCharacter(name: string, uuid?: string) {
|
||||
const char = await instanceCharacterObject(name);
|
||||
if (uuid) char.uuid = uuid;
|
||||
public async loadCharacter(
|
||||
name: string,
|
||||
pos?: Vector3,
|
||||
rot?: Quaternion,
|
||||
uuid?: string
|
||||
) {
|
||||
const char = await instanceCharacterObject(name, pos, uuid);
|
||||
const ctrl = getCharacterController(char);
|
||||
this.world.add(char);
|
||||
this.characters.push(ctrl);
|
||||
|
||||
if (rot) char.quaternion.copy(rot);
|
||||
|
||||
this.events.emit('sceneJoin', char);
|
||||
|
||||
if (uuid !== this.uuid) return;
|
||||
this.character = ctrl;
|
||||
|
||||
this.controls = new ThirdPersonCamera(
|
||||
|
@ -82,7 +114,6 @@ export class GameplayComponent extends EngineComponent {
|
|||
this.renderer.renderer.domElement
|
||||
);
|
||||
this.controls.initialize();
|
||||
this.events.emit('sceneJoin', char);
|
||||
}
|
||||
|
||||
private bindEvents() {
|
||||
|
@ -122,8 +153,13 @@ export class GameplayComponent extends EngineComponent {
|
|||
}
|
||||
};
|
||||
|
||||
const worldLoadedEvent = () => {
|
||||
this.loadCharacter('Diamond');
|
||||
const createCharacterEvent = (event: SpawnEvent) => {
|
||||
this.loadCharacter(
|
||||
event.playerName,
|
||||
event.position,
|
||||
event.rotation,
|
||||
event.playerId
|
||||
);
|
||||
};
|
||||
|
||||
const physicsLoadedEvent = () => {
|
||||
|
@ -132,12 +168,12 @@ export class GameplayComponent extends EngineComponent {
|
|||
|
||||
window.addEventListener('keydown', keyDownEvent);
|
||||
window.addEventListener('keyup', keyUpEvent);
|
||||
this.events.addListener('loadComplete', worldLoadedEvent);
|
||||
this.events.addListener('spawnCharacter', createCharacterEvent);
|
||||
this.events.addListener('physicsComplete', physicsLoadedEvent);
|
||||
return () => {
|
||||
window.removeEventListener('keydown', keyDownEvent);
|
||||
window.removeEventListener('keyup', keyUpEvent);
|
||||
this.events.removeEventListener('loadComplete', worldLoadedEvent);
|
||||
this.events.removeEventListener('spawnCharacter', createCharacterEvent);
|
||||
this.events.removeEventListener('physicsComplete', physicsLoadedEvent);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { EngineEvents } from '@freeblox/engine';
|
||||
import { Vector3 } from 'three';
|
||||
|
||||
export type Events = {};
|
||||
|
||||
|
|
|
@ -159,6 +159,7 @@ const explorerOperation = (
|
|||
};
|
||||
|
||||
register('initialized', () => createSceneMap());
|
||||
register('loadComplete', () => createSceneMap());
|
||||
register('sceneJoin', () => createSceneMap());
|
||||
register('sceneLeave', () => createSceneMap());
|
||||
register('selected', (event) => updateSelectionMap(event));
|
||||
|
|
|
@ -10,6 +10,12 @@
|
|||
"dev": "tsc --watch",
|
||||
"prepare": "npm run build"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"keywords": [
|
||||
"game",
|
||||
"engine",
|
||||
|
@ -19,11 +25,15 @@
|
|||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/three": "^0.152.1",
|
||||
"@types/uuid": "^9.0.2",
|
||||
"typescript": "^5.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dimforge/rapier3d": "^0.11.2",
|
||||
"buffer": "^6.0.3",
|
||||
"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) =>
|
||||
this.createObject(event.type, event.parent);
|
||||
const instanceEvent = (event: InstanceEvent) => {
|
||||
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 = () => {
|
||||
this.world.clear();
|
||||
|
|
|
@ -6,6 +6,7 @@ import { Humanoid } from '../gameobjects/humanoid.object';
|
|||
import { PhysicsObject } from '../gameobjects/physics.object';
|
||||
import { GameObject } from '../types/game-object';
|
||||
import type Rapier from '@dimforge/rapier3d';
|
||||
import { ServerTransformEvent } from '..';
|
||||
|
||||
export class PhysicsWorldComponent extends EngineComponent {
|
||||
public name = PhysicsWorldComponent.name;
|
||||
|
@ -58,14 +59,28 @@ export class PhysicsWorldComponent extends EngineComponent {
|
|||
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('sceneJoin', sceneJoinEvent);
|
||||
this.events.addListener('sceneLeave', sceneLeaveEvent);
|
||||
this.events.addListener('serverTransform', serverTransformEvent);
|
||||
|
||||
return () => {
|
||||
this.events.removeEventListener('loadComplete', worldLoadEvent);
|
||||
this.events.removeEventListener('sceneJoin', sceneJoinEvent);
|
||||
this.events.removeEventListener('sceneLeave', sceneLeaveEvent);
|
||||
this.events.removeEventListener('serverTransform', serverTransformEvent);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ export class PhysicsObject extends PhysicalObject implements PhysicsTicking {
|
|||
isTickingObject = true;
|
||||
|
||||
protected collider?: Rapier.Collider;
|
||||
protected rigidBody?: Rapier.RigidBody;
|
||||
public rigidBody?: Rapier.RigidBody;
|
||||
protected physicsWorldRef?: Rapier.World;
|
||||
|
||||
@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 './net';
|
||||
export * from './utils';
|
||||
export * from './types';
|
||||
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,
|
||||
ColorRepresentation,
|
||||
Vector3,
|
||||
Quaternion,
|
||||
} from 'three';
|
||||
import { SerializedObject } from './game-object';
|
||||
|
||||
export interface MousePositionEvent {
|
||||
position: Vector2;
|
||||
|
@ -63,7 +65,28 @@ export interface ReparentEvent {
|
|||
|
||||
export interface InstanceEvent {
|
||||
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 = {
|
||||
|
@ -84,4 +107,10 @@ export type EngineEvents = {
|
|||
physicsComplete: () => void;
|
||||
initialized: () => 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 { Group } from '../gameobjects/group.object';
|
||||
import { MeshPart } from '../gameobjects/mesh.object';
|
||||
|
@ -36,7 +36,11 @@ export const loadBaseCharacter = async () => {
|
|||
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 cloned = SkeletonUtils.clone(base.root!);
|
||||
|
@ -79,8 +83,9 @@ export const instanceCharacterObject = async (name: string) => {
|
|||
const controller = new Humanoid();
|
||||
controller.position.set(0, 4.75, 0);
|
||||
controller.archivable = false;
|
||||
if (uuid) controller.uuid = uuid;
|
||||
baseObject.add(controller);
|
||||
baseObject.position.set(0, 1, 0);
|
||||
baseObject.position.copy(pos);
|
||||
|
||||
return baseObject;
|
||||
};
|
||||
|
|
|
@ -2,3 +2,4 @@ export * from './debounce';
|
|||
export * from './events';
|
||||
export * from './read-metadata';
|
||||
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':
|
||||
specifier: ^0.11.2
|
||||
version: 0.11.2
|
||||
buffer:
|
||||
specifier: ^6.0.3
|
||||
version: 6.0.3
|
||||
reflect-metadata:
|
||||
specifier: ^0.1.13
|
||||
version: 0.1.13
|
||||
smart-buffer:
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0
|
||||
three:
|
||||
specifier: ^0.153.0
|
||||
version: 0.153.0
|
||||
uuid:
|
||||
specifier: ^9.0.0
|
||||
version: 9.0.0
|
||||
devDependencies:
|
||||
'@types/three':
|
||||
specifier: ^0.152.1
|
||||
version: 0.152.1
|
||||
'@types/uuid':
|
||||
specifier: ^9.0.2
|
||||
version: 9.0.2
|
||||
typescript:
|
||||
specifier: ^5.0.4
|
||||
version: 5.0.4
|
||||
|
@ -1633,6 +1645,10 @@ packages:
|
|||
lil-gui: 0.17.0
|
||||
dev: true
|
||||
|
||||
/@types/uuid@9.0.2:
|
||||
resolution: {integrity: sha512-kNnC1GFBLuhImSnV7w4njQkUiJi0ZXUycu1rUaouPqiKlXkh77JKgdRnTAp1x5eBwcIwbtI+3otwzuIDEuDoxQ==}
|
||||
dev: true
|
||||
|
||||
/@types/webxr@0.5.2:
|
||||
resolution: {integrity: sha512-szL74BnIcok9m7QwYtVmQ+EdIKwbjPANudfuvDrAF8Cljg9MKUlIoc1w5tjj9PMpeSH3U1Xnx//czQybJ0EfSw==}
|
||||
dev: true
|
||||
|
@ -2233,7 +2249,6 @@ packages:
|
|||
|
||||
/base64-js@1.5.1:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
dev: true
|
||||
|
||||
/big-integer@1.6.51:
|
||||
resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==}
|
||||
|
@ -2353,6 +2368,13 @@ packages:
|
|||
ieee754: 1.2.1
|
||||
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:
|
||||
resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==}
|
||||
engines: {node: '>=6'}
|
||||
|
@ -3814,7 +3836,6 @@ packages:
|
|||
|
||||
/ieee754@1.2.1:
|
||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||
dev: true
|
||||
|
||||
/ignore-walk@6.0.3:
|
||||
resolution: {integrity: sha512-C7FfFoTA+bI10qfeydT8aZbvr91vAEU+2W5BZUlzPec47oNb07SsOfwYrtxuvOYdUApPP/Qlh4DtAO51Ekk2QA==}
|
||||
|
@ -6104,7 +6125,6 @@ packages:
|
|||
/smart-buffer@4.2.0:
|
||||
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
|
||||
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
|
||||
dev: true
|
||||
|
||||
/smob@1.4.0:
|
||||
resolution: {integrity: sha512-MqR3fVulhjWuRNSMydnTlweu38UhQ0HXM4buStD/S3mc/BzX3CuM9OmhyQpmtYCvoYdl5ris6TI0ZqH355Ymqg==}
|
||||
|
|
Loading…
Reference in New Issue