* 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
211 lines
5.9 KiB
TypeScript
211 lines
5.9 KiB
TypeScript
/**
|
|
* 点击特效组件 - 在点击位置播放粒子效果
|
|
* Click FX Component - Play particle effects at click position
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* // 在编辑器中添加此组件到相机或空实体上
|
|
* // Add this component to camera or empty entity in editor
|
|
*
|
|
* // 配置粒子资产列表,点击时会轮换播放
|
|
* // Configure particle asset list, will cycle through on click
|
|
* clickFx.particleAssets = ['guid1', 'guid2', 'guid3'];
|
|
* ```
|
|
*/
|
|
|
|
import { Component, ECSComponent, Property, Serializable, Serialize } from '@esengine/ecs-framework';
|
|
|
|
/**
|
|
* 点击特效触发模式
|
|
* Click FX trigger mode
|
|
*/
|
|
export enum ClickFxTriggerMode {
|
|
/** 鼠标左键点击 | Left mouse button click */
|
|
LeftClick = 'leftClick',
|
|
/** 鼠标右键点击 | Right mouse button click */
|
|
RightClick = 'rightClick',
|
|
/** 任意鼠标按钮 | Any mouse button */
|
|
AnyClick = 'anyClick',
|
|
/** 触摸 | Touch */
|
|
Touch = 'touch',
|
|
/** 鼠标和触摸都响应 | Both mouse and touch */
|
|
All = 'all'
|
|
}
|
|
|
|
/**
|
|
* 点击特效组件
|
|
* Click FX Component
|
|
*
|
|
* 在用户点击/触摸屏幕时,在点击位置播放粒子效果。
|
|
* Plays particle effects at the click/touch position when user interacts.
|
|
*/
|
|
@ECSComponent('ClickFx')
|
|
@Serializable({ version: 1, typeId: 'ClickFx' })
|
|
export class ClickFxComponent extends Component {
|
|
/**
|
|
* 粒子资产 GUID 列表
|
|
* List of particle asset GUIDs
|
|
*
|
|
* 多个资产会轮换播放,实现多样化的点击效果。
|
|
* Multiple assets will cycle through for varied click effects.
|
|
*/
|
|
@Serialize()
|
|
@Property({
|
|
type: 'array',
|
|
label: 'Particle Assets',
|
|
itemType: { type: 'asset', extensions: ['.particle', '.particle.json'] },
|
|
reorderable: true
|
|
})
|
|
public particleAssets: string[] = [];
|
|
|
|
/**
|
|
* 触发模式
|
|
* Trigger mode
|
|
*/
|
|
@Serialize()
|
|
@Property({
|
|
type: 'enum',
|
|
label: 'Trigger Mode',
|
|
options: [
|
|
{ label: 'Left Click', value: ClickFxTriggerMode.LeftClick },
|
|
{ label: 'Right Click', value: ClickFxTriggerMode.RightClick },
|
|
{ label: 'Any Click', value: ClickFxTriggerMode.AnyClick },
|
|
{ label: 'Touch', value: ClickFxTriggerMode.Touch },
|
|
{ label: 'All', value: ClickFxTriggerMode.All }
|
|
]
|
|
})
|
|
public triggerMode: ClickFxTriggerMode = ClickFxTriggerMode.All;
|
|
|
|
/**
|
|
* 是否启用
|
|
* Whether enabled
|
|
*/
|
|
@Serialize()
|
|
@Property({ type: 'boolean', label: 'Enabled' })
|
|
public fxEnabled: boolean = true;
|
|
|
|
/**
|
|
* 最大同时播放数量
|
|
* Maximum concurrent effects
|
|
*
|
|
* 限制同时播放的粒子效果数量,防止性能问题。
|
|
* Limits concurrent particle effects to prevent performance issues.
|
|
*/
|
|
@Serialize()
|
|
@Property({ type: 'integer', label: 'Max Concurrent', min: 1, max: 50 })
|
|
public maxConcurrent: number = 10;
|
|
|
|
/**
|
|
* 粒子效果生命周期(秒)
|
|
* Particle effect lifetime in seconds
|
|
*
|
|
* 效果播放多长时间后自动销毁。
|
|
* How long before the effect is automatically destroyed.
|
|
*/
|
|
@Serialize()
|
|
@Property({ type: 'number', label: 'Effect Lifetime', min: 0.1, max: 30, step: 0.1 })
|
|
public effectLifetime: number = 3;
|
|
|
|
/**
|
|
* 位置偏移(相对于点击位置)
|
|
* Position offset (relative to click position)
|
|
*/
|
|
@Serialize()
|
|
@Property({ type: 'vector2', label: 'Position Offset' })
|
|
public positionOffset: { x: number; y: number } = { x: 0, y: 0 };
|
|
|
|
/**
|
|
* 缩放
|
|
* Scale
|
|
*/
|
|
@Serialize()
|
|
@Property({ type: 'number', label: 'Scale', min: 0.1, max: 10, step: 0.1 })
|
|
public scale: number = 1;
|
|
|
|
// ============= 运行时状态(不序列化)| Runtime state (not serialized) =============
|
|
|
|
/** 当前粒子索引 | Current particle index */
|
|
private _currentIndex: number = 0;
|
|
|
|
/** 活跃的特效实体 ID 列表 | Active effect entity IDs */
|
|
private _activeEffects: { entityId: number; startTime: number }[] = [];
|
|
|
|
/**
|
|
* 获取下一个要播放的粒子资产 GUID
|
|
* Get next particle asset GUID to play
|
|
*/
|
|
public getNextParticleAsset(): string | null {
|
|
if (this.particleAssets.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
const guid = this.particleAssets[this._currentIndex];
|
|
this._currentIndex = (this._currentIndex + 1) % this.particleAssets.length;
|
|
return guid;
|
|
}
|
|
|
|
/**
|
|
* 添加活跃特效
|
|
* Add active effect
|
|
*/
|
|
public addActiveEffect(entityId: number): void {
|
|
this._activeEffects.push({ entityId, startTime: Date.now() });
|
|
}
|
|
|
|
/**
|
|
* 获取活跃特效列表
|
|
* Get active effects list
|
|
*/
|
|
public getActiveEffects(): ReadonlyArray<{ entityId: number; startTime: number }> {
|
|
return this._activeEffects;
|
|
}
|
|
|
|
/**
|
|
* 移除活跃特效
|
|
* Remove active effect
|
|
*/
|
|
public removeActiveEffect(entityId: number): void {
|
|
const index = this._activeEffects.findIndex(e => e.entityId === entityId);
|
|
if (index !== -1) {
|
|
this._activeEffects.splice(index, 1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 清除所有活跃特效记录
|
|
* Clear all active effect records
|
|
*/
|
|
public clearActiveEffects(): void {
|
|
this._activeEffects = [];
|
|
}
|
|
|
|
/**
|
|
* 获取活跃特效数量
|
|
* Get active effect count
|
|
*/
|
|
public get activeEffectCount(): number {
|
|
return this._activeEffects.length;
|
|
}
|
|
|
|
/**
|
|
* 是否可以添加新特效
|
|
* Whether can add new effect
|
|
*/
|
|
public canAddEffect(): boolean {
|
|
return this._activeEffects.length < this.maxConcurrent;
|
|
}
|
|
|
|
/**
|
|
* 重置状态
|
|
* Reset state
|
|
*/
|
|
public reset(): void {
|
|
this._currentIndex = 0;
|
|
this._activeEffects = [];
|
|
}
|
|
|
|
override onRemovedFromEntity(): void {
|
|
this.reset();
|
|
}
|
|
}
|