653 lines
18 KiB
Markdown
653 lines
18 KiB
Markdown
# 定时器系统使用指南
|
||
|
||
定时器系统是游戏开发中的重要工具,用于处理延迟执行、重复任务、倒计时等功能。本指南详细介绍如何使用ECS框架的定时器系统。
|
||
|
||
## 定时器基础概念
|
||
|
||
### 什么是定时器?
|
||
|
||
定时器允许你:
|
||
- ⏰ **延迟执行** - 在指定时间后执行某个操作
|
||
- 🔄 **重复执行** - 定期重复执行某个操作
|
||
- 🛑 **取消执行** - 在执行前取消定时器
|
||
- 🎯 **精确控制** - 精确控制执行时机
|
||
|
||
### 定时器的优势
|
||
|
||
相比直接在游戏循环中计时,定时器系统提供:
|
||
- 🧹 **自动管理** - 自动处理定时器的生命周期
|
||
- 🎮 **游戏时间控制** - 支持游戏暂停、时间缩放
|
||
- 💾 **内存优化** - 自动回收完成的定时器
|
||
- 🔧 **易于使用** - 简单的API调用
|
||
|
||
## 基础定时器使用
|
||
|
||
### 1. 简单延迟执行
|
||
|
||
```typescript
|
||
import { Core, Timer } from '@esengine/ecs-framework';
|
||
|
||
// 3秒后执行一次
|
||
Core.schedule(3.0, false, this, (timer) => {
|
||
console.log("3秒钟到了!");
|
||
});
|
||
|
||
// 实际游戏例子:延迟显示提示
|
||
class GameTutorial {
|
||
startTutorial() {
|
||
// 2秒后显示第一个提示
|
||
Core.schedule(2.0, false, this, () => {
|
||
this.showTip("欢迎来到游戏世界!");
|
||
});
|
||
|
||
// 5秒后显示移动提示
|
||
Core.schedule(5.0, false, this, () => {
|
||
this.showTip("使用WASD键移动角色");
|
||
});
|
||
|
||
// 8秒后显示攻击提示
|
||
Core.schedule(8.0, false, this, () => {
|
||
this.showTip("按空格键攻击敌人");
|
||
});
|
||
}
|
||
|
||
private showTip(message: string) {
|
||
// 显示提示的逻辑
|
||
console.log(`提示: ${message}`);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2. 重复执行
|
||
|
||
```typescript
|
||
// 每1秒执行一次,持续执行
|
||
const repeatTimer = Core.schedule(1.0, true, this, (timer) => {
|
||
console.log("每秒执行一次");
|
||
});
|
||
|
||
// 实际游戏例子:生命值恢复
|
||
class HealthRegeneration {
|
||
private regenTimer: ITimer;
|
||
|
||
startRegeneration(entity: Entity) {
|
||
const health = entity.getComponent(HealthComponent);
|
||
|
||
// 每2秒恢复5点生命值
|
||
this.regenTimer = Core.schedule(2.0, true, this, () => {
|
||
if (health.currentHealth < health.maxHealth) {
|
||
health.currentHealth += 5;
|
||
health.currentHealth = Math.min(health.currentHealth, health.maxHealth);
|
||
|
||
console.log(`生命值恢复:${health.currentHealth}/${health.maxHealth}`);
|
||
|
||
// 满血时停止恢复
|
||
if (health.currentHealth >= health.maxHealth) {
|
||
this.stopRegeneration();
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
stopRegeneration() {
|
||
if (this.regenTimer) {
|
||
this.regenTimer.stop();
|
||
this.regenTimer = null;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3. 获取定时器引用进行控制
|
||
|
||
```typescript
|
||
import { ITimer } from '@esengine/ecs-framework';
|
||
|
||
class BombTimer {
|
||
private bombTimer: ITimer;
|
||
private explosionTime: number = 5.0;
|
||
|
||
startBomb(position: { x: number; y: number }) {
|
||
console.log("炸弹已放置!5秒后爆炸...");
|
||
|
||
// 创建定时器并保存引用
|
||
this.bombTimer = Core.schedule(this.explosionTime, false, this, () => {
|
||
this.explode(position);
|
||
});
|
||
}
|
||
|
||
defuseBomb() {
|
||
if (this.bombTimer && !this.bombTimer.isDone) {
|
||
// 拆除炸弹
|
||
this.bombTimer.stop();
|
||
console.log("炸弹已被拆除!");
|
||
}
|
||
}
|
||
|
||
getRemainingTime(): number {
|
||
if (this.bombTimer && !this.bombTimer.isDone) {
|
||
return this.explosionTime - this.bombTimer.elapsedTime;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
private explode(position: { x: number; y: number }) {
|
||
console.log("💥 炸弹爆炸了!");
|
||
// 爆炸效果逻辑...
|
||
}
|
||
}
|
||
```
|
||
|
||
## 高级定时器功能
|
||
|
||
### 1. 定时器链 - 顺序执行多个任务
|
||
|
||
```typescript
|
||
class CutsceneManager {
|
||
playCutscene() {
|
||
// 第一个镜头:2秒
|
||
Core.schedule(2.0, false, this, () => {
|
||
this.showScene("开场镜头");
|
||
|
||
// 第二个镜头:3秒后
|
||
Core.schedule(3.0, false, this, () => {
|
||
this.showScene("角色登场");
|
||
|
||
// 第三个镜头:2秒后
|
||
Core.schedule(2.0, false, this, () => {
|
||
this.showScene("背景介绍");
|
||
|
||
// 结束:1秒后
|
||
Core.schedule(1.0, false, this, () => {
|
||
this.endCutscene();
|
||
});
|
||
});
|
||
});
|
||
});
|
||
}
|
||
|
||
private showScene(sceneName: string) {
|
||
console.log(`播放场景: ${sceneName}`);
|
||
}
|
||
|
||
private endCutscene() {
|
||
console.log("过场动画结束,开始游戏!");
|
||
}
|
||
}
|
||
|
||
// 更优雅的链式写法
|
||
class ImprovedCutsceneManager {
|
||
playCutscene() {
|
||
this.scheduleSequence([
|
||
{ delay: 2.0, action: () => this.showScene("开场镜头") },
|
||
{ delay: 3.0, action: () => this.showScene("角色登场") },
|
||
{ delay: 2.0, action: () => this.showScene("背景介绍") },
|
||
{ delay: 1.0, action: () => this.endCutscene() }
|
||
]);
|
||
}
|
||
|
||
private scheduleSequence(sequence: Array<{delay: number, action: () => void}>) {
|
||
let currentDelay = 0;
|
||
|
||
sequence.forEach(step => {
|
||
currentDelay += step.delay;
|
||
Core.schedule(currentDelay, false, this, step.action);
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2. 条件定时器 - 满足条件时执行
|
||
|
||
```typescript
|
||
class ConditionalTimer {
|
||
waitForCondition(
|
||
condition: () => boolean,
|
||
action: () => void,
|
||
checkInterval: number = 0.1,
|
||
timeout: number = 10.0
|
||
) {
|
||
let timeElapsed = 0;
|
||
|
||
const checkTimer = Core.schedule(checkInterval, true, this, () => {
|
||
timeElapsed += checkInterval;
|
||
|
||
if (condition()) {
|
||
// 条件满足,执行动作并停止检查
|
||
checkTimer.stop();
|
||
action();
|
||
} else if (timeElapsed >= timeout) {
|
||
// 超时,停止检查
|
||
checkTimer.stop();
|
||
console.log("等待条件超时");
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
// 使用例子
|
||
class WaitForPlayerExample {
|
||
waitForPlayerToReachGoal() {
|
||
const player = this.getPlayer();
|
||
const goalPosition = { x: 500, y: 300 };
|
||
|
||
this.waitForCondition(
|
||
// 条件:玩家到达目标位置
|
||
() => {
|
||
const playerPos = player.getComponent(PositionComponent);
|
||
return playerPos.distanceTo(goalPosition) < 50;
|
||
},
|
||
// 动作:触发下一关
|
||
() => {
|
||
console.log("玩家到达目标!开始下一关");
|
||
this.loadNextLevel();
|
||
},
|
||
0.1, // 每0.1秒检查一次
|
||
30.0 // 30秒后超时
|
||
);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3. 可暂停的定时器
|
||
|
||
```typescript
|
||
class PausableTimer {
|
||
private timers: ITimer[] = [];
|
||
private isPaused: boolean = false;
|
||
|
||
schedule(delay: number, repeat: boolean, callback: () => void): ITimer {
|
||
const timer = Core.schedule(delay, repeat, this, callback);
|
||
this.timers.push(timer);
|
||
return timer;
|
||
}
|
||
|
||
pauseAll() {
|
||
this.isPaused = true;
|
||
this.timers.forEach(timer => {
|
||
if (!timer.isDone) {
|
||
timer.stop();
|
||
}
|
||
});
|
||
}
|
||
|
||
resumeAll() {
|
||
if (!this.isPaused) return;
|
||
|
||
this.isPaused = false;
|
||
// 重新启动所有未完成的定时器
|
||
// 注意:这是简化实现,实际需要保存剩余时间
|
||
this.timers = this.timers.filter(timer => !timer.isDone);
|
||
}
|
||
|
||
clearAll() {
|
||
this.timers.forEach(timer => timer.stop());
|
||
this.timers = [];
|
||
}
|
||
}
|
||
|
||
// 游戏暂停系统
|
||
class GamePauseSystem {
|
||
private gameTimers: PausableTimer = new PausableTimer();
|
||
private isGamePaused: boolean = false;
|
||
|
||
pauseGame() {
|
||
if (this.isGamePaused) return;
|
||
|
||
this.isGamePaused = true;
|
||
this.gameTimers.pauseAll();
|
||
|
||
// 显示暂停菜单
|
||
this.showPauseMenu();
|
||
}
|
||
|
||
resumeGame() {
|
||
if (!this.isGamePaused) return;
|
||
|
||
this.isGamePaused = false;
|
||
this.gameTimers.resumeAll();
|
||
|
||
// 隐藏暂停菜单
|
||
this.hidePauseMenu();
|
||
}
|
||
|
||
scheduleGameTimer(delay: number, repeat: boolean, callback: () => void) {
|
||
return this.gameTimers.schedule(delay, repeat, callback);
|
||
}
|
||
}
|
||
```
|
||
|
||
## 实际游戏应用示例
|
||
|
||
### 1. Buff/Debuff 系统
|
||
|
||
```typescript
|
||
class BuffSystem {
|
||
applyBuff(entity: Entity, buffType: string, duration: number) {
|
||
const buff = new BuffComponent(buffType, duration);
|
||
entity.addComponent(buff);
|
||
|
||
// 应用Buff效果
|
||
this.applyBuffEffect(entity, buffType);
|
||
|
||
// 设置定时器移除Buff
|
||
Core.schedule(duration, false, this, () => {
|
||
if (!entity.isDestroyed && entity.hasComponent(BuffComponent)) {
|
||
this.removeBuff(entity, buffType);
|
||
}
|
||
});
|
||
|
||
console.log(`应用了 ${buffType} Buff,持续时间 ${duration} 秒`);
|
||
}
|
||
|
||
private applyBuffEffect(entity: Entity, buffType: string) {
|
||
const stats = entity.getComponent(StatsComponent);
|
||
|
||
switch (buffType) {
|
||
case 'speed_boost':
|
||
stats.moveSpeed *= 1.5;
|
||
break;
|
||
case 'damage_boost':
|
||
stats.damage *= 2.0;
|
||
break;
|
||
case 'invincible':
|
||
entity.addComponent(new InvincibleComponent());
|
||
break;
|
||
}
|
||
}
|
||
|
||
private removeBuff(entity: Entity, buffType: string) {
|
||
const buff = entity.getComponent(BuffComponent);
|
||
if (buff && buff.buffType === buffType) {
|
||
entity.removeComponent(buff);
|
||
this.removeBuffEffect(entity, buffType);
|
||
console.log(`${buffType} Buff 已过期`);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2. 技能冷却系统
|
||
|
||
```typescript
|
||
class SkillSystem {
|
||
private cooldowns: Map<string, number> = new Map();
|
||
|
||
useSkill(player: Entity, skillName: string): boolean {
|
||
// 检查冷却
|
||
if (this.isOnCooldown(skillName)) {
|
||
const remainingTime = this.getCooldownRemaining(skillName);
|
||
console.log(`技能冷却中,还需 ${remainingTime.toFixed(1)} 秒`);
|
||
return false;
|
||
}
|
||
|
||
// 执行技能
|
||
this.executeSkill(player, skillName);
|
||
|
||
// 启动冷却
|
||
const cooldownTime = this.getSkillCooldown(skillName);
|
||
this.startCooldown(skillName, cooldownTime);
|
||
|
||
return true;
|
||
}
|
||
|
||
private startCooldown(skillName: string, duration: number) {
|
||
const endTime = Time.totalTime + duration;
|
||
this.cooldowns.set(skillName, endTime);
|
||
|
||
// 设置定时器清理冷却
|
||
Core.schedule(duration, false, this, () => {
|
||
this.cooldowns.delete(skillName);
|
||
console.log(`技能 ${skillName} 冷却完成!`);
|
||
});
|
||
}
|
||
|
||
private isOnCooldown(skillName: string): boolean {
|
||
const endTime = this.cooldowns.get(skillName);
|
||
return endTime !== undefined && Time.totalTime < endTime;
|
||
}
|
||
|
||
private getCooldownRemaining(skillName: string): number {
|
||
const endTime = this.cooldowns.get(skillName);
|
||
return endTime ? Math.max(0, endTime - Time.totalTime) : 0;
|
||
}
|
||
|
||
private executeSkill(player: Entity, skillName: string) {
|
||
switch (skillName) {
|
||
case 'fireball':
|
||
this.castFireball(player);
|
||
break;
|
||
case 'heal':
|
||
this.castHeal(player);
|
||
break;
|
||
case 'dash':
|
||
this.performDash(player);
|
||
break;
|
||
}
|
||
}
|
||
|
||
private getSkillCooldown(skillName: string): number {
|
||
const cooldowns = {
|
||
'fireball': 3.0,
|
||
'heal': 10.0,
|
||
'dash': 5.0
|
||
};
|
||
return cooldowns[skillName] || 1.0;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3. 关卡时间限制
|
||
|
||
```typescript
|
||
class LevelTimer {
|
||
private timeLimit: number;
|
||
private timeRemaining: number;
|
||
private timerActive: boolean = false;
|
||
private updateTimer: ITimer;
|
||
|
||
startLevel(timeLimitSeconds: number) {
|
||
this.timeLimit = timeLimitSeconds;
|
||
this.timeRemaining = timeLimitSeconds;
|
||
this.timerActive = true;
|
||
|
||
// 每秒更新倒计时
|
||
this.updateTimer = Core.schedule(1.0, true, this, () => {
|
||
this.updateCountdown();
|
||
});
|
||
|
||
console.log(`关卡开始!时间限制:${timeLimitSeconds} 秒`);
|
||
}
|
||
|
||
private updateCountdown() {
|
||
if (!this.timerActive) return;
|
||
|
||
this.timeRemaining--;
|
||
|
||
// 更新UI显示
|
||
this.updateTimerUI(this.timeRemaining);
|
||
|
||
// 时间警告
|
||
if (this.timeRemaining === 30) {
|
||
console.log("⚠️ 警告:还剩30秒!");
|
||
this.playWarningSound();
|
||
} else if (this.timeRemaining === 10) {
|
||
console.log("🚨 紧急:还剩10秒!");
|
||
this.playUrgentSound();
|
||
}
|
||
|
||
// 时间到
|
||
if (this.timeRemaining <= 0) {
|
||
this.timeUp();
|
||
}
|
||
}
|
||
|
||
private timeUp() {
|
||
this.timerActive = false;
|
||
this.updateTimer.stop();
|
||
|
||
console.log("⏰ 时间到!游戏结束");
|
||
|
||
// 触发游戏结束(需要在实际使用中获取EntityManager实例)
|
||
// 示例:entityManager.eventBus.emit('level:timeout');
|
||
console.log('触发关卡超时事件');
|
||
}
|
||
|
||
completeLevel() {
|
||
if (this.timerActive) {
|
||
this.timerActive = false;
|
||
this.updateTimer.stop();
|
||
|
||
const completionTime = this.timeLimit - this.timeRemaining;
|
||
console.log(`🎉 关卡完成!用时:${completionTime} 秒`);
|
||
|
||
// 根据剩余时间给予奖励
|
||
this.calculateTimeBonus(this.timeRemaining);
|
||
}
|
||
}
|
||
|
||
private calculateTimeBonus(timeLeft: number) {
|
||
const bonus = Math.floor(timeLeft * 10); // 每秒剩余10分
|
||
if (bonus > 0) {
|
||
console.log(`时间奖励:${bonus} 分`);
|
||
// 触发时间奖励事件(需要在实际使用中获取EntityManager实例)
|
||
// 示例:entityManager.eventBus.emit('score:time_bonus', { bonus });
|
||
}
|
||
}
|
||
|
||
getTimeRemaining(): number {
|
||
return this.timeRemaining;
|
||
}
|
||
|
||
getTimeRemainingFormatted(): string {
|
||
const minutes = Math.floor(this.timeRemaining / 60);
|
||
const seconds = this.timeRemaining % 60;
|
||
return `${minutes}:${seconds.toString().padStart(2, '0')}`;
|
||
}
|
||
}
|
||
```
|
||
|
||
## 定时器性能优化
|
||
|
||
### 1. 定时器池化
|
||
|
||
```typescript
|
||
class TimerPool {
|
||
private static instance: TimerPool;
|
||
private timerPool: ITimer[] = [];
|
||
|
||
static getInstance(): TimerPool {
|
||
if (!this.instance) {
|
||
this.instance = new TimerPool();
|
||
}
|
||
return this.instance;
|
||
}
|
||
|
||
getTimer(): ITimer {
|
||
return this.timerPool.pop() || this.createTimer();
|
||
}
|
||
|
||
releaseTimer(timer: ITimer) {
|
||
timer.stop();
|
||
this.timerPool.push(timer);
|
||
}
|
||
|
||
private createTimer(): ITimer {
|
||
// 创建新定时器的逻辑
|
||
return new Timer();
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2. 批量定时器管理
|
||
|
||
```typescript
|
||
class BatchTimerManager {
|
||
private timers: Set<ITimer> = new Set();
|
||
|
||
scheduleMany(configs: Array<{delay: number, repeat: boolean, callback: () => void}>) {
|
||
return configs.map(config => {
|
||
const timer = Core.schedule(config.delay, config.repeat, this, config.callback);
|
||
this.timers.add(timer);
|
||
return timer;
|
||
});
|
||
}
|
||
|
||
stopAll() {
|
||
this.timers.forEach(timer => timer.stop());
|
||
this.timers.clear();
|
||
}
|
||
|
||
cleanup() {
|
||
// 清理已完成的定时器
|
||
this.timers.forEach(timer => {
|
||
if (timer.isDone) {
|
||
this.timers.delete(timer);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
## 常见问题和最佳实践
|
||
|
||
### Q: 定时器会自动清理吗?
|
||
|
||
A: 是的,完成的定时器会自动清理。但如果需要提前停止,记得调用 `timer.stop()`。
|
||
|
||
### Q: 定时器会受到游戏暂停影响吗?
|
||
|
||
A: 定时器使用游戏时间,如果实现了时间缩放功能,定时器会相应调整。
|
||
|
||
### Q: 如何实现精确的帧同步定时器?
|
||
|
||
A: 使用帧计数而不是时间:
|
||
|
||
```typescript
|
||
class FrameTimer {
|
||
private frameCount: number = 0;
|
||
private targetFrame: number;
|
||
|
||
scheduleFrames(frames: number, callback: () => void) {
|
||
this.targetFrame = this.frameCount + frames;
|
||
|
||
const checkFrame = () => {
|
||
this.frameCount++;
|
||
if (this.frameCount >= this.targetFrame) {
|
||
callback();
|
||
} else {
|
||
requestAnimationFrame(checkFrame);
|
||
}
|
||
};
|
||
|
||
requestAnimationFrame(checkFrame);
|
||
}
|
||
}
|
||
```
|
||
|
||
### Q: 如何避免定时器内存泄漏?
|
||
|
||
A:
|
||
1. 及时停止不需要的定时器
|
||
2. 在对象销毁时清理所有定时器
|
||
3. 使用弱引用避免循环引用
|
||
|
||
```typescript
|
||
class SafeTimerUser {
|
||
private timers: ITimer[] = [];
|
||
|
||
scheduleTimer(delay: number, callback: () => void) {
|
||
const timer = Core.schedule(delay, false, this, callback);
|
||
this.timers.push(timer);
|
||
return timer;
|
||
}
|
||
|
||
destroy() {
|
||
// 清理所有定时器
|
||
this.timers.forEach(timer => timer.stop());
|
||
this.timers = [];
|
||
}
|
||
}
|
||
```
|
||
|
||
定时器是游戏开发中非常有用的工具,合理使用可以让你的游戏逻辑更加优雅和高效! |