refactor: Layer2 数据拆分至 data-system

This commit is contained in:
unanmed 2026-05-16 17:06:18 +08:00
parent 80b73938b4
commit 9ada0796b3
20 changed files with 110 additions and 155 deletions

39
dev.md
View File

@ -33,6 +33,13 @@
| `pnpm type` | 对仓库执行类型检查 |
| `pnpm check:circular` | 对仓库执行循环引用检查 |
## 术语统一
- 方法:一般指挂载到接口 `interface` 或类 `class` 上的函数。
- 函数:一般指在文件顶层定义的函数,有时也会指方法,需要根据语境判断。
- 接口:有时指 `interface`,有时也会指方法、成员等内容,需要根据语境判断。
- 成员/属性:一般指接口 `interface`、类 `class`、对象 `object` 上的字段。
## 开发原则
### 模块原则
@ -97,11 +104,29 @@
- **数据端**:可在 `node` 环境中单独运行,可直接用于录像验证,不负责任何渲染逻辑。
- **渲染端**:仅负责向数据端发送消息,不负责任何逻辑运算。
| 包 | 层级 | 说明 |
| ---------------------- | ------------ | ------------------------------------ |
| `@user/data-base` | 数据端系统层 | 负责数据端核心系统 |
| `@user/data-state` | 数据端实现层 | 依赖系统层实现完整的游戏实例 |
| `@user/client-base` | 渲染端系统层 | 负责渲染端核心系统 |
| `@user/client-modules` | 渲染端实现层 | 依赖系统层实现客户端的渲染与用户交互 |
数据端允许调用渲染端代码,但必须使用全局接口 `Mota.r(() => {})` 包裹。除非必要,否则不建议在数据端调用渲染端代码。
### 渲染端
渲染端目前已基本制作完成,分为两层:
| 包 | 层级 | 说明 |
| ---------------------- | ------ | ------------------------------------ |
| `@user/client-base` | 系统层 | 负责渲染端核心系统 |
| `@user/client-modules` | 实现层 | 依赖系统层实现客户端的渲染与用户交互 |
### 数据端
数据端目前正在从旧引擎进行彻底性重构,分为三层:
**Layer 0 — 公共层**:包含公共接口、工具函数等内容,不依赖任何外部游戏逻辑,可被任意高层直接引用。内容较少,与 Layer 1 共同放在 `@user/data-base` 中,不单独开包。
**Layer 1 — 数据层**:包含所有会影响游戏存档与流程的数据内容,如地图、怪物、玩家属性等。本层通过统一接口 `IStateBase` 对外暴露数据访问能力,各类数据模块均以此接口为核心组织。
**Layer 2 — 执行层**:直接引用 Layer 1负责产生影响游戏进程的动作如玩家控制、战斗计算等。本层内容不会进入存档仅通过修改 Layer 1 的数据来影响游戏状态,并通过统一接口 `ICoreState` 对外暴露执行能力。
| 包 | 层级 | 说明 |
| ------------------- | ----------------- | ------------------------------------------------------------------------------- |
| `@user/data-base` | Layer 0 / Layer 1 | 公共层与数据层,定义 `IStateBase` 及各类游戏数据(地图、怪物、玩家属性等) |
| `@user/data-state` | — | 数据端的顶层模块,指导 Layer 2 的执行行为,不直接参与执行 |
| `@user/data-system` | Layer 2 | 执行层,定义 `ICoreState`,依赖数据层实现玩家控制、战斗计算等影响游戏进程的动作 |

View File

@ -1,5 +1,6 @@
export * from './face';
export * from './faceManager';
export * from './indexer';
export * from './mover';
export * from './types';
export * from './utils';

View File

@ -1,4 +1,3 @@
export * from './combat';
export * from './common';
export * from './enemy';
export * from './flag';

View File

@ -4,6 +4,7 @@
"@motajs/types": "workspace:*",
"@motajs/common": "workspace:*",
"@user/data-base": "workspace:*",
"@user/data-system": "workspace:*",
"@user/data-utils": "workspace:*"
}
}

View File

@ -14,10 +14,9 @@ import {
IEnemyContext,
IEnemySpecialModifier,
IEnemyView,
IReadonlyEnemyHandler,
IReadonlyEnemy,
ISpecial
} from '@user/data-base';
IReadonlyEnemyHandler
} from '@user/data-system';
import { IReadonlyEnemy, ISpecial } from '@user/data-base';
import { IHaloValue } from './special';
import { IEnemyAttr } from './types';
import { IHeroAttr } from '../hero';

View File

@ -3,7 +3,7 @@ import {
IDamageCalculator,
IEnemyDamageInfo,
IReadonlyEnemyHandler
} from '@user/data-base';
} from '@user/data-system';
import { IEnemyAttr } from './types';
import { IVampireValue } from './special';
import { IHeroAttr } from '../hero';
@ -90,7 +90,8 @@ export class MainDamageCalculator implements IDamageCalculator<
const extraInfo = this.calculate({
enemy: guard.getComputedEnemy(),
locator,
hero
hero,
data: handler.data
});
turn += extraInfo.turn;
damage += extraInfo.damage;

