Files
esengine/docs/timer-guide.md
2025-06-19 10:38:31 +08:00

18 KiB
Raw Permalink Blame History

定时器系统使用指南

定时器系统是游戏开发中的重要工具用于处理延迟执行、重复任务、倒计时等功能。本指南详细介绍如何使用ECS框架的定时器系统。

定时器基础概念

什么是定时器?

定时器允许你:

  • 延迟执行 - 在指定时间后执行某个操作
  • 🔄 重复执行 - 定期重复执行某个操作
  • 🛑 取消执行 - 在执行前取消定时器
  • 🎯 精确控制 - 精确控制执行时机

定时器的优势

相比直接在游戏循环中计时,定时器系统提供:

  • 🧹 自动管理 - 自动处理定时器的生命周期
  • 🎮 游戏时间控制 - 支持游戏暂停、时间缩放
  • 💾 内存优化 - 自动回收完成的定时器
  • 🔧 易于使用 - 简单的API调用

基础定时器使用

1. 简单延迟执行

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. 重复执行

// 每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. 获取定时器引用进行控制

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. 定时器链 - 顺序执行多个任务

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. 条件定时器 - 满足条件时执行

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. 可暂停的定时器

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 系统

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. 技能冷却系统

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. 关卡时间限制

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. 定时器池化

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. 批量定时器管理

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: 使用帧计数而不是时间:

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. 使用弱引用避免循环引用
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 = [];
    }
}

定时器是游戏开发中非常有用的工具,合理使用可以让你的游戏逻辑更加优雅和高效!