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:
108
packages/world-streaming/src/systems/ChunkCullingSystem.ts
Normal file
108
packages/world-streaming/src/systems/ChunkCullingSystem.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { EntitySystem, Matcher, ECSSystem } from '@esengine/ecs-framework';
|
||||
import type { Entity } from '@esengine/ecs-framework';
|
||||
import { ChunkComponent } from '../components/ChunkComponent';
|
||||
import { EChunkState } from '../types';
|
||||
|
||||
/**
|
||||
* 区块裁剪系统
|
||||
*
|
||||
* Handles visibility culling for chunk entities.
|
||||
*
|
||||
* 处理区块实体的可见性裁剪。
|
||||
*/
|
||||
@ECSSystem('ChunkCulling', { updateOrder: -40 })
|
||||
export class ChunkCullingSystem extends EntitySystem {
|
||||
private _viewMinX: number = 0;
|
||||
private _viewMinY: number = 0;
|
||||
private _viewMaxX: number = 1920;
|
||||
private _viewMaxY: number = 1080;
|
||||
private _padding: number = 100;
|
||||
|
||||
constructor() {
|
||||
super(Matcher.all(ChunkComponent));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置视口边界
|
||||
*
|
||||
* Set viewport bounds for culling.
|
||||
*/
|
||||
setViewBounds(minX: number, minY: number, maxX: number, maxY: number): void {
|
||||
this._viewMinX = minX;
|
||||
this._viewMinY = minY;
|
||||
this._viewMaxX = maxX;
|
||||
this._viewMaxY = maxY;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置裁剪边距
|
||||
*
|
||||
* Set padding for culling bounds.
|
||||
*/
|
||||
setPadding(padding: number): void {
|
||||
this._padding = padding;
|
||||
}
|
||||
|
||||
protected process(entities: readonly Entity[]): void {
|
||||
const cullMinX = this._viewMinX - this._padding;
|
||||
const cullMinY = this._viewMinY - this._padding;
|
||||
const cullMaxX = this._viewMaxX + this._padding;
|
||||
const cullMaxY = this._viewMaxY + this._padding;
|
||||
|
||||
for (const entity of entities) {
|
||||
const chunk = entity.getComponent(ChunkComponent);
|
||||
if (!chunk) continue;
|
||||
|
||||
if (chunk.state !== EChunkState.Loaded) continue;
|
||||
|
||||
const bounds = chunk.bounds;
|
||||
const isVisible = this.boundsIntersect(
|
||||
bounds.minX,
|
||||
bounds.minY,
|
||||
bounds.maxX,
|
||||
bounds.maxY,
|
||||
cullMinX,
|
||||
cullMinY,
|
||||
cullMaxX,
|
||||
cullMaxY
|
||||
);
|
||||
|
||||
entity.enabled = isVisible;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查边界是否相交
|
||||
*
|
||||
* Check if two axis-aligned bounds intersect.
|
||||
*/
|
||||
private boundsIntersect(
|
||||
aMinX: number,
|
||||
aMinY: number,
|
||||
aMaxX: number,
|
||||
aMaxY: number,
|
||||
bMinX: number,
|
||||
bMinY: number,
|
||||
bMaxX: number,
|
||||
bMaxY: number
|
||||
): boolean {
|
||||
return aMinX < bMaxX && aMaxX > bMinX && aMinY < bMaxY && aMaxY > bMinY;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新视口(从相机)
|
||||
*
|
||||
* Update viewport from camera position and size.
|
||||
*/
|
||||
updateFromCamera(cameraX: number, cameraY: number, viewWidth: number, viewHeight: number): void {
|
||||
const halfWidth = viewWidth * 0.5;
|
||||
const halfHeight = viewHeight * 0.5;
|
||||
|
||||
this.setViewBounds(
|
||||
cameraX - halfWidth,
|
||||
cameraY - halfHeight,
|
||||
cameraX + halfWidth,
|
||||
cameraY + halfHeight
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user