refactor: 地图渲染器改为与 LayerState 绑定

This commit is contained in:
unanmed 2025-11-18 21:12:43 +08:00
parent 6ae0d6ca5f
commit ebaa35ea5b
17 changed files with 564 additions and 387 deletions

View File

@ -225,9 +225,10 @@ export class AutotileProcessor implements IAutotileProcessor {
const size = texture.height === 32 * 48 ? 32 : 48; const size = texture.height === 32 * 48 ? 32 : 48;
const index = distinctConnectionMap.get(connection); const index = distinctConnectionMap.get(connection);
if (isNil(index)) return null; if (isNil(index)) return null;
const { rect } = texture.render();
return { return {
source: texture.source, source: texture.source,
rect: { x: 0, y: size * index, w: size, h: size } rect: { x: rect.x, y: rect.y + size * index, w: size, h: size }
}; };
} }

View File

@ -8,6 +8,7 @@ import { Animate } from './animate';
import { createItemDetail } from './itemDetail'; import { createItemDetail } from './itemDetail';
import { logger } from '@motajs/common'; import { logger } from '@motajs/common';
import { MapRender } from '../map/element'; import { MapRender } from '../map/element';
import { state } from '@user/data-state';
export function createElements() { export function createElements() {
createCache(); createCache();
@ -71,14 +72,14 @@ export function createElements() {
tagMap.register('map-render', (_0, _1, props) => { tagMap.register('map-render', (_0, _1, props) => {
if (!props) { if (!props) {
logger.error(42); logger.error(42);
return new MapRender([]); return new MapRender(state.layer);
} }
const { layerList } = props; const { layerState } = props;
if (!layerList) { if (!layerState) {
logger.error(42); logger.error(42);
return new MapRender([]); return new MapRender(state.layer);
} }
return new MapRender(layerList); return new MapRender(layerState);
}); });
} }

View File

@ -11,7 +11,7 @@ import {
import { EAnimateEvent } from './animate'; import { EAnimateEvent } from './animate';
import { EIconEvent, EWinskinEvent } from './misc'; import { EIconEvent, EWinskinEvent } from './misc';
import { IEnemyCollection } from '@motajs/types'; import { IEnemyCollection } from '@motajs/types';
import { IRenderLayerData } from '../map/element'; import { ILayerState } from '@user/data-state';
export interface AnimateProps extends BaseProps {} export interface AnimateProps extends BaseProps {}
@ -62,7 +62,7 @@ export interface LayerProps extends BaseProps {
} }
export interface MapRenderProps extends BaseProps { export interface MapRenderProps extends BaseProps {
layerList: Iterable<IRenderLayerData>; layerState: ILayerState;
} }
declare module 'vue/jsx-runtime' { declare module 'vue/jsx-runtime' {

View File

@ -3,26 +3,13 @@ import {
RenderItem, RenderItem,
Transform Transform
} from '@motajs/render-core'; } from '@motajs/render-core';
import { IMapLayer } from '@user/data-state'; import { ILayerState, state } from '@user/data-state';
import { import { IMapRenderer, IMapRendererHooks } from './types';
IMapRenderer,
IMapRendererExtends,
MapTileAlign,
MapTileBehavior
} from './types';
import { MapRenderer } from './renderer'; import { MapRenderer } from './renderer';
import { materials } from '@user/client-base'; import { materials } from '@user/client-base';
import { ElementNamespace, ComponentInternalInstance } from 'vue'; import { ElementNamespace, ComponentInternalInstance } from 'vue';
import { CELL_HEIGHT, CELL_WIDTH, MAP_HEIGHT, MAP_WIDTH } from '../shared'; import { CELL_HEIGHT, CELL_WIDTH, MAP_HEIGHT, MAP_WIDTH } from '../shared';
import { IHookController } from '@motajs/common';
export interface IRenderLayerData {
/** 图层对象 */
readonly layer: IMapLayer;
/** 图层纵深 */
readonly zIndex: number;
/** 图层别名 */
readonly alias?: string;
}
export class MapRender extends RenderItem { export class MapRender extends RenderItem {
/** 地图渲染器 */ /** 地图渲染器 */
@ -35,43 +22,31 @@ export class MapRender extends RenderItem {
/** 画布上下文 */ /** 画布上下文 */
readonly gl: WebGL2RenderingContext; readonly gl: WebGL2RenderingContext;
constructor(layerList: Iterable<IRenderLayerData>) { private rendererHook: IHookController<IMapRendererHooks>;
constructor(readonly layerState: ILayerState) {
super('static'); super('static');
this.canvas = document.createElement('canvas'); this.canvas = document.createElement('canvas');
const gl = this.canvas.getContext('webgl2')!; const gl = this.canvas.getContext('webgl2')!;
this.gl = gl; this.gl = gl;
this.renderer = new MapRenderer(materials, this.gl, this.camera); this.renderer = new MapRenderer(
for (const layer of layerList) { materials,
this.renderer.addLayer(layer.layer, layer.alias); this.gl,
this.renderer.setZIndex(layer.layer, layer.zIndex); this.camera,
} state.layer
);
this.renderer.setLayerState(layerState);
this.renderer.useAsset(materials.trackedAsset); this.renderer.useAsset(materials.trackedAsset);
this.renderer.addExtends(new MapUpdateExtends(this)); this.rendererHook = this.renderer.addHook(new RendererUpdateHook(this));
this.rendererHook.load();
this.renderer.setTileBackground(1);
this.renderer.setCellSize(CELL_WIDTH, CELL_HEIGHT); this.renderer.setCellSize(CELL_WIDTH, CELL_HEIGHT);
this.renderer.setRenderSize(MAP_WIDTH, MAP_HEIGHT); this.renderer.setRenderSize(MAP_WIDTH, MAP_HEIGHT);
gl.viewport(0, 0, this.canvas.width, this.canvas.height); gl.viewport(0, 0, this.canvas.width, this.canvas.height);
} }
/**
*
* @param layerList
*/
updateLayerList(layerList: Iterable<IRenderLayerData>) {
this.renderer
.getSortedLayer()
.forEach(v => this.renderer.removeLayer(v));
for (const layer of layerList) {
this.renderer.addLayer(layer.layer, layer.alias);
this.renderer.setZIndex(layer.layer, layer.zIndex);
}
}
private sizeGL(width: number, height: number) { private sizeGL(width: number, height: number) {
const ratio = this.highResolution ? devicePixelRatio : 1; const ratio = this.highResolution ? devicePixelRatio : 1;
const scale = ratio * this.scale; const scale = ratio * this.scale;
@ -115,8 +90,8 @@ export class MapRender extends RenderItem {
parentComponent?: ComponentInternalInstance | null parentComponent?: ComponentInternalInstance | null
): void { ): void {
switch (key) { switch (key) {
case 'layerList': { case 'layerState': {
this.updateLayerList(nextValue); this.renderer.setLayerState(nextValue);
break; break;
} }
} }
@ -124,7 +99,7 @@ export class MapRender extends RenderItem {
} }
} }
class MapUpdateExtends implements IMapRendererExtends { class RendererUpdateHook implements Partial<IMapRendererHooks> {
constructor(readonly element: MapRender) {} constructor(readonly element: MapRender) {}
onUpdate(): void { onUpdate(): void {

View File

@ -17,7 +17,7 @@ import {
IMapBackgroundConfig, IMapBackgroundConfig,
IMapRenderConfig, IMapRenderConfig,
IMapRenderer, IMapRenderer,
IMapRendererExtends, IMapRendererHooks,
IMapVertexGenerator, IMapVertexGenerator,
IMapViewportController, IMapViewportController,
IMovingBlock, IMovingBlock,
@ -26,13 +26,13 @@ import {
MapTileBehavior, MapTileBehavior,
MapTileSizeTestMode MapTileSizeTestMode
} from './types'; } from './types';
import { ILayerState, ILayerStateHooks, IMapLayer } from '@user/data-state';
import { import {
IMapLayer, Hookable,
IMapLayerData, HookController,
IMapLayerExtends, IHookController,
IMapLayerExtendsController logger
} from '@user/data-state'; } from '@motajs/common';
import { logger } from '@motajs/common';
import { compileProgramWith } from '@motajs/client-base'; import { compileProgramWith } from '@motajs/client-base';
import { isNil, maxBy } from 'lodash-es'; import { isNil, maxBy } from 'lodash-es';
import { IMapDataGetter, MapVertexGenerator } from './vertex'; import { IMapDataGetter, MapVertexGenerator } from './vertex';
@ -57,23 +57,8 @@ const enum BackgroundType {
Tile Tile
} }
interface ILayerRenderData {
/** 顶点数组,与总的数组共享 ArrayBuffer */
readonly vertexArray: Float32Array;
/** 偏移数组,与总的数组共享 ArrayBuffer */
readonly offsetArray: Int16Array;
/** 顶点数组在原数组的起始索引值 */
readonly vertexStart: number;
/** 顶点数组在原数组的终止索引值 */
readonly vertexEnd: number;
/** 偏移数组在原数组的起始索引值 */
readonly offsetStart: number;
/** 偏移数组在原数组的终止索引值 */
readonly offsetEnd: number;
}
export class MapRenderer export class MapRenderer
extends Hookable<IMapRendererHooks>
implements implements
IMapRenderer, IMapRenderer,
IMovingRenderer, IMovingRenderer,
@ -99,17 +84,10 @@ export class MapRenderer
assetWidth: number = 4096; assetWidth: number = 4096;
assetHeight: number = 4096; assetHeight: number = 4096;
/** 拓展列表 */ layerState: ILayerState;
private readonly extendList: Set<IMapRendererExtends> = new Set(); /** 地图状态钩子控制器 */
private layerStateHook: IHookController<ILayerStateHooks>;
/** 这个渲染器添加的图层 */
readonly layers: Set<IMapLayer> = new Set();
/** 图层的 zIndex 映射 */
private layerZIndex: Map<IMapLayer, number> = new Map();
/** 图层的别名 */
private layerAlias: Map<string, IMapLayer> = new Map();
/** 图层到其对应别名的映射 */
private layerAliasInv: Map<IMapLayer, string> = new Map();
/** 排序后的图层 */ /** 排序后的图层 */
private sortedLayers: IMapLayer[] = []; private sortedLayers: IMapLayer[] = [];
/** 图层到排序索引的映射 */ /** 图层到排序索引的映射 */
@ -171,14 +149,12 @@ export class MapRenderer
/** 是否应该更新偏移池 uniform */ /** 是否应该更新偏移池 uniform */
private needUpdateOffsetPool: boolean = true; private needUpdateOffsetPool: boolean = true;
/** 地图渲染数据 */
private layerData: Map<IMapLayer, ILayerRenderData> = new Map();
/** 地图拓展映射 */
private layerController: Map<IMapLayer, IMapLayerExtendsController>;
/** 顶点数组的图层列表是否需要更新 */ /** 顶点数组的图层列表是否需要更新 */
private layerDirty: boolean = false; private layerListDirty: boolean = false;
/** 顶点数组的图层的尺寸是否需要更新 */ /** 顶点数组的图层的尺寸是否需要更新 */
private layerSizeDirty: boolean = false; private layerSizeDirty: boolean = false;
/** 是否整个地图都需要更新,一般只有在地图尺寸等发生变动时才会执行 */
private layerAllDirty: boolean = false;
/** 所有正在移动的图块 */ /** 所有正在移动的图块 */
private movingBlock: Set<IMovingBlock> = new Set(); private movingBlock: Set<IMovingBlock> = new Set();
@ -223,8 +199,15 @@ export class MapRenderer
constructor( constructor(
readonly manager: IMaterialManager, readonly manager: IMaterialManager,
readonly gl: WebGL2RenderingContext, readonly gl: WebGL2RenderingContext,
readonly transform: Transform readonly transform: Transform,
layerState: ILayerState
) { ) {
super();
this.layerState = layerState;
this.layerStateHook = layerState.addHook(
new RendererLayerStateHook(this)
);
this.layerStateHook.load();
// 上下文初始化要依赖于 offsetPool因此提前调用 // 上下文初始化要依赖于 offsetPool因此提前调用
const offsetPool = this.getOffsetPool(); const offsetPool = this.getOffsetPool();
this.offsetPool = offsetPool; this.offsetPool = offsetPool;
@ -233,7 +216,6 @@ export class MapRenderer
v => v / data.tileTextureWidth v => v / data.tileTextureWidth
); );
this.contextData = data; this.contextData = data;
this.layerController = new Map();
this.vertex = new MapVertexGenerator(this, data); this.vertex = new MapVertexGenerator(this, data);
this.autotile = new AutotileProcessor(manager); this.autotile = new AutotileProcessor(manager);
this.tick = this.tick.bind(this); this.tick = this.tick.bind(this);
@ -242,6 +224,7 @@ export class MapRenderer
this.viewport.bindTransform(this.transform); this.viewport.bindTransform(this.transform);
this.tileAnimater = new TextureColumnAnimater(); this.tileAnimater = new TextureColumnAnimater();
this.initVertexPointer(gl, data); this.initVertexPointer(gl, data);
layerState.addHook(new RendererLayerStateHook(this));
} }
/** /**
@ -314,6 +297,12 @@ export class MapRenderer
gl.bindVertexArray(null); gl.bindVertexArray(null);
} }
protected createController(
hook: Partial<IMapRendererHooks>
): IHookController<IMapRendererHooks> {
return new HookController(this, hook);
}
//#endregion //#endregion
//#region 图层处理 //#region 图层处理
@ -322,82 +311,58 @@ export class MapRenderer
* *
*/ */
private sortLayer() { private sortLayer() {
this.sortedLayers = [...this.layers].sort((a, b) => { this.sortedLayers = [...this.layerState.layerList].sort((a, b) => {
const za = this.layerZIndex.get(a) ?? -1; return a.zIndex - b.zIndex;
const zb = this.layerZIndex.get(b) ?? -1;
return za - zb;
}); });
this.sortedLayers.forEach((v, i) => this.layerIndexMap.set(v, i)); this.sortedLayers.forEach((v, i) => this.layerIndexMap.set(v, i));
this.forEachHook((hook, controller) => {
hook.onUpdate?.(controller);
});
this.layerListDirty = true;
} }
addLayer(layer: IMapLayer, identifier?: string): void { private updateAllLayers() {
this.layers.add(layer); this.layerState.layerList.forEach(v => {
this.layerZIndex.set(layer, 0); this.vertex.updateArea(v, 0, 0, v.width, v.height);
if (identifier) { });
this.layerAlias.set(identifier, layer);
}
const ex = new MapRendererExtends(this);
const controller = layer.addExtends(ex);
controller.load();
this.layerController.set(layer, controller);
this.sortLayer();
this.layerDirty = true;
this.layerCount = this.layers.size;
this.resizeLayer();
} }
removeLayer(layer: IMapLayer): void { updateLayerList() {
this.layers.delete(layer);
this.layerData.delete(layer);
this.layerZIndex.delete(layer);
const ex = this.layerController.get(layer);
if (ex) {
ex.unload();
}
this.layerController.delete(layer);
const alias = this.layerAliasInv.get(layer);
if (!isNil(alias)) {
this.layerAlias.delete(alias);
}
this.layerAliasInv.delete(layer);
this.sortLayer(); this.sortLayer();
this.layerDirty = true;
this.layerCount = this.layers.size;
this.resizeLayer(); this.resizeLayer();
this.layerCount = this.layerState.layerList.size;
this.layerAllDirty = true;
}
setLayerState(layerState: ILayerState): void {
this.layerStateHook.unload();
this.layerState = layerState;
this.layerStateHook = layerState.addHook(
new RendererLayerStateHook(this)
);
this.layerStateHook.load();
this.sortLayer();
this.resizeLayer();
this.layerCount = layerState.layerList.size;
this.layerAllDirty = true;
} }
getLayer(identifier: string): IMapLayer | null { getLayer(identifier: string): IMapLayer | null {
return this.layerAlias.get(identifier) ?? null; return this.layerState.getLayerByAlias(identifier) ?? null;
} }
hasLayer(layer: IMapLayer): boolean { hasLayer(layer: IMapLayer): boolean {
return this.layers.has(layer); return this.layerState.hasLayer(layer);
} }
getSortedLayer(): IMapLayer[] { getSortedLayer(): IMapLayer[] {
return this.sortedLayers.slice(); return this.sortedLayers.slice();
} }
setZIndex(layer: IMapLayer, zIndex: number): void {
this.layerZIndex.set(layer, zIndex);
this.sortLayer();
this.layerDirty = true;
}
getZIndex(layer: IMapLayer): number | undefined {
return this.layerZIndex.get(layer);
}
getLayerIndex(layer: IMapLayer): number { getLayerIndex(layer: IMapLayer): number {
return this.layerIndexMap.get(layer) ?? -1; return this.layerIndexMap.get(layer) ?? -1;
} }
getMapLayerData(layer: IMapLayer): Readonly<IMapLayerData> | null {
const ex = this.layerController.get(layer);
const data = ex?.getMapData();
return data ?? null;
}
/** /**
* *
*/ */
@ -409,13 +374,13 @@ export class MapRenderer
} }
this.mapWidth = maxWidth; this.mapWidth = maxWidth;
this.mapHeight = maxHeight; this.mapHeight = maxHeight;
this.layerDirty = true;
this.updateBackgroundVertex( this.updateBackgroundVertex(
this.gl, this.gl,
this.contextData, this.contextData,
this.contextData.backgroundWidth, this.contextData.backgroundWidth,
this.contextData.backgroundHeight this.contextData.backgroundHeight
); );
this.layerAllDirty = true;
} }
//#endregion //#endregion
@ -699,9 +664,9 @@ export class MapRenderer
tileTextureWidth: 4096, tileTextureWidth: 4096,
tileTextureHeight: 4096, tileTextureHeight: 4096,
tileTextureDepth: 1, tileTextureDepth: 1,
backgroundWidth: 32, backgroundWidth: 0,
backgroundHeight: 32, backgroundHeight: 0,
backgroundDepth: 1, backgroundDepth: 0,
tileTextureMark: Symbol(), tileTextureMark: Symbol(),
vertexMark: Symbol() vertexMark: Symbol()
}; };
@ -789,7 +754,6 @@ export class MapRenderer
data.tileTextureMark = this.assetData.mark(); data.tileTextureMark = this.assetData.mark();
gl.bindTexture(gl.TEXTURE_2D_ARRAY, tile); gl.bindTexture(gl.TEXTURE_2D_ARRAY, tile);
this.checkTextureArraySize(gl, data, sourceArray); this.checkTextureArraySize(gl, data, sourceArray);
console.time('texture-upload');
source.forEach((v, i) => { source.forEach((v, i) => {
gl.texSubImage3D( gl.texSubImage3D(
gl.TEXTURE_2D_ARRAY, gl.TEXTURE_2D_ARRAY,
@ -817,7 +781,6 @@ export class MapRenderer
gl.TEXTURE_MIN_FILTER, gl.TEXTURE_MIN_FILTER,
gl.NEAREST gl.NEAREST
); );
console.timeEnd('texture-upload');
} else { } else {
const dirty = this.assetData.dirtySince(data.tileTextureMark); const dirty = this.assetData.dirtySince(data.tileTextureMark);
if (dirty.size === 0) return; if (dirty.size === 0) return;
@ -829,7 +792,6 @@ export class MapRenderer
data, data,
sourceArray sourceArray
); );
console.time('texture-upload');
if (sizeChanged) { if (sizeChanged) {
// 尺寸变化,需要全部重新传递 // 尺寸变化,需要全部重新传递
source.forEach((v, i) => { source.forEach((v, i) => {
@ -866,7 +828,6 @@ export class MapRenderer
); );
}); });
} }
console.timeEnd('texture-upload');
} }
} }
@ -1226,8 +1187,10 @@ export class MapRenderer
gl.NEAREST gl.NEAREST
); );
this.backgroundPending = false; this.backgroundPending = false;
this.extendList.forEach(v => v.onUpdate?.());
gl.bindTexture(gl.TEXTURE_2D_ARRAY, null); gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
this.forEachHook((hook, controller) => {
hook.onUpdate?.(controller);
});
} }
render(): void { render(): void {
@ -1260,14 +1223,18 @@ export class MapRenderer
console.time('layer-check'); console.time('layer-check');
// 图层检查 // 图层检查
if (this.layerDirty) {
this.vertex.updateLayerArray();
this.layerDirty = false;
}
if (this.layerSizeDirty) { if (this.layerSizeDirty) {
this.vertex.resizeMap(); this.vertex.resizeMap();
this.layerSizeDirty = false; this.layerSizeDirty = false;
} }
if (this.layerListDirty) {
this.vertex.updateLayerArray();
this.layerListDirty = false;
}
if (this.layerAllDirty) {
this.updateAllLayers();
this.layerAllDirty = false;
}
this.vertex.checkRebuild(); this.vertex.checkRebuild();
console.timeEnd('layer-check'); console.timeEnd('layer-check');
@ -1352,9 +1319,6 @@ export class MapRenderer
// 由于 WebGL2 没有 glDrawArraysInstancedBaseInstance只能每次渲染的时候临时修改 VBO 读取方式 // 由于 WebGL2 没有 glDrawArraysInstancedBaseInstance只能每次渲染的时候临时修改 VBO 读取方式
const stride = INSTANCED_COUNT * 4; const stride = INSTANCED_COUNT * 4;
gl.bindBuffer(gl.ARRAY_BUFFER, instancedBuffer); gl.bindBuffer(gl.ARRAY_BUFFER, instancedBuffer);
console.log(area);
area.render.forEach(v => { area.render.forEach(v => {
const s = v.startIndex * INSTANCED_COUNT; const s = v.startIndex * INSTANCED_COUNT;
const o1 = s + 0; const o1 = s + 0;
@ -1393,7 +1357,9 @@ export class MapRenderer
h: number h: number
) { ) {
this.vertex.updateArea(layer, x, y, w, h); this.vertex.updateArea(layer, x, y, w, h);
this.extendList.forEach(v => v.onUpdate?.()); this.forEachHook((hook, controller) => {
hook.onUpdate?.(controller);
});
} }
/** /**
@ -1405,7 +1371,9 @@ export class MapRenderer
*/ */
updateLayerBlock(layer: IMapLayer, block: number, x: number, y: number) { updateLayerBlock(layer: IMapLayer, block: number, x: number, y: number) {
this.vertex.updateBlock(layer, block, x, y); this.vertex.updateBlock(layer, block, x, y);
this.extendList.forEach(v => v.onUpdate?.()); this.forEachHook((hook, controller) => {
hook.onUpdate?.(controller);
});
} }
//#endregion //#endregion
@ -1570,48 +1538,50 @@ export class MapRenderer
updateTransform(): void { updateTransform(): void {
this.needUpdateTransform = true; this.needUpdateTransform = true;
this.extendList.forEach(v => v.onUpdate?.()); this.forEachHook((hook, controller) => {
} hook.onUpdate?.(controller);
});
addExtends(ex: IMapRendererExtends): void {
this.extendList.add(ex);
}
close(): void {
this.layers.clear();
this.layerAlias.clear();
this.layerZIndex.clear();
this.extendList.clear();
} }
//#endregion //#endregion
} }
class MapRendererExtends implements IMapLayerExtends { class RendererLayerStateHook implements Partial<ILayerStateHooks> {
readonly id: string = 'map-render';
constructor(readonly renderer: MapRenderer) {} constructor(readonly renderer: MapRenderer) {}
onResize(): void { onChangeBackground(
_: IHookController<ILayerStateHooks>,
tile: number
): void {
this.renderer.setTileBackground(tile);
}
onResizeLayer(): void {
this.renderer.resizeLayer(); this.renderer.resizeLayer();
} }
onUpdateArea( onUpdateLayer(): void {
controller: IMapLayerExtendsController, this.renderer.updateLayerList();
}
onUpdateLayerArea(
_: IHookController<ILayerStateHooks>,
layer: IMapLayer,
x: number, x: number,
y: number, y: number,
width: number, width: number,
height: number height: number
): void { ): void {
this.renderer.updateLayerArea(controller.layer, x, y, width, height); this.renderer.updateLayerArea(layer, x, y, width, height);
} }
onUpdateBlock( onUpdateLayerBlock(
controller: IMapLayerExtendsController, _: IHookController<ILayerStateHooks>,
layer: IMapLayer,
block: number, block: number,
x: number, x: number,
y: number y: number
): void { ): void {
this.renderer.updateLayerBlock(controller.layer, block, x, y); this.renderer.updateLayerBlock(layer, block, x, y);
} }
} }

View File

@ -1,4 +1,10 @@
import { IDirtyMark, IDirtyTracker } from '@motajs/common'; import {
IDirtyMark,
IDirtyTracker,
IHookable,
IHookBase,
IHookController
} from '@motajs/common';
import { ITextureRenderable } from '@motajs/render-assets'; import { ITextureRenderable } from '@motajs/render-assets';
import { Transform } from '@motajs/render-core'; import { Transform } from '@motajs/render-core';
import { import {
@ -7,7 +13,7 @@ import {
IMaterialManager, IMaterialManager,
ITrackedAssetData ITrackedAssetData
} from '@user/client-base'; } from '@user/client-base';
import { IMapLayer } from '@user/data-state'; import { ILayerState, IMapLayer } from '@user/data-state';
import { TimingFn } from 'mutate-animate'; import { TimingFn } from 'mutate-animate';
export const enum MapBackgroundRepeat { export const enum MapBackgroundRepeat {
@ -221,14 +227,14 @@ export interface IMovingBlock {
destroy(): void; destroy(): void;
} }
export interface IMapRendererExtends { export interface IMapRendererHooks extends IHookBase {
/** /**
* *
*/ */
onUpdate?(): void; onUpdate(controller: IHookController<this>): void;
} }
export interface IMapRenderer { export interface IMapRenderer extends IHookable<IMapRendererHooks> {
/** 地图渲染器使用的资源管理器 */ /** 地图渲染器使用的资源管理器 */
readonly manager: IMaterialManager; readonly manager: IMaterialManager;
/** 画布渲染上下文 */ /** 画布渲染上下文 */
@ -242,6 +248,8 @@ export interface IMapRenderer {
readonly viewport: IMapViewportController; readonly viewport: IMapViewportController;
/** 顶点数组生成器 */ /** 顶点数组生成器 */
readonly vertex: IMapVertexGenerator; readonly vertex: IMapVertexGenerator;
/** 使用的地图状态对象 */
readonly layerState: ILayerState;
/** 地图宽度 */ /** 地图宽度 */
readonly mapWidth: number; readonly mapWidth: number;
@ -268,12 +276,6 @@ export interface IMapRenderer {
*/ */
useAsset(asset: ITrackedAssetData): void; useAsset(asset: ITrackedAssetData): void;
/**
*
* @param ex
*/
addExtends(ex: IMapRendererExtends): void;
/** /**
* 使 * 使
*/ */
@ -286,17 +288,10 @@ export interface IMapRenderer {
render(gl: WebGL2RenderingContext): void; render(gl: WebGL2RenderingContext): void;
/** /**
* * 使
* @param layer * @param layerState
* @param identifier {@link getLayer}
*/ */
addLayer(layer: IMapLayer, identifier?: string): void; setLayerState(layerState: ILayerState): void;
/**
*
* @param layer
*/
removeLayer(layer: IMapLayer): void;
/** /**
* *
@ -315,19 +310,6 @@ export interface IMapRenderer {
*/ */
getSortedLayer(): IMapLayer[]; getSortedLayer(): IMapLayer[];
/**
*
* @param layer
* @param zIndex
*/
setZIndex(layer: IMapLayer, zIndex: number): void;
/**
*
* @param layer
*/
getZIndex(layer: IMapLayer): number | undefined;
/** /**
* *
* @param layer * @param layer
@ -394,11 +376,6 @@ export interface IMapRenderer {
*/ */
setCellSize(width: number, height: number): void; setCellSize(width: number, height: number): void;
/**
*
*/
getTransform(): Transform;
/** /**
* *
* @param layer * @param layer
@ -449,11 +426,6 @@ export interface IMapRenderer {
* @param y * @param y
*/ */
setTileAlpha(layer: IMapLayer, alpha: number, x: number, y: number): void; setTileAlpha(layer: IMapLayer, alpha: number, x: number, y: number): void;
/**
* 使
*/
close(): void;
} }
export interface IMapVertexArray { export interface IMapVertexArray {

View File

@ -1,4 +1,4 @@
import { IMapLayer, IMapLayerData } from '@user/data-state'; import { IMapLayer } from '@user/data-state';
import { import {
IBlockData, IBlockData,
IBlockSplitter, IBlockSplitter,
@ -24,8 +24,6 @@ import { BlockCls, IMaterialFramedData } from '@user/client-base';
import { IRect, SizedCanvasImageSource } from '@motajs/render-assets'; import { IRect, SizedCanvasImageSource } from '@motajs/render-assets';
import { INSTANCED_COUNT } from './constant'; import { INSTANCED_COUNT } from './constant';
// todo: 潜在优化点:顶点数组的 z 坐标以及纹理的 z 坐标可以换为实例化绘制
export interface IMapDataGetter { export interface IMapDataGetter {
/** 图块缩小行为,即图块比格子大时应该如何处理 */ /** 图块缩小行为,即图块比格子大时应该如何处理 */
readonly tileMinifyBehavior: MapTileBehavior; readonly tileMinifyBehavior: MapTileBehavior;
@ -38,12 +36,6 @@ export interface IMapDataGetter {
/** 图块大小与格子大小判断方式 */ /** 图块大小与格子大小判断方式 */
readonly tileTestMode: MapTileSizeTestMode; readonly tileTestMode: MapTileSizeTestMode;
/**
*
* @param layer
*/
getMapLayerData(layer: IMapLayer): Readonly<IMapLayerData> | null;
/** /**
* *
* @param source * @param source
@ -593,8 +585,8 @@ export class MapVertexGenerator
const block = this.block.getBlockByDataLoc(x, y); const block = this.block.getBlockByDataLoc(x, y);
if (!block) return; if (!block) return;
const vertex = block.data.getLayerData(layer); const vertex = block.data.getLayerData(layer);
const data = this.renderer.getMapLayerData(layer); const data = layer.getMapRef();
if (!vertex || !data) return; if (!vertex) return;
const { array } = data; const { array } = data;
const dx = x - block.dataX; const dx = x - block.dataX;
const dy = y - block.dataY; const dy = y - block.dataY;
@ -789,8 +781,8 @@ export class MapVertexGenerator
if (!dirty || !dirty.dirty) return; if (!dirty || !dirty.dirty) return;
block.data.updated(); block.data.updated();
const vertex = block.data.getLayerData(layer); const vertex = block.data.getLayerData(layer);
const mapData = this.renderer.getMapLayerData(layer); const mapData = layer.getMapRef();
if (!vertex || !mapData) return; if (!vertex) return;
const { array } = mapData; const { array } = mapData;
const { dirtyLeft, dirtyTop, dirtyRight, dirtyBottom } = dirty; const { dirtyLeft, dirtyTop, dirtyRight, dirtyBottom } = dirty;
for (let nx = dirtyLeft; nx < dirtyRight; nx++) { for (let nx = dirtyLeft; nx < dirtyRight; nx++) {
@ -823,9 +815,9 @@ export class MapVertexGenerator
//#region 图块配置 //#region 图块配置
enableStaticFrameAnimate(layer: IMapLayer, x: number, y: number): void { enableStaticFrameAnimate(layer: IMapLayer, x: number, y: number): void {
const data = this.renderer.getMapLayerData(layer); const data = layer.getMapRef();
const block = this.block.getBlockByDataLoc(x, y); const block = this.block.getBlockByDataLoc(x, y);
if (!data || !block) return; if (!block) return;
const vertexArray = block.data.getLayerInstanced(layer); const vertexArray = block.data.getLayerInstanced(layer);
if (!vertexArray) return; if (!vertexArray) return;
const mapIndex = y * this.mapWidth + x; const mapIndex = y * this.mapWidth + x;

View File

@ -48,37 +48,9 @@ import { mainUIController } from './controller';
import { LayerGroup } from '../elements'; import { LayerGroup } from '../elements';
import { isNil } from 'lodash-es'; import { isNil } from 'lodash-es';
import { materials } from '@user/client-base'; import { materials } from '@user/client-base';
import { IRenderLayerData } from '../map/element';
const MainScene = defineComponent(() => { const MainScene = defineComponent(() => {
//#region 基本定义 //#region 基本定义
const layerList: IRenderLayerData[] = [
{
layer: state.layer.getLayerByAlias('bg')!,
zIndex: 10,
alias: 'bg'
},
{
layer: state.layer.getLayerByAlias('bg2')!,
zIndex: 20,
alias: 'bg2'
},
{
layer: state.layer.getLayerByAlias('event')!,
zIndex: 30,
alias: 'event'
},
{
layer: state.layer.getLayerByAlias('fg')!,
zIndex: 40,
alias: 'fg'
},
{
layer: state.layer.getLayerByAlias('fg2')!,
zIndex: 50,
alias: 'fg2'
}
];
const mainTextboxProps: Props<typeof Textbox> = { const mainTextboxProps: Props<typeof Textbox> = {
text: '', text: '',
@ -312,7 +284,7 @@ const MainScene = defineComponent(() => {
onMove={moveMap} onMove={moveMap}
> >
<map-render <map-render
layerList={layerList} layerState={state.layer}
loc={[0, 0, MAP_WIDTH, MAP_HEIGHT]} loc={[0, 0, MAP_WIDTH, MAP_HEIGHT]}
/> />
<Textbox id="main-textbox" {...mainTextboxProps}></Textbox> <Textbox id="main-textbox" {...mainTextboxProps}></Textbox>

View File

@ -1,18 +1,43 @@
import { logger } from '@motajs/common'; import {
import { IMapLayer, MapLayer } from '../map'; Hookable,
import { ILayerState } from './types'; HookController,
IHookController,
logger
} from '@motajs/common';
import {
IMapLayer,
IMapLayerHookController,
IMapLayerHooks,
MapLayer
} from '../map';
import { ILayerState, ILayerStateHooks } from './types';
export class LayerState implements ILayerState { export class LayerState
readonly layerList: WeakSet<IMapLayer> = new WeakSet(); extends Hookable<ILayerStateHooks>
implements ILayerState
{
readonly layerList: Set<IMapLayer> = new Set();
/** 图层到图层别名映射 */ /** 图层到图层别名映射 */
readonly layerAliasMap: WeakMap<IMapLayer, string> = new WeakMap(); readonly layerAliasMap: WeakMap<IMapLayer, string> = new WeakMap();
/** 图层别名到图层的映射 */ /** 图层别名到图层的映射 */
readonly aliasLayerMap: Map<symbol, IMapLayer> = new Map(); readonly aliasLayerMap: Map<symbol, IMapLayer> = new Map();
/** 背景图块 */
private backgroundTile: number = 0;
/** 图层钩子映射 */
private layerHookMap: Map<IMapLayer, IMapLayerHookController> = new Map();
addLayer(width: number, height: number): IMapLayer { addLayer(width: number, height: number): IMapLayer {
const array = new Uint32Array(width * height); const array = new Uint32Array(width * height);
const layer = new MapLayer(array, width, height); const layer = new MapLayer(array, width, height);
this.layerList.add(layer); this.layerList.add(layer);
this.forEachHook((hook, controller) => {
hook.onUpdateLayer?.(controller, this.layerList);
});
const controller = layer.addHook(new StateMapLayerHook(this));
this.layerHookMap.set(layer, controller);
controller.load();
return layer; return layer;
} }
@ -24,6 +49,17 @@ export class LayerState implements ILayerState {
this.aliasLayerMap.delete(symbol); this.aliasLayerMap.delete(symbol);
this.layerAliasMap.delete(layer); this.layerAliasMap.delete(layer);
} }
this.forEachHook((hook, controller) => {
hook.onUpdateLayer?.(controller, this.layerList);
});
const controller = this.layerHookMap.get(layer);
if (!controller) return;
controller.unload();
this.layerHookMap.delete(layer);
}
hasLayer(layer: IMapLayer): boolean {
return this.layerList.has(layer);
} }
setLayerAlias(layer: IMapLayer, alias: string): void { setLayerAlias(layer: IMapLayer, alias: string): void {
@ -57,4 +93,58 @@ export class LayerState implements ILayerState {
layer.resize2(width, height); layer.resize2(width, height);
} }
} }
setBackground(tile: number): void {
this.backgroundTile = tile;
this.forEachHook((hook, controller) => {
hook.onChangeBackground?.(controller, tile);
});
}
getBackground(): number {
return this.backgroundTile;
}
protected createController(
hook: Partial<ILayerStateHooks>
): IHookController<ILayerStateHooks> {
return new HookController(this, hook);
}
}
class StateMapLayerHook implements Partial<IMapLayerHooks> {
constructor(readonly state: LayerState) {}
onUpdateArea(
controller: IMapLayerHookController,
x: number,
y: number,
width: number,
height: number
): void {
this.state.forEachHook((hook, c) => {
hook.onUpdateLayerArea?.(c, controller.layer, x, y, width, height);
});
}
onUpdateBlock(
controller: IMapLayerHookController,
block: number,
x: number,
y: number
): void {
this.state.forEachHook((hook, c) => {
hook.onUpdateLayerBlock?.(c, controller.layer, block, x, y);
});
}
onResize(
controller: IMapLayerHookController,
width: number,
height: number
): void {
this.state.forEachHook((hook, c) => {
hook.onResizeLayer?.(c, controller.layer, width, height);
});
}
} }

View File

@ -1,8 +1,76 @@
import { IHookable, IHookBase, IHookController } from '@motajs/common';
import { IMapLayer } from '../map'; import { IMapLayer } from '../map';
export interface ILayerState { export interface ILayerStateHooks extends IHookBase {
/**
*
* @param controller
* @param tile
*/
onChangeBackground(controller: IHookController<this>, tile: number): void;
/**
*
* @param controller
* @param layerList
*/
onUpdateLayer(
controller: IHookController<this>,
layerList: Set<IMapLayer>
): void;
/**
*
* @param controller
* @param layer
* @param x
* @param y
* @param width
* @param height
*/
onUpdateLayerArea(
controller: IHookController<this>,
layer: IMapLayer,
x: number,
y: number,
width: number,
height: number
): void;
/**
*
* @param controller
* @param layer
* @param block
* @param x
* @param y
*/
onUpdateLayerBlock(
controller: IHookController<this>,
layer: IMapLayer,
block: number,
x: number,
y: number
): void;
/**
*
* @param controller
* @param layer
* @param width
* @param height
*/
onResizeLayer(
controller: IHookController<this>,
layer: IMapLayer,
width: number,
height: number
): void;
}
export interface ILayerState extends IHookable<ILayerStateHooks> {
/** 地图列表 */ /** 地图列表 */
readonly layerList: WeakSet<IMapLayer>; readonly layerList: Set<IMapLayer>;
/** /**
* *
@ -17,6 +85,12 @@ export interface ILayerState {
*/ */
removeLayer(layer: IMapLayer): void; removeLayer(layer: IMapLayer): void;
/**
*
* @param layer
*/
hasLayer(layer: IMapLayer): boolean;
/** /**
* *
* @param layer * @param layer
@ -49,6 +123,17 @@ export interface ILayerState {
height: number, height: number,
keepBlock?: boolean keepBlock?: boolean
): void; ): void;
/**
*
* @param tile
*/
setBackground(tile: number): void;
/**
* 0
*/
getBackground(): number;
} }
export interface ICoreState { export interface ICoreState {

View File

@ -2,25 +2,19 @@ import { isNil } from 'lodash-es';
import { import {
IMapLayer, IMapLayer,
IMapLayerData, IMapLayerData,
IMapLayerExtends, IMapLayerHookController,
IMapLayerExtendsController IMapLayerHooks
} from './types'; } from './types';
import { logger } from '@motajs/common'; import { Hookable, HookController, logger } from '@motajs/common';
interface IExtendsData { export class MapLayer
readonly ex: IMapLayerExtends; extends Hookable<IMapLayerHooks, IMapLayerHookController>
readonly controller: IMapLayerExtendsController; implements IMapLayer
} {
export class MapLayer implements IMapLayer {
width: number; width: number;
height: number; height: number;
empty: boolean = true; empty: boolean = true;
zIndex: number = 0;
/** 已经加载完毕的图层拓展 */
private loadedExtends: Set<IExtendsData> = new Set();
/** 添加的图层拓展 */
private addedExtends: Map<string, IExtendsData> = new Map();
/** 地图图块数组 */ /** 地图图块数组 */
private mapArray: Uint32Array; private mapArray: Uint32Array;
@ -28,6 +22,7 @@ export class MapLayer implements IMapLayer {
private mapData: IMapLayerData; private mapData: IMapLayerData;
constructor(array: Uint32Array, width: number, height: number) { constructor(array: Uint32Array, width: number, height: number) {
super();
this.width = width; this.width = width;
this.height = height; this.height = height;
const area = width * height; const area = width * height;
@ -42,9 +37,6 @@ export class MapLayer implements IMapLayer {
resize(width: number, height: number): void { resize(width: number, height: number): void {
if (this.width === width && this.height === height) { if (this.width === width && this.height === height) {
this.loadedExtends.forEach(v => {
v.ex.onResize?.(v.controller, width, height);
});
return; return;
} }
this.mapData.expired = true; this.mapData.expired = true;
@ -78,17 +70,14 @@ export class MapLayer implements IMapLayer {
expired: false, expired: false,
array: this.mapArray array: this.mapArray
}; };
this.loadedExtends.forEach(v => { this.forEachHook((hook, controller) => {
v.ex.onResize?.(v.controller, width, height); hook.onResize?.(controller, width, height);
}); });
} }
resize2(width: number, height: number): void { resize2(width: number, height: number): void {
if (this.width === width && this.height === height) { if (this.width === width && this.height === height) {
this.mapArray.fill(0); this.mapArray.fill(0);
this.loadedExtends.forEach(v => {
v.ex.onResize?.(v.controller, width, height);
});
return; return;
} }
this.mapData.expired = true; this.mapData.expired = true;
@ -99,17 +88,18 @@ export class MapLayer implements IMapLayer {
expired: false, expired: false,
array: this.mapArray array: this.mapArray
}; };
this.loadedExtends.forEach(v => {
v.ex.onResize?.(v.controller, width, height);
});
this.empty = true; this.empty = true;
this.forEachHook((hook, controller) => {
hook.onResize?.(controller, width, height);
});
} }
setBlock(block: number, x: number, y: number): void { setBlock(block: number, x: number, y: number): void {
const index = y * this.width + x; const index = y * this.width + x;
if (block === this.mapArray[index]) return;
this.mapArray[index] = block; this.mapArray[index] = block;
this.loadedExtends.forEach(v => { this.forEachHook((hook, controller) => {
v.ex.onUpdateBlock?.(v.controller, block, x, y); hook.onUpdateBlock?.(controller, block, x, y);
}); });
if (block !== 0) { if (block !== 0) {
this.empty = false; this.empty = false;
@ -131,8 +121,8 @@ export class MapLayer implements IMapLayer {
const height = Math.ceil(array.length / width); const height = Math.ceil(array.length / width);
if (width === this.width && height === this.height) { if (width === this.width && height === this.height) {
this.mapArray.set(array); this.mapArray.set(array);
this.loadedExtends.forEach(v => { this.forEachHook((hook, controller) => {
v.ex.onUpdateArea?.(v.controller, x, y, width, height); hook.onUpdateArea?.(controller, x, y, width, height);
}); });
return; return;
} }
@ -159,8 +149,8 @@ export class MapLayer implements IMapLayer {
} }
this.mapArray.set(array.subarray(start, start + nw), offset); this.mapArray.set(array.subarray(start, start + nw), offset);
} }
this.loadedExtends.forEach(v => { this.forEachHook((hook, controller) => {
v.ex.onUpdateArea?.(v.controller, x, y, width, height); hook.onUpdateArea?.(controller, x, y, width, height);
}); });
this.empty &&= empty; this.empty &&= empty;
} }
@ -213,51 +203,32 @@ export class MapLayer implements IMapLayer {
return this.mapData; return this.mapData;
} }
loadExtends(ex: IMapLayerExtends): boolean { protected createController(
if (!this.addedExtends.has(ex.id)) return false; hook: Partial<IMapLayerHooks>
ex.awake?.(); ): IMapLayerHookController {
const data = this.addedExtends.get(ex.id)!; return new MapLayerHookController(this, hook);
this.loadedExtends.add(data);
return true;
} }
addExtends(ex: IMapLayerExtends): IMapLayerExtendsController { setZIndex(zIndex: number): void {
const controller = new MapLayerExtendsController(this, ex); this.zIndex = zIndex;
this.addedExtends.set(ex.id, {
ex,
controller
});
return controller;
}
removeExtends(ex: IMapLayerExtends | string): void {
const id = typeof ex === 'string' ? ex : ex.id;
const data = this.addedExtends.get(id);
if (!data) return;
data.ex.destroy?.();
this.addedExtends.delete(id);
this.loadedExtends.delete(data);
} }
} }
class MapLayerExtendsController implements IMapLayerExtendsController { class MapLayerHookController
loaded: boolean = false; extends HookController<IMapLayerHooks>
implements IMapLayerHookController
{
hookable: MapLayer;
constructor( constructor(
readonly layer: MapLayer, readonly layer: MapLayer,
readonly ex: IMapLayerExtends hook: Partial<IMapLayerHooks>
) {} ) {
super(layer, hook);
load(): void { this.hookable = layer;
this.loaded = this.layer.loadExtends(this.ex);
} }
getMapData(): Readonly<IMapLayerData> { getMapData(): Readonly<IMapLayerData> {
return this.layer.getMapRef(); return this.layer.getMapRef();
} }
unload(): void {
this.layer.removeExtends(this.ex);
this.loaded = false;
}
} }

View File

@ -1,3 +1,5 @@
import { IHookable, IHookBase, IHookController } from '@motajs/common';
export interface IMapLayerData { export interface IMapLayerData {
/** 当前引用是否过期,当地图图层内部的地图数组引用更新时,此项会变为 `true` */ /** 当前引用是否过期,当地图图层内部的地图数组引用更新时,此项会变为 `true` */
expired: boolean; expired: boolean;
@ -5,26 +7,15 @@ export interface IMapLayerData {
array: Uint32Array; array: Uint32Array;
} }
export interface IMapLayerHooks { export interface IMapLayerHooks extends IHookBase {
/** /**
* * `resize`
* @param dependencies
*/
awake(): void;
/**
*
*/
destroy(): void;
/**
*
* @param controller * @param controller
* @param width * @param width
* @param height * @param height
*/ */
onResize( onResize(
controller: IMapLayerExtendsController, controller: IMapLayerHookController,
width: number, width: number,
height: number height: number
): void; ): void;
@ -38,7 +29,7 @@ export interface IMapLayerHooks {
* @param height * @param height
*/ */
onUpdateArea( onUpdateArea(
controller: IMapLayerExtendsController, controller: IMapLayerHookController,
x: number, x: number,
y: number, y: number,
width: number, width: number,
@ -53,47 +44,37 @@ export interface IMapLayerHooks {
* @param y * @param y
*/ */
onUpdateBlock( onUpdateBlock(
controller: IMapLayerExtendsController, controller: IMapLayerHookController,
block: number, block: number,
x: number, x: number,
y: number y: number
): void; ): void;
} }
export interface IMapLayerExtends extends Partial<IMapLayerHooks> { export interface IMapLayerHookController
/** 这个拓展对象的标识符 */ extends IHookController<IMapLayerHooks> {
readonly id: string;
}
export interface IMapLayerExtendsController {
/** 当前图层拓展是否已经被加载 */
readonly loaded: boolean;
/** 拓展所属的图层对象 */ /** 拓展所属的图层对象 */
readonly layer: IMapLayer; readonly layer: IMapLayer;
/**
*
*/
load(): void;
/** /**
* *
*/ */
getMapData(): Readonly<IMapLayerData>; getMapData(): Readonly<IMapLayerData>;
/**
*
*/
unload(): void;
} }
export interface IMapLayer { export interface IMapLayer
extends IHookable<IMapLayerHooks, IMapLayerHookController> {
/** 地图宽度 */ /** 地图宽度 */
readonly width: number; readonly width: number;
/** 地图高度 */ /** 地图高度 */
readonly height: number; readonly height: number;
/** 地图是否全部空白,此值具有保守性,即如果其为 `true`,则地图一定空白,但是如果其为 `false`,那么地图也有可能空白 */ /**
*
* `true` `false`
*/
readonly empty: boolean; readonly empty: boolean;
/** 图层纵深 */
readonly zIndex: number;
/** /**
* *
@ -153,15 +134,13 @@ export interface IMapLayer {
): Uint32Array; ): Uint32Array;
/** /**
* 使 *
* @param ex
* @returns
*/ */
addExtends(ex: IMapLayerExtends): IMapLayerExtendsController; getMapRef(): IMapLayerData;
/** /**
* *
* @param ex * @param zIndex
*/ */
removeExtends(ex: IMapLayerExtends | string): void; setZIndex(zIndex: number): void;
} }

View File

@ -0,0 +1,82 @@
import { logger } from './logger';
import { IHookable, IHookBase, IHookController, IHookObject } from './types';
export abstract class Hookable<
H extends IHookBase = IHookBase,
C extends IHookController<H> = IHookController<H>
> implements IHookable<H, C>
{
/** 加载完成的钩子列表 */
protected readonly loadedList: Set<IHookObject<H, C>> = new Set();
/** 钩子列表 */
private readonly hookList: Set<IHookObject<H, C>> = new Set();
/** 钩子对象到钩子存储对象的映射 */
private readonly hookMap: Map<Partial<H>, IHookObject<H, C>> = new Map();
/** 钩子控制器到钩子存储对象的映射 */
private readonly controllerMap: Map<C, IHookObject<H, C>> = new Map();
/**
*
* @param hook
*/
protected abstract createController(hook: Partial<H>): C;
addHook(hook: Partial<H>): C {
const controller = this.createController(hook);
const obj: IHookObject<H, C> = { hook, controller };
this.hookMap.set(hook, obj);
this.controllerMap.set(controller, obj);
this.hookList.add(obj);
return controller;
}
loadHook(hook: Partial<H>): void {
const obj = this.hookMap.get(hook);
if (!obj) {
logger.warn(85);
return;
}
hook.awake?.(obj.controller);
this.loadedList.add(obj);
}
removeHook(hook: Partial<H>): void {
const obj = this.hookMap.get(hook);
if (!obj) return;
obj.hook.destroy?.(obj.controller);
this.hookList.delete(obj);
this.loadedList.delete(obj);
this.hookMap.delete(hook);
this.controllerMap.delete(obj.controller);
}
removeHookByController(hook: C): void {
const obj = this.controllerMap.get(hook);
if (!obj) return;
obj.hook.destroy?.(obj.controller);
this.hookList.delete(obj);
this.loadedList.delete(obj);
this.controllerMap.delete(hook);
this.hookMap.delete(obj.hook);
}
forEachHook(fn: (hook: Partial<H>, controller: C) => void): void {
this.loadedList.forEach(v => fn(v.hook, v.controller));
}
}
export class HookController<H extends IHookBase> implements IHookController<H> {
constructor(
readonly hookable: IHookable<H, IHookController<H>>,
readonly hook: Partial<H>
) {}
load(): void {
this.hookable.loadHook(this.hook);
}
unload(): void {
this.hookable.removeHookByController(this);
}
}

View File

@ -1,4 +1,5 @@
export * from './dirtyTracker'; export * from './dirtyTracker';
export * from './hook';
export * from './logger'; export * from './logger';
export * from './utils'; export * from './utils';
export * from './types'; export * from './types';

View File

@ -41,7 +41,7 @@
"39": "Offset pool size exceeds WebGL2 limitation, ensure size type of your big image is less than $1.", "39": "Offset pool size exceeds WebGL2 limitation, ensure size type of your big image is less than $1.",
"40": "Material used by map block $1 is not found in built asset. Please ensure you have pack it into asset.", "40": "Material used by map block $1 is not found in built asset. Please ensure you have pack it into asset.",
"41": "You are trying to use a texture on moving block whose offset is not in the offset pool, please build it into asset after loading.", "41": "You are trying to use a texture on moving block whose offset is not in the offset pool, please build it into asset after loading.",
"42": "The layerList property of map-render element is required.", "42": "The layerState property of map-render element is required.",
"1101": "Shadow extension needs 'floor-hero' extension as dependency.", "1101": "Shadow extension needs 'floor-hero' extension as dependency.",
"1201": "Floor-damage extension needs 'floor-binder' extension as dependency.", "1201": "Floor-damage extension needs 'floor-binder' extension as dependency.",
"1301": "Portal extension need 'floor-binder' extension as dependency.", "1301": "Portal extension need 'floor-binder' extension as dependency.",
@ -132,6 +132,7 @@
"82": "Big image offset size is larger than 64. Considier reduce big image offset bucket.", "82": "Big image offset size is larger than 64. Considier reduce big image offset bucket.",
"83": "It seems that you call '$1' too frequently. This will extremely affect game's performance, so considering integrate them into one 'updateArea' or 'updateBlockList' call.", "83": "It seems that you call '$1' too frequently. This will extremely affect game's performance, so considering integrate them into one 'updateArea' or 'updateBlockList' call.",
"84": "Cannot set alias '$1' for layer, since '$1' is already an alias for another layer.", "84": "Cannot set alias '$1' for layer, since '$1' is already an alias for another layer.",
"85": "Hook to load does not belong to current hookable object.",
"1001": "Item-detail extension needs 'floor-binder' and 'floor-damage' extension as dependency.", "1001": "Item-detail extension needs 'floor-binder' and 'floor-damage' extension as dependency.",
"1101": "Cannot add new effect to point effect instance, for there's no more reserve space for it. Please increase the max count of the instance." "1101": "Cannot add new effect to point effect instance, for there's no more reserve space for it. Please increase the max count of the instance."
} }

View File

@ -1,3 +1,5 @@
//#region 脏标记
export interface IDirtyMarker<T> { export interface IDirtyMarker<T> {
/** /**
* *
@ -32,3 +34,83 @@ export interface IDirtyTracker<T> {
*/ */
hasMark(symbol: IDirtyMark): boolean; hasMark(symbol: IDirtyMark): boolean;
} }
//#endregion
//#region 钩子
export interface IHookObject<
H extends IHookBase,
C extends IHookController<H>
> {
/** 钩子对象 */
readonly hook: Partial<H>;
/** 钩子控制器 */
readonly controller: C;
}
export interface IHookController<H extends IHookBase = IHookBase> {
/** 控制器的钩子对象 */
readonly hook: Partial<H>;
/**
*
*/
load(): void;
/**
*
*/
unload(): void;
}
export interface IHookBase {
/**
*
* @param controller
*/
awake(controller: IHookController<this>): void;
/**
*
* @param controller
*/
destroy(controller: IHookController<this>): void;
}
export interface IHookable<
H extends IHookBase = IHookBase,
C extends IHookController<H> = IHookController<H>
> {
/**
*
* @param hook
*/
addHook(hook: Partial<H>): C;
/**
*
* @param hook
*/
loadHook(hook: Partial<H>): void;
/**
* `destroy`
* @param hook
*/
removeHook(hook: Partial<H>): void;
/**
*
* @param hook
*/
removeHookByController(hook: C): void;
/**
*
* @param fn
*/
forEachHook(fn: (hook: Partial<H>, controller: C) => void): void;
}
//#endregion

View File

@ -199,6 +199,9 @@ var functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a = {
const array = new Uint32Array(fg2.flat()); const array = new Uint32Array(fg2.flat());
fg2Layer.putMapData(array, 0, 0, width); fg2Layer.putMapData(array, 0, 0, width);
} }
const back = core.floors[floorId].defaultGround;
const id = core.maps.getNumberById(back);
state.layer.setBackground(id);
// 设置勇士的位置 // 设置勇士的位置
heroLoc.direction = core.turnDirection(heroLoc.direction); heroLoc.direction = core.turnDirection(heroLoc.direction);