1
This commit is contained in:
475
src/babylonjs/GameManager.ts
Normal file
475
src/babylonjs/GameManager.ts
Normal file
@ -0,0 +1,475 @@
|
||||
import { Mesh, PBRMaterial, Texture, AbstractMesh } from "@babylonjs/core";
|
||||
import { Monobehiver } from '../base/Monobehiver';
|
||||
import { Dictionary } from '../utils/Dictionary';
|
||||
import { AppConfig } from './AppConfig';
|
||||
|
||||
/**
|
||||
* 游戏管理器类 - 负责管理游戏逻辑、材质和纹理
|
||||
*/
|
||||
export class GameManager extends Monobehiver {
|
||||
private materialDic: Dictionary<PBRMaterial>;
|
||||
private meshDic: Dictionary<any>;
|
||||
private oldTextureDic: Dictionary<any>;
|
||||
|
||||
// 记录加载失败的贴图
|
||||
private failedTextures: Array<{
|
||||
path: string;
|
||||
materialName?: string;
|
||||
textureType?: string;
|
||||
error?: string;
|
||||
timestamp: Date;
|
||||
}>;
|
||||
|
||||
constructor(mainApp: any) {
|
||||
super(mainApp);
|
||||
this.materialDic = new Dictionary<PBRMaterial>();
|
||||
this.meshDic = new Dictionary<any>();
|
||||
this.oldTextureDic = new Dictionary<any>();
|
||||
this.failedTextures = [];
|
||||
}
|
||||
|
||||
/** 初始化游戏管理器 */
|
||||
async Awake() {
|
||||
const scene = this.mainApp.appScene?.object;
|
||||
if (!scene) {
|
||||
console.warn('Scene not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// 初始化材质字典
|
||||
for (const mat of scene.materials) {
|
||||
if (!this.materialDic.Has(mat.name)) {
|
||||
// 初始化材质属性
|
||||
mat.transparencyMode = PBRMaterial.PBRMATERIAL_ALPHABLEND;
|
||||
this.materialDic.Set(mat.name, mat as PBRMaterial);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化网格字典
|
||||
for (const mesh of scene.meshes) {
|
||||
if (mesh instanceof Mesh) {
|
||||
|
||||
this.meshDic.Set(mesh.name, mesh);
|
||||
}
|
||||
}
|
||||
console.log('材质字典:', this.materialDic);
|
||||
}
|
||||
|
||||
/** 初始化设置材质 */
|
||||
async initSetMaterial(oldObject: any) {
|
||||
if (!oldObject?.Component?.length) return;
|
||||
|
||||
|
||||
const { degreeId, Component } = oldObject;
|
||||
let degreeTextureDic = this.oldTextureDic.Get(degreeId) || {};
|
||||
const texturePromises: Promise<void>[] = [];
|
||||
|
||||
// 处理每个组件
|
||||
for (const component of Component) {
|
||||
const {
|
||||
name,
|
||||
albedoTexture,
|
||||
bumpTexture,
|
||||
alphaTexture,
|
||||
aoTexture,
|
||||
} = component;
|
||||
|
||||
if (!name) continue;
|
||||
|
||||
// 获取材质
|
||||
const mat = this.materialDic.Get(name);
|
||||
if (!mat) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取或初始化纹理字典
|
||||
const textureDic = degreeTextureDic[name] || {
|
||||
albedo: null,
|
||||
bump: null,
|
||||
alpha: null,
|
||||
ao: null
|
||||
};
|
||||
|
||||
// 定义纹理任务
|
||||
const textureTasks = [
|
||||
{
|
||||
key: "albedo",
|
||||
path: albedoTexture,
|
||||
property: "albedoTexture"
|
||||
},
|
||||
{
|
||||
key: "bump",
|
||||
path: bumpTexture,
|
||||
property: "bumpTexture"
|
||||
},
|
||||
{
|
||||
key: "alpha",
|
||||
path: alphaTexture,
|
||||
property: "opacityTexture"
|
||||
},
|
||||
{
|
||||
key: "ao",
|
||||
path: aoTexture,
|
||||
property: "ambientTexture"
|
||||
}
|
||||
];
|
||||
|
||||
// 处理每个纹理任务
|
||||
for (const task of textureTasks) {
|
||||
const { key, path, property } = task;
|
||||
if (!path) continue;
|
||||
|
||||
const fullPath = this.getPublicUrl() + path;
|
||||
let texture = textureDic[key];
|
||||
|
||||
if (!texture) {
|
||||
try {
|
||||
texture = this.createTextureWithFallback(fullPath);
|
||||
if (!texture) {
|
||||
// 记录失败的贴图信息
|
||||
this.failedTextures.push({
|
||||
path: fullPath,
|
||||
materialName: name,
|
||||
textureType: key,
|
||||
error: '贴图创建失败',
|
||||
timestamp: new Date()
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// 设置非ktx2格式的vScale
|
||||
if (!fullPath.toLowerCase().endsWith('.ktx2')) {
|
||||
texture.vScale = -1;
|
||||
}
|
||||
textureDic[key] = texture;
|
||||
} catch (error: any) {
|
||||
// 记录失败的贴图信息
|
||||
this.failedTextures.push({
|
||||
path: fullPath,
|
||||
materialName: name,
|
||||
textureType: key,
|
||||
error: error.message || error.toString(),
|
||||
timestamp: new Date()
|
||||
});
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 将纹理赋值任务加入队列
|
||||
texturePromises.push(
|
||||
this.handleTextureAssignment(mat, textureDic, key, (texture) => {
|
||||
(mat as any)[property] = texture;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// 更新纹理字典
|
||||
degreeTextureDic[name] = textureDic;
|
||||
}
|
||||
|
||||
// 等待所有纹理任务完成
|
||||
try {
|
||||
await Promise.all(texturePromises);
|
||||
|
||||
// 在所有贴图加载完成后设置材质属性
|
||||
for (const component of Component) {
|
||||
const { name, transparencyMode, bumpTextureLevel } = component;
|
||||
if (!name) continue;
|
||||
|
||||
const mat = this.materialDic.Get(name);
|
||||
if (!mat) continue;
|
||||
|
||||
mat.transparencyMode = transparencyMode;
|
||||
|
||||
if (mat.bumpTexture) {
|
||||
mat.bumpTexture.level = bumpTextureLevel;
|
||||
}
|
||||
|
||||
// 应用新的PBR材质属性
|
||||
this.applyPBRProperties(mat, component);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading textures:', error);
|
||||
} finally {
|
||||
if (this.mainApp.appDom?.load3D) {
|
||||
this.mainApp.appDom.load3D.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
// 保存更新后的纹理字典
|
||||
this.oldTextureDic.Set(degreeId, degreeTextureDic);
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用PBR材质属性
|
||||
* @param mat - PBR材质对象
|
||||
* @param component - 配置组件对象
|
||||
*/
|
||||
private applyPBRProperties(mat: PBRMaterial, component: any) {
|
||||
// 定义PBR属性映射任务
|
||||
const pbrTasks = [
|
||||
{
|
||||
key: "fresnel",
|
||||
value: component.fresnel,
|
||||
apply: (value: number) => {
|
||||
mat.indexOfRefraction = value;
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "clearcoat",
|
||||
value: component.clearcoat,
|
||||
apply: (value: number) => {
|
||||
mat.clearCoat.isEnabled = true;
|
||||
mat.clearCoat.intensity = value;
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "clearcoatRoughness",
|
||||
value: component.clearcoatRoughness,
|
||||
apply: (value: number) => {
|
||||
mat.clearCoat.roughness = value;
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "roughness",
|
||||
value: component.roughness,
|
||||
apply: (value: number) => {
|
||||
mat.roughness = value;
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "metallic",
|
||||
value: component.metallic,
|
||||
apply: (value: number) => {
|
||||
mat.metallic = value;
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "alpha",
|
||||
value: component.alpha,
|
||||
apply: (value: number) => {
|
||||
mat.alpha = value;
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "environmentIntensity",
|
||||
value: component.environmentIntensity,
|
||||
apply: (value: number) => {
|
||||
mat.environmentIntensity = value;
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "baseColor",
|
||||
value: component.baseColor,
|
||||
apply: (value: any) => {
|
||||
if (value && typeof value === 'object') {
|
||||
const { r, g, b } = value;
|
||||
if (r !== null && r !== undefined &&
|
||||
g !== null && g !== undefined &&
|
||||
b !== null && b !== undefined) {
|
||||
mat.albedoColor.set(r, g, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
// 处理每个PBR属性任务
|
||||
for (const task of pbrTasks) {
|
||||
if (task.value !== null && task.value !== undefined) {
|
||||
try {
|
||||
task.apply(task.value);
|
||||
} catch (error) {
|
||||
console.warn('Error applying PBR property:', task.key, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 通用的批量卸载贴图资源的方法 */
|
||||
private clearTextures(textureDic: Dictionary<any>): Promise<void> {
|
||||
return new Promise<void>((resolve) => {
|
||||
textureDic.Values().forEach((textures) => {
|
||||
for (const key in textures) {
|
||||
const texture = textures[key];
|
||||
if (texture && texture instanceof Texture) {
|
||||
texture.dispose();
|
||||
}
|
||||
}
|
||||
});
|
||||
textureDic.Clear();
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
/** 处理纹理赋值 */
|
||||
private async handleTextureAssignment(mat: any, oldtextureDic: any, textureKey: string, assignCallback: (texture: Texture) => void) {
|
||||
const texture = oldtextureDic[textureKey];
|
||||
if (texture) {
|
||||
await this.checkTextureLoadedWithPromise(texture);
|
||||
assignCallback(texture);
|
||||
}
|
||||
}
|
||||
|
||||
/** 检查纹理是否加载完成 */
|
||||
private checkTextureLoadedWithPromise(texture: Texture): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
if (texture.isReady()) {
|
||||
resolve();
|
||||
} else {
|
||||
texture.onLoadObservable.addOnce(() => {
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** 重置相机位置 */
|
||||
reSet() {
|
||||
if (this.mainApp.appCamera?.object?.position) {
|
||||
this.mainApp.appCamera.object.position.set(160, 50, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取公共URL */
|
||||
private getPublicUrl(): string {
|
||||
// 尝试从环境变量获取
|
||||
if (import.meta && import.meta.env && import.meta.env.VITE_PUBLIC_URL) {
|
||||
return import.meta.env.VITE_PUBLIC_URL;
|
||||
}
|
||||
// 默认返回空字符串
|
||||
return '';
|
||||
}
|
||||
|
||||
/** 清理资源 */
|
||||
dispose() {
|
||||
// 清理所有材质资源
|
||||
this.materialDic.Values().forEach((material) => {
|
||||
if (material && material.dispose) {
|
||||
material.dispose();
|
||||
}
|
||||
});
|
||||
this.materialDic.Clear();
|
||||
|
||||
// 清理所有贴图资源
|
||||
this.clearTextures(this.oldTextureDic);
|
||||
|
||||
// 清理所有网格
|
||||
this.meshDic.Values().forEach((mesh) => {
|
||||
if (mesh && mesh.dispose) {
|
||||
mesh.dispose();
|
||||
}
|
||||
});
|
||||
this.meshDic.Clear();
|
||||
|
||||
// 清空失败贴图记录
|
||||
this.failedTextures = [];
|
||||
}
|
||||
|
||||
/** 更新 */
|
||||
update() { }
|
||||
|
||||
/** 尝试创建贴图的方法,支持多种格式回退 */
|
||||
private createTextureWithFallback(texturePath: string): Texture | null {
|
||||
const failureReasons: string[] = [];
|
||||
|
||||
try {
|
||||
const texture = new Texture(texturePath);
|
||||
|
||||
if (texture) {
|
||||
return texture;
|
||||
} else {
|
||||
failureReasons.push(`原始路径创建失败: ${texturePath}`);
|
||||
throw new Error('Texture creation returned null');
|
||||
}
|
||||
} catch (error: any) {
|
||||
const errorMessage = error.message || error.toString();
|
||||
|
||||
// 特别处理KTX错误
|
||||
if (errorMessage.includes('KTX identifier') || errorMessage.includes('missing KTX') ||
|
||||
(texturePath.toLowerCase().endsWith('.ktx2') && errorMessage)) {
|
||||
this.failedTextures.push({
|
||||
path: texturePath,
|
||||
textureType: 'KTX2',
|
||||
error: `KTX错误: ${errorMessage}`,
|
||||
timestamp: new Date()
|
||||
});
|
||||
}
|
||||
|
||||
failureReasons.push(`原始路径加载异常: ${texturePath} - ${errorMessage}`);
|
||||
|
||||
// 如果是ktx2文件加载失败,尝试查找对应的jpg/png文件
|
||||
if (texturePath.toLowerCase().endsWith('.ktx2')) {
|
||||
// 尝试jpg格式
|
||||
const jpgPath = texturePath.replace(/\.ktx2$/i, '.jpg');
|
||||
try {
|
||||
const jpgTexture = new Texture(jpgPath);
|
||||
if (jpgTexture) {
|
||||
return jpgTexture;
|
||||
}
|
||||
} catch (jpgError: any) {
|
||||
failureReasons.push(`JPG回退失败: ${jpgPath} - ${jpgError}`);
|
||||
}
|
||||
|
||||
// 尝试png格式
|
||||
const pngPath = texturePath.replace(/\.ktx2$/i, '.png');
|
||||
try {
|
||||
const pngTexture = new Texture(pngPath);
|
||||
if (pngTexture) {
|
||||
return pngTexture;
|
||||
}
|
||||
} catch (pngError: any) {
|
||||
failureReasons.push(`PNG回退失败: ${pngPath} - ${pngError}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 所有格式都失败,记录详细失败信息
|
||||
this.failedTextures.push({
|
||||
path: texturePath,
|
||||
textureType: '回退机制',
|
||||
error: failureReasons.join('; '),
|
||||
timestamp: new Date()
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用材质
|
||||
* @param target 目标对象
|
||||
* @param material 材质路径
|
||||
*/
|
||||
applyMaterial(target: string, attribute: string, value: number | string): void {
|
||||
// 这里需要根据实际的材质管理逻辑实现
|
||||
console.log(`Applying attribute ${attribute} to ${value}`);
|
||||
|
||||
// 示例实现:根据目标和材质路径应用材质
|
||||
// 1. 查找目标网格
|
||||
const targetMaterials: PBRMaterial[] = [];
|
||||
this.materialDic.Values().forEach(material => {
|
||||
if (material.name.includes(target)) {
|
||||
console.log(`${this.materialDic.Get(material.name)}`,material);
|
||||
targetMaterials.push(material);
|
||||
}
|
||||
});
|
||||
|
||||
if (targetMaterials.length === 0) {
|
||||
console.warn(`Target not found: ${target}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 处理材质路径
|
||||
// 这里可以根据材质路径加载对应的材质配置
|
||||
// 例如:paint/blue 可以映射到特定的材质配置
|
||||
|
||||
// 3. 应用材质到目标网格
|
||||
targetMaterials.forEach(material => {
|
||||
if (material[attribute]) {
|
||||
material[attribute] = value;
|
||||
}
|
||||
console.log(`Applying attribute ${attribute} to ${value} to mesh: ${material.name}`);
|
||||
// 这里需要根据实际的材质系统实现
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user