mirror of
https://github.com/motajs/template.git
synced 2026-09-18 05:00:17 +08:00
refactor: 存档系统挪至渲染端
This commit is contained in:
parent
89434537c8
commit
94ceb50cfe
@ -6,3 +6,6 @@ export function create() {
|
||||
|
||||
export * from './load';
|
||||
export * from './material';
|
||||
export * from './save';
|
||||
|
||||
export * from './types';
|
||||
|
||||
2
packages-user/client-base/src/save/index.ts
Normal file
2
packages-user/client-base/src/save/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './system';
|
||||
export * from './types';
|
||||
@ -4,11 +4,10 @@ import {
|
||||
IGlobalTrasaction,
|
||||
ISaveRead,
|
||||
ISaveSystem,
|
||||
ISaveSystemConfig,
|
||||
ISaveableContent,
|
||||
SaveCompression
|
||||
ISaveSystemConfig
|
||||
} from './types';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { SaveCompression, ISaveableContent } from '@user/data-common';
|
||||
|
||||
interface ISaveRecord {
|
||||
/** 存档 id */
|
||||
142
packages-user/client-base/src/save/types.ts
Normal file
142
packages-user/client-base/src/save/types.ts
Normal file
@ -0,0 +1,142 @@
|
||||
import { Dexie, Table } from 'dexie';
|
||||
import { SaveCompression, ISaveableContent } from '@user/data-common';
|
||||
|
||||
export interface IGlobalTrasaction {
|
||||
/** 全局存储对应的表 */
|
||||
readonly table: Table<unknown, string>;
|
||||
|
||||
/**
|
||||
* 获取指定键值对应的数据
|
||||
* @param key 全局键值
|
||||
*/
|
||||
get<T>(key: string): Promise<T>;
|
||||
|
||||
/**
|
||||
* 设置指定键值存储的数据
|
||||
* @param key 全局键值
|
||||
* @param value 存储数据
|
||||
*/
|
||||
set(key: string, value: unknown): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ISaveSystemConfig {
|
||||
/** 自动存档使用的压缩等级 */
|
||||
autosaveLevel: SaveCompression;
|
||||
/** 普通存档使用的压缩等级 */
|
||||
commonSaveLevel: SaveCompression;
|
||||
/** 可容忍的最大存档耗时,超过此值会抛出警告 */
|
||||
saveTimeTolerance: number;
|
||||
/** 可容忍的最大自动存档耗时,超过此值会抛出警告 */
|
||||
autosaveTimeTolerance: number;
|
||||
/** 自动存档栈最大大小 */
|
||||
autosaveStackSize: number;
|
||||
}
|
||||
|
||||
export interface ISaveRead {
|
||||
/** 该存档的压缩等级 */
|
||||
readonly compression: SaveCompression;
|
||||
/** 该存档的数据 */
|
||||
readonly data: Map<string, unknown>;
|
||||
}
|
||||
|
||||
export interface ISaveSystem {
|
||||
/** Dexie 数据库 */
|
||||
readonly db: Dexie;
|
||||
|
||||
/**
|
||||
* 初始化存档数据库
|
||||
* @param name 数据库名称
|
||||
*/
|
||||
init(name: string): void;
|
||||
|
||||
/**
|
||||
* 配置此存档系统
|
||||
* @param config 配置对象
|
||||
*/
|
||||
config(config: Readonly<Partial<ISaveSystemConfig>>): void;
|
||||
|
||||
/**
|
||||
* 从 `undo` 栈读取上一个自动存档,然后将当前状态加入 `redo` 栈
|
||||
* @param current 当前游戏状态,需要加入 `redo` 栈
|
||||
*/
|
||||
undoAutosave(
|
||||
current: Map<string, ISaveableContent<unknown>>
|
||||
): ISaveRead | null;
|
||||
|
||||
/**
|
||||
* 从 `redo` 栈读取自动存档,并将当前状态加入 `undo` 栈
|
||||
* @param current 当前游戏状态,需要加入 `undo` 栈
|
||||
*/
|
||||
redoAutosave(
|
||||
current: Map<string, ISaveableContent<unknown>>
|
||||
): ISaveRead | null;
|
||||
|
||||
/**
|
||||
* 获取当前的撤回栈
|
||||
*/
|
||||
getUndoStack(): ISaveRead[];
|
||||
|
||||
/**
|
||||
* 获取当前的重做栈
|
||||
*/
|
||||
getRedoStack(): ISaveRead[];
|
||||
|
||||
/**
|
||||
* 进行自动存档,加入撤回栈
|
||||
* @param state 状态对象
|
||||
*/
|
||||
autosave(state: Map<string, ISaveableContent<unknown>>): void;
|
||||
|
||||
/**
|
||||
* 将 `undo` 栈顶的自动存档真正存入 `IndexedDB`
|
||||
*/
|
||||
saveAutosaveToDB(): Promise<void>;
|
||||
|
||||
/**
|
||||
* 将状态对象存入存档
|
||||
* @param id 存档 id,用于建立存档索引及查询
|
||||
* @param state 状态对象
|
||||
*/
|
||||
save(
|
||||
id: number,
|
||||
state: Map<string, ISaveableContent<unknown>>
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* 根据 id 读取指定存档
|
||||
* @param id 存档 id
|
||||
*/
|
||||
load(id: number): Promise<ISaveRead | null>;
|
||||
|
||||
/**
|
||||
* 删除指定存档
|
||||
* @param id 存档 id
|
||||
*/
|
||||
deleteSave(id: number): Promise<void>;
|
||||
|
||||
/**
|
||||
* 获取最后一次存档的存档栏位
|
||||
*/
|
||||
getLastSlot(): Promise<number>;
|
||||
|
||||
/**
|
||||
* 获取指定键值对应的全局存储
|
||||
* @param key 全局键值
|
||||
*/
|
||||
getGlobal<T>(key: string): Promise<T | null>;
|
||||
|
||||
/**
|
||||
* 设置指定键值对应的全局存储
|
||||
* @param key 全局键值
|
||||
* @param value 存储数据
|
||||
*/
|
||||
setGlobal(key: string, value: unknown): Promise<void>;
|
||||
|
||||
/**
|
||||
* 进行全局存储的事务处理,适用于多内容查询与设置,当出现错误时其中的任何写入操作都不会真正存储
|
||||
* @param handle 事务处理函数
|
||||
*/
|
||||
startGlobalTransaction<R>(
|
||||
handle: (transaction: IGlobalTrasaction) => PromiseLike<R>
|
||||
): Promise<R>;
|
||||
}
|
||||
12
packages-user/client-base/src/types.ts
Normal file
12
packages-user/client-base/src/types.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { ISaveSystem } from '@user/data-common';
|
||||
import { ICoreState } from '@user/data-state';
|
||||
|
||||
export interface IClientBase extends ICoreState {
|
||||
/** 存档系统 */
|
||||
readonly save: ISaveSystem;
|
||||
}
|
||||
|
||||
export interface IClientBaseExtended {
|
||||
/** 当前对象的渲染端基本层对象(Layer 4 对象) */
|
||||
readonly state: IClientBase;
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import { ICoreState } from '@user/data-state';
|
||||
import { CoreState } from '@user/data-state';
|
||||
import { IClientCore } from './types';
|
||||
import {
|
||||
IMotaAudioContext,
|
||||
@ -15,7 +15,9 @@ import {
|
||||
IAutotileProcessor,
|
||||
MotaAssetsLoader,
|
||||
MaterialManager,
|
||||
AutotileProcessor
|
||||
AutotileProcessor,
|
||||
ISaveSystem,
|
||||
SaveSystem
|
||||
} from '@user/client-base';
|
||||
import {
|
||||
IMapRenderer,
|
||||
@ -41,9 +43,12 @@ import {
|
||||
import { loading } from '@user/data-base';
|
||||
import { fallbackLoad } from './fallback/load';
|
||||
|
||||
export class ClientCore implements IClientCore {
|
||||
readonly loader: IMotaAssetsLoader;
|
||||
export class ClientCore extends CoreState implements IClientCore {
|
||||
// Layer 4 渲染基础层
|
||||
readonly save: ISaveSystem;
|
||||
|
||||
// Layer 5 渲染顶层
|
||||
readonly loader: IMotaAssetsLoader;
|
||||
readonly materials: IMaterialManager;
|
||||
readonly autotile: IAutotileProcessor;
|
||||
|
||||
@ -57,7 +62,14 @@ export class ClientCore implements IClientCore {
|
||||
readonly soundPlayer: ISoundPlayer<SoundIds>;
|
||||
readonly bgmPlayer: IBGMPlayer<BgmIds>;
|
||||
|
||||
constructor(public data: ICoreState) {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
//#region Layer 4
|
||||
|
||||
this.save = new SaveSystem();
|
||||
this.save.init(`@game/${core.firstData.name}`);
|
||||
|
||||
//#region 音频系统
|
||||
|
||||
this.audioContext = new MotaAudioContext();
|
||||
@ -74,8 +86,8 @@ export class ClientCore implements IClientCore {
|
||||
//#endregion
|
||||
|
||||
this.loader = new MotaAssetsLoader(
|
||||
data.loadProgress,
|
||||
data.dataLoader,
|
||||
this.loadProgress,
|
||||
this.dataLoader,
|
||||
this.audioContext,
|
||||
this.soundPlayer,
|
||||
this.materials
|
||||
@ -114,12 +126,12 @@ export class ClientCore implements IClientCore {
|
||||
// 使用分频器,用户可以在设置中调整,如果设备性能较差调高分频有助于提高性能表现
|
||||
excitaion: excitationDivider
|
||||
});
|
||||
this.mainMapRenderer = new MapRenderer(this.materials, data.maps);
|
||||
this.mainMapRenderer = new MapRenderer(this.materials);
|
||||
this.mainMapExtension = new MapExtensionManager(this.mainMapRenderer);
|
||||
|
||||
// 兼容层
|
||||
loading.once('assetBuilt', () => {
|
||||
this.createMainExtension();
|
||||
this.initMapExtensions();
|
||||
});
|
||||
|
||||
//#endregion
|
||||
@ -128,24 +140,16 @@ export class ClientCore implements IClientCore {
|
||||
/**
|
||||
* 进行地图渲染拓展初始化
|
||||
*/
|
||||
private async createMainExtension() {
|
||||
private async initMapExtensions() {
|
||||
// 算是一种妥协吧,等之后加载系统重构之后应该会清晰很多
|
||||
await this.materials.trackedAsset.then();
|
||||
|
||||
this.mainMapRenderer.useAsset(this.materials.trackedAsset);
|
||||
const layer = this.data.maps.getLayerByAlias('event');
|
||||
if (layer) {
|
||||
this.mainMapExtension.addHero(this.data.hero.mover, layer);
|
||||
this.mainMapExtension.addDoor(layer);
|
||||
}
|
||||
// const layer = this.maps.getLayerByAlias('event');
|
||||
// if (layer) {
|
||||
// this.mainMapExtension.addHero(this.hero.mover, layer);
|
||||
// this.mainMapExtension.addDoor(layer);
|
||||
// }
|
||||
this.mainMapExtension.addText();
|
||||
}
|
||||
|
||||
bindDataState(state: ICoreState): void {
|
||||
this.data = state;
|
||||
}
|
||||
|
||||
getDataState(): ICoreState {
|
||||
return this.data;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { state } from '@user/data-state';
|
||||
import { ClientCore } from './client';
|
||||
|
||||
// TODO: 逐渐弱化 ClientCore 的单例概念,每个接口都通过参数传入 IClientCore 对象
|
||||
|
||||
/** 客户端实例 */
|
||||
export const client = new ClientCore(state);
|
||||
export const client = new ClientCore();
|
||||
|
||||
@ -78,9 +78,9 @@ export class MapRenderer
|
||||
assetWidth: number = 4096;
|
||||
assetHeight: number = 4096;
|
||||
|
||||
layerState: IGameMap;
|
||||
map: IGameMap | null = null;
|
||||
/** 地图状态钩子控制器 */
|
||||
private layerStateHook: IHookController<IGameMapHooks>;
|
||||
private mapHook: IHookController<IGameMapHooks> | null = null;
|
||||
|
||||
/** 排序后的图层 */
|
||||
private sortedLayers: IMapLayer[] = [];
|
||||
@ -205,13 +205,8 @@ export class MapRenderer
|
||||
/**
|
||||
* 创建地图渲染器
|
||||
* @param manager 素材管理器
|
||||
* @param gl 画布 WebGL2 上下文
|
||||
* @param transform 视角变换矩阵
|
||||
*/
|
||||
constructor(
|
||||
readonly manager: IMaterialManager,
|
||||
layerState: IGameMap
|
||||
) {
|
||||
constructor(readonly manager: IMaterialManager) {
|
||||
this.movingIndexPool.push(
|
||||
...Array.from({ length: this.movingCount }, (_, i) => i).reverse()
|
||||
);
|
||||
@ -219,11 +214,6 @@ export class MapRenderer
|
||||
this.gl = this.canvas.getContext('webgl2')!;
|
||||
this.transform = new Transform();
|
||||
this.transform.bind(this);
|
||||
this.layerState = layerState;
|
||||
this.layerStateHook = layerState.addHook(
|
||||
new RendererLayerStateHook(this)
|
||||
);
|
||||
this.layerStateHook.load();
|
||||
// 上下文初始化要依赖于 offsetPool,因此提前调用
|
||||
const offsetPool = this.getOffsetPool();
|
||||
this.offsetPool = offsetPool;
|
||||
@ -414,40 +404,42 @@ export class MapRenderer
|
||||
* 图层排序
|
||||
*/
|
||||
private sortLayer() {
|
||||
this.sortedLayers = [...this.layerState.layerList].sort((a, b) => {
|
||||
if (!this.map) return;
|
||||
this.sortedLayers = [...this.map.layerList].sort((a, b) => {
|
||||
return a.zIndex - b.zIndex;
|
||||
});
|
||||
this.sortedLayers.forEach((v, i) => this.layerIndexMap.set(v, i));
|
||||
}
|
||||
|
||||
updateLayerList() {
|
||||
if (!this.map) return;
|
||||
this.sortLayer();
|
||||
this.resizeLayer();
|
||||
this.layerCount = this.layerState.layerList.size;
|
||||
this.layerCount = this.map.layerList.size;
|
||||
this.vertex.updateLayerArray();
|
||||
}
|
||||
|
||||
setLayerState(layerState: IGameMap): void {
|
||||
if (layerState === this.layerState) return;
|
||||
this.layerStateHook.unload();
|
||||
this.layerState = layerState;
|
||||
this.layerStateHook = layerState.addHook(
|
||||
new RendererLayerStateHook(this)
|
||||
);
|
||||
this.layerStateHook.load();
|
||||
setLayerState(map: IGameMap): void {
|
||||
if (map === this.map) return;
|
||||
this.mapHook?.unload();
|
||||
this.map = map;
|
||||
this.mapHook = map.addHook(new RendererLayerStateHook(this));
|
||||
this.mapHook.load();
|
||||
this.sortLayer();
|
||||
this.resizeLayer();
|
||||
this.layerCount = layerState.layerList.size;
|
||||
this.layerCount = map.layerList.size;
|
||||
this.vertex.updateLayerArray();
|
||||
this.vertex.resizeMap();
|
||||
}
|
||||
|
||||
getLayer(identifier: string): IMapLayer | null {
|
||||
return this.layerState.getLayerByAlias(identifier) ?? null;
|
||||
if (!this.map) return null;
|
||||
return this.map.getLayerByAlias(identifier) ?? null;
|
||||
}
|
||||
|
||||
hasLayer(layer: IMapLayer): boolean {
|
||||
return this.layerState.hasLayer(layer);
|
||||
if (!this.map) return false;
|
||||
return this.map.hasLayer(layer);
|
||||
}
|
||||
|
||||
getSortedLayer(): IMapLayer[] {
|
||||
|
||||
@ -339,7 +339,7 @@ export interface IMapRenderer {
|
||||
/** 顶点数组生成器 */
|
||||
readonly vertex: IMapVertexGenerator;
|
||||
/** 使用的地图状态对象 */
|
||||
readonly layerState: IGameMap;
|
||||
readonly map: IGameMap | null;
|
||||
|
||||
/** 地图宽度 */
|
||||
readonly mapWidth: number;
|
||||
|
||||
@ -1,18 +1,16 @@
|
||||
import { IRenderTreeRoot } from '@motajs/render';
|
||||
import { ICoreState } from '@user/data-state';
|
||||
import { IMapExtensionManager, IMapRenderer } from './render/map';
|
||||
import { IBGMPlayer, IMotaAudioContext, ISoundPlayer } from '@motajs/audio';
|
||||
import {
|
||||
IAutotileProcessor,
|
||||
IMaterialManager,
|
||||
IMotaAssetsLoader
|
||||
IMotaAssetsLoader,
|
||||
IClientBase
|
||||
} from '@user/client-base';
|
||||
import { IExcitation, IExcitationDivider } from '@motajs/animate';
|
||||
|
||||
export interface IClientCore {
|
||||
/** 数据端状态对象 */
|
||||
readonly data: ICoreState;
|
||||
|
||||
export interface IClientCore extends IClientBase {
|
||||
/** 渲染端加载对象 */
|
||||
readonly loader: IMotaAssetsLoader;
|
||||
|
||||
/** 素材管理器 */
|
||||
@ -39,15 +37,4 @@ export interface IClientCore {
|
||||
readonly soundPlayer: ISoundPlayer<SoundIds>;
|
||||
/** BGM 播放器 */
|
||||
readonly bgmPlayer: IBGMPlayer<BgmIds>;
|
||||
|
||||
/**
|
||||
* 绑定数据端状态
|
||||
* @param state 数据端状态
|
||||
*/
|
||||
bindDataState(state: ICoreState): void;
|
||||
|
||||
/**
|
||||
* 获取当前绑定的数据端状态对象
|
||||
*/
|
||||
getDataState(): ICoreState;
|
||||
}
|
||||
|
||||
@ -1,2 +1 @@
|
||||
export * from './system';
|
||||
export * from './types';
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import { Dexie, Table } from 'dexie';
|
||||
|
||||
export const enum SaveCompression {
|
||||
/** 不进行压缩,仅提取必要数据 */
|
||||
NoCompression,
|
||||
@ -23,143 +21,3 @@ export interface ISaveableContent<T> {
|
||||
*/
|
||||
loadState(state: T, compression: SaveCompression): void;
|
||||
}
|
||||
|
||||
export interface IGlobalTrasaction {
|
||||
/** 全局存储对应的表 */
|
||||
readonly table: Table<unknown, string>;
|
||||
|
||||
/**
|
||||
* 获取指定键值对应的数据
|
||||
* @param key 全局键值
|
||||
*/
|
||||
get<T>(key: string): Promise<T>;
|
||||
|
||||
/**
|
||||
* 设置指定键值存储的数据
|
||||
* @param key 全局键值
|
||||
* @param value 存储数据
|
||||
*/
|
||||
set(key: string, value: unknown): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ISaveSystemConfig {
|
||||
/** 自动存档使用的压缩等级 */
|
||||
autosaveLevel: SaveCompression;
|
||||
/** 普通存档使用的压缩等级 */
|
||||
commonSaveLevel: SaveCompression;
|
||||
/** 可容忍的最大存档耗时,超过此值会抛出警告 */
|
||||
saveTimeTolerance: number;
|
||||
/** 可容忍的最大自动存档耗时,超过此值会抛出警告 */
|
||||
autosaveTimeTolerance: number;
|
||||
/** 自动存档栈最大大小 */
|
||||
autosaveStackSize: number;
|
||||
}
|
||||
|
||||
export interface ISaveRead {
|
||||
/** 该存档的压缩等级 */
|
||||
readonly compression: SaveCompression;
|
||||
/** 该存档的数据 */
|
||||
readonly data: Map<string, unknown>;
|
||||
}
|
||||
|
||||
export interface ISaveSystem {
|
||||
/** Dexie 数据库 */
|
||||
readonly db: Dexie;
|
||||
|
||||
/**
|
||||
* 初始化存档数据库
|
||||
* @param name 数据库名称
|
||||
*/
|
||||
init(name: string): void;
|
||||
|
||||
/**
|
||||
* 配置此存档系统
|
||||
* @param config 配置对象
|
||||
*/
|
||||
config(config: Readonly<Partial<ISaveSystemConfig>>): void;
|
||||
|
||||
/**
|
||||
* 从 `undo` 栈读取上一个自动存档,然后将当前状态加入 `redo` 栈
|
||||
* @param current 当前游戏状态,需要加入 `redo` 栈
|
||||
*/
|
||||
undoAutosave(
|
||||
current: Map<string, ISaveableContent<unknown>>
|
||||
): ISaveRead | null;
|
||||
|
||||
/**
|
||||
* 从 `redo` 栈读取自动存档,并将当前状态加入 `undo` 栈
|
||||
* @param current 当前游戏状态,需要加入 `undo` 栈
|
||||
*/
|
||||
redoAutosave(
|
||||
current: Map<string, ISaveableContent<unknown>>
|
||||
): ISaveRead | null;
|
||||
|
||||
/**
|
||||
* 获取当前的撤回栈
|
||||
*/
|
||||
getUndoStack(): ISaveRead[];
|
||||
|
||||
/**
|
||||
* 获取当前的重做栈
|
||||
*/
|
||||
getRedoStack(): ISaveRead[];
|
||||
|
||||
/**
|
||||
* 进行自动存档,加入撤回栈
|
||||
* @param state 状态对象
|
||||
*/
|
||||
autosave(state: Map<string, ISaveableContent<unknown>>): void;
|
||||
|
||||
/**
|
||||
* 将 `undo` 栈顶的自动存档真正存入 `IndexedDB`
|
||||
*/
|
||||
saveAutosaveToDB(): Promise<void>;
|
||||
|
||||
/**
|
||||
* 将状态对象存入存档
|
||||
* @param id 存档 id,用于建立存档索引及查询
|
||||
* @param state 状态对象
|
||||
*/
|
||||
save(
|
||||
id: number,
|
||||
state: Map<string, ISaveableContent<unknown>>
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* 根据 id 读取指定存档
|
||||
* @param id 存档 id
|
||||
*/
|
||||
load(id: number): Promise<ISaveRead | null>;
|
||||
|
||||
/**
|
||||
* 删除指定存档
|
||||
* @param id 存档 id
|
||||
*/
|
||||
deleteSave(id: number): Promise<void>;
|
||||
|
||||
/**
|
||||
* 获取最后一次存档的存档栏位
|
||||
*/
|
||||
getLastSlot(): Promise<number>;
|
||||
|
||||
/**
|
||||
* 获取指定键值对应的全局存储
|
||||
* @param key 全局键值
|
||||
*/
|
||||
getGlobal<T>(key: string): Promise<T | null>;
|
||||
|
||||
/**
|
||||
* 设置指定键值对应的全局存储
|
||||
* @param key 全局键值
|
||||
* @param value 存储数据
|
||||
*/
|
||||
setGlobal(key: string, value: unknown): Promise<void>;
|
||||
|
||||
/**
|
||||
* 进行全局存储的事务处理,适用于多内容查询与设置,当出现错误时其中的任何写入操作都不会真正存储
|
||||
* @param handle 事务处理函数
|
||||
*/
|
||||
startGlobalTransaction<R>(
|
||||
handle: (transaction: IGlobalTrasaction) => PromiseLike<R>
|
||||
): Promise<R>;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user