refactor: 跟随者与勇士移动

This commit is contained in:
AncTe committed 2026-06-27 20:33:49 +08:00
1 parent 02bf39a62d
commit fadcc98864
20 files changed
+1011 -310

No files matched your search

+140
View File
@@ -0,0 +1,140 @@
# 需求综述
对勇士跟随者相关接口进行结构性重构。核心动机是让跟随者复用勇士已有的位置与渲染接口(`IHeroLocation`、`IHeroRendering`),而非为跟随者定义独立的位置/渲染类型。这样跟随者与勇士共享同一套移动与渲染模型,简化接口层级,避免重复定义。
重构四个接口:`IHeroLocation`、`IHeroRendering`、`IHeroFollower`、`IHeroFollowersController`。前两者是勇士与跟随者共用的基础接口,后两者专属于跟随者管理。同时需要将旧的 `HeroMoveController` 类拆分为 `HeroLocation`、`HeroRendering`、`HeroFollowersController` 三个独立类,`HeroFollower` 为新实现类,并修改 `HeroState` 适配新接口。
# 接口设计分析
## IHeroLocation
### 接口综述
勇士位置接口,代表一个可移动对象在游戏世界中的坐标与朝向,并挂载移动器负责移动控制。该接口继承 `ISaveableContent<IHeroLocationSave>` 实现存档读档,继承 `IObjectMovable` 获得基础位置读写能力。存档格式为 `IFacedTileLocator`(包含 `x`、`y`、`direction`)。移动能力由 `IObjectMover<this>` 提供,支持行走、跳跃、瞬移、朝向控制等全部需求。
### 接口分析
- `IHeroLocation.mover`:预期频率**高频**。几乎所有移动操作都经由 `mover` 进行,在剧情演出与逻辑中频繁出现。典型使用场景:演出中让角色沿指定路线移动。
## IHeroRendering
### 接口综述
勇士渲染信息接口,存在于数据端,存放无需进入渲染端即可管理的渲染相关数据。当前仅包含透明度(`alpha`),后续可能扩展。继承 `ISaveableContent<IHeroRenderingSave>` 实现存档读档,继承 `IHookable<IHeroRenderingHooks>` 支持透明度变化时触发回调。旧实现中的 `image` 作为纯渲染端概念被移除。
### 接口分析
- `IHeroRendering.alpha`:预期频率**低频**。直接读取透明度值的需求极少。
- `IHeroRendering.setAlpha`:预期频率**低频**。设置透明度仅在特殊效果中出现,一座塔中调用次数有限。
## IHeroFollower
### 接口综述
跟随者接口,包含跟随者的渲染信息与位置信息。直接复用 `IHeroRendering` 和 `IHeroLocation`,使跟随者拥有与勇士完全一致的移动与渲染能力。存档格式 `IHeroFollowerSave` 嵌套包含 `IHeroRenderingSave` 和 `IHeroLocationSave`。对比旧 `IHeroFollower = { num, identifier, alpha }` 的纯数据结构,新接口让跟随者从静态数据升级为拥有完整移动能力的独立对象。
### 接口分析
- `IHeroFollower.rendering`:预期频率**低频**。
- `IHeroFollower.location`:预期频率**低频**。
> 跟随者本身就是一个使用场景极低的需求,大多数游戏中都不会使用到一两次,所以几乎所有跟随者相关接口都应该是低频。
## IHeroFollowersController
### 接口综述
跟随者控制器接口,负责跟随者的增删查改与聚集操作。继承 `IHookable<IHeroFollowersControllerHooks>`,提供添加、移除、聚集三个事件的钩子支持。对比旧实现:索引取代 `identifier` 作为查找方式,`setFollowerAlpha` 移除(改为通过 `follower.rendering.setAlpha` 操作),`addFollower` 的 `num` 参数合并了旧的图块数字与标识符。
### 接口分析
- `IHeroFollowersController.addFollower`:预期频率**低频**。
- `IHeroFollowersController.getFollower`:预期频率**低频**。
- `IHeroFollowersController.getFollowersById`:预期频率**低频**。
- `IHeroFollowersController.getAllFollowers`:预期频率**低频**。
- `IHeroFollowersController.removeFollower`:预期频率**低频**。
- `IHeroFollowersController.removeAllFollowers`:预期频率**低频**。
- `IHeroFollowersController.gatherFollowers`:预期频率**低频**。
- `IHeroFollowersController.gatherFollowersSync`:预期频率**低频**。
### 预期体量
预期代码体量合计约 250–300 行。分析如下:
- `HeroLocation`:需实现 `IObjectMovable` 和 `ISaveableContent`,并创建内部 `ObjectMover` 子类,预计 70–90 行。
- `HeroRendering`:仅管理 `alpha` 的存取和钩子触发,预计 25–35 行。
- `HeroFollower`:组合 `HeroRendering` 和 `HeroLocation` 并实现存档读档,预计 35–45 行。
- `HeroFollowersController`:维护跟随者列表和增删查改聚集操作,预计 120–150 行。
# 涉及文件
## 需要修改的文件
### `@user/data-base/hero/types.ts`
- [x] 重构 `IHeroLocation` 接口:改为继承 `ISaveableContent<IHeroLocationSave>` 与 `IObjectMovable`,仅保留 `mover` 成员。
- [x] 新增 `IHeroLocationSave` 类型别名。
- [x] 重构 `IHeroRendering` 接口:新增继承 `ISaveableContent<IHeroRenderingSave>` 与 `IHookable<IHeroRenderingHooks>`。
- [x] 新增 `IHeroRenderingSave` 接口。
- [x] 新增 `IHeroRenderingHooks` 接口。
- [x] 新增 `IHeroFollowerSave` 接口。
- [x] 重构 `IHeroFollower` 接口:改为组合 `rendering` 与 `location`,继承 `ISaveableContent<IHeroFollowerSave>`。
- [x] 重构 `IHeroFollowersController` 接口:修改为上文中的新设计。
- [x] 新增 `IHeroFollowersControllerHooks` 接口。
- [x] 修改 `IHeroState` 接口:新增 `location`、`rendering`、`followers` 成员,移除旧的 `mover`、`attachMover`、`getHeroMover`。
- [x] 修改 `IHeroStateSave`:`locator` 类型改为 `IHeroLocationSave`,`followers` 类型改为 `readonly IHeroFollowerSave[]`。
- [ ] 删除旧的 `IHeroMoveController`、`IHeroMoveControllerHooks` 接口及旧 `IHeroFollower`、`HeroAnimateDirection` 枚举。
### `@user/data-base/hero/mover.ts`
删除此文件,内容拆分至 `location.ts`、`rendering.ts`、`followersController.ts`。
### `@user/data-base/hero/location.ts`
新建文件。
- [ ] 实现 `HeroLocation` 类:基于新 `IHeroLocation` 接口。
- [ ] 内部创建 `ObjectMover<this>` 子类实例,挂载为 `mover`。
### `@user/data-base/hero/rendering.ts`
新建文件。
- [ ] 实现 `HeroRendering` 类:基于新 `IHeroRendering` 接口。
- [ ] 实现 `setAlpha`:更新值并触发 `onSetAlpha` 钩子。
### `@user/data-base/hero/follower.ts`
新建文件。
- [ ] 实现 `HeroFollower` 类:组合 `HeroRendering` 与 `HeroLocation` 实例,实现新 `IHeroFollower` 接口。
- [ ] 内部保存 `num`,供 `getFollowersById` 查询匹配。
### `@user/data-base/hero/followersController.ts`
新建文件。
- [ ] 实现 `HeroFollowersController` 类:基于新 `IHeroFollowersController` 接口。
- [ ] 内部持有勇士 `IHeroLocation` 引用,供聚集操作使用。
- [ ] 实现 `addFollower(num)`:创建 `HeroFollower`,加入列表,触发 `onAddFollower` 钩子。
- [ ] 实现 `getFollower(index)`:按索引返回跟随者,越界返回 `null`。
- [ ] 实现 `getFollowersById(num)`:返回匹配 `num` 的 `[index, follower]` 迭代器。
- [ ] 实现 `getAllFollowers()`:返回跟随者列表。
- [ ] 实现 `removeFollower(index)`:按索引移除跟随者,触发 `onRemoveFollower` 钩子,动画结束后兑现。
- [ ] 实现 `removeAllFollowers()`:清空跟随者列表,触发钩子,动画结束后兑现。
- [ ] 实现 `gatherFollowers()`:将所有跟随者移动至勇士位置并同步朝向,动画结束后兑现。
- [ ] 实现 `gatherFollowersSync()`:立即将所有跟随者瞬移至勇士位置并同步朝向。
### `@user/data-base/hero/state.ts`
修改 `HeroState` 类。
- [ ] 修改构造函数参数并新增 `location`、`rendering`、`followers` 成员,替代旧的 `mover`。
- [ ] 删除 `attachMover`、`getHeroMover` 方法。
- [ ] 修改 `saveState`:通过 `location.saveState`、`rendering.saveState` 及各跟随者的 `saveState` 获取存档数据。
- [ ] 修改 `loadState`:通过 `location.loadState`、`rendering.loadState` 恢复位置与渲染,重建跟随者列表。
### `@user/data-base/hero/index.ts`
- [ ] 新增导出 `location.ts`、`rendering.ts`、`follower.ts`、`followersController.ts`。
- [ ] 移除 `mover.ts` 的导出。
+121
View File
@@ -0,0 +1,121 @@
# 需求综述
将旧样板中的 `HeroMover`(`packages-user/data-state/src/legacy/move.ts`)基于新的 `IObjectMover` / `ObjectMover` 基类进行彻底性重构,编写新的 `IHeroMover` 接口和 `HeroMover` 实现类。
新实现的核心变化:
- 基类从旧 `ObjectMoverBase`(EventEmitter 模式)迁移至 `ObjectMover<IHeroLocation>`(Hook 模式)。
- `inLockControl` 删除,重构后不再需要。
- 移动速度不再由 `HeroMover` 存储,速度变更通过 `ObjectMover.speed()` 方法体现,实际动画时长在 Hook 中自行处理。
- `noRoute`、`ignoreTerrain`、`autoSave` 不再作为公开属性暴露,改为通过 `config()` / `getConfig()` 方法管理,配置对象为 `IHeroMoverConfig`。
- 摒弃所有 `core.xxx` 调用,这些功能将在后续通过新接口逐步补充。
- `HeroMover` 的实例化由 `IHeroLocation` 的实现类内部完成,构造时传入 `this` 作为 `tile`,解决循环依赖。
# 接口设计分析
`IHeroMover` 直接扩展 `IObjectMover<IHeroLocation>`,以下仅分析新增内容。
## IHeroMover
### 接口综述
`IHeroMover` 在通用对象移动器基础上,通过 `config()` 方法接收一个 `IHeroMoverConfig` 配置对象来控制本次移动的行为模式(是否忽略地形、是否记录路径、是否触发自动存档)。这种方法将三个相关标志聚合到统一的配置接口中,调用方便且后续扩展新配置项时无需新增独立属性。`getConfig()` 返回当前生效的只读配置,供内部逻辑和 Hook 查询移动上下文。
### 接口分析
- `IHeroMover.config(config)`:预期频率**中频**。在移动开始前统一配置本次移动的行为标志,一次性设置多个相关选项,比分别设置三个独立属性更加便利。典型使用场景:自动寻路系统在每次移动前调用 `mover.config({ noRoute: true }).forward(3).start()`,通过链式调用一气呵成。
- `IHeroMover.getConfig()`:预期频率**低频**。查询当前移动器的配置快照,主要用于 Hook 内部判断移动上下文,或调试时检查配置状态。典型使用场景:Hook 中的 `onStepStart` 获取 `mover.getConfig()` 以判断当前步是否需要路线记录。
### IHeroMoverConfig 成员分析
- `noRoute`:预期频率**中频**。控制本次移动是否记录进路线系统。路线回放、自动寻路等系统均需跳过路线记录,涉及面较广,故为中频。
- `ignoreTerrain`:预期频率**低频**。控制本次移动是否跳过地形碰撞检测,仅用于瞬移、强制移动等特殊场景。
- `autoSave`:预期频率**低频**。控制本次移动是否在特定时机触发自动存档,仅在地图伤害相关全局系统内部设置。
> 三个配置项在 `config()` 参数中均为可选,未显式设置的项保持当前值不变,`getConfig()` 返回的是已补全默认值的完整配置只读快照。
### 预期体量
接口定义部分(`IHeroMover`、`IHeroMoverConfig`)约 15-20 行。
`HeroMover` 类的预期代码体量为 200-250 行。分析如下:
- 类定义与私有属性(`noRoute`、`ignoreTerrain`、`autoSave` 各自独立声明),以及 `config` / `getConfig` 实现,约 30 行。
- `onMoveStart` 实现(同步渲染端状态、预检查首步地形等),约 30 行。
- `onMoveEnd` 实现(恢复状态、通知渲染端结束、清除自动寻路状态),约 20 行。
- `onStepStart` 实现(地形检测、自动存档判断、渲染端动画调用等),约 55 行。
- `onStepEnd` 实现(根据移动代码分发处理、返回新坐标、路线记录、触发器等),约 65 行。
- 辅助方法(坐标计算、地形检测等),约 30 行。
- `HeroMoveCode` 枚举定义,约 10 行。
### 可能风险
- `IHeroLocation` 接口已定义 `readonly mover: IObjectMover<this>`。由于 `IHeroMover extends IObjectMover<IHeroLocation>`,若 `HeroMover` 的构造在 `IHeroLocation` 内部完成(传入 `this`),类型可自然满足且无循环依赖问题。但需确保 `IHeroLocation` 的实现类确实在其构造过程中创建并持有 `HeroMover` 实例。
# 实现思路
## 1. 在 `types.ts` 中新增相关接口与枚举
新增 `IHeroMoverConfig` 接口,包含 `noRoute`、`ignoreTerrain`、`autoSave` 三个可选布尔成员。
新增 `IHeroMover` 接口,继承 `IObjectMover<IHeroLocation>`,新增 `config(config)` 和 `getConfig()` 两个方法。
新增 `HeroMoveCode` 枚举,公开导出,包含正常移动、停止、撞击、不可移动等代码,供 Hook 用户判断每步移动结果。
## 2. 完成 `HeroMover` 类
在 `packages-user/data-state/src/hero/mover.ts` 中实现 `HeroMover extends ObjectMover<IHeroLocation>`。
构造函数接收两个参数:
- `tile: IHeroLocation` — 勇士位置对象,同时满足 `IObjectMovable`,作为基类移动操作的绑定目标。
- `faceHandler: IFaceHandler<FaceDirection>` — 朝向处理器,由 `IHeroLocation` 实现类在创建 `HeroMover` 时传入(本样式中为 `Dir4FaceHandler`)。
`config(config)`:将传入配置中已显式提供的字段分别赋值到对应的私有属性 `noRoute`、`ignoreTerrain`、`autoSave`,未传入的字段保持原值,返回 `this` 以支持链式调用。
`getConfig()`:从三个私有属性分别读取当前值,组装为 `Readonly<IHeroMoverConfig>` 返回,默认值均为 `false`。
`onMoveStart`:移动开始时读取当前配置,通过 `IHeroLocation.mover`(渲染端 `HeroMoveController` 实例)启动渲染端移动动画。若 `ignoreTerrain` 为 `false`,预检查第一步是否能通过。
`onMoveEnd`:移动结束时通知渲染端移动结束,清除自动寻路相关状态。
`onStepStart`:根据 `ignoreTerrain` 判断是否进行地形检测,若不可通过则返回对应的 `HeroMoveCode`。处理 `autoSave` 的存档判断逻辑。启动渲染端单步移动动画。根据 `noRoute` 判断是否记录路线。
> **注意**:地形检测、路线记录、触发器、中毒伤害等具体逻辑依赖的新接口尚不存在,`core.xxx` 调用在此重构中摒弃。当前在这些位置以 `// TODO:` 标记占位,等待后续接口补充。
`onStepEnd`:根据 `onStepStart` 返回的 `HeroMoveCode` 执行对应逻辑:
- `Step`:返回目标坐标 `ITileLocator` 让基类更新位置,执行路线记录与后置逻辑。
- `Hit`:停止移动,触发目标格触发器。
- `CannotMove`:停止移动,不触发触发器。
- `Stop`:停止移动,清除自动寻路。
## 3. 在 `hero/index.ts` 中导出
在 `packages-user/data-state/src/hero/index.ts` 中新增 `export * from './mover'`。
# 涉及文件
## 需要引用的文件
- `@motajs/common`:引用 `ITileLocator` 等基础接口。
- `@user/data-common`:引用 `IObjectMover`、`IObjectMovable`、`ObjectMover`、`ObjectMoveStep`、`ObjectMoveStepType`、`FaceDirection`、`IFaceHandler`、`IFaceDescriptor` 等类型。
- `@user/data-base`:引用 `IHeroLocation`、`IHeroMoveController` 等勇士相关接口。
- `types.ts`(同包顶层):引用 `IHeroMover`、`IHeroMoverConfig`、`HeroMoveCode`(类实现接口时自引用)。
## 需要修改的文件
### `packages-user/data-state/src/types.ts`
- [ ] 新增 `IHeroMoverConfig` 接口:定义移动配置对象的三个可选布尔成员 `noRoute`、`ignoreTerrain`、`autoSave`。
- [ ] 新增 `IHeroMover` 接口:继承 `IObjectMover<IHeroLocation>`,新增 `config()` 和 `getConfig()` 方法。
- [ ] 新增 `HeroMoveCode` 枚举:定义勇士移动步骤返回码,公开导出供 Hook 使用。
### `packages-user/data-state/src/hero/mover.ts`(新建)
- [ ] 实现 `HeroMover` 类:继承 `ObjectMover<IHeroLocation>`,实现 `config`、`getConfig` 及基类四个抽象方法。
### `packages-user/data-state/src/hero/index.ts`
- [ ] 新增 `export * from './mover'`:导出新模块。