Files
esengine/docs/modules/timer/index.md
YHH 4a16e30794 docs(modules): 添加框架模块文档 (#350)
* docs(modules): 添加框架模块文档

添加以下模块的完整文档:
- FSM (状态机): 状态定义、转换条件、优先级、事件监听
- Timer (定时器): 定时器调度、冷却系统、服务令牌
- Spatial (空间索引): GridSpatialIndex、AOI 兴趣区域管理
- Pathfinding (寻路): A* 算法、网格地图、导航网格、路径平滑
- Procgen (程序化生成): 噪声函数、种子随机数、加权随机

所有文档均基于实际源码 API 编写,包含:
- 快速开始示例
- 完整 API 参考
- 实际使用案例
- 蓝图节点说明
- 最佳实践建议

* docs(modules): 添加 Blueprint 模块文档和所有模块英文版

新增中文文档:
- Blueprint (蓝图可视化脚本): VM、自定义节点、组合系统、触发器

新增英文文档 (docs/en/modules/):
- FSM: State machine API, transitions, ECS integration
- Timer: Timers, cooldowns, service tokens
- Spatial: Grid spatial index, AOI management
- Pathfinding: A*, grid map, NavMesh, path smoothing
- Procgen: Noise functions, seeded random, weighted random
- Blueprint: Visual scripting, custom nodes, composition

所有文档均基于实际源码 API 编写。
2025-12-26 20:02:21 +08:00

11 KiB
Raw Blame History

定时器系统 (Timer)

@esengine/timer 提供了一个灵活的定时器和冷却系统,用于游戏中的延迟执行、重复任务、技能冷却等场景。

安装

npm install @esengine/timer

快速开始

import { createTimerService } from '@esengine/timer';

// 创建定时器服务
const timerService = createTimerService();

// 一次性定时器1秒后执行
const handle = timerService.schedule('myTimer', 1000, () => {
    console.log('Timer fired!');
});

// 重复定时器每100毫秒执行
timerService.scheduleRepeating('heartbeat', 100, () => {
    console.log('Tick');
});

// 冷却系统5秒冷却
timerService.startCooldown('skill_fireball', 5000);

if (timerService.isCooldownReady('skill_fireball')) {
    // 可以使用技能
    useFireball();
    timerService.startCooldown('skill_fireball', 5000);
}

// 游戏循环中更新
function gameLoop(deltaTime: number) {
    timerService.update(deltaTime);
}

核心概念

定时器 vs 冷却

特性 定时器 (Timer) 冷却 (Cooldown)
用途 延迟执行代码 限制操作频率
回调 有回调函数 无回调函数
重复 支持重复执行 一次性
查询 查询剩余时间 查询进度/是否就绪

TimerHandle

调度定时器后返回的句柄对象,用于控制定时器:

interface TimerHandle {
    readonly id: string;      // 定时器 ID
    readonly isValid: boolean; // 是否有效(未被取消)
    cancel(): void;            // 取消定时器
}

TimerInfo

定时器信息对象:

interface TimerInfo {
    readonly id: string;        // 定时器 ID
    readonly remaining: number; // 剩余时间(毫秒)
    readonly repeating: boolean; // 是否重复执行
    readonly interval?: number;  // 间隔时间(仅重复定时器)
}

CooldownInfo

冷却信息对象:

interface CooldownInfo {
    readonly id: string;       // 冷却 ID
    readonly duration: number;  // 总持续时间(毫秒)
    readonly remaining: number; // 剩余时间(毫秒)
    readonly progress: number;  // 进度0-10=刚开始1=结束)
    readonly isReady: boolean;  // 是否已就绪
}

API 参考

createTimerService

function createTimerService(config?: TimerServiceConfig): ITimerService

配置选项:

属性 类型 默认值 描述
maxTimers number 0 最大定时器数量0 表示无限制)
maxCooldowns number 0 最大冷却数量0 表示无限制)

