* refactor: reorganize package structure and decouple framework packages ## Package Structure Reorganization - Reorganized 55 packages into categorized subdirectories: - packages/framework/ - Generic framework (Laya/Cocos compatible) - packages/engine/ - ESEngine core modules - packages/rendering/ - Rendering modules (WASM dependent) - packages/physics/ - Physics modules - packages/streaming/ - World streaming - packages/network-ext/ - Network extensions - packages/editor/ - Editor framework and plugins - packages/rust/ - Rust WASM engine - packages/tools/ - Build tools and SDK ## Framework Package Decoupling - Decoupled behavior-tree and blueprint packages from ESEngine dependencies - Created abstracted interfaces (IBTAssetManager, IBehaviorTreeAssetContent) - ESEngine-specific code moved to esengine/ subpath exports - Framework packages now usable with Cocos/Laya without ESEngine ## CI Configuration - Updated CI to only type-check and lint framework packages - Added type-check:framework and lint:framework scripts ## Breaking Changes - Package import paths changed due to directory reorganization - ESEngine integrations now use subpath imports (e.g., '@esengine/behavior-tree/esengine') * fix: update es-engine file path after directory reorganization * docs: update README to focus on framework over engine * ci: only build framework packages, remove Rust/WASM dependencies * fix: remove esengine subpath from behavior-tree and blueprint builds ESEngine integration code will only be available in full engine builds. Framework packages are now purely engine-agnostic. * fix: move network-protocols to framework, build both in CI * fix: update workflow paths from packages/core to packages/framework/core * fix: exclude esengine folder from type-check in behavior-tree and blueprint * fix: update network tsconfig references to new paths * fix: add test:ci:framework to only test framework packages in CI * fix: only build core and math npm packages in CI * fix: exclude test files from CodeQL and fix string escaping security issue
342 lines
9.5 KiB
TypeScript
342 lines
9.5 KiB
TypeScript
import type { Particle } from '../Particle';
|
||
import type { IParticleModule } from './IParticleModule';
|
||
|
||
// 从 physics-rapier2d 导入共享接口
|
||
// Import shared interface from physics-rapier2d
|
||
import type { IPhysics2DQuery } from '@esengine/physics-rapier2d';
|
||
|
||
// 重新导出以保持向后兼容
|
||
// Re-export for backward compatibility
|
||
export type { IPhysics2DQuery };
|
||
|
||
/**
|
||
* 物理碰撞行为
|
||
* Physics collision behavior
|
||
*/
|
||
export enum Physics2DCollisionBehavior {
|
||
/** 销毁粒子 | Kill particle */
|
||
Kill = 'kill',
|
||
/** 反弹 | Bounce */
|
||
Bounce = 'bounce',
|
||
/** 停止运动 | Stop movement */
|
||
Stop = 'stop'
|
||
}
|
||
|
||
/**
|
||
* 碰撞回调数据
|
||
* Collision callback data
|
||
*/
|
||
export interface ParticleCollisionInfo {
|
||
/** 粒子引用 | Particle reference */
|
||
particle: Particle;
|
||
/** 碰撞位置 | Collision point */
|
||
point: { x: number; y: number };
|
||
/** 碰撞法线 | Collision normal */
|
||
normal: { x: number; y: number };
|
||
/** 碰撞的实体 ID | Collided entity ID */
|
||
entityId: number;
|
||
/** 碰撞体句柄 | Collider handle */
|
||
colliderHandle: number;
|
||
}
|
||
|
||
/**
|
||
* 2D 物理碰撞模块
|
||
* 2D Physics collision module
|
||
*
|
||
* 使用 Physics2DService 的查询 API 检测粒子与场景碰撞体的碰撞。
|
||
* Uses Physics2DService query API to detect particle collisions with scene colliders.
|
||
*
|
||
* @example
|
||
* ```typescript
|
||
* // 获取物理服务
|
||
* const physicsService = scene.services.resolve(Physics2DService);
|
||
*
|
||
* // 创建模块
|
||
* const collisionModule = new Physics2DCollisionModule();
|
||
* collisionModule.setPhysicsQuery(physicsService);
|
||
* collisionModule.particleRadius = 4;
|
||
* collisionModule.behavior = Physics2DCollisionBehavior.Bounce;
|
||
*
|
||
* // 添加到粒子系统
|
||
* particleSystem.addModule(collisionModule);
|
||
*
|
||
* // 监听碰撞事件
|
||
* collisionModule.onCollision = (info) => {
|
||
* console.log('Particle hit entity:', info.entityId);
|
||
* };
|
||
* ```
|
||
*/
|
||
export class Physics2DCollisionModule implements IParticleModule {
|
||
readonly name = 'Physics2DCollision';
|
||
enabled = true;
|
||
|
||
// ============= 物理查询 | Physics Query =============
|
||
|
||
/** 物理查询接口 | Physics query interface */
|
||
private _physicsQuery: IPhysics2DQuery | null = null;
|
||
|
||
// ============= 碰撞设置 | Collision Settings =============
|
||
|
||
/**
|
||
* 粒子碰撞半径
|
||
* Particle collision radius
|
||
*
|
||
* 用于圆形重叠检测的半径
|
||
*/
|
||
particleRadius: number = 4;
|
||
|
||
/**
|
||
* 碰撞行为
|
||
* Collision behavior
|
||
*/
|
||
behavior: Physics2DCollisionBehavior = Physics2DCollisionBehavior.Bounce;
|
||
|
||
/**
|
||
* 碰撞层掩码
|
||
* Collision layer mask
|
||
*
|
||
* 默认 0xFFFF 表示与所有层碰撞
|
||
* Default 0xFFFF means collide with all layers
|
||
*/
|
||
collisionMask: number = 0xFFFF;
|
||
|
||
/**
|
||
* 反弹系数 (0-1)
|
||
* Bounce factor (0-1)
|
||
*
|
||
* 1 = 完全弹性,0 = 无弹性
|
||
* 1 = fully elastic, 0 = no bounce
|
||
*/
|
||
bounceFactor: number = 0.6;
|
||
|
||
/**
|
||
* 反弹时的生命损失 (0-1)
|
||
* Life loss on bounce (0-1)
|
||
*/
|
||
lifeLossOnBounce: number = 0;
|
||
|
||
/**
|
||
* 最小速度阈值
|
||
* Minimum velocity threshold
|
||
*
|
||
* 低于此速度时销毁粒子(防止无限小弹跳)
|
||
* Kill particle when velocity falls below this (prevents infinite tiny bounces)
|
||
*/
|
||
minVelocityThreshold: number = 5;
|
||
|
||
/**
|
||
* 使用射线检测代替重叠检测
|
||
* Use raycast instead of overlap detection
|
||
*
|
||
* 射线检测更精确,可防止快速粒子穿透,但性能开销更大
|
||
* Raycast is more accurate and prevents fast particle tunneling, but more expensive
|
||
*/
|
||
useRaycast: boolean = false;
|
||
|
||
/**
|
||
* 检测频率(每 N 帧检测一次)
|
||
* Detection frequency (detect every N frames)
|
||
*
|
||
* 增大此值可提高性能,但降低精度
|
||
* Increase to improve performance at cost of accuracy
|
||
*/
|
||
detectionInterval: number = 1;
|
||
|
||
// ============= 内部状态 | Internal State =============
|
||
|
||
/** 帧计数器 | Frame counter */
|
||
private _frameCounter: number = 0;
|
||
|
||
/** 需要销毁的粒子 | Particles to kill */
|
||
private _particlesToKill: Set<Particle> = new Set();
|
||
|
||
// ============= 回调 | Callbacks =============
|
||
|
||
/**
|
||
* 碰撞回调
|
||
* Collision callback
|
||
*
|
||
* 每次粒子碰撞时调用
|
||
*/
|
||
onCollision: ((info: ParticleCollisionInfo) => void) | null = null;
|
||
|
||
// ============= 公开方法 | Public Methods =============
|
||
|
||
/**
|
||
* 设置物理查询接口
|
||
* Set physics query interface
|
||
*
|
||
* @param query - 物理查询接口(通常是 Physics2DService)| Physics query (usually Physics2DService)
|
||
*/
|
||
setPhysicsQuery(query: IPhysics2DQuery | null): void {
|
||
this._physicsQuery = query;
|
||
}
|
||
|
||
/**
|
||
* 获取需要销毁的粒子
|
||
* Get particles to kill
|
||
*/
|
||
getParticlesToKill(): Set<Particle> {
|
||
return this._particlesToKill;
|
||
}
|
||
|
||
/**
|
||
* 清除死亡标记
|
||
* Clear death flags
|
||
*/
|
||
clearDeathFlags(): void {
|
||
this._particlesToKill.clear();
|
||
}
|
||
|
||
/**
|
||
* 重置帧计数器
|
||
* Reset frame counter
|
||
*/
|
||
resetFrameCounter(): void {
|
||
this._frameCounter = 0;
|
||
}
|
||
|
||
// ============= IParticleModule 实现 | IParticleModule Implementation =============
|
||
|
||
update(p: Particle, dt: number, _normalizedAge: number): void {
|
||
if (!this._physicsQuery) return;
|
||
|
||
// 检测频率控制 | Detection frequency control
|
||
this._frameCounter++;
|
||
if (this._frameCounter % this.detectionInterval !== 0) {
|
||
return;
|
||
}
|
||
|
||
if (this.useRaycast) {
|
||
this._updateWithRaycast(p, dt);
|
||
} else {
|
||
this._updateWithOverlap(p);
|
||
}
|
||
}
|
||
|
||
// ============= 私有方法 | Private Methods =============
|
||
|
||
/**
|
||
* 使用圆形重叠检测
|
||
* Update using circle overlap detection
|
||
*/
|
||
private _updateWithOverlap(p: Particle): void {
|
||
if (!this._physicsQuery) return;
|
||
|
||
const result = this._physicsQuery.overlapCircle(
|
||
{ x: p.x, y: p.y },
|
||
this.particleRadius,
|
||
this.collisionMask
|
||
);
|
||
|
||
if (result.entityIds.length > 0) {
|
||
// 发生碰撞 | Collision occurred
|
||
const entityId = result.entityIds[0];
|
||
const colliderHandle = result.colliderHandles[0];
|
||
|
||
// 估算法线(从粒子速度反向)| Estimate normal (from particle velocity)
|
||
const speed = Math.sqrt(p.vx * p.vx + p.vy * p.vy);
|
||
const normal = speed > 0.001
|
||
? { x: -p.vx / speed, y: -p.vy / speed }
|
||
: { x: 0, y: 1 };
|
||
|
||
this._handleCollision(p, { x: p.x, y: p.y }, normal, entityId, colliderHandle);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 使用射线检测
|
||
* Update using raycast detection
|
||
*/
|
||
private _updateWithRaycast(p: Particle, dt: number): void {
|
||
if (!this._physicsQuery) return;
|
||
|
||
const speed = Math.sqrt(p.vx * p.vx + p.vy * p.vy);
|
||
if (speed < 0.001) return;
|
||
|
||
// 归一化方向 | Normalize direction
|
||
const direction = { x: p.vx / speed, y: p.vy / speed };
|
||
const distance = speed * dt + this.particleRadius;
|
||
|
||
const hit = this._physicsQuery.raycast(
|
||
{ x: p.x, y: p.y },
|
||
direction,
|
||
distance,
|
||
this.collisionMask
|
||
);
|
||
|
||
if (hit) {
|
||
this._handleCollision(p, hit.point, hit.normal, hit.entityId, hit.colliderHandle);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 处理碰撞
|
||
* Handle collision
|
||
*/
|
||
private _handleCollision(
|
||
p: Particle,
|
||
point: { x: number; y: number },
|
||
normal: { x: number; y: number },
|
||
entityId: number,
|
||
colliderHandle: number
|
||
): void {
|
||
// 触发回调 | Trigger callback
|
||
if (this.onCollision) {
|
||
this.onCollision({
|
||
particle: p,
|
||
point,
|
||
normal,
|
||
entityId,
|
||
colliderHandle
|
||
});
|
||
}
|
||
|
||
switch (this.behavior) {
|
||
case Physics2DCollisionBehavior.Kill:
|
||
this._particlesToKill.add(p);
|
||
break;
|
||
|
||
case Physics2DCollisionBehavior.Bounce:
|
||
this._applyBounce(p, normal);
|
||
break;
|
||
|
||
case Physics2DCollisionBehavior.Stop:
|
||
p.vx = 0;
|
||
p.vy = 0;
|
||
break;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 应用反弹
|
||
* Apply bounce effect
|
||
*/
|
||
private _applyBounce(p: Particle, normal: { x: number; y: number }): void {
|
||
// 反射公式: v' = v - 2 * (v · n) * n
|
||
// Reflection formula: v' = v - 2 * (v · n) * n
|
||
const dot = p.vx * normal.x + p.vy * normal.y;
|
||
|
||
// 只在粒子朝向表面时反弹 | Only bounce if particle is moving towards surface
|
||
if (dot < 0) {
|
||
p.vx = (p.vx - 2 * dot * normal.x) * this.bounceFactor;
|
||
p.vy = (p.vy - 2 * dot * normal.y) * this.bounceFactor;
|
||
|
||
// 稍微移开粒子防止重复碰撞 | Move particle slightly away to prevent repeated collision
|
||
p.x += normal.x * this.particleRadius * 0.1;
|
||
p.y += normal.y * this.particleRadius * 0.1;
|
||
}
|
||
|
||
// 应用生命损失 | Apply life loss
|
||
if (this.lifeLossOnBounce > 0) {
|
||
p.lifetime *= (1 - this.lifeLossOnBounce);
|
||
}
|
||
|
||
// 检查最小速度 | Check minimum velocity
|
||
const speed = Math.sqrt(p.vx * p.vx + p.vy * p.vy);
|
||
if (speed < this.minVelocityThreshold) {
|
||
this._particlesToKill.add(p);
|
||
}
|
||
}
|
||
}
|