Files
esengine/docs/modules/blueprint/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

12 KiB
Raw Blame History

蓝图可视化脚本 (Blueprint)

@esengine/blueprint 提供了一个功能完整的可视化脚本系统,支持节点式编程、事件驱动和蓝图组合。

安装

npm install @esengine/blueprint

快速开始

import {
    createBlueprintSystem,
    createBlueprintComponentData,
    NodeRegistry,
    RegisterNode
} from '@esengine/blueprint';

// 创建蓝图系统
const blueprintSystem = createBlueprintSystem(scene);

// 加载蓝图资产
const blueprint = await loadBlueprintAsset('player.bp');

// 创建蓝图组件数据
const componentData = createBlueprintComponentData();
componentData.blueprintAsset = blueprint;

// 在游戏循环中更新
function gameLoop(dt: number) {
    blueprintSystem.process(entities, dt);
}

核心概念

蓝图资产结构

蓝图保存为 .bp 文件,包含以下结构:

interface BlueprintAsset {
    version: number;           // 格式版本
    type: 'blueprint';         // 资产类型
    metadata: BlueprintMetadata; // 元数据
    variables: BlueprintVariable[]; // 变量定义
    nodes: BlueprintNode[];    // 节点实例
    connections: BlueprintConnection[]; // 连接
}

节点类型

节点按功能分为以下类别:

类别 说明 颜色
event 事件节点(入口点) 红色
flow 流程控制 灰色
entity 实体操作 蓝色
component 组件访问 青色
math 数学运算 绿色
logic 逻辑运算 红色
variable 变量访问 紫色
time 时间工具 青色
debug 调试工具 灰色

引脚类型

节点通过引脚连接:

interface BlueprintPinDefinition {
    name: string;        // 引脚名称
    type: PinDataType;   // 数据类型
    direction: 'input' | 'output';
    isExec?: boolean;    // 是否是执行引脚
    defaultValue?: unknown;
}

// 支持的数据类型
type PinDataType =
    | 'exec'      // 执行流
    | 'boolean'   // 布尔值
    | 'number'    // 数字
    | 'string'    // 字符串
    | 'vector2'   // 2D 向量
    | 'vector3'   // 3D 向量
    | 'entity'    // 实体引用
    | 'component' // 组件引用
    | 'any';      // 任意类型

变量作用域

type VariableScope =
    | 'local'     // 每次执行独立
    | 'instance'  // 每个实体独立
    | 'global';   // 全局共享

虚拟机 API

BlueprintVM

蓝图虚拟机负责执行蓝图图:

import { BlueprintVM } from '@esengine/blueprint';

// 创建 VM
const vm = new BlueprintVM(blueprintAsset, entity, scene);

// 启动(触发 BeginPlay
vm.start();

// 每帧更新(触发 Tick
vm.tick(deltaTime);

// 停止(触发 EndPlay
vm.stop();

// 暂停/恢复
vm.pause();
vm.resume();

// 触发事件
vm.triggerEvent('EventCollision', { other: otherEntity });
vm.triggerCustomEvent('OnDamage', { amount: 50 });

// 调试模式
vm.debug = true;

执行上下文

interface ExecutionContext {
    blueprint: BlueprintAsset;  // 蓝图资产
    entity: Entity;             // 当前实体
    scene: IScene;              // 当前场景
    deltaTime: number;          // 帧间隔时间
    time: number;               // 总运行时间

    // 获取输入值
    getInput<T>(nodeId: string, pinName: string): T;

    // 设置输出值
    setOutput(nodeId: string, pinName: string, value: unknown): void;

    // 变量访问
    getVariable<T>(name: string): T;
    setVariable(name: string, value: unknown): void;
}

执行结果

interface ExecutionResult {
    outputs?: Record<string, unknown>; // 输出值
    nextExec?: string | null;          // 下一个执行引脚
    delay?: number;                    // 延迟执行(毫秒)
    yield?: boolean;                   // 暂停到下一帧
    error?: string;                    // 错误信息
}

自定义节点

定义节点模板

import { BlueprintNodeTemplate } from '@esengine/blueprint';

const MyNodeTemplate: BlueprintNodeTemplate = {
    type: 'MyCustomNode',
    title: 'My Custom Node',
    category: 'custom',
    description: 'A custom node example',
    keywords: ['custom', 'example'],
    inputs: [
        { name: 'exec', type: 'exec', direction: 'input', isExec: true },
        { name: 'value', type: 'number', direction: 'input', defaultValue: 0 }
    ],
    outputs: [
        { name: 'exec', type: 'exec', direction: 'output', isExec: true },
        { name: 'result', type: 'number', direction: 'output' }
    ]
};

实现节点执行器

import { INodeExecutor, RegisterNode } from '@esengine/blueprint';

@RegisterNode(MyNodeTemplate)
class MyNodeExecutor implements INodeExecutor {
    execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
        // 获取输入
        const value = context.getInput<number>(node.id, 'value');

        // 执行逻辑
        const result = value * 2;

        // 返回结果
        return {
            outputs: { result },
            nextExec: 'exec'  // 继续执行
        };
    }
}

使用装饰器注册

// 方式 1: 使用装饰器
@RegisterNode(MyNodeTemplate)
class MyNodeExecutor implements INodeExecutor { ... }

// 方式 2: 手动注册
NodeRegistry.instance.register(MyNodeTemplate, new MyNodeExecutor());

节点注册表

import { NodeRegistry } from '@esengine/blueprint';

// 获取单例
const registry = NodeRegistry.instance;

// 获取所有模板
const allTemplates = registry.getAllTemplates();

// 按类别获取
const mathNodes = registry.getTemplatesByCategory('math');

// 搜索节点
const results = registry.searchTemplates('add');