定时器 API

schedule

调度一次性定时器:

const handle = timerService.schedule('explosion', 2000, () => {
    createExplosion();
});

// 提前取消
handle.cancel();

scheduleRepeating

调度重复定时器:

// 每秒执行
timerService.scheduleRepeating('regen', 1000, () => {
    player.hp += 5;
});

// 立即执行一次,然后每秒重复
timerService.scheduleRepeating('tick', 1000, () => {
    console.log('Tick');
}, true); // immediate = true

cancel / cancelById

取消定时器:

// 通过句柄取消
handle.cancel();
// 或
timerService.cancel(handle);

// 通过 ID 取消
timerService.cancelById('regen');

hasTimer

检查定时器是否存在:

if (timerService.hasTimer('explosion')) {
    console.log('Explosion is pending');
}

getTimerInfo

获取定时器信息:

const info = timerService.getTimerInfo('explosion');
if (info) {
    console.log(`剩余时间: ${info.remaining}ms`);
    console.log(`是否重复: ${info.repeating}`);
}

冷却 API

startCooldown

开始冷却:

// 5秒冷却
timerService.startCooldown('skill_fireball', 5000);

isCooldownReady / isOnCooldown

检查冷却状态:

if (timerService.isCooldownReady('skill_fireball')) {
    // 可以使用技能
    castFireball();
    timerService.startCooldown('skill_fireball', 5000);
} else {
    console.log('技能还在冷却中');
}

// 或使用 isOnCooldown
if (timerService.isOnCooldown('skill_fireball')) {
    console.log('冷却中...');
}

getCooldownProgress / getCooldownRemaining

获取冷却进度:

// 进度 0-10=刚开始1=完成)
const progress = timerService.getCooldownProgress('skill_fireball');
console.log(`冷却进度: ${(progress * 100).toFixed(0)}%`);

// 剩余时间(毫秒)
const remaining = timerService.getCooldownRemaining('skill_fireball');
console.log(`剩余时间: ${(remaining / 1000).toFixed(1)}s`);

getCooldownInfo

获取完整冷却信息:

const info = timerService.getCooldownInfo('skill_fireball');
if (info) {
    console.log(`总时长: ${info.duration}ms`);
    console.log(`剩余: ${info.remaining}ms`);
    console.log(`进度: ${info.progress}`);
    console.log(`就绪: ${info.isReady}`);
}

resetCooldown / clearAllCooldowns

重置冷却:

// 重置单个冷却
timerService.resetCooldown('skill_fireball');

// 清除所有冷却(例如角色复活时)
timerService.clearAllCooldowns();

生命周期

update

更新定时器服务(需要每帧调用):

function gameLoop(deltaTime: number) {
    // deltaTime 单位是毫秒
    timerService.update(deltaTime);
}

clear

清除所有定时器和冷却:

timerService.clear();

调试属性

// 获取活跃定时器数量
console.log(timerService.activeTimerCount);

// 获取活跃冷却数量
console.log(timerService.activeCooldownCount);

// 获取所有活跃定时器 ID
const timerIds = timerService.getActiveTimerIds();

// 获取所有活跃冷却 ID
const cooldownIds = timerService.getActiveCooldownIds();

实际示例

技能冷却系统

import { createTimerService, type ITimerService } from '@esengine/timer';

class SkillSystem {
    private timerService: ITimerService;
    private skills: Map<string, SkillData> = new Map();

    constructor() {
        this.timerService = createTimerService();
    }

    registerSkill(id: string, data: SkillData): void {
        this.skills.set(id, data);
    }

    useSkill(skillId: string): boolean {
        const skill = this.skills.get(skillId);
        if (!skill) return false;

        // 检查冷却
        if (!this.timerService.isCooldownReady(skillId)) {
            const remaining = this.timerService.getCooldownRemaining(skillId);
            console.log(`技能 ${skillId} 冷却中,剩余 ${remaining}ms`);
            return false;
        }

        // 使用技能
        this.executeSkill(skill);

        // 开始冷却
        this.timerService.startCooldown(skillId, skill.cooldown);
        return true;
    }

