Files
esengine/packages/material-editor/src/index.ts
YHH 63f006ab62 feat: 添加跨平台运行时、资产系统和UI适配功能 (#256)
* feat(platform-common): 添加WASM加载器和环境检测API

* feat(rapier2d): 新增Rapier2D WASM绑定包

* feat(physics-rapier2d): 添加跨平台WASM加载器

* feat(asset-system): 添加运行时资产目录和bundle格式

* feat(asset-system-editor): 新增编辑器资产管理包

* feat(editor-core): 添加构建系统和模块管理

* feat(editor-app): 重构浏览器预览使用import maps

* feat(platform-web): 添加BrowserRuntime和资产读取

* feat(engine): 添加材质系统和着色器管理

* feat(material): 新增材质系统和着色器编辑器

* feat(tilemap): 增强tilemap编辑器和动画系统

* feat(modules): 添加module.json配置

* feat(core): 添加module.json和类型定义更新

* chore: 更新依赖和构建配置

* refactor(plugins): 更新插件模板使用ModuleManifest

* chore: 添加第三方依赖库

* chore: 移除BehaviourTree-ai和ecs-astar子模块

* docs: 更新README和文档主题样式

* fix: 修复Rust文档测试和添加rapier2d WASM绑定

* fix(tilemap-editor): 修复画布高DPI屏幕分辨率适配问题

* feat(ui): 添加UI屏幕适配系统(CanvasScaler/SafeArea)

* fix(ecs-engine-bindgen): 添加缺失的ecs-framework-math依赖

* fix: 添加缺失的包依赖修复CI构建

* fix: 修复CodeQL检测到的代码问题

* fix: 修复构建错误和缺失依赖

* fix: 修复类型检查错误

* fix(material-system): 修复tsconfig配置支持TypeScript项目引用

* fix(editor-core): 修复Rollup构建配置添加tauri external

* fix: 修复CodeQL检测到的代码问题

* fix: 修复CodeQL检测到的代码问题
2025-12-03 22:15:22 +08:00

269 lines
8.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @esengine/material-editor
*
* Editor support for @esengine/material-system - file creation templates and material asset management
*
* 材质编辑器模块 - 提供材质文件创建功能
* 注意:材质不是独立组件,而是作为渲染组件(如 SpriteComponent的属性使用
* 材质文件 (.mat) 的预览和编辑在 Inspector 中完成
*/
import type { ServiceContainer } from '@esengine/ecs-framework';
import { Core } from '@esengine/ecs-framework';
import type {
IEditorModuleLoader,
FileCreationTemplate,
IPlugin,
ModuleManifest
} from '@esengine/editor-core';
import {
MessageHub,
FileActionRegistry,
InspectorRegistry,
IInspectorRegistry,
IFileSystemService
} from '@esengine/editor-core';
// Inspector provider
import { MaterialAssetInspectorProvider } from './providers/MaterialAssetInspectorProvider';
// Runtime imports from @esengine/material-system
import {
MaterialRuntimeModule,
BlendMode,
BuiltInShaders
} from '@esengine/material-system';
// Editor components - for re-export only
import { MaterialEditorPanel } from './components/MaterialEditorPanel';
import { useMaterialEditorStore } from './stores/MaterialEditorStore';
// Import styles
import './styles/MaterialEditorPanel.css';
const DEFAULT_MATERIAL_TEMPLATE = {
name: 'New Material',
shader: BuiltInShaders.DefaultSprite,
blendMode: BlendMode.Alpha,
uniforms: {}
};
/**
* Material Editor Module
*
* 提供:
* - 材质文件 (.mat) 创建模板
* - 着色器文件 (.shader) 创建模板
* - 材质资产创建消息处理(用于 PropertyInspector 中的创建按钮)
*
* 注意:.mat 文件的预览和编辑在 Inspector 中完成,不需要单独的编辑器面板
*/
export class MaterialEditorModule implements IEditorModuleLoader {
private inspectorProvider?: MaterialAssetInspectorProvider;
async install(services: ServiceContainer): Promise<void> {
// Register file creation templates
const fileActionRegistry = services.resolve(FileActionRegistry);
if (fileActionRegistry) {
for (const template of this.getFileCreationTemplates()) {
fileActionRegistry.registerCreationTemplate(template);
}
// Register asset creation mapping for .mat files
fileActionRegistry.registerAssetCreationMapping({
extension: '.mat',
createMessage: 'material:create',
canCreate: true
});
}
// Register Material Asset Inspector Provider
const inspectorRegistry = services.resolve<InspectorRegistry>(IInspectorRegistry);
if (inspectorRegistry) {
this.inspectorProvider = new MaterialAssetInspectorProvider();
// Set up save handler using file system service
const fileSystem = services.tryResolve(IFileSystemService) as { writeFile(path: string, content: string): Promise<void> } | null;
if (fileSystem) {
this.inspectorProvider.setSaveHandler(async (path, content) => {
await fileSystem.writeFile(path, content);
});
}
inspectorRegistry.register(this.inspectorProvider);
}
// Subscribe to material:create message
const messageHub = services.resolve(MessageHub);
if (messageHub) {
messageHub.subscribe('material:create', async (payload: {
entityId?: string;
onChange?: (value: string | null) => void;
}) => {
await this.handleCreateMaterialAsset(payload);
});
}
}
async uninstall(): Promise<void> {
// Reset store state
useMaterialEditorStore.getState().reset();
}
getFileCreationTemplates(): FileCreationTemplate[] {
return [
{
id: 'create-material',
label: 'Material',
extension: 'mat',
icon: 'Palette',
category: 'Rendering',
getContent: (fileName: string): string => {
const materialName = fileName.replace(/\.mat$/i, '');
return JSON.stringify(
{
...DEFAULT_MATERIAL_TEMPLATE,
name: materialName
},
null,
2
);
}
},
{
id: 'create-shader',
label: 'Shader',
extension: 'shader',
icon: 'Code',
category: 'Rendering',
getContent: (fileName: string): string => {
const shaderName = fileName.replace(/\.shader$/i, '');
return JSON.stringify(
{
version: 1,
shader: {
name: shaderName,
vertexSource: `#version 300 es
precision highp float;
in vec2 a_position;
in vec2 a_texCoord;
in vec4 a_color;
uniform mat4 u_projection;
uniform mat4 u_view;
out vec2 v_texCoord;
out vec4 v_color;
void main() {
gl_Position = u_projection * u_view * vec4(a_position, 0.0, 1.0);
v_texCoord = a_texCoord;
v_color = a_color;
}`,
fragmentSource: `#version 300 es
precision highp float;
in vec2 v_texCoord;
in vec4 v_color;
uniform sampler2D u_texture;
out vec4 fragColor;
void main() {
vec4 texColor = texture(u_texture, v_texCoord);
fragColor = texColor * v_color;
}`
}
},
null,
2
);
}
}
];
}
/**
* Handle create material asset request
*/
private async handleCreateMaterialAsset(
payload: { entityId?: string; onChange?: (value: string | null) => void }
): Promise<void> {
// Import dialog and file system services dynamically
const { IDialogService, IFileSystemService } = await import('@esengine/editor-core');
type IDialog = { saveDialog(options: any): Promise<string | null> };
type IFileSystem = { writeFile(path: string, content: string): Promise<void> };
const dialog = Core.services.tryResolve(IDialogService) as IDialog | null;
const fileSystem = Core.services.tryResolve(IFileSystemService) as IFileSystem | null;
if (!dialog || !fileSystem) {
console.error('[MaterialEditorModule] Dialog or FileSystem service not available');
return;
}
const filePath = await dialog.saveDialog({
title: 'Create Material Asset',
filters: [{ name: 'Material', extensions: ['mat'] }],
defaultPath: 'new-material.mat'
});
if (!filePath) {
return;
}
const materialName = filePath.split(/[\\/]/).pop()?.replace(/\.mat$/i, '') || 'New Material';
const materialData = {
...DEFAULT_MATERIAL_TEMPLATE,
name: materialName
};
await fileSystem.writeFile(filePath, JSON.stringify(materialData, null, 2));
if (payload.onChange) {
payload.onChange(filePath);
}
}
}
export const materialEditorModule = new MaterialEditorModule();
// Re-exports
export { MaterialEditorPanel } from './components/MaterialEditorPanel';
export { useMaterialEditorStore, createDefaultMaterialData } from './stores/MaterialEditorStore';
export type { MaterialEditorState } from './stores/MaterialEditorStore';
/**
* Material Plugin Manifest
*/
const manifest: ModuleManifest = {
id: '@esengine/material-system',
name: '@esengine/material-system',
displayName: 'Material System',
version: '1.0.0',
description: 'Material and shader system for custom rendering effects',
category: 'Rendering',
isCore: true,
defaultEnabled: true,
isEngineModule: true,
canContainContent: true,
dependencies: ['engine-core'],
exports: {
components: ['MaterialComponent'],
other: ['Material', 'Shader', 'BlendMode']
}
};
/**
* Complete Material Plugin (runtime + editor)
*/
export const MaterialPlugin: IPlugin = {
manifest,
runtimeModule: new MaterialRuntimeModule(),
editorModule: materialEditorModule
};
export default materialEditorModule;