// 检查是否存在
if (registry.has('MyCustomNode')) { ... }

内置节点

事件节点

节点 说明
EventBeginPlay 蓝图启动时触发
EventTick 每帧触发
EventEndPlay 蓝图停止时触发
EventCollision 碰撞时触发
EventInput 输入事件触发
EventTimer 定时器触发
EventMessage 自定义消息触发

时间节点

节点 说明
Delay 延迟执行
GetDeltaTime 获取帧间隔
GetTime 获取运行时间

数学节点

节点 说明
Add 加法
Subtract 减法
Multiply 乘法
Divide 除法
Abs 绝对值
Clamp 限制范围
Lerp 线性插值
Min / Max 最小/最大值

调试节点

节点 说明
Print 打印到控制台

蓝图组合

蓝图片段

将可复用的逻辑封装为片段:

import { createFragment } from '@esengine/blueprint';

const healthFragment = createFragment('HealthSystem', {
    inputs: [
        { name: 'damage', type: 'number', internalNodeId: 'input1', internalPinName: 'value' }
    ],
    outputs: [
        { name: 'isDead', type: 'boolean', internalNodeId: 'output1', internalPinName: 'value' }
    ],
    graph: {
        nodes: [...],
        connections: [...],
        variables: [...]
    }
});

组合蓝图

import { createComposer, FragmentRegistry } from '@esengine/blueprint';

// 注册片段
FragmentRegistry.instance.register('health', healthFragment);
FragmentRegistry.instance.register('movement', movementFragment);

// 创建组合器
const composer = createComposer('PlayerBlueprint');

// 添加片段到槽位
composer.addFragment(healthFragment, 'slot1', { position: { x: 0, y: 0 } });
composer.addFragment(movementFragment, 'slot2', { position: { x: 400, y: 0 } });

// 连接槽位
composer.connect('slot1', 'onDeath', 'slot2', 'disable');

// 验证
const validation = composer.validate();
if (!validation.isValid) {
    console.error(validation.errors);
}

// 编译成蓝图
const blueprint = composer.compile();

触发器系统

定义触发条件

import { TriggerCondition, TriggerDispatcher } from '@esengine/blueprint';

const lowHealthCondition: TriggerCondition = {
    type: 'comparison',
    left: { type: 'variable', name: 'health' },
    operator: '<',
    right: { type: 'constant', value: 20 }
};

使用触发器分发器

const dispatcher = new TriggerDispatcher();

// 注册触发器
dispatcher.register('lowHealth', lowHealthCondition, (context) => {
    context.triggerEvent('OnLowHealth');
});

// 每帧评估
dispatcher.evaluate(context);

与 ECS 集成

使用蓝图系统

import { createBlueprintSystem } from '@esengine/blueprint';

class GameScene {
    private blueprintSystem: BlueprintSystem;

    initialize() {
        this.blueprintSystem = createBlueprintSystem(this.scene);
    }

    update(dt: number) {
        // 处理所有带蓝图组件的实体
        this.blueprintSystem.process(this.entities, dt);
    }
}

触发蓝图事件

import { triggerBlueprintEvent, triggerCustomBlueprintEvent } from '@esengine/blueprint';

// 触发内置事件
triggerBlueprintEvent(entity, 'Collision', { other: otherEntity });

// 触发自定义事件
triggerCustomBlueprintEvent(entity, 'OnPickup', { item: itemEntity });

实际示例

玩家控制蓝图

// 定义输入处理节点
const InputMoveTemplate: BlueprintNodeTemplate = {
    type: 'InputMove',
    title: 'Get Movement Input',
    category: 'input',
    inputs: [],
    outputs: [
        { name: 'direction', type: 'vector2', direction: 'output' }
    ],
    isPure: true
};

@RegisterNode(InputMoveTemplate)
class InputMoveExecutor implements INodeExecutor {
    execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
        const input = context.scene.services.get(InputServiceToken);
        const direction = {
            x: input.getAxis('horizontal'),
            y: input.getAxis('vertical')
        };
        return { outputs: { direction } };
    }
}

状态切换逻辑

// 在蓝图中实现状态机逻辑
const stateBlueprint = createEmptyBlueprint('PlayerState');

// 添加状态变量
stateBlueprint.variables.push({
    name: 'currentState',
    type: 'string',
    defaultValue: 'idle',
    scope: 'instance'
});

// 在 Tick 事件中检查状态转换
// ... 通过节点连接实现

序列化

保存蓝图

import { validateBlueprintAsset } from '@esengine/blueprint';

function saveBlueprint(blueprint: BlueprintAsset, path: string): void {
    if (!validateBlueprintAsset(blueprint)) {
        throw new Error('Invalid blueprint structure');
    }
    const json = JSON.stringify(blueprint, null, 2);
    fs.writeFileSync(path, json);
}

加载蓝图

async function loadBlueprint(path: string): Promise<BlueprintAsset> {
    const json = await fs.readFile(path, 'utf-8');
    const asset = JSON.parse(json);

    if (!validateBlueprintAsset(asset)) {
        throw new Error('Invalid blueprint file');
    }

    return asset;
}

最佳实践

  1. 使用片段复用逻辑

    • 将通用逻辑封装为片段
    • 通过组合器构建复杂蓝图
  2. 合理使用变量作用域

    • local: 临时计算结果
    • instance: 实体状态(如生命值)
    • global: 游戏全局状态
  3. 避免无限循环

    • VM 有每帧最大执行步数限制(默认 1000
    • 使用 Delay 节点打断长执行链
  4. 调试技巧

    • 启用 vm.debug = true 查看执行日志
    • 使用 Print 节点输出中间值
  5. 性能优化

    • 纯节点(isPure: true)的输出会被缓存
    • 避免在 Tick 中执行重计算