mirror of
https://github.com/motajs/template.git
synced 2026-11-14 15:34:30 +08:00
refactor: 地图 MapLayer
This commit is contained in:
1 parent
122567b050
commit
b262918082
15 files changed
+281
-267
No files matched your search
@@ -10,6 +10,10 @@ export class LayerEventView implements ILayerEventView {
|
||||
/** 当前存储与参考基准不一致的条目数量 */
|
||||
private dirtyEntries: number = 0;
|
||||
|
||||
[Symbol.iterator]() {
|
||||
return this.store.entries();
|
||||
}
|
||||
|
||||
get(): ReadonlyMap<number, string> {
|
||||
return this.store;
|
||||
}
|
||||
|
||||
@@ -59,8 +59,7 @@ export class GameMap extends Hookable<IGameMapHooks> implements IGameMap {
|
||||
|
||||
@shouldReplay('Adding game map layer should be replayed.')
|
||||
addLayer(alias: string): IMapLayer {
|
||||
const array = new Uint32Array(this.width * this.height);
|
||||
const layer = new MapLayer(array, this.width, this.height, this, alias);
|
||||
const layer = new MapLayer(this.width, this.height, this, alias);
|
||||
this.layerList.add(layer);
|
||||
this.forEachHook(hook => {
|
||||
hook.onUpdateLayer?.(this.layerList);
|
||||
|
||||
@@ -5,11 +5,11 @@ import {
|
||||
IGameMap,
|
||||
ILayerEventView,
|
||||
ILayerLocation,
|
||||
IMapLayer,
|
||||
IMapLayerData,
|
||||
IMapLayerHookController,
|
||||
IMapLayerHooks,
|
||||
IMapLayerSave,
|
||||
IResizableMapLayer,
|
||||
IStaticBlockSave,
|
||||
IStaticTile
|
||||
} from './types';
|
||||
@@ -17,6 +17,7 @@ import { Hookable, HookController, ITileLocator, logger } from '@motajs/common';
|
||||
import {
|
||||
FaceDirection,
|
||||
IDataCommon,
|
||||
ILocationHelper,
|
||||
IRoleFaceBinder,
|
||||
SaveCompression,
|
||||
shouldReplay
|
||||
@@ -27,9 +28,10 @@ import { StaticTile } from './staticTile';
|
||||
|
||||
export class MapLayer
|
||||
extends Hookable<IMapLayerHooks, IMapLayerHookController>
|
||||
implements IMapLayer
|
||||
implements IResizableMapLayer
|
||||
{
|
||||
readonly state: IDataCommon;
|
||||
readonly indexer: ILocationHelper;
|
||||
|
||||
width: number;
|
||||
height: number;
|
||||
@@ -59,7 +61,6 @@ export class MapLayer
|
||||
private readonly pointEvents: Map<number, ILayerEventView> = new Map();
|
||||
|
||||
constructor(
|
||||
array: Uint32Array,
|
||||
width: number,
|
||||
height: number,
|
||||
readonly map: IGameMap,
|
||||
@@ -72,128 +73,31 @@ export class MapLayer
|
||||
this.height = height;
|
||||
const area = width * height;
|
||||
this.mapArray = new Uint32Array(area);
|
||||
this.mapArray.set(array);
|
||||
this.mapData = {
|
||||
expired: false,
|
||||
array: this.mapArray
|
||||
};
|
||||
this.indexer = map.indexer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将动态图块登记到索引表中
|
||||
* @param tile 动态图块
|
||||
*/
|
||||
private addDynamicToPosMap(tile: IDynamicTile): void {
|
||||
const { x, y } = tile;
|
||||
const xMap = this.tilePosMap.getOrInsertComputed(y, () => new Map());
|
||||
const set = xMap.getOrInsertComputed(x, () => new Set());
|
||||
set.add(tile);
|
||||
// 需要使用不同引用的 locator,避免与 tile 本身的 locator 同引用导致缓存位置失效
|
||||
// 缓存位置的目的是能在动态图块位置更新时还持有旧位置,能够执行内部存储的移动
|
||||
this.cachedDynamics.set(tile, { x, y });
|
||||
}
|
||||
|
||||
/**
|
||||
* 将动态图块从指定缓存坐标的索引表中移除
|
||||
* @param tile 动态图块
|
||||
* @param x 横坐标
|
||||
* @param y 纵坐标
|
||||
*/
|
||||
private removeDynamic(tile: IDynamicTile, x: number, y: number): void {
|
||||
this.cachedDynamics.delete(tile);
|
||||
this.tilePosMap.get(y)?.get(x)?.delete(tile);
|
||||
protected createController(
|
||||
hook: Partial<IMapLayerHooks>
|
||||
): IMapLayerHookController {
|
||||
return new MapLayerHookController(this, hook);
|
||||
}
|
||||
|
||||
//#region 事件操作
|
||||
|
||||
/**
|
||||
* 将动态图块的事件同步回当前静态格点
|
||||
* @param tile 动态图块
|
||||
* @param keepEvent 是否保留事件
|
||||
*/
|
||||
private syncStaticEvent(tile: IDynamicTile, keepEvent: boolean): void {
|
||||
const staticTile = this.getTile(tile.x, tile.y);
|
||||
if (!staticTile) return;
|
||||
staticTile.set(staticTile.num());
|
||||
if (keepEvent) {
|
||||
const staticEvent = staticTile.tileEvent();
|
||||
const dynamicEvent = tile.tileEvent();
|
||||
staticEvent.clear();
|
||||
for (const [priority, id] of dynamicEvent.get()) {
|
||||
staticEvent.set(priority, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将所有已创建的点事件视图恢复到原始纯基准
|
||||
*/
|
||||
private resetPointEvents(): void {
|
||||
for (const eventView of this.pointEvents.values()) {
|
||||
const reference = eventView.ref();
|
||||
eventView.clear();
|
||||
for (const [priority, id] of reference) {
|
||||
eventView.set(priority, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 裁剪超出新图层范围的点事件,并按新宽度重建索引
|
||||
* @param width 新图层宽度
|
||||
* @param height 新图层高度
|
||||
*/
|
||||
private cropPointEvents(width: number, height: number): void {
|
||||
const pointEvents = new Map<number, ILayerEventView>();
|
||||
for (const [index, eventView] of this.pointEvents) {
|
||||
const x = index % this.width;
|
||||
const y = Math.floor(index / this.width);
|
||||
if (x < width && y < height) {
|
||||
pointEvents.set(y * width + x, eventView);
|
||||
}
|
||||
}
|
||||
this.pointEvents.clear();
|
||||
for (const [index, eventView] of pointEvents) {
|
||||
this.pointEvents.set(index, eventView);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集需要保存的点事件并复制其内部 Map
|
||||
*/
|
||||
private savePointEvents(): Map<number, ReadonlyMap<number, string>> {
|
||||
const pointEvents = new Map<number, ReadonlyMap<number, string>>();
|
||||
for (const [index, eventView] of this.pointEvents) {
|
||||
if (!eventView.dirty()) continue;
|
||||
pointEvents.set(index, new Map(eventView.get()));
|
||||
}
|
||||
return pointEvents;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在图层基准上叠加点事件存档
|
||||
* @param save 点事件存档,可省略
|
||||
*/
|
||||
private loadPointEvents(
|
||||
save?: ReadonlyMap<number, ReadonlyMap<number, string>>
|
||||
): void {
|
||||
this.resetPointEvents();
|
||||
if (!save) return;
|
||||
for (const [index, events] of save) {
|
||||
const x = index % this.width;
|
||||
const y = Math.floor(index / this.width);
|
||||
const eventView = this.event(x, y);
|
||||
if (!eventView) continue;
|
||||
eventView.clear();
|
||||
for (const [priority, id] of events) {
|
||||
eventView.set(priority, id);
|
||||
}
|
||||
}
|
||||
this.pointEvents.forEach(v => v.reset());
|
||||
}
|
||||
|
||||
event(x: number, y: number): ILayerEventView | null {
|
||||
if (!this.inMap(x, y)) return null;
|
||||
const index = y * this.width + x;
|
||||
const index = this.indexer.index(x, y);
|
||||
let eventView = this.pointEvents.get(index);
|
||||
if (!eventView) {
|
||||
eventView = new LayerEventView();
|
||||
@@ -217,7 +121,7 @@ export class MapLayer
|
||||
@shouldReplay('Setting map layer block should be replayed.')
|
||||
setBlock(block: number, x: number, y: number): void {
|
||||
if (!this.inMap(x, y)) return;
|
||||
const index = y * this.width + x;
|
||||
const index = this.indexer.index(x, y);
|
||||
if (block === this.mapArray[index]) return;
|
||||
this.mapArray[index] = block;
|
||||
this.layerDirty = true;
|
||||
@@ -248,7 +152,7 @@ export class MapLayer
|
||||
|
||||
getTile(x: number, y: number): IStaticTile | null {
|
||||
if (!this.inMap(x, y)) return null;
|
||||
const index = this.map.indexer.locToIndex(x, y);
|
||||
const index = this.map.indexer.index(x, y);
|
||||
let staticTile = this.staticTileCache.get(index);
|
||||
if (!staticTile) {
|
||||
staticTile = new StaticTile(x, y, this);
|
||||
@@ -260,7 +164,7 @@ export class MapLayer
|
||||
getLocationData(x: number, y: number): ILayerLocation | null {
|
||||
if (!this.inMap(x, y)) return null;
|
||||
const staticTile = this.getTile(x, y);
|
||||
const num = staticTile?.num() ?? -1;
|
||||
const num = staticTile?.num() ?? 0;
|
||||
const dynamics = this.getDynamicTilesAt(x, y);
|
||||
return {
|
||||
locator: { x, y },
|
||||
@@ -340,14 +244,18 @@ export class MapLayer
|
||||
if (x < 0 || y < 0 || r > w || b > h) {
|
||||
logger.warn(81);
|
||||
}
|
||||
const res = new Uint32Array(width * height);
|
||||
const nl = Math.max(x, 0);
|
||||
const nt = Math.max(y, 0);
|
||||
const nr = Math.min(r, w);
|
||||
const nb = Math.min(b, this.height);
|
||||
const nw = nr - nl;
|
||||
const nh = nb - nt;
|
||||
const res = new Uint32Array(nw * nh);
|
||||
const arr = this.mapArray;
|
||||
const nb = Math.min(b, h);
|
||||
for (let ny = y; ny < nb; ny++) {
|
||||
const lineStart = ny * w + x;
|
||||
const lineEnd = lineStart + width;
|
||||
for (let ny = nt; ny < nb; ny++) {
|
||||
const lineStart = ny * w + nl;
|
||||
const dy = ny - y;
|
||||
res.set(arr.subarray(lineStart, lineEnd), dy * width);
|
||||
res.set(arr.subarray(lineStart, nr), dy * width);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@@ -378,7 +286,7 @@ export class MapLayer
|
||||
return this.mapData;
|
||||
}
|
||||
|
||||
@shouldReplay('Setting map layer block direction should be replayed.')
|
||||
@shouldReplay('Setting map layer block direction should be replayed.')
|
||||
setStaticDirection(x: number, y: number, direction: FaceDirection): number {
|
||||
const tile = this.getTile(x, y);
|
||||
if (!tile) return -1;
|
||||
@@ -400,19 +308,36 @@ export class MapLayer
|
||||
|
||||
//#region 动态图层操作
|
||||
|
||||
/**
|
||||
* 将动态图块登记到索引表中
|
||||
* @param tile 动态图块
|
||||
*/
|
||||
private addDynamic(tile: IDynamicTile): void {
|
||||
const { x, y } = tile;
|
||||
const xMap = this.tilePosMap.getOrInsertComputed(y, () => new Map());
|
||||
const set = xMap.getOrInsertComputed(x, () => new Set());
|
||||
set.add(tile);
|
||||
// 需要使用不同引用的 locator,避免与 tile 本身的 locator 同引用导致缓存位置失效
|
||||
// 缓存位置的目的是能在动态图块位置更新时还持有旧位置,能够执行内部存储的移动
|
||||
this.cachedDynamics.set(tile, { x, y });
|
||||
}
|
||||
|
||||
/**
|
||||
* 将动态图块从指定缓存坐标的索引表中移除,注意动态图块本身的位置与其所在的索引表可能不同,
|
||||
* 在动态图块位置更新时,指定位置可以用来移除旧位置的动态图块
|
||||
* @param tile 动态图块
|
||||
* @param x 横坐标
|
||||
* @param y 纵坐标
|
||||
*/
|
||||
private removeDynamic(tile: IDynamicTile, x: number, y: number): void {
|
||||
this.cachedDynamics.delete(tile);
|
||||
this.tilePosMap.get(y)?.get(x)?.delete(tile);
|
||||
}
|
||||
|
||||
@shouldReplay('Creating dynamic tile should be replayed.')
|
||||
createDynamic(num: number, x: number, y: number): IDynamicTile {
|
||||
const tile = new DynamicTile(num, x, y, this);
|
||||
const location = this.getLocationData(x, y);
|
||||
if (location?.static) {
|
||||
const tileEvent = tile.tileEvent();
|
||||
const staticEvent = location.static.tileEvent();
|
||||
tileEvent.clear();
|
||||
for (const [priority, id] of staticEvent.get()) {
|
||||
tileEvent.set(priority, id);
|
||||
}
|
||||
}
|
||||
this.addDynamicToPosMap(tile);
|
||||
this.addDynamic(tile);
|
||||
this.forEachHook(hook => hook.onCreateDynamic?.(tile));
|
||||
return tile;
|
||||
}
|
||||
@@ -433,12 +358,12 @@ export class MapLayer
|
||||
}
|
||||
this.setBlock(0, x, y);
|
||||
const tile = this.createDynamic(num, x, y);
|
||||
const staticTile = this.getTile(x, y);
|
||||
if (staticTile) {
|
||||
staticTile.tileEvent().clear();
|
||||
}
|
||||
if (!keepEvent) {
|
||||
tile.tileEvent().clear();
|
||||
if (keepEvent) {
|
||||
const staticTile = this.getTile(x, y);
|
||||
if (staticTile) {
|
||||
tile.syncTileEvent(staticTile);
|
||||
staticTile.tileEvent().clear();
|
||||
}
|
||||
}
|
||||
return tile;
|
||||
}
|
||||
@@ -456,12 +381,7 @@ export class MapLayer
|
||||
const staticTile = this.getTile(tile.x, tile.y);
|
||||
if (!staticTile) return null;
|
||||
if (keepEvent) {
|
||||
const staticEvent = staticTile.tileEvent();
|
||||
const dynamicEvent = tile.tileEvent();
|
||||
staticEvent.clear();
|
||||
for (const [priority, id] of dynamicEvent.get()) {
|
||||
staticEvent.set(priority, id);
|
||||
}
|
||||
staticTile.syncTileEvent(tile);
|
||||
}
|
||||
this.removeDynamic(tile, tile.x, tile.y);
|
||||
this.forEachHook(hook => hook.onDeleteDynamic?.(tile));
|
||||
@@ -512,11 +432,6 @@ export class MapLayer
|
||||
return this.cachedDynamics.keys();
|
||||
}
|
||||
|
||||
@shouldReplay('Setting map layer block direction should be replayed.')
|
||||
setDynamicDirection(tile: IDynamicTile, direction: FaceDirection): number {
|
||||
return tile.setFaceDirection(direction);
|
||||
}
|
||||
|
||||
@shouldReplay('Updating dynamic tile position should be replayed.')
|
||||
updateDynamicTile(tile: IDynamicTile): void {
|
||||
const oldPos = this.cachedDynamics.get(tile);
|
||||
@@ -524,9 +439,9 @@ export class MapLayer
|
||||
this.removeDynamic(tile, oldPos.x, oldPos.y);
|
||||
oldPos.x = tile.x;
|
||||
oldPos.y = tile.y;
|
||||
this.addDynamicToPosMap(tile);
|
||||
this.addDynamic(tile);
|
||||
} else {
|
||||
this.addDynamicToPosMap(tile);
|
||||
this.addDynamic(tile);
|
||||
this.cachedDynamics.set(tile, { x: tile.x, y: tile.y });
|
||||
}
|
||||
this.forEachHook(hook => hook.onUpdateDynamicPosition?.(tile));
|
||||
@@ -538,7 +453,7 @@ export class MapLayer
|
||||
|
||||
@shouldReplay('Opening door should be replayed.')
|
||||
async openDoor(x: number, y: number): Promise<void> {
|
||||
const index = y * this.width + x;
|
||||
const index = this.indexer.index(x, y);
|
||||
const num = this.mapArray[index];
|
||||
if (num === 0) return;
|
||||
await Promise.all(
|
||||
@@ -551,7 +466,7 @@ export class MapLayer
|
||||
|
||||
@shouldReplay('Closing door should be replayed.')
|
||||
async closeDoor(num: number, x: number, y: number): Promise<void> {
|
||||
const index = y * this.width + x;
|
||||
const index = this.indexer.index(x, y);
|
||||
const nowNum = this.mapArray[index];
|
||||
if (nowNum !== 0) {
|
||||
logger.error(46, x.toString(), y.toString());
|
||||
@@ -608,10 +523,18 @@ export class MapLayer
|
||||
this.layerDirty = !this.isEqualToRef();
|
||||
}
|
||||
|
||||
protected createController(
|
||||
hook: Partial<IMapLayerHooks>
|
||||
): IMapLayerHookController {
|
||||
return new MapLayerHookController(this, hook);
|
||||
/**
|
||||
* 裁剪超出新图层范围的点事件,并按新宽度重建索引
|
||||
* @param width 新图层宽度
|
||||
* @param height 新图层高度
|
||||
*/
|
||||
private cropPointEvents(width: number, height: number): void {
|
||||
for (const [index, eventView] of [...this.pointEvents]) {
|
||||
const { x, y } = this.indexer.locator(index);
|
||||
if (x < width && y < height) {
|
||||
this.pointEvents.set(index, eventView);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@shouldReplay('Resizing map layer should be replayed.')
|
||||
@@ -619,8 +542,9 @@ export class MapLayer
|
||||
if (this.width === width && this.height === height) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 图层数组
|
||||
this.layerDirty = true;
|
||||
this.cropPointEvents(width, height);
|
||||
this.mapData.expired = true;
|
||||
const before = this.mapArray;
|
||||
const beforeWidth = this.width;
|
||||
@@ -631,7 +555,6 @@ export class MapLayer
|
||||
const area = width * height;
|
||||
const newArray = new Uint32Array(area);
|
||||
this.mapArray = newArray;
|
||||
this.staticTileCache.clear();
|
||||
if (beforeArea > area) {
|
||||
for (let ny = 0; ny < height; ny++) {
|
||||
const begin = ny * beforeWidth;
|
||||
@@ -640,16 +563,18 @@ export class MapLayer
|
||||
} else {
|
||||
for (let ny = 0; ny < beforeHeight; ny++) {
|
||||
const begin = ny * beforeWidth;
|
||||
newArray.set(
|
||||
before.subarray(begin, begin + beforeWidth),
|
||||
ny * width
|
||||
);
|
||||
const end = begin + beforeWidth;
|
||||
newArray.set(before.subarray(begin, end), ny * width);
|
||||
}
|
||||
}
|
||||
this.mapData = {
|
||||
expired: false,
|
||||
array: this.mapArray
|
||||
};
|
||||
|
||||
// 其他杂项清理
|
||||
this.cropPointEvents(width, height);
|
||||
this.staticTileCache.clear();
|
||||
this.forEachHook(hook => {
|
||||
hook.onResize?.(width, height);
|
||||
});
|
||||
@@ -658,23 +583,27 @@ export class MapLayer
|
||||
@shouldReplay('Resizing map layer should be replayed.')
|
||||
resize2(width: number, height: number): void {
|
||||
this.layerDirty = true;
|
||||
this.pointEvents.clear();
|
||||
if (this.width === width && this.height === height) {
|
||||
this.empty = true;
|
||||
this.mapArray.fill(0);
|
||||
this.staticTileCache.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// 图层数组
|
||||
this.mapData.expired = true;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.mapArray = new Uint32Array(width * height);
|
||||
this.staticTileCache.clear();
|
||||
this.mapData = {
|
||||
expired: false,
|
||||
array: this.mapArray
|
||||
};
|
||||
this.empty = true;
|
||||
|
||||
// 其他杂项清理
|
||||
this.pointEvents.clear();
|
||||
this.staticTileCache.clear();
|
||||
this.forEachHook(hook => {
|
||||
hook.onResize?.(width, height);
|
||||
});
|
||||
@@ -686,31 +615,33 @@ export class MapLayer
|
||||
|
||||
/**
|
||||
* 保存静态图块实例
|
||||
* @param compression 压缩级别
|
||||
*/
|
||||
private saveStatics(): Map<number, IStaticBlockSave> {
|
||||
private saveStaticTiles(
|
||||
compression: SaveCompression
|
||||
): Map<number, IStaticBlockSave> {
|
||||
const blocks = new Map<number, IStaticBlockSave>();
|
||||
for (const location of this.iterateBlocks()) {
|
||||
const tile = location.static;
|
||||
if (!tile || !tile.shouldSave()) continue;
|
||||
const index = this.map.indexer.locaterToIndex(location.locator);
|
||||
blocks.set(index, tile.saveState(SaveCompression.NoCompression));
|
||||
const index = this.map.indexer.locatorToIndex(location.locator);
|
||||
blocks.set(index, tile.saveState(compression));
|
||||
}
|
||||
return blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存动态图块实例
|
||||
* @param compression 压缩级别
|
||||
*/
|
||||
private saveDynamics(): Map<number, IDynamicBlockSave[]> {
|
||||
private saveDynamicTiles(
|
||||
compression: SaveCompression
|
||||
): Map<number, IDynamicBlockSave[]> {
|
||||
const blocks = new Map<number, IDynamicBlockSave[]>();
|
||||
for (const tile of this.iterateDynamicTiles()) {
|
||||
const index = this.map.indexer.locaterToIndex(tile.locator);
|
||||
let list = blocks.get(index);
|
||||
if (!list) {
|
||||
list = [];
|
||||
blocks.set(index, list);
|
||||
}
|
||||
list.push(tile.saveState(SaveCompression.NoCompression));
|
||||
const index = this.map.indexer.locatorToIndex(tile.locator);
|
||||
const list = blocks.getOrInsert(index, []);
|
||||
list.push(tile.saveState(compression));
|
||||
}
|
||||
return blocks;
|
||||
}
|
||||
@@ -742,8 +673,8 @@ export class MapLayer
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
fullMap: new Uint32Array(this.mapArray),
|
||||
staticBlocks: this.saveStatics(),
|
||||
dynamicBlocks: this.saveDynamics(),
|
||||
staticBlocks: this.saveStaticTiles(SaveCompression.NoCompression),
|
||||
dynamicBlocks: this.saveDynamicTiles(SaveCompression.NoCompression),
|
||||
pointEvents: this.savePointEvents()
|
||||
};
|
||||
}
|
||||
@@ -752,23 +683,23 @@ export class MapLayer
|
||||
* 以低压缩方式序列化当前图层
|
||||
*/
|
||||
private saveLowCompression(): IMapLayerSave {
|
||||
const staticBlocks = this.saveStatics();
|
||||
const dynamicBlocks = this.saveDynamics();
|
||||
const statics = this.saveStaticTiles(SaveCompression.LowCompression);
|
||||
const dynamics = this.saveDynamicTiles(SaveCompression.LowCompression);
|
||||
if (this.layerDirty && (!this.refArray || !this.isEqualToRef())) {
|
||||
return {
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
fullMap: new Uint32Array(this.mapArray),
|
||||
staticBlocks,
|
||||
dynamicBlocks,
|
||||
staticBlocks: statics,
|
||||
dynamicBlocks: dynamics,
|
||||
pointEvents: this.savePointEvents()
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
staticBlocks,
|
||||
dynamicBlocks,
|
||||
staticBlocks: statics,
|
||||
dynamicBlocks: dynamics,
|
||||
pointEvents: this.savePointEvents()
|
||||
};
|
||||
}
|
||||
@@ -778,16 +709,16 @@ export class MapLayer
|
||||
* 以高压缩方式序列化当前图层
|
||||
*/
|
||||
private saveHighCompression(): IMapLayerSave {
|
||||
const staticBlocks = this.saveStatics();
|
||||
const dynamicBlocks = this.saveDynamics();
|
||||
const statics = this.saveStaticTiles(SaveCompression.HighCompression);
|
||||
const dynamics = this.saveDynamicTiles(SaveCompression.HighCompression);
|
||||
if (this.layerDirty) {
|
||||
if (this.refArray) {
|
||||
return {
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
rows: this.diffRows(this.refArray),
|
||||
staticBlocks,
|
||||
dynamicBlocks,
|
||||
staticBlocks: statics,
|
||||
dynamicBlocks: dynamics,
|
||||
pointEvents: this.savePointEvents()
|
||||
};
|
||||
} else {
|
||||
@@ -795,8 +726,8 @@ export class MapLayer
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
fullMap: new Uint32Array(this.mapArray),
|
||||
staticBlocks,
|
||||
dynamicBlocks,
|
||||
staticBlocks: statics,
|
||||
dynamicBlocks: dynamics,
|
||||
pointEvents: this.savePointEvents()
|
||||
};
|
||||
}
|
||||
@@ -804,8 +735,8 @@ export class MapLayer
|
||||
return {
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
staticBlocks,
|
||||
dynamicBlocks,
|
||||
staticBlocks: statics,
|
||||
dynamicBlocks: dynamics,
|
||||
pointEvents: this.savePointEvents()
|
||||
};
|
||||
}
|
||||
@@ -824,26 +755,34 @@ export class MapLayer
|
||||
/**
|
||||
* 读取静态图块实例存档数据
|
||||
* @param save 静态图块实例存档
|
||||
* @param compression 压缩级别
|
||||
*/
|
||||
private loadStatics(save: ReadonlyMap<number, IStaticBlockSave>): void {
|
||||
private loadStaticTiles(
|
||||
save: ReadonlyMap<number, IStaticBlockSave>,
|
||||
compression: SaveCompression
|
||||
): void {
|
||||
for (const [index, tileSave] of save) {
|
||||
const { x, y } = this.map.indexer.indexToLocator(index);
|
||||
const { x, y } = this.map.indexer.locator(index);
|
||||
const location = this.getLocationData(x, y);
|
||||
if (!location?.static) continue;
|
||||
location.static.loadState(tileSave, SaveCompression.NoCompression);
|
||||
location.static.loadState(tileSave, compression);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取动态图块实例存档数据
|
||||
* @param save 动态图块实例存档
|
||||
* @param compression 压缩级别
|
||||
*/
|
||||
private loadDynamics(save: ReadonlyMap<number, IDynamicBlockSave[]>): void {
|
||||
private loadDynamics(
|
||||
save: ReadonlyMap<number, IDynamicBlockSave[]>,
|
||||
compression: SaveCompression
|
||||
): void {
|
||||
for (const [index, dynamics] of save) {
|
||||
const { x, y } = this.map.indexer.indexToLocator(index);
|
||||
const { x, y } = this.map.indexer.locator(index);
|
||||
for (const block of dynamics) {
|
||||
const tile = this.createDynamic(block.num, x, y);
|
||||
tile.loadState(block, SaveCompression.NoCompression);
|
||||
tile.loadState(block, compression);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -855,12 +794,45 @@ export class MapLayer
|
||||
private clearDynamics(): void {
|
||||
const tiles = [...this.iterateDynamicTiles()];
|
||||
for (const tile of tiles) {
|
||||
this.syncStaticEvent(tile, false);
|
||||
this.removeDynamic(tile, tile.x, tile.y);
|
||||
this.forEachHook(hook => hook.onDeleteDynamic?.(tile));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集需要保存的点事件并复制其内部 Map
|
||||
*/
|
||||
private savePointEvents(): Map<number, ReadonlyMap<number, string>> {
|
||||
const pointEvents = new Map<number, ReadonlyMap<number, string>>();
|
||||
for (const [index, eventView] of this.pointEvents) {
|
||||
if (!eventView.dirty()) continue;
|
||||
pointEvents.set(index, new Map(eventView.get()));
|
||||
}
|
||||
return pointEvents;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在图层基准上叠加点事件存档
|
||||
* @param save 点事件存档,可省略
|
||||
*/
|
||||
private loadPointEvents(
|
||||
save?: ReadonlyMap<number, ReadonlyMap<number, string>>
|
||||
): void {
|
||||
this.resetPointEvents();
|
||||
if (!save) return;
|
||||
for (const [index, events] of save) {
|
||||
const { x, y } = this.indexer.locator(index);
|
||||
const eventView = this.event(x, y);
|
||||
if (!eventView) continue;
|
||||
eventView.clear();
|
||||
for (const [priority, id] of events) {
|
||||
eventView.set(priority, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: 不同大小的图层读取
|
||||
|
||||
/**
|
||||
* 以无压缩方式读取当前图层
|
||||
* @param save 图层存档
|
||||
@@ -870,10 +842,16 @@ export class MapLayer
|
||||
this.setMapRef(new Uint32Array(save.fullMap));
|
||||
}
|
||||
if (save.staticBlocks) {
|
||||
this.loadStatics(save.staticBlocks);
|
||||
this.loadStaticTiles(
|
||||
save.staticBlocks,
|
||||
SaveCompression.NoCompression
|
||||
);
|
||||
}
|
||||
if (save.dynamicBlocks) {
|
||||
this.loadDynamics(save.dynamicBlocks);
|
||||
this.loadDynamics(
|
||||
save.dynamicBlocks,
|
||||
SaveCompression.NoCompression
|
||||
);
|
||||
}
|
||||
this.layerDirty = !this.isEqualToRef();
|
||||
}
|
||||
@@ -894,10 +872,16 @@ export class MapLayer
|
||||
}
|
||||
|
||||
if (save.staticBlocks) {
|
||||
this.loadStatics(save.staticBlocks);
|
||||
this.loadStaticTiles(
|
||||
save.staticBlocks,
|
||||
SaveCompression.LowCompression
|
||||
);
|
||||
}
|
||||
if (save.dynamicBlocks) {
|
||||
this.loadDynamics(save.dynamicBlocks);
|
||||
this.loadDynamics(
|
||||
save.dynamicBlocks,
|
||||
SaveCompression.LowCompression
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -925,10 +909,16 @@ export class MapLayer
|
||||
}
|
||||
|
||||
if (save.staticBlocks) {
|
||||
this.loadStatics(save.staticBlocks);
|
||||
this.loadStaticTiles(
|
||||
save.staticBlocks,
|
||||
SaveCompression.HighCompression
|
||||
);
|
||||
}
|
||||
if (save.dynamicBlocks) {
|
||||
this.loadDynamics(save.dynamicBlocks);
|
||||
this.loadDynamics(
|
||||
save.dynamicBlocks,
|
||||
SaveCompression.HighCompression
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
ILayerEventView,
|
||||
IMapBlockSaveBase,
|
||||
IMapLayer,
|
||||
IReadonlyTileBase,
|
||||
ITileBase
|
||||
} from './types';
|
||||
|
||||
@@ -81,6 +82,14 @@ export abstract class MapTileBase<TSave extends IMapBlockSaveBase>
|
||||
return this.layer.event(this.locator.x, this.locator.y);
|
||||
}
|
||||
|
||||
syncTileEvent(origin: IReadonlyTileBase): void {
|
||||
const ev = this.tileEvents;
|
||||
ev.clear();
|
||||
for (const [priority, id] of origin.tileEvent()) {
|
||||
ev.set(priority, id);
|
||||
}
|
||||
}
|
||||
|
||||
abstract saveState(): Readonly<TSave>;
|
||||
|
||||
abstract loadState(save: Readonly<TSave>): void;
|
||||
|
||||
@@ -35,6 +35,8 @@ export interface IDynamicBlockSave extends IMapBlockSaveBase {
|
||||
}
|
||||
|
||||
export interface IReadonlyEventView {
|
||||
[Symbol.iterator](): Iterator<[priority: number, event: string]>;
|
||||
|
||||
/**
|
||||
* 获取所有的事件
|
||||
* @returns 键表示优先级,值表示事件在 `IGameEventStore` 的索引
|
||||
@@ -131,6 +133,12 @@ export interface ITileBase extends IReadonlyTileBase {
|
||||
* 获取此图块所在位置的点事件
|
||||
*/
|
||||
pointEvent(): ILayerEventView | null;
|
||||
|
||||
/**
|
||||
* 复制某个图块的图块事件至当前图块
|
||||
* @param origin 要从哪个图块复制
|
||||
*/
|
||||
syncTileEvent(origin: IReadonlyTileBase): void;
|
||||
}
|
||||
|
||||
export interface IStaticTile
|
||||
@@ -359,7 +367,7 @@ interface ILayerStatic {
|
||||
setStaticDirection(x: number, y: number, direction: FaceDirection): number;
|
||||
|
||||
/**
|
||||
* 迭代所有的图块
|
||||
* 迭代所有的非空图块。非空定义为此格图块数字不为 0,因此哪怕有事件也会视为空图块
|
||||
*/
|
||||
iterateBlocks(): Iterable<ILayerLocation>;
|
||||
}
|
||||
@@ -423,14 +431,6 @@ interface ILayerDynamic {
|
||||
*/
|
||||
iterateDynamicTiles(): Iterable<IDynamicTile>;
|
||||
|
||||
/**
|
||||
* 设置动态图块的朝向
|
||||
* @param tile 要设置朝向的动态图块
|
||||
* @param direction 目标朝向
|
||||
* @returns 更新朝向后该动态图块的图块数字
|
||||
*/
|
||||
setDynamicDirection(tile: IDynamicTile, direction: FaceDirection): number;
|
||||
|
||||
/**
|
||||
* 更新动态图块位置,用于在图块发生移动时更新内部存储,一般不需要手动调用
|
||||
* @param tile 动态图块
|
||||
@@ -477,6 +477,9 @@ export interface IMapLayer
|
||||
ILayerStatic,
|
||||
ILayerDynamic,
|
||||
ISaveableContent<IMapLayerSave> {
|
||||
/** 地图使用的坐标索引器 */
|
||||
readonly indexer: ILocationHelper;
|
||||
|
||||
/** 地图宽度 */
|
||||
readonly width: number;
|
||||
/** 地图高度 */
|
||||
|
||||
Reference in new issue
Block a user