View File

@ -1,4 +1,4 @@
import { IEnemyFinalEffect, IEnemyHandler } from '@user/data-base';
import { IEnemyFinalEffect, IEnemyHandler } from '@user/data-system';
import { IEnemyAttr } from './types';
import { IHeroAttr } from '../hero';

View File

@ -16,11 +16,13 @@ import {
IMapDamageInfoExtra,
IMapDamageReducer,
IReadonlyEnemyHandler,
IMapDamageView
} from '@user/data-system';
import {
IFaceHandler,
ISpecial,
IMapDamageView,
IReadonlyHeroAttribute,
IReadonlyEnemy,
IFaceHandler,
InternalFaceGroup
} from '@user/data-base';
import { IZoneValue } from './special';

View File

@ -1,4 +1,4 @@
import { IEnemyView } from '@user/data-base';
import { IEnemyView } from '@user/data-system';
export interface IEnemyAttr {
/** 怪物生命值 */

View File

@ -0,0 +1,7 @@
{
"name": "@user/data-system",
"dependencies": {
"@motajs/common": "workspace:*",
"@user/data-base": "workspace:*"
}
}

View File

@ -14,11 +14,16 @@ import {
IMapDamage,
IReadonlyEnemyHandler
} from './types';
import { IReadonlyHeroAttribute } from '../hero';
import { IEnemy, IReadonlyEnemy, ISpecial } from '../enemy';
import {
IReadonlyHeroAttribute,
IEnemy,
IReadonlyEnemy,
ISpecial,
IStateBase,
ILocationIndexer,
MapLocIndexer
} from '@user/data-base';
import { EnemyView } from './enemy';
import { ILocationIndexer, MapLocIndexer } from '../common/indexer';
import { IStateBase } from '../types';
export class EnemyContext<TEnemy, THero> implements IEnemyContext<
TEnemy,

View File

@ -11,9 +11,12 @@ import {
IReadonlyEnemyHandler,
IEnemyView
} from './types';
import { IHeroAttribute, IReadonlyHeroAttribute } from '../hero';
import { IReadonlyEnemy } from '../enemy';
import { IStateBase } from '../types';
import {
IHeroAttribute,
IReadonlyHeroAttribute,
IReadonlyEnemy,
IStateBase
} from '@user/data-base';
interface ICriticalSearchResult {
/** 此临界点的属性值 */

View File

@ -1,4 +1,4 @@
import { IEnemy, IReadonlyEnemy } from '../enemy';
import { IEnemy, IReadonlyEnemy } from '@user/data-base';
import { IEnemyView, IEnemyContext } from './types';
export class EnemyView<TAttr> implements IEnemyView<TAttr> {

View File

@ -9,8 +9,7 @@ import {
IMapDamageReducer,
IMapDamageView
} from './types';
import { ILocationHelper } from '../common/indexer';
import { IStateBase } from '../types';
import { ILocationHelper, IStateBase } from '@user/data-base';
interface IPointInfo {
/** 该点所有的地图伤害 */

View File

@ -1,8 +1,13 @@
import { ITileLocator, IRange } from '@motajs/common';
import { IEnemy, IReadonlyEnemy, ISpecial } from '../enemy';
import { IReadonlyHeroAttribute, IHeroAttribute } from '../hero';
import { ILocationHelper } from '../common/indexer';
import { IStateBase } from '../types';
import {
IEnemy,
IReadonlyEnemy,
ISpecial,
IReadonlyHeroAttribute,
IHeroAttribute,
IStateBase,
ILocationHelper
} from '@user/data-base';
//#region 辅助接口

View File

@ -0,0 +1 @@
export * from './combat';

View File

@ -1,22 +1,3 @@
/**
*
* @param arr
* @param delta
*/
export function slide<T>(arr: T[], delta: number): T[] {
if (delta === 0) return arr;
delta %= arr.length;
if (delta > 0) {
arr.unshift(...arr.splice(arr.length - delta, delta));
return arr;
}
if (delta < 0) {
arr.push(...arr.splice(0, -delta));
return arr;
}
return arr;
}
const backDirMap: Record<Dir2, Dir2> = {
up: 'down',
down: 'up',
@ -38,23 +19,8 @@ export function has<T>(v: T): v is NonNullable<T> {
return v !== null && v !== void 0;
}
export function maxGameScale(n: number = 0) {
const index = core.domStyle.availableScale.indexOf(core.domStyle.scale);
core.control.setDisplayScale(
core.domStyle.availableScale.length - 1 - index - n
);
if (!core.isPlaying() && core.flags.enableHDCanvas) {
// @ts-ignore
core.domStyle.ratio = Math.max(
window.devicePixelRatio || 1,
core.domStyle.scale
);
core.resize();
}
}
export function ensureArray<T>(arr: T): T extends any[] ? T : T[] {
// @ts-ignore
// @ts-expect-error 需要弃用
return arr instanceof Array ? arr : [arr];
}
@ -70,23 +36,6 @@ export function manhattan(x1: number, y1: number, x2: number, y2: number) {
return Math.abs(x1 - x2) + Math.abs(y1 - y2);
}
/**
* v2
*/
export function checkV2(x?: number, y?: number) {
return (
has(x) &&
has(y) &&
!(
core.bigmap.v2 &&
(x < core.bigmap.posX - core.bigmap.extend ||
x > core.bigmap.posX + core._WIDTH_ + core.bigmap.extend ||
y < core.bigmap.posY - core.bigmap.extend ||
y > core.bigmap.posY + core._HEIGHT_ + core.bigmap.extend)
)
);
}
export function formatDamage(damage: number): DamageString {
let dam = '';
let color = '';
@ -105,64 +54,6 @@ export function formatDamage(damage: number): DamageString {
return { damage: dam, color: color as Color };
}
/**
*
* @param arr
*/
export function equal(arr: number[]): boolean;
/**
*
* @param arr
* @param key
*/
export function equal<T>(arr: T[], key: keyof T): boolean;
export function equal(arr: any, key?: any) {
if (has(key)) {
for (let i = 1; i < arr.length; i++) {
if (arr[i][key] !== arr[0][key]) return false;
}
return true;
} else {
for (let i = 1; i < arr.length; i++) {
if (arr[i] !== arr[0]) return false;
}
return true;
}
}
/**
*
* @param arr
*/
export function boundary(arr: number[]): [number, number];
/**
*
* @param arr
* @param key
*/
export function boundary<T>(arr: T[], key: keyof T): [number, number];
export function boundary(arr: any, key?: any) {
if (has(key)) {
let min = arr[0][key];
let max = arr[0][key];
for (let i = 1; i < arr.length; i++) {
const ele = arr[i][key];
if (ele < min) min = ele;
if (ele > max) max = ele;
}
return [min, max];
} else {
let min = arr[0];
let max = arr[0];
for (let i = 1; i < arr.length; i++) {
const ele = arr[i];
if (ele < min) min = ele;
if (ele > max) max = ele;
}
return [min, max];
}
}
/**
*
* @param from

View File

@ -315,10 +315,22 @@ importers:
'@user/data-base':
specifier: workspace:*
version: link:../data-base
'@user/data-system':
specifier: workspace:*
version: link:../data-system
'@user/data-utils':
specifier: workspace:*
version: link:../data-utils
packages-user/data-system:
dependencies:
'@motajs/common':
specifier: workspace:*
version: link:../../packages/common
'@user/data-base':
specifier: workspace:*
version: link:../data-base
packages-user/data-utils:
dependencies:
'@user/data-base':
@ -409,8 +421,6 @@ importers:
specifier: workspace:*
version: link:../data-utils
packages-user/types: {}
packages/animate: {}
packages/audio:
@ -447,12 +457,6 @@ importers:
specifier: workspace:*
version: link:../common
packages/legacy-data:
dependencies:
'@motajs/common':
specifier: workspace:*
version: link:../common
packages/legacy-system:
dependencies:
'@motajs/system':

View File

@ -38,7 +38,23 @@
```md
# 需求综述
描述清楚需求的内容与目的。
描述清楚需求的内容、动机与目的。
# 接口设计与预期
这是相当重要的一章。需要按接口逐一列出其方法与成员,分析每个成员和方法的预期使用频率(高 / 中 / 低)并说明判断依据;对于中频和高频成员,还需列出至少一个典型使用场景。
**命名长度原则**:频率越高,成员名应越短。高频成员以一个单词为宜,最多不超过两个单词;中频成员不超过三个单词;低频成员可以稍长,但不宜过长。
**频率的定义**:此处频率指**用户编写此调用的可能性或频率**,而非运行时的调用频率。例如某成员每秒被调用千次,但整个游戏中只在一处出现过,仍属低频。
示例如下:
## IObjectMover
- `IObjectMover.forward()`:预期频率**高频**。向前移动一格是地图行走、动画演出等场景的核心需求,在逻辑与演出中都会频繁出现,故为高频。典型使用场景:演出中玩家或 NPC 沿某方向连续移动。
- `IObjectMover.speed()`:预期频率**中频**。移动中修改移速有一定使用场景,但远不及 `forward`、`step` 等移动接口的频率通常只在特殊演出或逻辑中出现故为中频。典型使用场景NPC 逃离怪物时先定在原地,随后逐渐加速逃跑。
- `IObjectMover.stepFace()`:预期频率**低频**。移动方向与朝向不同的常见场景(后退)已由 `backward` 覆盖,只有极特殊情况才需要此接口(如角色朝向固定但沿垂直方向平移),相当罕见,故为低频。
# 实现思路
@ -52,10 +68,6 @@
...
# 接口预期
这里列出每个接口,然后分析它们的预期使用频率(高中低),对于中频率和高频率,列出至少一个可能的使用场景。需要注意,使用频率越高,其接口名长度理应越短,对于高频接口,应该尽量不超过两个单词,以一个单词为宜;对于中频接口,应该尽量不超过三个单词;低频接口长度可以长一些,但不宜过长。
# 涉及文件
## 需要引用的文件