feat(world-streaming): 添加世界流式加载系统 (#288)

实现基于区块的世界流式加载系统,支持开放世界游戏:

运行时包 (@esengine/world-streaming):
- ChunkComponent: 区块实体组件,包含坐标、边界、状态
- StreamingAnchorComponent: 流式锚点组件(玩家/摄像机)
- ChunkLoaderComponent: 流式加载配置组件
- ChunkStreamingSystem: 区块加载/卸载调度系统
- ChunkCullingSystem: 区块可见性剔除系统
- ChunkManager: 区块生命周期管理服务
- SpatialHashGrid: 空间哈希网格
- ChunkSerializer: 区块序列化

编辑器包 (@esengine/world-streaming-editor):
- ChunkVisualizer: 区块可视化覆盖层
- ChunkLoaderInspectorProvider: 区块加载器检视器
- StreamingAnchorInspectorProvider: 流式锚点检视器
- WorldStreamingPlugin: 完整插件导出
This commit is contained in:
YHH
2025-12-06 13:56:01 +08:00
committed by GitHub
parent 3cbfa1e4cb
commit 0c03b13d74
31 changed files with 2802 additions and 0 deletions

View File

@@ -0,0 +1,227 @@
/**
* 世界流式加载编辑器模块
* World Streaming Editor Module
*
* Registers chunk visualizer, inspector providers and tools for world streaming.
*/
import type { ServiceContainer, Entity } from '@esengine/ecs-framework';
import { Core } from '@esengine/ecs-framework';
import type {
IEditorModuleLoader,
PanelDescriptor,
EntityCreationTemplate,
ComponentInspectorProviderDef,
IPlugin,
ModuleManifest
} from '@esengine/editor-core';
import {
PanelPosition,
InspectorRegistry,
EntityStoreService,
MessageHub,
ComponentRegistry
} from '@esengine/editor-core';
import { TransformComponent } from '@esengine/engine-core';
import {
ChunkComponent,
StreamingAnchorComponent,
ChunkLoaderComponent,
WorldStreamingModule
} from '@esengine/world-streaming';
import { ChunkLoaderInspectorProvider } from './providers/ChunkLoaderInspectorProvider';
import { StreamingAnchorInspectorProvider } from './providers/StreamingAnchorInspectorProvider';
import './styles/ChunkVisualizer.css';
/**
* 世界流式加载编辑器模块
* World Streaming Editor Module
*/
export class WorldStreamingEditorModule implements IEditorModuleLoader {
async install(services: ServiceContainer): Promise<void> {
const inspectorRegistry = services.resolve(InspectorRegistry);
if (inspectorRegistry) {
inspectorRegistry.register(new ChunkLoaderInspectorProvider());
inspectorRegistry.register(new StreamingAnchorInspectorProvider());
}
const componentRegistry = services.resolve(ComponentRegistry);
if (componentRegistry) {
componentRegistry.register({
name: 'ChunkLoader',
type: ChunkLoaderComponent,
category: 'components.category.streaming',
description: 'Chunk-based world streaming controller',
icon: 'Grid3X3'
});
componentRegistry.register({
name: 'StreamingAnchor',
type: StreamingAnchorComponent,
category: 'components.category.streaming',
description: 'Streaming anchor point (player/camera)',
icon: 'Anchor'
});
componentRegistry.register({
name: 'Chunk',
type: ChunkComponent,
category: 'components.category.streaming',
description: 'Chunk entity marker',
icon: 'Square'
});
}
}
async uninstall(): Promise<void> {
// 清理 | Clean up
}
getPanels(): PanelDescriptor[] {
return [];
}
getInspectorProviders(): ComponentInspectorProviderDef[] {
return [
{
componentType: 'ChunkLoader',
priority: 100,
render: (component, entity, onChange) => {
const provider = new ChunkLoaderInspectorProvider();
return provider.render(
{ entityId: String(entity.id), component },
{ target: component, onChange }
);
}
},
{
componentType: 'StreamingAnchor',
priority: 100,
render: (component, entity, onChange) => {
const provider = new StreamingAnchorInspectorProvider();
return provider.render(
{ entityId: String(entity.id), component },
{ target: component, onChange }
);
}
}
];
}
getEntityCreationTemplates(): EntityCreationTemplate[] {
return [
{
id: 'create-streaming-anchor',
label: '创建流式锚点',
icon: 'Anchor',
category: 'streaming',
order: 100,
create: (): number => {
const scene = Core.scene;
if (!scene) {
throw new Error('Scene not available');
}
const entityStore = Core.services.resolve(EntityStoreService);
const messageHub = Core.services.resolve(MessageHub);
if (!entityStore || !messageHub) {
throw new Error('EntityStoreService or MessageHub not available');
}
const anchorCount = entityStore.getAllEntities()
.filter((e: Entity) => e.name.startsWith('StreamingAnchor ')).length;
const entityName = `StreamingAnchor ${anchorCount + 1}`;
const entity = scene.createEntity(entityName);
entity.addComponent(new TransformComponent());
entity.addComponent(new StreamingAnchorComponent());
entityStore.addEntity(entity);
messageHub.publish('entity:added', { entity });
messageHub.publish('scene:modified', {});
entityStore.selectEntity(entity);
return entity.id;
}
},
{
id: 'create-chunk-loader',
label: '创建区块加载器',
icon: 'Grid3X3',
category: 'streaming',
order: 101,
create: (): number => {
const scene = Core.scene;
if (!scene) {
throw new Error('Scene not available');
}
const entityStore = Core.services.resolve(EntityStoreService);
const messageHub = Core.services.resolve(MessageHub);
if (!entityStore || !messageHub) {
throw new Error('EntityStoreService or MessageHub not available');
}
const entity = scene.createEntity('ChunkLoader');
entity.addComponent(new ChunkLoaderComponent());
entityStore.addEntity(entity);
messageHub.publish('entity:added', { entity });
messageHub.publish('scene:modified', {});
entityStore.selectEntity(entity);
return entity.id;
}
}
];
}
getFileActionHandlers() {
return [];
}
getFileCreationTemplates() {
return [];
}
}
export const worldStreamingEditorModule = new WorldStreamingEditorModule();
/**
* 世界流式加载插件清单
* World Streaming Plugin Manifest
*/
const manifest: ModuleManifest = {
id: '@esengine/world-streaming',
name: '@esengine/world-streaming',
displayName: 'World Streaming',
version: '1.0.0',
description: 'Chunk-based world streaming for open world games',
category: 'Other',
isCore: false,
defaultEnabled: true,
isEngineModule: true,
canContainContent: false,
dependencies: ['engine-core'],
exports: {
components: ['ChunkComponent', 'StreamingAnchorComponent', 'ChunkLoaderComponent'],
systems: ['ChunkStreamingSystem', 'ChunkCullingSystem'],
other: ['ChunkManager']
}
};
/**
* 完整的世界流式加载插件(运行时 + 编辑器)
* Complete World Streaming Plugin (runtime + editor)
*/
export const WorldStreamingPlugin: IPlugin = {
manifest,
runtimeModule: new WorldStreamingModule(),
editorModule: worldStreamingEditorModule
};
export default worldStreamingEditorModule;