refactor: 加载系统重构

This commit is contained in:
unanmed 2026-09-20 20:53:46 +08:00
parent d12790280a
commit cc434a6c66
27 changed files with 409 additions and 266 deletions

View File

@ -1,3 +0,0 @@
export * from './loader';
export * from './processor';
export * from './types';

View File

@ -1,63 +0,0 @@
import {
ILoadManager,
LoadDataType,
ILoadTask,
ILoadTaskProcessor
} from '@motajs/loader';
import { CustomLoadFunc, IMotaDataLoader } from './types';
import { LoadJSONProcessor } from './processor';
interface LoadTaskStore<T extends LoadDataType = LoadDataType, R = any> {
/** 加载任务对象 */
readonly task: ILoadTask<T, R>;
/** 当 `onLoaded` 兑现后兑现的 `Promise` */
readonly loadPromise: Promise<R>;
/** 兑现 `loadPromise` */
readonly loadResolve: (data: R) => void;
/** 当加载任务完成时执行的函数 */
readonly onLoaded: CustomLoadFunc<R>;
}
export class MotaDataLoader implements IMotaDataLoader {
/** 当前已添加的加载任务 */
private readonly tasks: Set<LoadTaskStore> = new Set();
readonly jsonProcessor: ILoadTaskProcessor<LoadDataType.JSON, any>;
constructor(readonly progress: ILoadManager) {
this.jsonProcessor = new LoadJSONProcessor();
}
//#region 对外接口
initSystemLoadTask(): void {}
addCustomLoadTask<R>(
task: ILoadTask<LoadDataType, R>,
onLoaded: CustomLoadFunc<R>
): Promise<R> {
this.progress.addTask(task);
const { promise, resolve } = Promise.withResolvers<R>();
const store: LoadTaskStore<LoadDataType, R> = {
task,
onLoaded,
loadPromise: promise,
loadResolve: resolve
};
this.tasks.add(store);
return promise;
}
async load(): Promise<void> {
const tasks = [...this.tasks].map(async task => {
task.task.start();
const data = await task.task.loaded();
await task.onLoaded(data);
task.loadResolve(data);
return data;
});
await Promise.all(tasks);
}
//#endregion
}

View File

@ -1,66 +0,0 @@
import { IMotaAudioContext } from '@motajs/audio';
import { ILoadTask, ILoadTaskProcessor, LoadDataType } from '@motajs/loader';
import JSZip from 'jszip';
export class LoadImageProcessor implements ILoadTaskProcessor<
LoadDataType.Blob,
ImageBitmap
> {
process(response: Blob): Promise<ImageBitmap> {
return createImageBitmap(response);
}
}
export class LoadAudioProcessor implements ILoadTaskProcessor<
LoadDataType.Uint8Array,
AudioBuffer | null
> {
constructor(private readonly ac: IMotaAudioContext) {}
process(response: Uint8Array<ArrayBuffer>): Promise<AudioBuffer | null> {
return this.ac.decodeToAudioBuffer(response);
}
}
export class LoadFontProcessor implements ILoadTaskProcessor<
LoadDataType.ArrayBuffer,
FontFace
> {
process(
response: ArrayBuffer,
task: ILoadTask<LoadDataType.ArrayBuffer, FontFace>
): Promise<FontFace> {
const font = new FontFace(task.identifier, response);
if (font.status === 'loaded') return Promise.resolve(font);
else return font.load();
}
}
export class LoadZipProcessor implements ILoadTaskProcessor<
LoadDataType.ArrayBuffer,
JSZip
> {
async process(response: ArrayBuffer): Promise<JSZip> {
const zip = new JSZip();
await zip.loadAsync(response);
return zip;
}
}
export class LoadTextProcessor implements ILoadTaskProcessor<
LoadDataType.Text,
string
> {
process(response: string): Promise<string> {
return Promise.resolve(response);
}
}
export class LoadJSONProcessor<T> implements ILoadTaskProcessor<
LoadDataType.JSON,
T
> {
process(response: any): Promise<T> {
return Promise.resolve(response);
}
}

View File

@ -1,38 +0,0 @@
import {
ILoadManager,
ILoadTask,
ILoadTaskProcessor,
LoadDataType
} from '@motajs/loader';
export type CustomLoadFunc<R> = (data: R) => Promise<void>;
export interface IMotaDataLoader {
/** 加载进度对象 */
readonly progress: ILoadManager;
/** json 处理器 */
readonly jsonProcessor: ILoadTaskProcessor<LoadDataType.JSON, any>;
/**
* 初始化系统加载任务
*/
initSystemLoadTask(): void;
/**
* 添加自定义加载任务
* @param task 自定义加载任务
* @param onLoad 当任务加载完成时执行
* @returns 一个 `Promise`,当添加的任务加载完毕,且 `onLoad` 返回的 `Promise` 兑现后兑现
*/
addCustomLoadTask<R>(
task: ILoadTask<LoadDataType, R>,
onLoad: CustomLoadFunc<R>
): Promise<R>;
/**
* 开始所有加载任务的加载工作
* @returns 一个 `Promise`,当所有加载任务加载完成后兑现
*/
load(): Promise<void>;
}

