mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-08-01 04:45:51 +00:00
wip
This commit is contained in:
@@ -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';
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
@@ -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,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');
|
||||
}
|
||||
|
||||
@@ -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]);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user