mirror of
https://github.com/motajs/template.git
synced 2026-11-13 23:04:32 +08:00
refactor: 加载系统重构
This commit is contained in:
1 parent
d12790280a
commit
cc434a6c66
27 files changed
+409
-266
No files matched your search
@@ -0,0 +1,93 @@
|
||||
import { IHeroAttr } from '@user/data-common';
|
||||
|
||||
//#region 数据端主配置
|
||||
|
||||
export interface ICoreStateContentDefaultConfig {
|
||||
/** 怪物默认对象路径 */
|
||||
readonly enemyDefault: string;
|
||||
/** 道具默认对象路径 */
|
||||
readonly itemDefault: string;
|
||||
/** 地图默认对象路径 */
|
||||
readonly mapDefault: string;
|
||||
/** 图块默认对象路径 */
|
||||
readonly tileDefault: string;
|
||||
}
|
||||
|
||||
export interface ICoreStateContentConfig {
|
||||
/** 配置的默认对象配置路径 */
|
||||
readonly defaults: ICoreStateContentDefaultConfig;
|
||||
/** 地图文件夹路径 */
|
||||
readonly mapsDir: string;
|
||||
/** 需要加载的地图列表文件的文件路径 */
|
||||
readonly mapListDir: string;
|
||||
/** 怪物数据文件路径 */
|
||||
readonly enemyDir: string;
|
||||
/** 道具数据文件路径 */
|
||||
readonly itemDir: string;
|
||||
/** 道具效果文件路径 */
|
||||
readonly itemEffectDir: string;
|
||||
/** 图库数据文件路径 */
|
||||
readonly tileDir: string;
|
||||
}
|
||||
|
||||
export interface ICoreStateHeroInitConfig<T> {
|
||||
/** 勇士的初始横坐标 */
|
||||
readonly x: number;
|
||||
/** 勇士的初始纵坐标 */
|
||||
readonly y: number;
|
||||
/** 勇士初始所在楼层 */
|
||||
readonly floorId: string;
|
||||
/** 勇士的初始装备栏 */
|
||||
readonly equipSlots: string[];
|
||||
/** 勇士的初始属性 */
|
||||
readonly attribute: Readonly<T>;
|
||||
}
|
||||
|
||||
export interface ICoreStateInitConfig {
|
||||
/** 初始事件 id,游戏开始时将在执行完毕此事件后正式开始 */
|
||||
readonly startEvent: string;
|
||||
/** 勇士初始状态,包含起始位置、初始属性等 */
|
||||
readonly hero: ICoreStateHeroInitConfig<IHeroAttr>;
|
||||
}
|
||||
|
||||
export interface ICoreStateFlagConfig {
|
||||
/** 楼层传送是否要求在楼梯边才能使用 */
|
||||
readonly flyNearStair: boolean;
|
||||
/** 楼层传送是否为平面塔模式,即传送时会传送至上一次离开此位置的位置而非楼梯口 */
|
||||
readonly flyRecordLocation: boolean;
|
||||
/** 道具在首次拾取时是否显示其文字说明 */
|
||||
readonly itemHint: boolean;
|
||||
/** 是否启用负伤机制,开启后怪物总伤害可以变为负值 */
|
||||
readonly enableNegativeDamage: boolean;
|
||||
/** 是否限制夹击最高伤害为两夹击怪物中伤害最高者 */
|
||||
readonly betweenDamageMax: boolean;
|
||||
/** 是否启用轻按机制,即可以按下空格来拾取玩家四周的物品 */
|
||||
readonly enablePick: boolean;
|
||||
/** 若在寻路时需要穿过楼梯,是否允许穿越 */
|
||||
readonly ignoreChangeFLoor: boolean;
|
||||
/** 若继续前进会导致勇士受到地图伤害而死亡,是否允许前进 */
|
||||
readonly allowDeadZone: boolean;
|
||||
/** 是否允许点击瞬移 */
|
||||
readonly enableTeleport: boolean;
|
||||
/** 是否开启录像折叠,这可以使走路的录像大幅减少,但可能导致某些机制出错,可查看文档详细了解 */
|
||||
readonly enableRouteFolding: boolean;
|
||||
/** 是否开启瞬移合并机制,开启后,连续瞬移会在录像中合并为单次瞬移,但可能导致某些机制出错 */
|
||||
readonly enableTeleportMerge: boolean;
|
||||
}
|
||||
|
||||
export interface ICoreStateCoreConfig {
|
||||
/** 游戏的唯一标识符 */
|
||||
readonly name: string;
|
||||
/** 呈现给玩家的游戏名称 */
|
||||
readonly title: string;
|
||||
/** 游戏版本号,仅呈现作用,对游戏本身没有任何影响 */
|
||||
readonly version: string;
|
||||
/** 游戏数据配置 */
|
||||
readonly content: ICoreStateContentConfig;
|
||||
/** 游戏的初始状态数据 */
|
||||
readonly init: ICoreStateInitConfig;
|
||||
/** 游戏的全局设置 */
|
||||
readonly flag: ICoreStateFlagConfig;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
@@ -1,5 +1,4 @@
|
||||
import { logger } from '@motajs/common';
|
||||
import { ILoadManager, LoadManager } from '@motajs/loader';
|
||||
import {
|
||||
IRoleFaceBinder,
|
||||
IFaceManager,
|
||||
@@ -33,8 +32,6 @@ import {
|
||||
IHeroState,
|
||||
IFlagSystem,
|
||||
FlagSystem,
|
||||
IMotaDataLoader,
|
||||
MotaDataLoader,
|
||||
IMapState,
|
||||
MapState
|
||||
} from '@user/data-base';
|
||||
@@ -70,6 +67,10 @@ import {
|
||||
ReplayUnequip,
|
||||
ReplayUseItem
|
||||
} from './replay';
|
||||
import { IMotaDataLoader } from './loader/types';
|
||||
import { MotaDataLoader } from './loader/loader';
|
||||
import { ILoadManager, LoadManager } from '@motajs/loader';
|
||||
import { DefaultDataLoaderHook } from './loader/hook';
|
||||
|
||||
export class CoreState implements ICoreState {
|
||||
// Layer 0 公共层,最底层的接口,不会依赖任何其他内容,一般是工具性接口及不需要存档的数据
|
||||
@@ -93,8 +94,8 @@ export class CoreState implements ICoreState {
|
||||
readonly pathfinding: IPathfindingSystem;
|
||||
|
||||
// Layer 3 用户层,也就是最顶层的内容,一般仅用于初始化以及仅供渲染端调用的顶层模块
|
||||
readonly loadProgress: ILoadManager;
|
||||
readonly dataLoader: IMotaDataLoader;
|
||||
readonly loader: IMotaDataLoader;
|
||||
readonly loadManager: ILoadManager;
|
||||
|
||||
/** 可存档对象映射 */
|
||||
private readonly saveables: Map<string, ISaveableContent<any>> = new Map();
|
||||
@@ -143,9 +144,6 @@ export class CoreState implements ICoreState {
|
||||
const heroState = new HeroState(this, dir8, heroAttribute);
|
||||
this.hero = heroState;
|
||||
|
||||
this.loadProgress = new LoadManager();
|
||||
this.dataLoader = new MotaDataLoader(this.loadProgress);
|
||||
|
||||
// 怪物管理器
|
||||
const comparer = new MainEnemyComparer();
|
||||
const enemyManager = new EnemyManager(new EnemyLegacyBridge());
|
||||
@@ -201,6 +199,15 @@ export class CoreState implements ICoreState {
|
||||
|
||||
//#region L3 初始化
|
||||
|
||||
// 加载
|
||||
this.loadManager = new LoadManager();
|
||||
this.loader = new MotaDataLoader(
|
||||
this.loadManager,
|
||||
config.coreURL,
|
||||
config.loadStarter
|
||||
);
|
||||
this.loader.addHook(new DefaultDataLoaderHook());
|
||||
|
||||
// 存档内容
|
||||
this.addSaveableContent('@system/hero', this.hero);
|
||||
this.addSaveableContent('@system/flags', this.flags);
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { ICoreStateCoreConfig } from '../common';
|
||||
import { IMotaDataLoader, IMotaDataLoaderHooks } from './types';
|
||||
|
||||
export class DefaultDataLoaderHook implements IMotaDataLoaderHooks {
|
||||
awake(): void {}
|
||||
|
||||
destroy(): void {}
|
||||
|
||||
onCoreConfigLoaded(
|
||||
coreConfig: ICoreStateCoreConfig,
|
||||
loader: IMotaDataLoader
|
||||
): Promise<void> {
|
||||
const content = coreConfig.content;
|
||||
loader.addExtraConfig('enemy', content.enemyDir);
|
||||
loader.addExtraConfig('item', content.itemDir);
|
||||
loader.addExtraConfig('map-list', content.mapListDir);
|
||||
loader.addExtraConfig('tile', content.tileDir);
|
||||
const defaults = content.defaults;
|
||||
loader.addExtraConfig('enemy-default', defaults.enemyDefault);
|
||||
loader.addExtraConfig('item-default', defaults.itemDefault);
|
||||
loader.addExtraConfig('map-default', defaults.mapDefault);
|
||||
loader.addExtraConfig('tile-default', defaults.tileDefault);
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
Whitespace-only changes.
@@ -0,0 +1,29 @@
|
||||
import { logger } from '@motajs/common';
|
||||
import { ILoadTask, ILoadTaskProcessor, LoadDataType } from '@motajs/loader';
|
||||
|
||||
export class PrefixedJSONCProcessor<T> implements ILoadTaskProcessor<
|
||||
LoadDataType.Text,
|
||||
T
|
||||
> {
|
||||
constructor(readonly prefixSplit: string) {}
|
||||
|
||||
process(
|
||||
response: string,
|
||||
task: ILoadTask<LoadDataType.Text, T>
|
||||
): Promise<T> {
|
||||
const splitted = response.split(this.prefixSplit);
|
||||
if (splitted.length !== 2) {
|
||||
logger.error(66, task.url, this.prefixSplit);
|
||||
return Promise.resolve({} as T);
|
||||
}
|
||||
|
||||
const raw = splitted[1];
|
||||
try {
|
||||
const obj = JSON.parse(raw);
|
||||
return Promise.resolve(obj as T);
|
||||
} catch (e) {
|
||||
logger.error(67, task.url, String(e));
|
||||
return Promise.resolve({} as T);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
import {
|
||||
ILoadManager,
|
||||
ILoadTask,
|
||||
ILoadTaskStarter,
|
||||
LoadDataType,
|
||||
LoadTask
|
||||
} from '@motajs/loader';
|
||||
import { IMotaDataLoader, IMotaDataLoaderHooks } from './types';
|
||||
import { PrefixedJSONCProcessor } from './jsoncProcessor';
|
||||
import { ICoreStateCoreConfig } from '../common';
|
||||
import {
|
||||
Hookable,
|
||||
HookController,
|
||||
IHookController,
|
||||
logger
|
||||
} from '@motajs/common';
|
||||
|
||||
export class MotaDataLoader
|
||||
extends Hookable<IMotaDataLoaderHooks>
|
||||
implements IMotaDataLoader
|
||||
{
|
||||
/** 加载完毕时兑现的 `Promise` */
|
||||
private loadedPromise: Promise<void>;
|
||||
/** 调用以使 `loadedPromise` 兑现 */
|
||||
private loadResolve: () => void;
|
||||
/** 当前是否正在加载 */
|
||||
private loading: boolean = false;
|
||||
/** 当前是否已经加载完毕 */
|
||||
private dataLoaded: boolean = false;
|
||||
|
||||
/** 额外配置对象的路径 */
|
||||
private extraConfigs: Map<string, string> = new Map();
|
||||
/** 配置文件列表 */
|
||||
private configMap: Map<string, any> = new Map();
|
||||
|
||||
/** 自定义加载任务 */
|
||||
private customTask: Set<ILoadTask<LoadDataType, unknown>> = new Set();
|
||||
|
||||
constructor(
|
||||
readonly manager: ILoadManager,
|
||||
readonly coreDir: string,
|
||||
readonly starter: ILoadTaskStarter
|
||||
) {
|
||||
super();
|
||||
const { promise, resolve } = Promise.withResolvers<void>();
|
||||
this.loadedPromise = promise;
|
||||
this.loadResolve = resolve;
|
||||
}
|
||||
|
||||
protected createController(
|
||||
hook: Partial<IMotaDataLoaderHooks>
|
||||
): IHookController<IMotaDataLoaderHooks> {
|
||||
return new HookController(this, hook);
|
||||
}
|
||||
|
||||
addExtraConfig(identifier: string, url: string): void {
|
||||
this.extraConfigs.set(identifier, url);
|
||||
}
|
||||
|
||||
addCustomTask<T>(task: LoadTask<LoadDataType, T>): Promise<T> {
|
||||
this.customTask.add(task);
|
||||
return task.loaded();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析加载路径,根据运行环境确认应当使用什么路径格式
|
||||
* @param url 加载路径
|
||||
*/
|
||||
private resolveURL(url: string) {
|
||||
if (import.meta.env.DEV) {
|
||||
// 开发时 content 文件都在 src 文件夹下,因此前置 src
|
||||
return `${import.meta.env.BASE_URL}src/${url}`;
|
||||
} else {
|
||||
// 而构建后就会把文件移动至游戏根目录下,因此不需要前置 src
|
||||
return `${import.meta.env.BASE_URL}${url}`;
|
||||
}
|
||||
}
|
||||
|
||||
async *start(): AsyncIterable<number> {
|
||||
if (this.loading || this.dataLoaded) return;
|
||||
|
||||
// 首先加载核心配置
|
||||
const processor = new PrefixedJSONCProcessor<ICoreStateCoreConfig>(
|
||||
'// --- SYSTEM PREFIX END --- //'
|
||||
);
|
||||
const coreTask = new LoadTask<LoadDataType.Text, ICoreStateCoreConfig>({
|
||||
identifier: 'data-core-config',
|
||||
url: this.resolveURL(this.coreDir),
|
||||
dataType: LoadDataType.Text
|
||||
});
|
||||
coreTask.setProcessor(processor);
|
||||
coreTask.setStarter(this.starter);
|
||||
this.manager.addTask(coreTask);
|
||||
yield* this.manager.load();
|
||||
|
||||
// 然后加载其他配置文件
|
||||
const coreConfig = await coreTask.loaded();
|
||||
this.configMap.set('core', coreConfig);
|
||||
await Promise.all(
|
||||
this.forEachHook(hook =>
|
||||
hook.onCoreConfigLoaded?.(coreConfig, this)
|
||||
)
|
||||
);
|
||||
|
||||
for (const [id, url] of this.extraConfigs) {
|
||||
const task = new LoadTask({
|
||||
identifier: id,
|
||||
url: this.resolveURL(`content/${url}`),
|
||||
dataType: LoadDataType.Text
|
||||
});
|
||||
task.setProcessor(processor);
|
||||
task.setStarter(this.starter);
|
||||
this.manager.addTask(task);
|
||||
task.loaded().then(obj => {
|
||||
if (this.configMap.has(id)) {
|
||||
logger.error(68, id);
|
||||
return;
|
||||
}
|
||||
this.configMap.set(id, obj);
|
||||
});
|
||||
}
|
||||
|
||||
yield* this.manager.load();
|
||||
|
||||
await Promise.all(
|
||||
this.forEachHook(hook => hook.onExtraConfigLoaded?.(this))
|
||||
);
|
||||
|
||||
for (const task of this.customTask) {
|
||||
this.manager.addTask(task);
|
||||
}
|
||||
|
||||
yield* this.manager.load();
|
||||
|
||||
this.loadResolve();
|
||||
}
|
||||
|
||||
getConfig<T>(identifier: string): T | null {
|
||||
return this.configMap.get(identifier) ?? null;
|
||||
}
|
||||
|
||||
loaded(): Promise<void> {
|
||||
return this.loadedPromise;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { IHookable, IHookBase } from '@motajs/common';
|
||||
import { ILoadManager, LoadDataType, LoadTask } from '@motajs/loader';
|
||||
import { ICoreStateCoreConfig } from '../common';
|
||||
|
||||
export interface IMotaDataLoaderHooks extends IHookBase {
|
||||
/**
|
||||
* 当核心配置对象加载完毕时触发,往往用于添加自定义配置文件
|
||||
* @param coreConfig 核心配置对象
|
||||
* @param loader 数据端加载对象
|
||||
*/
|
||||
onCoreConfigLoaded?(
|
||||
coreConfig: ICoreStateCoreConfig,
|
||||
loader: IMotaDataLoader
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* 当所有的额外配置对象加载完毕后执行,往往用于根据配置文件添加额外加载任务
|
||||
* @param loader 数据端加载对象
|
||||
*/
|
||||
onExtraConfigLoaded?(loader: IMotaDataLoader): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IMotaDataLoader extends IHookable<IMotaDataLoaderHooks> {
|
||||
/** 当前使用的加载管理器 */
|
||||
readonly manager: ILoadManager;
|
||||
|
||||
/**
|
||||
* 开始执行加载
|
||||
*/
|
||||
start(): AsyncIterable<number>;
|
||||
|
||||
/**
|
||||
* 获取当加载执行完毕时兑现的 `Promise`
|
||||
*/
|
||||
loaded(): Promise<void>;
|
||||
|
||||
/**
|
||||
* 添加额外配置文件,仅允许在 `onCoreConfigLoaded` 中调用或在加载前调用,加载中及加载后后调用无效。
|
||||
* 文件必须放在 `src/content` 下,并填写相对于 `src/content` 文件夹的路径,文件包含如下要求:
|
||||
*
|
||||
* - 文件必须为 `jsonc` 格式
|
||||
* - 文件的注释必须全部集中在开头
|
||||
* - 文件中的最后一行注释必须是 `// --- SYSTEM PREFIX END --- //`
|
||||
* @param identifier 加载任务与配置文件的标识符
|
||||
* @param url 额外配置文件路径
|
||||
*/
|
||||
addExtraConfig(identifier: string, url: string): void;
|
||||
|
||||
/**
|
||||
* 添加自定义加载任务,仅允许在 `onCoreConfigLoaded` 和 `onExtraConfigLoaded` 中调用或在加载前调用,
|
||||
* 加载中及加载后后调用无效。所有的自定义加载任务都会在配置文件加载完毕后开始加载。
|
||||
* @param task 自定义加载任务实例
|
||||
* @returns 任务加载完毕后兑现,兑现值为经过加载处理器处理后的值
|
||||
*/
|
||||
addCustomTask<T>(task: LoadTask<LoadDataType, T>): Promise<T>;
|
||||
|
||||
/**
|
||||
* 获取指定配置文件,例如核心配置文件是 `core`,怪物配置文件是 `enemy` 等等
|
||||
* @param identifier 配置文件标识符
|
||||
*/
|
||||
getConfig<T>(identifier: string): T | null;
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import { IMotaDataLoader, IStateBase } from '@user/data-base';
|
||||
import { IStateBase } from '@user/data-base';
|
||||
import { ILoadManager, ILoadTaskStarter } from '@motajs/loader';
|
||||
import { IStateSystem } from '@user/data-system';
|
||||
import { IHeroAttr, ISaveableContent } from '@user/data-common';
|
||||
import { ISaveableContent } from '@user/data-common';
|
||||
import { IMotaDataLoader } from './loader/types';
|
||||
|
||||
//#region 数据端主对象
|
||||
|
||||
@@ -26,10 +27,10 @@ export interface ISaveableExecutor<T> {
|
||||
|
||||
export interface ICoreState
|
||||
extends IStateSystem, ISaveableContent<ReadonlyMap<string, unknown>> {
|
||||
/** 加载进度对象 */
|
||||
readonly loadProgress: ILoadManager;
|
||||
/** 数据端加载对象 */
|
||||
readonly dataLoader: IMotaDataLoader;
|
||||
readonly loader: IMotaDataLoader;
|
||||
/** 加载管理器 */
|
||||
readonly loadManager: ILoadManager;
|
||||
|
||||
/**
|
||||
* 将某个存档执行器绑定至指定的可存档对象,一个可存档对象只能绑定一个执行器,
|
||||
@@ -44,80 +45,3 @@ export interface ICoreState
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 数据端主配置
|
||||
|
||||
export interface ICoreStateContentConfig {
|
||||
/** 地图文件夹路径 */
|
||||
readonly mapsDir: string;
|
||||
/** 需要加载的地图列表文件的文件路径 */
|
||||
readonly mapListDir: string;
|
||||
/** 怪物数据文件路径 */
|
||||
readonly enemyDir: string;
|
||||
/** 道具数据文件路径 */
|
||||
readonly itemDir: string;
|
||||
/** 图库数据文件路径 */
|
||||
readonly tileDir: string;
|
||||
}
|
||||
|
||||
export interface ICoreStateHeroInitConfig<T> {
|
||||
/** 勇士的初始横坐标 */
|
||||
readonly x: number;
|
||||
/** 勇士的初始纵坐标 */
|
||||
readonly y: number;
|
||||
/** 勇士初始所在楼层 */
|
||||
readonly floorId: string;
|
||||
/** 勇士的初始装备栏 */
|
||||
readonly equipSlots: string[];
|
||||
/** 勇士的初始属性 */
|
||||
readonly attribute: Readonly<T>;
|
||||
}
|
||||
|
||||
export interface ICoreStateInitConfig {
|
||||
/** 初始事件 id,游戏开始时将在执行完毕此事件后正式开始 */
|
||||
readonly startEvent: string;
|
||||
/** 勇士初始状态,包含起始位置、初始属性等 */
|
||||
readonly hero: ICoreStateHeroInitConfig<IHeroAttr>;
|
||||
}
|
||||
|
||||
export interface ICoreStateFlagConfig {
|
||||
/** 楼层传送是否要求在楼梯边才能使用 */
|
||||
readonly flyNearStair: boolean;
|
||||
/** 楼层传送是否为平面塔模式,即传送时会传送至上一次离开此位置的位置而非楼梯口 */
|
||||
readonly flyRecordLocation: boolean;
|
||||
/** 道具在首次拾取时是否显示其文字说明 */
|
||||
readonly itemHint: boolean;
|
||||
/** 是否启用负伤机制,开启后怪物总伤害可以变为负值 */
|
||||
readonly enableNegativeDamage: boolean;
|
||||
/** 是否限制夹击最高伤害为两夹击怪物中伤害最高者 */
|
||||
readonly betweenDamageMax: boolean;
|
||||
/** 是否启用轻按机制,即可以按下空格来拾取玩家四周的物品 */
|
||||
readonly enablePick: boolean;
|
||||
/** 若在寻路时需要穿过楼梯,是否允许穿越 */
|
||||
readonly ignoreChangeFLoor: boolean;
|
||||
/** 若继续前进会导致勇士受到地图伤害而死亡,是否允许前进 */
|
||||
readonly allowDeadZone: boolean;
|
||||
/** 是否允许点击瞬移 */
|
||||
readonly enableTeleport: boolean;
|
||||
/** 是否开启录像折叠,这可以使走路的录像大幅减少,但可能导致某些机制出错,可查看文档详细了解 */
|
||||
readonly enableRouteFolding: boolean;
|
||||
/** 是否开启瞬移合并机制,开启后,连续瞬移会在录像中合并为单次瞬移,但可能导致某些机制出错 */
|
||||
readonly enableTeleportMerge: boolean;
|
||||
}
|
||||
|
||||
export interface ICoreStateCoreConfig {
|
||||
/** 游戏的唯一标识符 */
|
||||
readonly name: string;
|
||||
/** 呈现给玩家的游戏名称 */
|
||||
readonly title: string;
|
||||
/** 游戏版本号,仅呈现作用,对游戏本身没有任何影响 */
|
||||
readonly version: string;
|
||||
/** 游戏数据配置 */
|
||||
readonly content: ICoreStateContentConfig;
|
||||
/** 游戏的初始状态数据 */
|
||||
readonly init: ICoreStateInitConfig;
|
||||
/** 游戏的全局设置 */
|
||||
readonly flag: ICoreStateFlagConfig;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
Reference in new issue
Block a user