350 lines
10 KiB
TypeScript
350 lines
10 KiB
TypeScript
import { Mesh, PBRMaterial, Texture, AbstractMesh, Plane, Vector3, Scene, Color3 } from "@babylonjs/core";
|
||
import { Observer } from "@babylonjs/core/Misc/observable";
|
||
import { Nullable } from "@babylonjs/core/types";
|
||
import { Monobehiver } from '../base/Monobehiver';
|
||
import { Dictionary } from '../utils/Dictionary';
|
||
|
||
type RollerDoorOptions = {
|
||
upY?: number;
|
||
downY?: number;
|
||
speed?: number;
|
||
meshNames?: string[];
|
||
};
|
||
|
||
/**
|
||
* 游戏管理器类 - 负责材质管理和场景控制
|
||
*
|
||
* 核心功能:
|
||
* - 材质管理(applyMaterial)
|
||
* - 卷帘门动画(待确认是否实际使用)
|
||
* - Y轴剖切(待确认是否实际使用)
|
||
*/
|
||
export class GameManager extends Monobehiver {
|
||
private materialDic: Dictionary<PBRMaterial>;
|
||
private meshDic: Dictionary<any>;
|
||
|
||
// 卷帘门相关(如未使用可删除)
|
||
private rollerDoorMeshes: AbstractMesh[];
|
||
private rollerDoorGroup: AbstractMesh | null;
|
||
private rollerDoorInitialY: Map<string, number>;
|
||
private rollerDoorObserver: Nullable<Observer<Scene>>;
|
||
private rollerDoorIsOpen: boolean;
|
||
private rollerDoorNames: string[];
|
||
|
||
// Y轴剖切相关(如未使用可删除)
|
||
private yClipPlane: Plane | null;
|
||
private yClipTargets: string[] | null;
|
||
|
||
constructor(mainApp: any) {
|
||
super(mainApp);
|
||
this.materialDic = new Dictionary<PBRMaterial>();
|
||
this.meshDic = new Dictionary<any>();
|
||
this.rollerDoorMeshes = [];
|
||
this.rollerDoorGroup = null;
|
||
this.rollerDoorInitialY = new Map();
|
||
this.rollerDoorObserver = null;
|
||
this.rollerDoorIsOpen = false;
|
||
this.rollerDoorNames = ["Box006.001", "Box005.001"];
|
||
this.yClipPlane = null;
|
||
this.yClipTargets = null;
|
||
}
|
||
|
||
/** 调试:返回当前场景中所有网格名称 */
|
||
listMeshNames(): string[] {
|
||
return this.meshDic.Keys();
|
||
}
|
||
|
||
/** 初始化游戏管理器 */
|
||
async Awake() {
|
||
const scene = this.mainApp.appScene?.object;
|
||
if (!scene) {
|
||
console.warn('Scene not found');
|
||
return;
|
||
}
|
||
|
||
this.updateDictionaries();
|
||
}
|
||
|
||
/**
|
||
* 更新材质和网格字典(从场景中同步)
|
||
*/
|
||
updateDictionaries(): void {
|
||
const scene = this.mainApp.appScene?.object;
|
||
if (!scene) return;
|
||
|
||
this.materialDic.Clear();
|
||
this.meshDic.Clear();
|
||
|
||
// 更新材质字典
|
||
for (const mat of scene.materials) {
|
||
if (!(mat as any)._isDisposed && !this.materialDic.Has(mat.name)) {
|
||
this.materialDic.Set(mat.name, mat as PBRMaterial);
|
||
}
|
||
}
|
||
|
||
// 更新网格字典
|
||
for (const mesh of scene.meshes) {
|
||
if (mesh instanceof Mesh && !mesh.isDisposed() && !this.meshDic.Has(mesh.name)) {
|
||
this.meshDic.Set(mesh.name, mesh);
|
||
}
|
||
|
||
const mat = mesh.material;
|
||
if (mat instanceof PBRMaterial && !(mat as any)._isDisposed) {
|
||
this.materialDic.Set(mat.name, mat);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 应用材质属性
|
||
* @param options 材质配置选项
|
||
*/
|
||
applyMaterial(options: {
|
||
target: string;
|
||
modelId?: string;
|
||
albedoColor?: string;
|
||
albedoTexture?: string;
|
||
normalMap?: string;
|
||
metallicTexture?: string;
|
||
roughness?: number;
|
||
metallic?: number;
|
||
}): void {
|
||
this.updateDictionaries();
|
||
|
||
const targetMaterials: PBRMaterial[] = [];
|
||
|
||
// 如果提供了 modelId,只查找该模型的材质
|
||
if (options.modelId) {
|
||
const modelMeshes = this.mainApp.appModel.modelDic.Get(options.modelId);
|
||
|
||
if (!modelMeshes || modelMeshes.length === 0) {
|
||
console.warn(`Model not found: ${options.modelId}`);
|
||
return;
|
||
}
|
||
|
||
modelMeshes.forEach((mesh: AbstractMesh) => {
|
||
if (mesh.material && mesh.material instanceof PBRMaterial) {
|
||
const material = mesh.material as PBRMaterial;
|
||
if (material.name === options.target || material.name.startsWith(`${options.target}_`)) {
|
||
if (!targetMaterials.includes(material)) {
|
||
targetMaterials.push(material);
|
||
}
|
||
}
|
||
}
|
||
});
|
||
} else {
|
||
// 没有提供 modelId,全局查找
|
||
this.materialDic.Values().forEach(material => {
|
||
if (material.name === options.target || material.name.startsWith(`${options.target}_`)) {
|
||
targetMaterials.push(material);
|
||
}
|
||
});
|
||
}
|
||
|
||
if (targetMaterials.length === 0) {
|
||
console.warn(`Material not found: ${options.target}${options.modelId ? ` in model ${options.modelId}` : ''}`);
|
||
return;
|
||
}
|
||
|
||
// 应用材质属性
|
||
targetMaterials.forEach(material => {
|
||
// 应用颜色
|
||
if (options.albedoColor) {
|
||
const color = Color3.FromHexString(options.albedoColor);
|
||
material.albedoColor.copyFrom(color);
|
||
}
|
||
|
||
// 应用反照率纹理
|
||
if (options.albedoTexture !== undefined) {
|
||
if (options.albedoTexture) {
|
||
material.albedoTexture = new Texture(options.albedoTexture, this.mainApp.appScene.object);
|
||
} else {
|
||
material.albedoTexture = null;
|
||
}
|
||
}
|
||
|
||
// 应用法线贴图
|
||
if (options.normalMap !== undefined) {
|
||
if (options.normalMap) {
|
||
material.bumpTexture = new Texture(options.normalMap, this.mainApp.appScene.object);
|
||
} else {
|
||
material.bumpTexture = null;
|
||
}
|
||
}
|
||
|
||
// 应用金属度贴图
|
||
if (options.metallicTexture !== undefined) {
|
||
if (options.metallicTexture) {
|
||
material.metallicTexture = new Texture(options.metallicTexture, this.mainApp.appScene.object);
|
||
} else {
|
||
material.metallicTexture = null;
|
||
}
|
||
}
|
||
|
||
// 应用粗糙度
|
||
if (options.roughness !== undefined) {
|
||
if (material.roughness !== options.roughness) {
|
||
material.roughness = options.roughness;
|
||
}
|
||
}
|
||
|
||
// 应用金属度
|
||
if (options.metallic !== undefined) {
|
||
if (material.metallic !== options.metallic) {
|
||
material.metallic = options.metallic;
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
/** 卷帘门开合:再次调用会反向动作 */
|
||
toggleRollerDoor(options?: RollerDoorOptions): void {
|
||
this.setRollerDoorState(!this.rollerDoorIsOpen, options);
|
||
}
|
||
|
||
/** 直接设置卷帘门状态 */
|
||
setRollerDoorState(open: boolean, options?: RollerDoorOptions): void {
|
||
const scene = this.mainApp.appScene?.object;
|
||
if (!scene) {
|
||
console.warn('Scene not found for roller door');
|
||
return;
|
||
}
|
||
|
||
this.cacheRollerDoorMeshes(options?.meshNames);
|
||
|
||
if (!this.rollerDoorGroup || !this.rollerDoorMeshes.length) {
|
||
console.warn('Roller door group or meshes not found');
|
||
return;
|
||
}
|
||
|
||
const upY = options?.upY ?? (this.rollerDoorInitialY.get(this.rollerDoorMeshes[0].name) || 0) + 3;
|
||
const downY = options?.downY ?? (this.rollerDoorInitialY.get(this.rollerDoorMeshes[0].name) || 0);
|
||
const speed = options?.speed ?? 1;
|
||
|
||
const targetY = open ? upY : downY;
|
||
|
||
if (this.rollerDoorObserver) {
|
||
scene.onBeforeRenderObservable.remove(this.rollerDoorObserver);
|
||
this.rollerDoorObserver = null;
|
||
}
|
||
|
||
this.rollerDoorObserver = scene.onBeforeRenderObservable.add(() => {
|
||
if (!this.rollerDoorGroup) return;
|
||
|
||
const delta = scene.getEngine().getDeltaTime() / 1000;
|
||
const step = speed * delta;
|
||
const currentY = this.rollerDoorGroup.position.y;
|
||
|
||
if (Math.abs(currentY - targetY) < step) {
|
||
this.rollerDoorGroup.position.y = targetY;
|
||
if (this.rollerDoorObserver) {
|
||
scene.onBeforeRenderObservable.remove(this.rollerDoorObserver);
|
||
this.rollerDoorObserver = null;
|
||
}
|
||
this.rollerDoorIsOpen = open;
|
||
} else {
|
||
this.rollerDoorGroup.position.y += (targetY > currentY ? step : -step);
|
||
}
|
||
});
|
||
}
|
||
|
||
/** 查询卷帘门当前是否已开启 */
|
||
isRollerDoorOpen(): boolean {
|
||
return this.rollerDoorIsOpen;
|
||
}
|
||
|
||
/**
|
||
* 缓存卷帘门网格
|
||
*/
|
||
private cacheRollerDoorMeshes(meshNames?: string[]): void {
|
||
const scene = this.mainApp.appScene?.object;
|
||
if (!scene) return;
|
||
|
||
const targetNames = meshNames || this.rollerDoorNames;
|
||
|
||
this.rollerDoorMeshes = scene.meshes.filter(mesh =>
|
||
targetNames.includes(mesh.name)
|
||
) as AbstractMesh[];
|
||
|
||
if (this.rollerDoorMeshes.length === 0) return;
|
||
|
||
// 记录初始Y坐标
|
||
this.rollerDoorMeshes.forEach(mesh => {
|
||
if (!this.rollerDoorInitialY.has(mesh.name)) {
|
||
this.rollerDoorInitialY.set(mesh.name, mesh.position.y);
|
||
}
|
||
});
|
||
|
||
// 创建父节点统一控制
|
||
if (!this.rollerDoorGroup) {
|
||
this.rollerDoorGroup = this.rollerDoorMeshes[0];
|
||
for (let i = 1; i < this.rollerDoorMeshes.length; i++) {
|
||
this.rollerDoorMeshes[i].setParent(this.rollerDoorGroup);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Y轴剖切:保留指定高度以上或以下的部分
|
||
*/
|
||
setYAxisClip(
|
||
height: number,
|
||
keepAbove: boolean = true,
|
||
onlyMeshNames?: string[],
|
||
excludeMeshNames?: string[]
|
||
): void {
|
||
const scene = this.mainApp.appScene?.object;
|
||
if (!scene) return;
|
||
|
||
this.yClipPlane = new Plane(0, keepAbove ? -1 : 1, 0, keepAbove ? height : -height);
|
||
this.yClipTargets = onlyMeshNames || null;
|
||
|
||
scene.meshes.forEach(mesh => {
|
||
if (excludeMeshNames && excludeMeshNames.includes(mesh.name)) {
|
||
return;
|
||
}
|
||
|
||
if (this.yClipTargets && !this.yClipTargets.includes(mesh.name)) {
|
||
return;
|
||
}
|
||
|
||
if (mesh.material) {
|
||
const materials = Array.isArray(mesh.material)
|
||
? (mesh.material as any[])
|
||
: [mesh.material];
|
||
|
||
materials.forEach(mat => {
|
||
if (!mat.clipPlane) {
|
||
mat.clipPlane = this.yClipPlane;
|
||
}
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 关闭Y轴剖切
|
||
*/
|
||
clearYAxisClip(): void {
|
||
const scene = this.mainApp.appScene?.object;
|
||
if (!scene || !this.yClipPlane) return;
|
||
|
||
scene.meshes.forEach(mesh => {
|
||
if (mesh.material) {
|
||
const materials = Array.isArray(mesh.material)
|
||
? (mesh.material as any[])
|
||
: [mesh.material];
|
||
|
||
materials.forEach(mat => {
|
||
if (mat.clipPlane === this.yClipPlane) {
|
||
mat.clipPlane = null;
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
this.yClipPlane = null;
|
||
this.yClipTargets = null;
|
||
}
|
||
}
|