template/docs/intermediate/data-state-overview.md
unanmed cfa602f25c docs(10-11): 按 D-48 重做实操(概念+基础接口)
- 概念/基础接口 12 篇的实操部分改为六步照抄式(目标/改哪里/复制什么/改什么/怎么验证/注意)

- 片段一律去掉 import,统一附复制说明并指向 code-basics 的复制约定

- code-basics 删除 dev.md 的 import 置顶约定句,新增「照抄教程里的代码时,怎么处理 import」小节

- 仅改 docs/intermediate/*.md 与本篇审阅记录
2026-10-19 15:15:45 +08:00

7.0 KiB
Raw Blame History

数据端总览

数据端的一切都从一个叫 state 的对象开始。本篇先讲清 state 是什么、怎样拿到它,再列出作者最常需要的对象与接口,作为中级教程其余篇章的查表入口。

state:一切入口

state 是数据端的主对象(类型为 ICoreState),可以把它想成整个游戏的总目录:勇士、地图、怪物、战斗、Flag(全局可存档变量)等每一个子系统都挂在它下面。拿到 state,就等于拿到了通往整个数据端的入口。

// 从 state 出发访问勇士、地图与 Flag
const hp = state.hero.attribute.getFinalAttribute('hp');
const floorId = state.hero.location.floorId;
const cleared = state.flags.getFieldValue('cleared');

state 从哪来

写代码的位置不同,state 的来路也不同,常见的有三类。

一是框架作为参数传入。 你写的函数由框架调用时,state 常常就是它的一个参数:

  • 道具效果:useEffect(item, state),主对象是第二个参数;
  • 数据插件:awake(state, ...)、initialize(state, ...)、afterLoad(state, ...),主对象是第一个参数;
  • 事件内建函数:从环境对象上取 env.state。
// 道具效果:state 由框架作为第二个参数传入
function useEffect(item, state) {
    state.hero.getModifiableAttribute().add('atk', 5);
}

二是从处理对象上取。 战斗、怪物等上下文里的处理对象自带一个 state,直接读 handler.state 即可,不必再向外找。

// 处理对象上带着主对象
handler.state.flags.setFieldValue('battle-start', true);

三是存到自己的对象上。 自己写的类可以在构造时接收 state 并保存下来,之后用 this.state 访问它。

class MySystem {
    constructor(private readonly state) {}

    mark() {
        this.state.flags.setFieldValue('my-system', true);
    }
}

本教程后续的代码示例都假设 state 已经在当前作用域中(由上面任一方式获得),因此示例不再重复它的来源;示例也一律省略 import,照抄时怎么处理引入见 代码基础 的「照抄教程里的代码时,怎么处理 import」。

常用对象与获取方式

下表把最常用的对象与接口列在一起——只要拿到了 state,就能按表中的方式访问它们:

对象 获取方式 用途 分层
hero state.hero 勇士状态(属性、道具、装备、位置) L1
maps state.maps 楼层集合与楼层激活 L1
flags state.flags 全局可存档变量 L1
enemyManager state.enemyManager 怪物创建与查询 L1
enemyContext state.enemyContext 怪物属性构建与光环 L2
combat state.combat 战斗流程 L2
eventSystem state.eventSystem 地图事件执行 L2
pathfinding state.pathfinding 勇士寻路 L2
tileStore state.tileStore 图块定义存储 L0
itemStore state.itemStore 道具定义存储 L0
mapStore state.mapStore 地图定义存储 L0
eventStore state.eventStore 游戏事件存储 L0
faceManager state.faceManager 朝向管理 L0
roleFace state.roleFace 朝向绑定 L0
replaySystem state.replaySystem 录像系统 L0
loader state.loader 数据端加载对象 L3
loadManager state.loadManager 加载管理器 L3
getDataPlugin(id) state.getDataPlugin('xxx') 取得已加装的插件 L3
emitTip(...) state.emitTip('提示') 通知渲染端提示 L0
const plugin = state.getDataPlugin('main');
state.emitTip('获得黄钥匙');

分层标注(L0–L3)

上表的「分层」列标注了对象所属的模块层级:L0 公共层、L1 数据层、L2 系统层、L3 顶层。分层的意义在于区分「哪些内容会进存档」「哪些内容负责产生动作」,但不影响你如何使用它们——所有对象都能从 state 直接访问,调用方式完全一致。

// 无论哪个分层,访问方式都相同
state.flags.setFieldValue('door-open', true); // L1
const map = state.maps.getActiveMap('floor-1'); // L1
state.emitTip('获得黄钥匙'); // L0

为什么有些地方的 state 看不到全部层级

state 在运行时始终是同一个主对象,分层只是它的静态类型在逐层累加。类型定义一层叠一层,每层只声明自己这一层拥有的成员:

  • L0 IDataCommon:公共层,声明 tileStore / itemStore / mapStore / eventStore / faceManager / roleFace / replaySystem / emitTip 等共享设施;
  • L1 IStateBase:在 L0 之上加数据层成员,即 hero / maps / flags / enemyManager;
  • L2 IStateSystem:在 L1 之上加系统层成员,即 enemyContext / combat / eventSystem / pathfinding;
  • L3 ICoreState:在 L2 之上加顶层成员,即 loader / loadManager / getDataPlugin 等。

所以,当你从某个底层对象上取到 state 时,它的类型可能只标到 L0 或 L1——编辑器里就看不到 hero、flags 这些更高层的成员。这并不是它们不存在,而是当前这层接口没有声明它们:运行时是同一个对象,只是编译期类型更窄。这正是「分层」的实际体现:越往上,成员越全。

要使用完整成员,就拿 L3 的 ICoreState,也就是框架作为参数传入、或从 handler.state 取到的那个 state(获取方式见上文「state 从哪来」)。这套分层的设计原理(为什么这样拆、每层负责什么)会在高级教程中展开(预留标记:高级教程 · 数据端分层);待高级教程就位后,此处会替换为链接。

从这里继续