View File

@ -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

View File

@ -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);

View File

@ -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();
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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

View File

@ -65,6 +65,9 @@
"63": "Cannot create game map '$2' from raw data since the '$1' container is missing, null, or not an object.",
"64": "Cannot create game map '$2' from raw data since the '$1' value has an invalid type or range.",
"65": "Cannot start pathfinding movement because the mover is already active.",
"66": "Load url '$1' error, since specified a prefixed jsonc parser, but no(or more than one) prefix split string '$2' found in response string.",
"67": "Load url '$1' error since the raw json string splitted by prefix split string cannot be parsed to a valid JavaScript object. Parse error info: $2",
"68": "Extra config identifier '$1' has conflicts. More than one extra config use this identifier, and the final load result is unpredictable since network decides which one to be loaded fast.",
"2001": "Replay($1): Replay parameter count mismatch: expected $2, got $3.",
"2002": "Replay($1): Replay parameter type mismatch: index $2, expected $3, got $4.",
"2003": "Replay(move): Expected hero to be stopped before execute moving step.",

View File

@ -1,4 +1,4 @@
export * from './progress';
export * from './manager';
export * from './starter';
export * from './stream';
export * from './task';

View File

@ -49,6 +49,11 @@ export class LoadManager
}
async *load(): AsyncIterable<number> {
for (const task of this.addedTasks) {
if (!task.contentLoaded) {
task.start();
}
}
while (true) {
if (this.loadedTasks.size === this.addedTasks.size) {
return;

View File

@ -1 +1,2 @@
// --- SYSTEM PREFIX END --- //
{}

View File

@ -1,14 +1,22 @@
// Type: packages-user/data-state/src/types.ts ICoreStateCoreConfig
// --- SYSTEM PREFIX END --- //
{
"name": "template",
"title": "魔塔样板",
"version": "Ver 2.C",
"content": {
"mapsDir": "content/maps",
"mapListDir": "content/maps/.list.jsonc",
"enemyDir": "content/enemy.jsonc",
"itemDir": "content/item.jsonc",
"tileDir": "content/tile.jsonc"
"mapsDir": "maps",
"mapListDir": "maps/.list.jsonc",
"enemyDir": "enemy.jsonc",
"itemDir": "item.jsonc",
"itemEffectDir": "item.js",
"tileDir": "tile.jsonc",
"defaults": {
"enemyDefault": "defaults/enemy.jsonc",
"itemDefault": "defaults/item.jsonc",
"mapDefault": "defaults/map.jsonc",
"tileDefault": "defaults/tile.jsonc"
}
},
"init": {
"startEvent": "game-start",

View File

@ -1,6 +1,7 @@
// Type: packages-user/data-common/src/store/types IEnemyRawData
// "@required" means the property is required.
// For objects, system will compare every properties.
// --- SYSTEM PREFIX END --- //
{
"num": "@required",
"attribute": {

View File

@ -1,6 +1,7 @@
// Type: packages-user/data-common/src/store/types IItemRawData
// "@required" means the property is required.
// For objects, system will compare every properties.
// --- SYSTEM PREFIX END --- //
{
"num": "@required",
"category": 0,

View File

@ -1,6 +1,7 @@
// Type: packages-user/data-common/src/store/types IMapRawData
// "@required" means the property is required.
// For objects, system will compare every properties.
// --- SYSTEM PREFIX END --- //
{
"floorId": "@required",
"width": 13,

View File

@ -1,6 +1,7 @@
// Type: packages-user/data-common/src/store/types ITileRawData
// "@required" means the property is required.
// For objects, system will compare every properties.
// --- SYSTEM PREFIX END --- //
{
"num": "@required",
"id": "@required",

View File

@ -1,4 +1,5 @@
// Type: packages-user/data-common/src/store/types IEnemyRawData
// --- SYSTEM PREFIX END --- //
[
{
"num": 100,

View File

@ -1,5 +1,6 @@
// Type: packages-user/data-common/src/store/types IItemRawData
// ItemEffect: ./item.ts
// --- SYSTEM PREFIX END --- //
[
{
"num": 200,

View File

@ -1,2 +1,3 @@
// Type: string[], without `.jsonc` suffix
// --- SYSTEM PREFIX END --- //
["MT0"]

View File

@ -1,4 +1,5 @@
// Type: packages-user/data-common/src/store/types IMapRawData
// --- SYSTEM PREFIX END --- //
{
"floorId": "MT0",
"width": 13,

View File

@ -1,2 +1,3 @@
// Type: type define required
// --- SYSTEM PREFIX END --- //
{}

View File

@ -1,4 +1,5 @@
// Type: packages-user/data-common/src/store/types ITileRawData
// --- SYSTEM PREFIX END --- //
[
{
"num": 1,