插件系统
插件系统将引擎功能组织为可复用的模块。每个插件提供一组组件、系统和资源。Estella 内置了多个插件,它们会自动安装——你无需手动配置。
内置插件
在编辑器中创建项目时,所有内置插件默认处于激活状态。它们提供了你在检查器(Inspector)中看到的组件,以及驱动引擎每帧行为的系统。
核心插件
| 插件 | 提供的功能 |
|---|---|
| 资源加载 | AssetServer,用于在运行时加载纹理、Spine 数据、预制体等资源 |
| 预制体 | Prefabs,用于实例化预制体 |
| 输入 | Input,包含键盘、鼠标和触摸输入——每帧末尾清除 |
| 时间轴 | TimelinePlayer 组件用于播放时间轴动画,TimelineControl 运行时控制 API |
UI 插件
| 插件 | 组件 |
|---|---|
| 文本 | Text — 每帧将文本内容渲染为精灵纹理 |
| 图片 | Image — 显示图片,支持切片、平铺和填充模式 |
| UI 遮罩 | UIMask — 基于裁剪/模板测试的 UI 元素遮罩 |
| UI 布局 | UIRect — 相对于相机的锚点 UI 布局 |
| UI 渲染排序 | 自动为 UI 层级分配渲染顺序 |
| UI 交互 | Interactable、Button、UIInteraction、UIEvents — 指针命中测试和按钮状态 |
| 文本输入 | TextInput — 可编辑的文本输入框,使用平台原生输入 |
| 开关 | Toggle、ToggleGroup — 复选框和单选按钮行为 |
| 滑块 | Slider — 带填充和拖拽手柄的交互式值滑块 |
| 进度条 | ProgressBar — 非交互式进度显示 |
| 下拉框 | Dropdown — 下拉列表选择器 |
| 滚动视图 | ScrollView — 支持惯性和弹性回弹的可滚动容器 |
| 列表视图 | ListView — 大列表的虚拟滚动 |
| 拖拽 | Draggable — UI 元素的拖拽移动 |
| 焦点 | Focusable — 键盘焦点和 Tab 导航 |
| 安全区域 | SafeArea — 适配设备安全区域(刘海、圆角) |
| 布局组 | LayoutGroup — 带方向、间距和对齐的网格式布局 |
以上插件在每个项目中默认激活。你可以直接从编辑器的组件选择器中使用它们的组件——无需任何设置。
用户脚本
扩展游戏的主要方式是在项目的 src/ 目录中编写 TypeScript 脚本。脚本会被自动编译并包含在构建产物中。
自定义组件
使用 defineComponent() 创建可在编辑器检查器中显示的组件:
import { defineComponent } from 'esengine';
const Health = defineComponent('Health', { hp: 100, maxHp: 100,});定义后,Health 会出现在编辑器的添加组件菜单中。你可以像内置组件一样将其添加到实体并在检查器中编辑属性。
使用 defineTag() 创建无数据的标记组件:
import { defineTag } from 'esengine';
const Enemy = defineTag('Enemy');系统
使用 defineSystem() 和 addSystem() 添加每帧执行的游戏逻辑:
import { defineSystem, addSystem, Schedule } from 'esengine';
addSystem(defineSystem([], () => { // 每帧在 Update 阶段执行}));你也可以指定特定的调度阶段:
import { addSystemToSchedule, defineSystem, Schedule } from 'esengine';
addSystemToSchedule(Schedule.FixedUpdate, defineSystem([], () => { // 以固定时间步执行,适合物理逻辑}));对于一次性初始化,使用 addStartupSystem():
import { addStartupSystem, defineSystem } from 'esengine';
addStartupSystem(defineSystem([], () => { // 游戏启动时执行一次}));资源
资源是可在系统间共享的全局单例数据:
import { defineResource, addStartupSystem, defineSystem, Commands } from 'esengine';
const GameState = defineResource<{ score: number; level: number }>( { score: 0, level: 1 }, 'GameState');
addStartupSystem(defineSystem([Commands()], (cmds) => { cmds.insertResource(GameState, { score: 0, level: 1 });}));事件
事件用于系统间的解耦通信:
import { defineEvent, EventWriter, EventReader, defineSystem, addSystem } from 'esengine';
const ScoreEvent = defineEvent<{ points: number }>('Score');
addSystem(defineSystem([EventWriter(ScoreEvent)], (writer) => { writer.send({ points: 10 });}));
addSystem(defineSystem([EventReader(ScoreEvent)], (reader) => { for (const e of reader) { console.log('Scored:', e.points); }}));系统调度
系统按照定义的顺序在每帧中执行:
| 阶段 | 执行时机 |
|---|---|
Startup | 游戏启动时执行一次 |
First | 每帧开始 |
FixedPreUpdate | 固定时间步更新之前 |
FixedUpdate | 固定时间步更新(物理) |
FixedPostUpdate | 固定时间步更新之后 |
PreUpdate | 主更新之前 |
Update | 主更新(addSystem 的默认阶段) |
PostUpdate | 主更新之后 |
Last | 每帧结束 |
进阶:插件接口
插件是实现了 Plugin 接口的对象:
import { type Plugin, type App, defineResource, defineComponent, defineSystem, Schedule } from 'esengine';
const ScoreData = defineResource<{ value: number }>({ value: 0 }, 'Score');const Health = defineComponent('Health', { hp: 100, maxHp: 100 });
const myPlugin: Plugin = { name: 'MyGamePlugin', build(app: App) { app.insertResource(ScoreData, { value: 0 }); app.addSystemToSchedule(Schedule.Update, defineSystem([], () => { // 游戏逻辑 })); }, finish(app: App) { // 所有插件 build 完成后调用 — 可安全读取其他插件注册的资源 }, cleanup(app?: App) { // 应用关闭时调用 — 释放资源、移除监听器等 },};插件生命周期
| 方法 | 调用时机 | 用途 |
|---|---|---|
build(app) | 插件被添加时 | 注册资源、系统和组件 |
finish?(app) | 所有插件 build 完成后 | 依赖其他插件就绪的跨插件初始化 |
cleanup?(app) | 应用关闭时 | 释放资源、清理监听器和状态 |
插件可以声明资源依赖:
const analyticsPlugin: Plugin = { name: 'AnalyticsPlugin', dependencies: [Assets], build(app: App) { const assets = app.getResource(Assets); // 使用资源服务器... },};注册自定义插件
在脚本的 setup 函数中注册自定义插件:
import { type App } from 'esengine';
export function setup(app: App) { app.addPlugin(myPlugin);}