节点事件系统
节点事件系统是组件间通信的重要机制,它提供了一种松耦合的方式让组件之间进行交互。
基础用法
事件监听和触发
typescript
@apclass("EventDemo")
class EventDemo extends Component<GameEntity> {
start() {
// 监听事件
this.node.on("scoreChange", this.onScoreChange, this);
// 触发事件
this.node.emit("scoreChange", 100);
}
private onScoreChange(score: number) {
console.log(`分数变化: ${score}`);
}
onDestroy() {
// 清理事件监听
this.node.off("scoreChange", this.onScoreChange, this);
}
}
一次性事件监听
typescript
@apclass("OneTimeEventDemo")
class OneTimeEventDemo extends Component<GameEntity> {
start() {
// 只监听一次事件
this.node.once("gameStart", () => {
console.log("游戏开始!");
});
}
}
全局事件系统
使用全局事件总线
typescript
import { Emitter, Component } from "@dao3fun/component";
@apclass("GlobalEventDemo")
class GlobalEventDemo extends Component<GameEntity> {
start() {
// 监听全局事件
Emitter.on("playerJoin", this.onPlayerJoin, this);
// 触发全局事件
Emitter.emit("playerJoin", { id: 1, name: "玩家1" });
}
private onPlayerJoin(player: any) {
console.log(`玩家加入: ${player.name}`);
}
onDestroy() {
// 清理全局事件监听
Emitter.off("playerJoin", this.onPlayerJoin, this);
}
}
实战示例
游戏状态管理系统
typescript
// 游戏状态管理器
@apclass("GameStateManager")
class GameStateManager extends Component<GameEntity> {
private score: number = 0;
private gameState: "idle" | "playing" | "paused" | "over" = "idle";
start() {
// 监听游戏相关事件
this.node.on("startGame", this.startGame, this);
this.node.on("pauseGame", this.pauseGame, this);
this.node.on("resumeGame", this.resumeGame, this);
this.node.on("gameOver", this.gameOver, this);
this.node.on("addScore", this.addScore, this);
}
private startGame() {
this.gameState = "playing";
this.score = 0;
this.node.emit("gameStateChanged", this.gameState);
}
private pauseGame() {
this.gameState = "paused";
this.node.emit("gameStateChanged", this.gameState);
}
private resumeGame() {
this.gameState = "playing";
this.node.emit("gameStateChanged", this.gameState);
}
private gameOver() {
this.gameState = "over";
this.node.emit("gameStateChanged", this.gameState);
this.node.emit("finalScore", this.score);
}
private addScore(points: number) {
this.score += points;
this.node.emit("scoreUpdated", this.score);
}
onDestroy() {
// 清理所有事件监听
this.node.off("startGame", this.startGame, this);
this.node.off("pauseGame", this.pauseGame, this);
this.node.off("resumeGame", this.resumeGame, this);
this.node.off("gameOver", this.gameOver, this);
this.node.off("addScore", this.addScore, this);
}
}
// UI 控制器
@apclass("UIController")
class UIController extends Component<GameEntity> {
start() {
// 监听游戏状态变化
this.node.on("gameStateChanged", this.updateUI, this);
this.node.on("scoreUpdated", this.updateScore, this);
this.node.on("finalScore", this.showGameOver, this);
}
private updateUI(state: string) {
console.log(`更新UI状态: ${state}`);
}
private updateScore(score: number) {
console.log(`更新分数显示: ${score}`);
}
private showGameOver(finalScore: number) {
console.log(`游戏结束,最终分数: ${finalScore}`);
}
onDestroy() {
this.node.off("gameStateChanged", this.updateUI, this);
this.node.off("scoreUpdated", this.updateScore, this);
this.node.off("finalScore", this.showGameOver, this);
}
}
最佳实践
1. 事件命名规范
typescript
// 使用动词+名词的形式
this.node.emit("playerSpawn"); // ✅ 好的命名
this.node.emit("spawnPlayer"); // ✅ 好的命名
this.node.emit("player"); // ❌ 不好的命名
// 使用驼峰命名法
this.node.emit("scoreUpdate"); // ✅ 好的命名
this.node.emit("score_update"); // ❌ 不好的命名
2. 事件参数传递
typescript
@apclass("EventParamsDemo")
class EventParamsDemo extends Component<GameEntity> {
start() {
// 传递单个参数
this.node.emit("scoreChange", 100);
// 传递多个参数
this.node.emit("playerUpdate", {
health: 100,
position: { x: 0, y: 0, z: 0 },
status: "active",
});
}
}
3. 事件清理
typescript
@apclass("EventCleanupDemo")
class EventCleanupDemo extends Component<GameEntity> {
private handlers: Map<string, Function> = new Map();
start() {
// 保存事件处理函数的引用
const handler = (data: any) => {
console.log(data);
};
this.node.on("myEvent", handler, this);
this.handlers.set("myEvent", handler);
}
onDestroy() {
// 清理所有事件监听
this.handlers.forEach((handler, event) => {
this.node.off(event, handler, this);
});
this.handlers.clear();
}
}
注意事项
内存泄漏防范
- 始终在组件销毁时清理事件监听
- 使用 Map 或数组记录添加的事件监听器
- 避免重复添加相同的事件监听
性能优化
- 避免过于频繁的事件触发
- 合理使用事件参数,避免传递过大的数据
- 考虑使用事件节流或防抖
事件传播
- 明确事件的作用范围
- 合理选择使用节点事件还是全局事件
- 避免事件循环调用
常见问题
Q: 如何避免事件监听器重复添加?
A: 可以在添加前先移除旧的监听器:
typescript
this.node.off("myEvent", this.handler, this);
this.node.on("myEvent", this.handler, this);
Q: 全局事件和节点事件该如何选择?
A:
- 节点事件:组件间的局部通信
- 全局事件:跨节点的系统级通信
Q: 如何处理事件监听器的内存泄漏?
A:
- 在 onDestroy 中清理所有事件
- 使用 once 代替 on 处理一次性事件
- 保持对事件监听器的引用以便清理