    getSkillCooldownProgress(skillId: string): number {
        return this.timerService.getCooldownProgress(skillId);
    }

    update(dt: number): void {
        this.timerService.update(dt);
    }
}

interface SkillData {
    cooldown: number;
    // ... other properties
}

延迟和定时效果

class EffectSystem {
    private timerService: ITimerService;

    constructor(timerService: ITimerService) {
        this.timerService = timerService;
    }

    // 延迟爆炸
    scheduleExplosion(position: { x: number; y: number }, delay: number): void {
        this.timerService.schedule(`explosion_${Date.now()}`, delay, () => {
            this.createExplosion(position);
        });
    }

    // DOT 伤害(每秒造成伤害)
    applyDOT(target: Entity, damage: number, duration: number): void {
        const dotId = `dot_${target.id}_${Date.now()}`;
        let elapsed = 0;

        this.timerService.scheduleRepeating(dotId, 1000, () => {
            elapsed += 1000;
            target.takeDamage(damage);

            if (elapsed >= duration) {
                this.timerService.cancelById(dotId);
            }
        });
    }

    // BUFF 效果(持续一段时间)
    applyBuff(target: Entity, buffId: string, duration: number): void {
        target.addBuff(buffId);

        this.timerService.schedule(`buff_expire_${buffId}`, duration, () => {
            target.removeBuff(buffId);
        });
    }
}

与 ECS 集成

import { Component, EntitySystem, Matcher } from '@esengine/ecs-framework';
import { createTimerService, type ITimerService } from '@esengine/timer';

// 定时器组件
class TimerComponent extends Component {
    timerService: ITimerService;

    constructor() {
        super();
        this.timerService = createTimerService();
    }
}

// 定时器系统
class TimerSystem extends EntitySystem {
    constructor() {
        super(Matcher.all(TimerComponent));
    }

    protected processEntity(entity: Entity, dt: number): void {
        const timer = entity.getComponent(TimerComponent);
        timer.timerService.update(dt);
    }
}

// 冷却组件(用于共享冷却)
class CooldownComponent extends Component {
    constructor(public timerService: ITimerService) {
        super();
    }
}

蓝图节点

Timer 模块提供了可视化脚本支持的蓝图节点:

冷却节点

  • StartCooldown - 开始冷却
  • IsCooldownReady - 检查冷却是否就绪
  • GetCooldownProgress - 获取冷却进度
  • GetCooldownInfo - 获取详细冷却信息
  • ResetCooldown - 重置冷却

定时器节点

  • HasTimer - 检查定时器是否存在
  • CancelTimer - 取消定时器
  • GetTimerRemaining - 获取定时器剩余时间

服务令牌

在依赖注入场景中使用:

import { TimerServiceToken, createTimerService } from '@esengine/timer';

// 注册服务
services.register(TimerServiceToken, createTimerService());

// 获取服务
const timerService = services.get(TimerServiceToken);

最佳实践

  1. 使用有意义的 ID:使用描述性的 ID 便于调试和管理

    // 好
    timerService.startCooldown('skill_fireball', 5000);
    
    // 不好
    timerService.startCooldown('cd1', 5000);
    
  2. 避免重复 ID:相同 ID 的定时器会覆盖之前的

    // 使用唯一 ID
    const uniqueId = `explosion_${entity.id}_${Date.now()}`;
    timerService.schedule(uniqueId, 1000, callback);
    
  3. 及时清理:在适当时机清理不需要的定时器和冷却

    // 实体销毁时
    onDestroy() {
        this.timerService.cancelById(this.timerId);
    }
    
  4. 配置限制:在生产环境考虑设置最大数量限制

    const timerService = createTimerService({
        maxTimers: 1000,
        maxCooldowns: 500
    });