This commit is contained in:
syuilo
2026-07-31 12:14:10 +09:00
parent f02afababf
commit ed66ce5568
13 changed files with 298 additions and 248 deletions
@@ -5,9 +5,6 @@
import * as BABYLON from '@babylonjs/core/pure.js';
import EventEmitter from 'eventemitter3';
import { PlayerContainer, type PlayerProfile, type PlayerState } from './PlayerContainer.js';
// TODO: multiplayer関連は全ての子クラスで必要とは限らない(preview用など)ため、このクラスを継承する別のabstract classに分離
const IN_WEB_WORKER = typeof window === 'undefined';
@@ -29,11 +26,6 @@ export abstract class EngineBase<EVs extends EngineBaseEvents> extends EventEmit
protected fps: number | null = null;
protected disposed = false;
protected playerProfiles: Record<string, PlayerProfile> = {};
protected playerContainers: PlayerContainer[] = [];
protected showUsernameOnAvatar: boolean;
protected show2dAvatarOnAvatar: boolean;
public inputs: EventEmitter<{
'click': (event: { x: number; y: number; }) => void;
'keydown': (event: { code: string; shiftKey: boolean; }) => void;
@@ -123,105 +115,6 @@ export abstract class EngineBase<EVs extends EngineBaseEvents> extends EventEmit
public abstract resize(): void;
public updatePlayerProfiles(profiles: Record<string, PlayerProfile>) {
this.playerProfiles = profiles;
for (const playerContainer of this.playerContainers) {
if (this.playerProfiles[playerContainer.id] == null) {
this.sr.disableSnapshotRendering();
playerContainer.destroy();
this.sr.enableSnapshotRendering();
}
}
this.playerContainers = this.playerContainers.filter(p => this.playerProfiles[p.id] != null);
}
public updatePlayerStates(states: Record<string, PlayerState>) {
for (const [k, v] of Object.entries(this.playerProfiles)) {
const playerContainer = this.playerContainers.find(p => p.id === k);
if (playerContainer == null) {
const p = new PlayerContainer({
id: k,
profile: v,
state: states[k],
scene: this.scene,
sr: this.sr,
showUsername: this.showUsernameOnAvatar,
show2dAvatar: this.show2dAvatarOnAvatar,
});
// TODO: loadFurnitureのものとある程度共通化
p.registerMeshes = (meshes) => {
for (const mesh of meshes) {
mesh.receiveShadows = false;
mesh.metadata = { isPlayer: true, playerId: k };
//if (mesh.material) (mesh.material as BABYLON.PBRMaterial).ambientColor = new BABYLON.Color3(0.2, 0.2, 0.2);
if (mesh.material) {
if (mesh.material instanceof BABYLON.MultiMaterial) {
for (const subMat of mesh.material.subMaterials) {
if ((subMat as BABYLON.PBRMaterial).subSurface.isRefractionEnabled) {
(subMat as BABYLON.PBRMaterial).subSurface.isRefractionEnabled = false; // 有効にするとドローコールが激増する
(subMat as BABYLON.PBRMaterial).transparencyMode = BABYLON.PBRMaterial.PBRMATERIAL_ALPHABLEND;
(subMat as BABYLON.PBRMaterial).alpha = 0.5;
(subMat as BABYLON.PBRMaterial).metallic = 1;
}
(subMat as BABYLON.PBRMaterial).reflectionTexture = this.getEnvMap();
if ((subMat as BABYLON.PBRMaterial).metadata == null) (subMat as BABYLON.PBRMaterial).metadata = {};
(subMat as BABYLON.PBRMaterial).metadata.useEnvMap = true;
(subMat as BABYLON.PBRMaterial).useGLTFLightFalloff = true; // Clustered Lightingではphysical falloffを持つマテリアルはアーチファクトが発生する https://doc.babylonjs.com/features/featuresDeepDive/lights/clusteredLighting/#materials-with-a-physical-falloff-may-cause-artefacts
(subMat as BABYLON.PBRMaterial).anisotropy.isEnabled = false; // なんかきれいにレンダリングされないため
}
} else {
if ((mesh.material as BABYLON.PBRMaterial).subSurface.isRefractionEnabled) {
(mesh.material as BABYLON.PBRMaterial).subSurface.isRefractionEnabled = false; // 有効にするとドローコールが激増する
(mesh.material as BABYLON.PBRMaterial).transparencyMode = BABYLON.PBRMaterial.PBRMATERIAL_ALPHABLEND;
(mesh.material as BABYLON.PBRMaterial).alpha = 0.5;
(mesh.material as BABYLON.PBRMaterial).metallic = 1;
}
(mesh.material as BABYLON.PBRMaterial).reflectionTexture = this.getEnvMap();
if ((mesh.material as BABYLON.PBRMaterial).metadata == null) (mesh.material as BABYLON.PBRMaterial).metadata = {};
(mesh.material as BABYLON.PBRMaterial).metadata.useEnvMap = true;
(mesh.material as BABYLON.PBRMaterial).useGLTFLightFalloff = true; // Clustered Lightingではphysical falloffを持つマテリアルはアーチファクトが発生する https://doc.babylonjs.com/features/featuresDeepDive/lights/clusteredLighting/#materials-with-a-physical-falloff-may-cause-artefacts
(mesh.material as BABYLON.PBRMaterial).anisotropy.isEnabled = false; // なんかきれいにレンダリングされないため
}
}
if (!this.scene.meshes.includes(mesh)) this.scene.addMesh(mesh);
}
};
p.loadAvatar().then(() => {
this.sr.disableSnapshotRendering();
this.sr.enableSnapshotRendering();
});
this.playerContainers.push(p);
} else {
if (states[k] != null) {
playerContainer.applyState(states[k]);
}
}
}
}
public clearPlayers() {
this.sr.disableSnapshotRendering();
for (const playerContainer of this.playerContainers) {
playerContainer.destroy();
}
this.sr.enableSnapshotRendering();
this.playerContainers = [];
}
public updateAvatarDisplayOptions(options: { showUsername: boolean; show2dAvatar: boolean }) {
this.showUsernameOnAvatar = options.showUsername;
this.show2dAvatarOnAvatar = options.show2dAvatar;
this.sr.disableSnapshotRendering();
for (const playerContainer of this.playerContainers) {
playerContainer.updateUserInfoDisplayOptions(options);
}
this.sr.enableSnapshotRendering();
}
public destroy() {
this.babylonEngine.stopRenderLoop();
if (this.currentRafId != null) {
@@ -229,9 +122,6 @@ export abstract class EngineBase<EVs extends EngineBaseEvents> extends EventEmit
cancelAnimationFrame(this.currentRafId);
this.currentRafId = null;
}
for (const playerContainer of this.playerContainers) {
playerContainer.destroy();
}
this.babylonEngine.dispose();
this.scene.dispose();
this.disposed = true;
@@ -0,0 +1,143 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core/pure.js';
import { EngineBase } from './EngineBase.js';
import { PlayerContainer, type PlayerProfile, type PlayerState } from './PlayerContainer.js';
const IN_WEB_WORKER = typeof window === 'undefined';
export type MultiplayEngineBaseEvents = {
'loadingProgress': (ctx: { progress: number }) => void;
'contextlost': (ctx: { reason: string; message: string; }) => void;
};
export abstract class MultiplayEngineBase<EVs extends MultiplayEngineBaseEvents> extends EngineBase<EVs> {
protected playerProfiles: Record<string, PlayerProfile> = {};
protected playerContainers: PlayerContainer[] = [];
protected showUsernameOnAvatar: boolean;
protected show2dAvatarOnAvatar: boolean;
constructor(options: {
babylonEngine: BABYLON.WebGPUEngine;
fps: number | null;
showUsernameOnAvatar: boolean;
show2dAvatarOnAvatar: boolean;
}) {
super({
babylonEngine: options.babylonEngine,
fps: options.fps,
});
this.showUsernameOnAvatar = options.showUsernameOnAvatar;
this.show2dAvatarOnAvatar = options.show2dAvatarOnAvatar;
}
public updatePlayerProfiles(profiles: Record<string, PlayerProfile>) {
this.playerProfiles = profiles;
for (const playerContainer of this.playerContainers) {
if (this.playerProfiles[playerContainer.id] == null) {
this.sr.disableSnapshotRendering();
playerContainer.destroy();
this.sr.enableSnapshotRendering();
}
}
this.playerContainers = this.playerContainers.filter(p => this.playerProfiles[p.id] != null);
}
public updatePlayerStates(states: Record<string, PlayerState>) {
for (const [k, v] of Object.entries(this.playerProfiles)) {
const playerContainer = this.playerContainers.find(p => p.id === k);
if (playerContainer == null) {
const p = new PlayerContainer({
id: k,
profile: v,
state: states[k],
scene: this.scene,
sr: this.sr,
showUsername: this.showUsernameOnAvatar,
show2dAvatar: this.show2dAvatarOnAvatar,
});
// TODO: loadFurnitureのものとある程度共通化
p.registerMeshes = (meshes) => {
for (const mesh of meshes) {
mesh.receiveShadows = false;
mesh.metadata = { isPlayer: true, playerId: k };
//if (mesh.material) (mesh.material as BABYLON.PBRMaterial).ambientColor = new BABYLON.Color3(0.2, 0.2, 0.2);
if (mesh.material) {
if (mesh.material instanceof BABYLON.MultiMaterial) {
for (const subMat of mesh.material.subMaterials) {
if ((subMat as BABYLON.PBRMaterial).subSurface.isRefractionEnabled) {
(subMat as BABYLON.PBRMaterial).subSurface.isRefractionEnabled = false; // 有効にするとドローコールが激増する
(subMat as BABYLON.PBRMaterial).transparencyMode = BABYLON.PBRMaterial.PBRMATERIAL_ALPHABLEND;
(subMat as BABYLON.PBRMaterial).alpha = 0.5;
(subMat as BABYLON.PBRMaterial).metallic = 1;
}
(subMat as BABYLON.PBRMaterial).reflectionTexture = this.getEnvMap();
if ((subMat as BABYLON.PBRMaterial).metadata == null) (subMat as BABYLON.PBRMaterial).metadata = {};
(subMat as BABYLON.PBRMaterial).metadata.useEnvMap = true;
(subMat as BABYLON.PBRMaterial).useGLTFLightFalloff = true; // Clustered Lightingではphysical falloffを持つマテリアルはアーチファクトが発生する https://doc.babylonjs.com/features/featuresDeepDive/lights/clusteredLighting/#materials-with-a-physical-falloff-may-cause-artefacts
(subMat as BABYLON.PBRMaterial).anisotropy.isEnabled = false; // なんかきれいにレンダリングされないため
}
} else {
if ((mesh.material as BABYLON.PBRMaterial).subSurface.isRefractionEnabled) {
(mesh.material as BABYLON.PBRMaterial).subSurface.isRefractionEnabled = false; // 有効にするとドローコールが激増する
(mesh.material as BABYLON.PBRMaterial).transparencyMode = BABYLON.PBRMaterial.PBRMATERIAL_ALPHABLEND;
(mesh.material as BABYLON.PBRMaterial).alpha = 0.5;
(mesh.material as BABYLON.PBRMaterial).metallic = 1;
}
(mesh.material as BABYLON.PBRMaterial).reflectionTexture = this.getEnvMap();
if ((mesh.material as BABYLON.PBRMaterial).metadata == null) (mesh.material as BABYLON.PBRMaterial).metadata = {};
(mesh.material as BABYLON.PBRMaterial).metadata.useEnvMap = true;
(mesh.material as BABYLON.PBRMaterial).useGLTFLightFalloff = true; // Clustered Lightingではphysical falloffを持つマテリアルはアーチファクトが発生する https://doc.babylonjs.com/features/featuresDeepDive/lights/clusteredLighting/#materials-with-a-physical-falloff-may-cause-artefacts
(mesh.material as BABYLON.PBRMaterial).anisotropy.isEnabled = false; // なんかきれいにレンダリングされないため
}
}
if (!this.scene.meshes.includes(mesh)) this.scene.addMesh(mesh);
}
};
p.loadAvatar().then(() => {
this.sr.disableSnapshotRendering();
this.sr.enableSnapshotRendering();
});
this.playerContainers.push(p);
} else {
if (states[k] != null) {
playerContainer.applyState(states[k]);
}
}
}
}
public clearPlayers() {
this.sr.disableSnapshotRendering();
for (const playerContainer of this.playerContainers) {
playerContainer.destroy();
}
this.sr.enableSnapshotRendering();
this.playerContainers = [];
}
public updateAvatarDisplayOptions(options: { showUsername: boolean; show2dAvatar: boolean }) {
this.showUsernameOnAvatar = options.showUsername;
this.show2dAvatarOnAvatar = options.show2dAvatar;
this.sr.disableSnapshotRendering();
for (const playerContainer of this.playerContainers) {
playerContainer.updateUserInfoDisplayOptions(options);
}
this.sr.enableSnapshotRendering();
}
public destroy() {
for (const playerContainer of this.playerContainers) {
playerContainer.destroy();
}
super.destroy();
}
}
@@ -8,14 +8,14 @@ import { registerBuiltInLoaders } from '@babylonjs/loaders/dynamic.js';
import { cm, WORLD_SCALE } from 'misskey-world/src/utility.js';
import { FreeCameraManualInput, GRAPHICS_QUALITY, Timer } from './utility.js';
import { TIME_MAP } from './utility.js';
import { EngineBase } from './EngineBase.js';
import { MultiplayEngineBase } from './MultiplayEngineBase.js';
import { LobbyEnvManager } from './envs/lobby.js';
import type { PlayerContainer, PlayerProfile, PlayerState } from './PlayerContainer.js';
import type { WorldEnvManager } from './env.js';
const IN_WEB_WORKER = typeof window === 'undefined';
export class WorldEngine extends EngineBase<{
export class WorldEngine extends MultiplayEngineBase<{
'changeMyPlayerState': (ctx: PlayerState) => void;
'playerPointed': (ctx: { playerId: string; }) => void;
'playSfxUrl': (ctx: {
@@ -22,7 +22,7 @@ import * as BABYLON from '@babylonjs/core/pure.js';
import { registerBuiltInLoaders } from '@babylonjs/loaders/dynamic.js';
import { cm, WORLD_SCALE } from 'misskey-world/src/utility.js';
import { TIME_MAP, getMeshesBoundingBox, Timer, getYRotationDirection, FreeCameraManualInput, remap, GRAPHICS_QUALITY } from '../utility.js';
import { EngineBase } from '../EngineBase.js';
import { MultiplayEngineBase } from '../MultiplayEngineBase.js';
import { genId } from '../id.js';
import { deepClone } from '../clone.js';
import { PlayerContainer, type PlayerProfile, type PlayerState } from '../PlayerContainer.js';
@@ -63,7 +63,7 @@ function intersectBoundingBoxes(bb1: { min: BABYLON.Vector3; max: BABYLON.Vector
return true;
}
export class RoomEngine extends EngineBase<{
export class RoomEngine extends MultiplayEngineBase<{
'changeSelectedState': (ctx: {
selected: {
furnitureId: string;
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onMounted, ref, useTemplateRef } from 'vue';
import { Joystick } from '@/world/joystick.js';
import { Joystick } from '@/world/Joystick.js';
const emit = defineEmits<{
(ev: 'update', vector: { x: number; y: number; }): void;
@@ -189,7 +189,7 @@ import { prefer } from '@/preferences.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { miLocalStorage } from '@/local-storage.js';
import { FURNITURE_UI_DEFS } from '@/world/room/furniture-ui-defs.js';
import { Multiplayer } from '@/world/multiplayer.js';
import { Multiplayer } from '@/world/Multiplayer.js';
import { $i } from '@/i.js';
import { userPage } from '@/filters/user.js';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
+1 -1
View File
@@ -85,7 +85,7 @@ import { prefer } from '@/preferences.js';
import { isTouchUsing } from '@/utility/touch.js';
import { deviceKind } from '@/utility/device-kind.js';
import MkProgressBar from '@/components/MkProgressBar.vue';
import { Multiplayer } from '@/world/multiplayer.js';
import { Multiplayer } from '@/world/Multiplayer.js';
import { $i } from '@/i.js';
import { userPage } from '@/filters/user.js';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
@@ -0,0 +1,52 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { shallowRef } from 'vue';
import { EngineControllerBase } from './EngineControllerBase.js';
import type { PlayerProfile, PlayerState } from 'misskey-world-engine/src/PlayerContainer.js';
import type { MultiplayEngineBase, MultiplayEngineBaseEvents } from 'misskey-world-engine/src/MultiplayEngineBase.js';
export type MultiplayerEngineControllerBaseOptions = {
workerMode?: boolean;
graphicsQuality: number;
fps: number | null;
resolution: number;
antialias: boolean;
};
export abstract class MultiplayerEngineControllerBase<T extends MultiplayEngineBase<MultiplayEngineBaseEvents>> extends EngineControllerBase<T> {
public myPlayerState = shallowRef<PlayerState>({
position: [0, 0, 0],
rotation: [0, 0, 0],
});
public sit() {
this.call('sit');
}
public lyingDown() {
this.call('lyingDown');
}
public standUp() {
this.call('standUp');
}
public updatePlayerProfiles(profiles: Record<string, PlayerProfile>) {
this.call('updatePlayerProfiles', [profiles]);
}
public updatePlayerStates(states: Record<string, PlayerState>) {
this.call('updatePlayerStates', [states]);
}
public clearPlayers() {
this.call('clearPlayers');
}
public updateAvatarDisplayOptions(options: { showUsername: boolean; show2dAvatar: boolean }) {
this.call('updateAvatarDisplayOptions', [options]);
}
}
+82
View File
@@ -0,0 +1,82 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class Wasd {
private isWPressing = false;
private isSPressing = false;
private isAPressing = false;
private isDPressing = false;
private isDashing = false;
private setCameraMoveVector: (vec: { x: number; y: number }, dash: boolean) => void;
constructor(options: {
setCameraMoveVector: Wasd['setCameraMoveVector'];
}) {
this.setCameraMoveVector = options.setCameraMoveVector;
}
private calcWasdVec() {
const vec = { x: 0, y: 0 };
if (this.isWPressing) vec.y -= 1;
if (this.isSPressing) vec.y += 1;
if (this.isAPressing) vec.x -= 1;
if (this.isDPressing) vec.x += 1;
return vec;
}
public keydown(ev: KeyboardEvent) {
if (ev.repeat) return;
switch (ev.code) {
case 'KeyW':
this.isWPressing = true;
this.setCameraMoveVector(this.calcWasdVec(), this.isDashing);
break;
case 'KeyS':
this.isSPressing = true;
this.setCameraMoveVector(this.calcWasdVec(), this.isDashing);
break;
case 'KeyA':
this.isAPressing = true;
this.setCameraMoveVector(this.calcWasdVec(), this.isDashing);
break;
case 'KeyD':
this.isDPressing = true;
this.setCameraMoveVector(this.calcWasdVec(), this.isDashing);
break;
case 'ShiftLeft':
case 'ShiftRight':
this.isDashing = true;
this.setCameraMoveVector(this.calcWasdVec(), this.isDashing);
break;
}
}
public keyup(ev: KeyboardEvent) {
switch (ev.code) {
case 'KeyW':
this.isWPressing = false;
this.setCameraMoveVector(this.calcWasdVec(), this.isDashing);
break;
case 'KeyS':
this.isSPressing = false;
this.setCameraMoveVector(this.calcWasdVec(), this.isDashing);
break;
case 'KeyA':
this.isAPressing = false;
this.setCameraMoveVector(this.calcWasdVec(), this.isDashing);
break;
case 'KeyD':
this.isDPressing = false;
this.setCameraMoveVector(this.calcWasdVec(), this.isDashing);
break;
case 'ShiftLeft':
case 'ShiftRight':
this.isDashing = false;
this.setCameraMoveVector(this.calcWasdVec(), this.isDashing);
break;
}
}
}
+4 -8
View File
@@ -4,7 +4,8 @@
*/
import { shallowRef } from 'vue';
import { EngineControllerBase, WASD } from './EngineControllerBase.js';
import { MultiplayerEngineControllerBase } from './MultiplayerEngineControllerBase.js';
import { Wasd } from './Wasd.js';
import type { WorldEngine } from 'misskey-world-engine/src/engine.js';
import type { PlayerProfile, PlayerState } from 'misskey-world-engine/src/PlayerContainer.js';
@@ -17,14 +18,9 @@ export type WorldEngineControllerOptions = {
};
// 抽象化レイヤー
export class WorldEngineController extends EngineControllerBase<WorldEngine> {
public myPlayerState = shallowRef<PlayerState>({
position: [0, 0, 0],
rotation: [0, 0, 0],
});
export class WorldEngineController extends MultiplayerEngineControllerBase<WorldEngine> {
constructor(options: WorldEngineControllerOptions) {
super(options, new WASD({
super(options, new Wasd({
setCameraMoveVector: (vec, dash) => {
this.call('cameraMove', [vec, dash]);
},
@@ -6,12 +6,10 @@
import { reactive, ref, shallowRef, triggerRef, watch } from 'vue';
import { EventEmitter } from 'eventemitter3';
import type { EngineBase, EngineBaseEvents } from 'frontend-misskey-world-engine/src/EngineBase.js';
import type { PlayerProfile, PlayerState } from 'misskey-world-engine/src/PlayerContainer.js';
import type { Wasd } from './Wasd.js';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
// TODO: multiplayer関連は全ての子クラスで必要とは限らない(preview用など)ため、このクラスを継承する別のabstract classに分離
export type EngineControllerBaseOptions = {
workerMode?: boolean;
graphicsQuality: number;
@@ -24,84 +22,6 @@ type EngineEventsOf<T> = T extends EngineBase<infer X> ? X : EngineBaseEvents;
type ControllerEvents = EventEmitter.ValidEventTypes;
export class WASD {
private isWPressing = false;
private isSPressing = false;
private isAPressing = false;
private isDPressing = false;
private isDashing = false;
private setCameraMoveVector: (vec: { x: number; y: number }, dash: boolean) => void;
constructor(options: {
setCameraMoveVector: WASD['setCameraMoveVector'];
}) {
this.setCameraMoveVector = options.setCameraMoveVector;
}
private calcWasdVec() {
const vec = { x: 0, y: 0 };
if (this.isWPressing) vec.y -= 1;
if (this.isSPressing) vec.y += 1;
if (this.isAPressing) vec.x -= 1;
if (this.isDPressing) vec.x += 1;
return vec;
}
public keydown(ev: KeyboardEvent) {
if (ev.repeat) return;
switch (ev.code) {
case 'KeyW':
this.isWPressing = true;
this.setCameraMoveVector(this.calcWasdVec(), this.isDashing);
break;
case 'KeyS':
this.isSPressing = true;
this.setCameraMoveVector(this.calcWasdVec(), this.isDashing);
break;
case 'KeyA':
this.isAPressing = true;
this.setCameraMoveVector(this.calcWasdVec(), this.isDashing);
break;
case 'KeyD':
this.isDPressing = true;
this.setCameraMoveVector(this.calcWasdVec(), this.isDashing);
break;
case 'ShiftLeft':
case 'ShiftRight':
this.isDashing = true;
this.setCameraMoveVector(this.calcWasdVec(), this.isDashing);
break;
}
}
public keyup(ev: KeyboardEvent) {
switch (ev.code) {
case 'KeyW':
this.isWPressing = false;
this.setCameraMoveVector(this.calcWasdVec(), this.isDashing);
break;
case 'KeyS':
this.isSPressing = false;
this.setCameraMoveVector(this.calcWasdVec(), this.isDashing);
break;
case 'KeyA':
this.isAPressing = false;
this.setCameraMoveVector(this.calcWasdVec(), this.isDashing);
break;
case 'KeyD':
this.isDPressing = false;
this.setCameraMoveVector(this.calcWasdVec(), this.isDashing);
break;
case 'ShiftLeft':
case 'ShiftRight':
this.isDashing = false;
this.setCameraMoveVector(this.calcWasdVec(), this.isDashing);
break;
}
}
}
// UIとエンジンの間に挟まり抽象化を行うレイヤー。
// UIからは、エンジンが直で動いててもワーカーで動いてても同じように操作できるように見える
export abstract class EngineControllerBase<T extends EngineBase<EngineBaseEvents>, E extends EventEmitter.ValidEventTypes = EventEmitter.ValidEventTypes> extends EventEmitter<ControllerEvents & E> {
@@ -113,11 +33,11 @@ export abstract class EngineControllerBase<T extends EngineBase<EngineBaseEvents
public isReady = ref(false);
public initializeProgress = ref(0);
private pointerDownPosition: { x: number; y: number } | null = null;
private wasd: WASD | null = null;
private wasd: Wasd | null = null;
private abortController = new AbortController();
private destroyed = false;
constructor(options: EngineControllerBaseOptions, wasd?: WASD) {
constructor(options: EngineControllerBaseOptions, wasd?: Wasd) {
super();
this.options = options;
this.wasd = wasd ?? null;
@@ -417,34 +337,6 @@ export abstract class EngineControllerBase<T extends EngineBase<EngineBaseEvents
this.call('resumeRender');
}
public sit() {
this.call('sit');
}
public lyingDown() {
this.call('lyingDown');
}
public standUp() {
this.call('standUp');
}
public updatePlayerProfiles(profiles: Record<string, PlayerProfile>) {
this.call('updatePlayerProfiles', [profiles]);
}
public updatePlayerStates(states: Record<string, PlayerState>) {
this.call('updatePlayerStates', [states]);
}
public clearPlayers() {
this.call('clearPlayers');
}
public updateAvatarDisplayOptions(options: { showUsername: boolean; show2dAvatar: boolean }) {
this.call('updateAvatarDisplayOptions', [options]);
}
public takeScreenshot() {
return this.callAndWaitReturn('takeScreenshot');
}
+3 -5
View File
@@ -4,23 +4,21 @@
*/
import { reactive, ref, shallowRef, triggerRef, watch } from 'vue';
import { EventEmitter } from 'eventemitter3';
import * as Misskey from 'misskey-js';
import type { PlayerProfile, PlayerState } from 'misskey-world-engine/src/PlayerContainer.js';
import type { EngineControllerBase } from './EngineControllerBase.js';
import type { MultiplayerEngineControllerBase } from './MultiplayerEngineControllerBase.js';
import { useStream } from '@/stream.js';
import * as os from '@/os.js';
import { withTimeout } from '@/utility/promise-timeout.js';
import { deepEqual } from '@/utility/deep-equal.js';
export class Multiplayer {
public isOnline = ref(false);
private controller: EngineControllerBase<any>;
private controller: MultiplayerEngineControllerBase<any>;
private connection: Misskey.IChannelConnection<Misskey.Channels['world']> | null = null;
private spaceKey: string;
public playerProfiles: Record<string, PlayerProfile> = {};
constructor(spaceKey: string, controller: EngineControllerBase<any>) {
constructor(spaceKey: string, controller: MultiplayerEngineControllerBase<any>) {
this.spaceKey = spaceKey;
this.controller = controller;
@@ -5,7 +5,8 @@
import { ref, shallowRef } from 'vue';
import { cm } from 'misskey-world/src/utility.js';
import { EngineControllerBase, WASD } from '../EngineControllerBase.js';
import { MultiplayerEngineControllerBase } from '../MultiplayerEngineControllerBase.js';
import { Wasd } from '../Wasd.js';
import type { ShallowRef } from 'vue';
import type { RoomState_InstalledFurniture } from 'misskey-world/src/room/furniture.js';
import type { RoomEngine } from 'misskey-world-engine/src/room/engine.js';
@@ -28,7 +29,7 @@ export type RoomControllerOptions = {
};
// 抽象化レイヤー
export class RoomController extends EngineControllerBase<RoomEngine, {
export class RoomController extends MultiplayerEngineControllerBase<RoomEngine, {
'playerPointed': { playerId: string; };
}> {
public isSitting = ref(false);
@@ -46,13 +47,9 @@ export class RoomController extends EngineControllerBase<RoomEngine, {
}[];
} | null>(null);
public roomState: ShallowRef<RoomState>;
public myPlayerState = shallowRef<PlayerState>({
position: [0, 0, 0],
rotation: [0, 0, 0],
});
constructor(roomState: RoomState, options: RoomControllerOptions) {
super(options, new WASD({
super(options, new Wasd({
setCameraMoveVector: (vec, dash) => {
this.call('cameraMove', [vec, dash]);
},