diff --git a/packages-user/client-modules/src/render/map/element.ts b/packages-user/client-modules/src/render/map/element.ts index 680017f..225a8ae 100644 --- a/packages-user/client-modules/src/render/map/element.ts +++ b/packages-user/client-modules/src/render/map/element.ts @@ -4,57 +4,41 @@ import { Transform } from '@motajs/render-core'; import { ILayerState, state } from '@user/data-state'; -import { IMapRenderer, IMapRendererHooks } from './types'; +import { IMapRenderer } from './types'; import { MapRenderer } from './renderer'; import { materials } from '@user/client-base'; import { ElementNamespace, ComponentInternalInstance } from 'vue'; import { CELL_HEIGHT, CELL_WIDTH, MAP_HEIGHT, MAP_WIDTH } from '../shared'; -import { IHookController } from '@motajs/common'; export class MapRender extends RenderItem { /** 地图渲染器 */ readonly renderer: IMapRenderer; - /** 地图视角变换矩阵 */ - readonly camera: Transform = new Transform(); - - /** 地图画布 */ - readonly canvas: HTMLCanvasElement; - /** 画布上下文 */ - readonly gl: WebGL2RenderingContext; - - private rendererHook: IHookController; constructor(readonly layerState: ILayerState) { super('static'); - this.canvas = document.createElement('canvas'); - const gl = this.canvas.getContext('webgl2')!; - this.gl = gl; - - this.renderer = new MapRenderer( - materials, - this.gl, - this.camera, - state.layer - ); + this.renderer = new MapRenderer(materials, state.layer); this.renderer.setLayerState(layerState); this.renderer.useAsset(materials.trackedAsset); - this.rendererHook = this.renderer.addHook(new RendererUpdateHook(this)); - this.rendererHook.load(); + this.renderer.setCanvasSize(this.width, this.height); this.renderer.setCellSize(CELL_WIDTH, CELL_HEIGHT); this.renderer.setRenderSize(MAP_WIDTH, MAP_HEIGHT); - this.delegateTicker(time => this.renderer.tick(time)); - - gl.viewport(0, 0, this.canvas.width, this.canvas.height); + this.delegateTicker(time => { + this.renderer.tick(time); + if (this.renderer.needUpdate()) { + this.update(); + } + }); } private sizeGL(width: number, height: number) { const ratio = this.highResolution ? devicePixelRatio : 1; const scale = ratio * this.scale; - this.canvas.width = width * scale; - this.canvas.height = height * scale; - this.gl.viewport(0, 0, this.canvas.width, this.canvas.height); + const w = width * scale; + const h = height * scale; + this.renderer.setCanvasSize(w, h); + this.renderer.setViewport(0, 0, w, h); } onResize(scale: number): void { @@ -75,11 +59,9 @@ export class MapRender extends RenderItem { } protected render(canvas: MotaOffscreenCanvas2D): void { - console.time('map-element-render'); - this.renderer.render(this.gl); - - canvas.ctx.drawImage(this.canvas, 0, 0, canvas.width, canvas.height); - console.timeEnd('map-element-render'); + this.renderer.clear(true, true); + const map = this.renderer.render(); + canvas.ctx.drawImage(map, 0, 0, canvas.width, canvas.height); } patchProp( @@ -98,11 +80,3 @@ export class MapRender extends RenderItem { super.patchProp(key, prevValue, nextValue, namespace, parentComponent); } } - -class RendererUpdateHook implements Partial { - constructor(readonly element: MapRender) {} - - onUpdate(): void { - this.element.update(); - } -} diff --git a/packages-user/client-modules/src/render/map/moving.ts b/packages-user/client-modules/src/render/map/moving.ts index 06623f2..97c02cb 100644 --- a/packages-user/client-modules/src/render/map/moving.ts +++ b/packages-user/client-modules/src/render/map/moving.ts @@ -3,6 +3,7 @@ import { IMapRenderer, IMapVertexGenerator, IMovingBlock } from './types'; import { IMaterialFramedData, IMaterialManager } from '@user/client-base'; import { logger } from '@motajs/common'; import { IMapLayer } from '@user/data-state'; +import { DynamicBlockStatus } from './status'; export interface IMovingRenderer { /** 素材管理器 */ @@ -22,13 +23,13 @@ export interface IMovingRenderer { deleteMoving(block: IMovingBlock): void; } -export class MovingBlock implements IMovingBlock { +export class MovingBlock extends DynamicBlockStatus implements IMovingBlock { readonly texture: IMaterialFramedData; readonly tile: number; readonly renderer: IMovingRenderer; - readonly index: number; readonly layer: IMapLayer; + index: number; x: number = 0; y: number = 0; @@ -59,7 +60,10 @@ export class MovingBlock implements IMovingBlock { /** 动画开始时纵坐标 */ private startY: number = 0; /** 当前动画是否已经结束 */ - private end: boolean = false; + private end: boolean = true; + + /** 是否通过 `setPos` 设置了位置 */ + private posUpdated: boolean = false; /** 兑现函数 */ private promiseFunc: () => void = () => {}; @@ -68,15 +72,12 @@ export class MovingBlock implements IMovingBlock { renderer: IMovingRenderer & IMapRenderer, index: number, layer: IMapLayer, - block: number | IMaterialFramedData, - x: number, - y: number + block: number | IMaterialFramedData ) { + super(layer, renderer.vertex, index); this.renderer = renderer; this.index = index; this.layer = layer; - this.x = x; - this.y = y; if (typeof block === 'number') { this.texture = renderer.manager.getTile(block)!; this.tile = block; @@ -92,6 +93,13 @@ export class MovingBlock implements IMovingBlock { } } + setPos(x: number, y: number): void { + if (!this.end) return; + this.x = x; + this.y = y; + this.posUpdated = true; + } + lineTo( x: number, y: number, @@ -167,7 +175,13 @@ export class MovingBlock implements IMovingBlock { } stepMoving(timestamp: number): boolean { - if (this.end) return false; + if (this.end) { + if (this.posUpdated) { + this.posUpdated = false; + return true; + } + return false; + } const dt = timestamp - this.startTime; if (this.line) { if (dt > this.time) { @@ -211,18 +225,6 @@ export class MovingBlock implements IMovingBlock { return true; } - enableFrameAnimate(): void { - this.renderer.vertex.enableDynamicFrameAnimate(this); - } - - disableFrameAnimate(): void { - this.renderer.vertex.disableDynamicFrameAnimate(this); - } - - setAlpha(alpha: number): void { - this.renderer.vertex.setDynamicAlpha(this, alpha); - } - destroy(): void { this.renderer.deleteMoving(this); } diff --git a/packages-user/client-modules/src/render/map/renderer.ts b/packages-user/client-modules/src/render/map/renderer.ts index bbe6ceb..9f9efad 100644 --- a/packages-user/client-modules/src/render/map/renderer.ts +++ b/packages-user/client-modules/src/render/map/renderer.ts @@ -13,11 +13,11 @@ import { ITrackedAssetData } from '@user/client-base'; import { + IBlockStatus, IContextData, IMapBackgroundConfig, IMapRenderConfig, IMapRenderer, - IMapRendererHooks, IMapVertexGenerator, IMapViewportController, IMovingBlock, @@ -27,12 +27,7 @@ import { MapTileSizeTestMode } from './types'; import { ILayerState, ILayerStateHooks, IMapLayer } from '@user/data-state'; -import { - Hookable, - HookController, - IHookController, - logger -} from '@motajs/common'; +import { IHookController, logger } from '@motajs/common'; import { compileProgramWith } from '@motajs/client-base'; import { isNil, maxBy } from 'lodash-es'; import { IMapDataGetter, MapVertexGenerator } from './vertex'; @@ -50,6 +45,7 @@ import { import { ITransformUpdatable, Transform } from '@motajs/render-core'; import { MapViewport } from './viewport'; import { INSTANCED_COUNT } from './constant'; +import { StaticBlockStatus } from './status'; const enum BackgroundType { Static, @@ -58,7 +54,6 @@ const enum BackgroundType { } export class MapRenderer - extends Hookable implements IMapRenderer, IMovingRenderer, @@ -149,13 +144,6 @@ export class MapRenderer /** 是否应该更新偏移池 uniform */ private needUpdateOffsetPool: boolean = true; - /** 顶点数组的图层列表是否需要更新 */ - private layerListDirty: boolean = false; - /** 顶点数组的图层的尺寸是否需要更新 */ - private layerSizeDirty: boolean = false; - /** 是否整个地图都需要更新,一般只有在地图尺寸等发生变动时才会执行 */ - private layerAllDirty: boolean = false; - /** 所有正在移动的图块 */ private movingBlock: Set = new Set(); /** 移动图块对象索引池 */ @@ -178,10 +166,19 @@ export class MapRenderer /** 帧动画速率 */ private frameSpeed: number = 300; + /** 画布元素 */ + readonly canvas: HTMLCanvasElement; + /** 画布 WebGL2 上下文 */ + readonly gl: WebGL2RenderingContext; /** 画布上下文数据 */ private contextData: IContextData; + + /** 地图变换矩阵 */ + transform: Transform; /** 是否需要更新变换矩阵 */ private needUpdateTransform: boolean = true; + /** 是否需要重新渲染 */ + private updateRequired: boolean = true; /** 图块动画器 */ private readonly tileAnimater: ITextureAnimater; @@ -198,11 +195,11 @@ export class MapRenderer */ constructor( readonly manager: IMaterialManager, - readonly gl: WebGL2RenderingContext, - readonly transform: Transform, layerState: ILayerState ) { - super(); + this.canvas = document.createElement('canvas'); + this.gl = this.canvas.getContext('webgl2')!; + this.transform = new Transform(); this.layerState = layerState; this.layerStateHook = layerState.addHook( new RendererLayerStateHook(this) @@ -219,12 +216,9 @@ export class MapRenderer this.vertex = new MapVertexGenerator(this, data); this.autotile = new AutotileProcessor(manager); this.tick = this.tick.bind(this); - this.transform.bind(this); this.viewport = new MapViewport(this); - this.viewport.bindTransform(this.transform); this.tileAnimater = new TextureColumnAnimater(); - this.initVertexPointer(gl, data); - layerState.addHook(new RendererLayerStateHook(this)); + this.initVertexPointer(this.gl, data); } /** @@ -268,7 +262,7 @@ export class MapRenderer new Float32Array([ // 左下,右下,左上,右上,前两个是顶点坐标,后两个是纹理坐标 // 因为我们已经在数据处理阶段将数据归一化到了 [-1, 1] 的范围,因此顶点坐标应该是 [0, 1] 的范围 - // 同时又因为我们以左上角为原点,因此纵坐标需要取反 + // 同时又因为我们以左上角为原点,纵坐标与 WebGL2 相反,因此纵坐标需要取反 0, 0, 0, 0, 1, 0, 1, 0, 0, -1, 0, 1, @@ -297,10 +291,33 @@ export class MapRenderer gl.bindVertexArray(null); } - protected createController( - hook: Partial - ): IHookController { - return new HookController(this, hook); + //#endregion + + //#region 状态控制 + + setTransform(transform: Transform): void { + this.transform.bind(); + this.transform = transform; + transform.bind(this); + this.viewport.bindTransform(transform); + this.needUpdateTransform = true; + } + + setCanvasSize(width: number, height: number): void { + this.canvas.width = width; + this.canvas.height = height; + this.updateRequired = true; + } + + setViewport(x: number, y: number, width: number, height: number): void { + this.gl.viewport(x, y, width, height); + } + + clear(color: boolean, depth: boolean): void { + let bit = 0; + if (color) bit |= this.gl.COLOR_BUFFER_BIT; + if (depth) bit |= this.gl.DEPTH_BUFFER_BIT; + if (bit > 0) this.gl.clear(bit); } //#endregion @@ -315,21 +332,13 @@ export class MapRenderer return a.zIndex - b.zIndex; }); this.sortedLayers.forEach((v, i) => this.layerIndexMap.set(v, i)); - this.layerListDirty = true; - this.requestUpdate(); - } - - private updateAllLayers() { - this.layerState.layerList.forEach(v => { - this.vertex.updateArea(v, 0, 0, v.width, v.height); - }); } updateLayerList() { this.sortLayer(); this.resizeLayer(); this.layerCount = this.layerState.layerList.size; - this.layerAllDirty = true; + this.vertex.updateLayerArray(); } setLayerState(layerState: ILayerState): void { @@ -342,7 +351,8 @@ export class MapRenderer this.sortLayer(); this.resizeLayer(); this.layerCount = layerState.layerList.size; - this.layerAllDirty = true; + this.vertex.updateLayerArray(); + this.vertex.resizeMap(); } getLayer(identifier: string): IMapLayer | null { @@ -367,8 +377,8 @@ export class MapRenderer resizeLayer() { const maxWidth = maxBy(this.sortedLayers, v => v.width)?.width ?? 0; const maxHeight = maxBy(this.sortedLayers, v => v.height)?.height ?? 0; - if (this.mapWidth !== maxWidth || this.mapHeight !== maxHeight) { - this.layerSizeDirty = true; + if (this.mapWidth === maxWidth && this.mapHeight === maxHeight) { + return; } this.mapWidth = maxWidth; this.mapHeight = maxHeight; @@ -378,8 +388,7 @@ export class MapRenderer this.contextData.backgroundWidth, this.contextData.backgroundHeight ); - this.layerAllDirty = true; - this.requestUpdate(); + this.vertex.resizeMap(); } //#endregion @@ -420,25 +429,24 @@ export class MapRenderer } configBackground(config: Partial): void { - let needUpdate = false; if (!isNil(config.renderWidth)) { - needUpdate = true; + this.updateRequired = true; this.backRenderWidth = config.renderWidth; } if (!isNil(config.renderHeight)) { - needUpdate = true; + this.updateRequired = true; this.backRenderHeight = config.renderHeight; } if (!isNil(config.repeatX)) { - needUpdate = true; + this.updateRequired = true; this.backRepeatModeX = config.repeatX; } if (!isNil(config.repeatY)) { - needUpdate = true; + this.updateRequired = true; this.backRepeatModeY = config.repeatY; } if (!isNil(config.useImageSize)) { - needUpdate = true; + this.updateRequired = true; this.backUseImageSize = config.useImageSize; } if (!isNil(config.frameSpeed)) { @@ -450,9 +458,6 @@ export class MapRenderer this.contextData.backgroundWidth, this.contextData.backgroundHeight ); - if (needUpdate) { - this.requestUpdate(); - } } getBackgroundConfig(): Readonly { @@ -483,7 +488,6 @@ export class MapRenderer this.sortedLayers.forEach(v => { this.vertex.updateArea(v, 0, 0, this.mapWidth, this.mapHeight); }); - this.requestUpdate(); } setCellSize(width: number, height: number): void { @@ -492,37 +496,32 @@ export class MapRenderer this.sortedLayers.forEach(v => { this.vertex.updateArea(v, 0, 0, this.mapWidth, this.mapHeight); }); - this.requestUpdate(); } configRendering(config: Partial): void { - let needUpdate = false; if (!isNil(config.minBehavior)) { this.tileMinifyBehavior = config.minBehavior; - needUpdate = true; + this.updateRequired = true; } if (!isNil(config.magBehavior)) { this.tileMagnifyBehavior = config.magBehavior; - needUpdate = true; + this.updateRequired = true; } if (!isNil(config.tileAlignX)) { this.tileAlignX = config.tileAlignX; - needUpdate = true; + this.updateRequired = true; } if (!isNil(config.tileAlignY)) { this.tileAlignY = config.tileAlignY; - needUpdate = true; + this.updateRequired = true; } if (!isNil(config.tileTestMode)) { this.tileTestMode = config.tileTestMode; - needUpdate = true; + this.updateRequired = true; } if (!isNil(config.frameSpeed)) { this.frameSpeed = config.frameSpeed; } - if (needUpdate) { - this.requestUpdate(); - } } getRenderingConfig(): Readonly { @@ -536,10 +535,6 @@ export class MapRenderer }; } - getTransform(): Transform { - return this.transform; - } - private getOffsetPool(): number[] { const pool = new Set([32]); // 其他的都是 bigImage 了,直接遍历获取 @@ -1196,15 +1191,15 @@ export class MapRenderer ); this.backgroundPending = false; gl.bindTexture(gl.TEXTURE_2D_ARRAY, null); - this.requestUpdate(); + this.updateRequired = true; } - render(): void { + render(): HTMLCanvasElement { const gl = this.gl; const data = this.contextData; if (!this.assetData) { logger.error(31); - return; + return this.canvas; } const { @@ -1227,18 +1222,6 @@ export class MapRenderer } = data; // 图层检查 - if (this.layerSizeDirty) { - this.vertex.resizeMap(); - this.layerSizeDirty = false; - } - if (this.layerListDirty) { - this.vertex.updateLayerArray(); - this.layerListDirty = false; - } - if (this.layerAllDirty) { - this.updateAllLayers(); - this.layerAllDirty = false; - } this.vertex.checkRebuild(); // 数据检查 @@ -1272,7 +1255,6 @@ export class MapRenderer gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); gl.useProgram(backProgram); if (this.needUpdateBackgroundFrame) { - this.needUpdateBackgroundFrame = false; gl.uniform1f(backNowFrameLocation, this.backgroundFrame); } if (this.needUpdateTransform) { @@ -1290,11 +1272,9 @@ export class MapRenderer // 图块 gl.useProgram(tileProgram); if (this.needUpdateOffsetPool) { - this.needUpdateOffsetPool = false; gl.uniform1fv(offsetPoolLocation, this.normalizedOffsetPool); } if (this.needUpdateFrameCounter) { - this.needUpdateFrameCounter = false; gl.uniform1f(nowFrameLocation, this.frameCounter); } if (this.needUpdateTransform) { @@ -1304,7 +1284,6 @@ export class MapRenderer this.transform.mat ); } - this.needUpdateTransform = false; gl.bindTexture(gl.TEXTURE_2D_ARRAY, tileTexture); gl.bindVertexArray(tileVAO); @@ -1325,6 +1304,16 @@ export class MapRenderer }); gl.bindVertexArray(null); gl.bindTexture(gl.TEXTURE_2D_ARRAY, null); + + // 清空更新状态标识 + this.updateRequired = false; + this.needUpdateFrameCounter = false; + this.needUpdateBackgroundFrame = false; + this.needUpdateTransform = false; + this.needUpdateOffsetPool = false; + this.vertex.renderDynamic(); + + return this.canvas; } //#endregion @@ -1347,7 +1336,7 @@ export class MapRenderer h: number ) { this.vertex.updateArea(layer, x, y, w, h); - this.requestUpdate(); + this.updateRequired = true; } /** @@ -1359,7 +1348,18 @@ export class MapRenderer */ updateLayerBlock(layer: IMapLayer, block: number, x: number, y: number) { this.vertex.updateBlock(layer, block, x, y); - this.requestUpdate(); + this.updateRequired = true; + } + + getBlockStatus( + layer: IMapLayer, + x: number, + y: number + ): IBlockStatus | null { + if (x < 0 || y < 0 || x > this.mapWidth || y > this.mapHeight) { + return null; + } + return new StaticBlockStatus(layer, this.vertex, x, y); } //#endregion @@ -1385,22 +1385,10 @@ export class MapRenderer private reduceMoving() { const half = Math.round(this.movingCount / 2); if (half < DYNAMIC_RESERVE) return; - if (this.movingIndexPool.length < half) return; - const needMap: number[] = []; - const restPool: number[] = []; - this.movingIndexPool.forEach(v => { - if (v < half) restPool.push(v); - else needMap.push(v); - }); - // 这个判断理论上不可能成立,但是还是判断下吧 - if (needMap.length > restPool.length) return; - const map = new Map(); - needMap.forEach(v => { - const item = restPool.pop()!; - map.set(v, item); - }); - this.vertex.reduceMoving(half, map); - this.requestUpdate(); + for (const moving of this.movingBlock) { + if (moving.index >= half) return; + } + this.vertex.reduceMoving(half); } /** @@ -1436,11 +1424,11 @@ export class MapRenderer y: number ): IMovingBlock { const index = this.requireMovingIndex(); - const moving = new MovingBlock(this, index, layer, block, x, y); + const moving = new MovingBlock(this, index, layer, block); + moving.setPos(x, y); this.movingBlock.add(moving); this.movingIndexMap.set(index, moving); this.vertex.updateMoving(moving, true); - this.requestUpdate(); return moving; } @@ -1457,7 +1445,6 @@ export class MapRenderer this.movingBlock.delete(block); this.movingIndexMap.delete(block.index); this.vertex.deleteMoving(block); - this.requestUpdate(); } hasMoving(moving: IMovingBlock): boolean { @@ -1466,44 +1453,20 @@ export class MapRenderer //#endregion - //#region 图块配置 - - enableTileFrameAnimate(layer: IMapLayer, x: number, y: number): void { - this.vertex.enableStaticFrameAnimate(layer, x, y); - } - - disableTileFrameAnimate(layer: IMapLayer, x: number, y: number): void { - this.vertex.disableStaticFrameAnimate(layer, x, y); - } - - setTileAlpha(layer: IMapLayer, alpha: number, x: number, y: number): void { - this.vertex.setStaticAlpha(layer, alpha, x, y); - } - - //#endregion - //#region 其他方法 - private requestUpdate() { - this.forEachHook((hook, controller) => { - hook.onUpdate?.(controller); - }); - } - getTimestamp(): number { return this.timestamp; } tick(timestamp: number) { this.timestamp = timestamp; - let update = false; // 移动数组 const expandDT = timestamp - this.lastExpandTime; if (expandDT > MOVING_TOLERANCE * 1000) { this.reduceMoving(); this.lastExpandTime = timestamp; - update = true; } // 背景 @@ -1513,7 +1476,6 @@ export class MapRenderer this.backgroundFrame %= this.backgroundFrameCount; this.backLastFrame = timestamp; this.needUpdateBackgroundFrame = true; - update = true; } // 地图帧动画 @@ -1522,7 +1484,6 @@ export class MapRenderer this.lastFrameTime = timestamp; this.frameCounter++; this.needUpdateFrameCounter = true; - update = true; } // 图块移动 @@ -1533,17 +1494,22 @@ export class MapRenderer if (move) toUpdate.push(v); }); this.vertex.updateMovingList(toUpdate, false); - update = true; - } - - if (update) { - this.requestUpdate(); } } updateTransform(): void { this.needUpdateTransform = true; - this.requestUpdate(); + } + + needUpdate(): boolean { + return ( + this.updateRequired || + this.needUpdateFrameCounter || + this.needUpdateBackgroundFrame || + this.needUpdateTransform || + this.vertex.dynamicRenderDirty || + this.needUpdateOffsetPool + ); } //#endregion diff --git a/packages-user/client-modules/src/render/map/status.ts b/packages-user/client-modules/src/render/map/status.ts new file mode 100644 index 0000000..922b14b --- /dev/null +++ b/packages-user/client-modules/src/render/map/status.ts @@ -0,0 +1,70 @@ +import { IMapLayer } from '@user/data-state'; +import { IBlockStatus, IMapVertexStatus } from './types'; + +export class StaticBlockStatus implements IBlockStatus { + /** + * @param layer 图层对象 + * @param vertex 顶点数组生成器对象 + * @param x 图块横坐标 + * @param y 图块纵坐标 + */ + constructor( + readonly layer: IMapLayer, + readonly vertex: IMapVertexStatus, + readonly x: number, + readonly y: number + ) {} + + setAlpha(alpha: number): void { + this.vertex.setStaticAlpha(this.layer, alpha, this.x, this.y); + } + + getAlpha(): number { + return this.vertex.getStaticAlpha(this.layer, this.x, this.y); + } + + useGlobalFrame(): void { + this.vertex.setStaticFrame(this.layer, this.x, this.y, -1); + } + + useSpecifiedFrame(frame: number): void { + this.vertex.setStaticFrame(this.layer, this.x, this.y, frame); + } + + getFrame(): number { + return this.vertex.getStaticFrame(this.layer, this.x, this.y); + } +} + +export class DynamicBlockStatus implements IBlockStatus { + /** + * @param layer 图层对象 + * @param vertex 顶点数组生成器对象 + * @param index 图块索引 + */ + constructor( + readonly layer: IMapLayer, + readonly vertex: IMapVertexStatus, + readonly index: number + ) {} + + setAlpha(alpha: number): void { + this.vertex.setDynamicAlpha(this.index, alpha); + } + + getAlpha(): number { + return this.vertex.getDynamicAlpha(this.index); + } + + useGlobalFrame(): void { + this.vertex.setDynamicFrame(this.index, -1); + } + + useSpecifiedFrame(frame: number): void { + this.vertex.setDynamicFrame(this.index, frame); + } + + getFrame(): number { + return this.vertex.getDynamicFrame(this.index); + } +} diff --git a/packages-user/client-modules/src/render/map/types.ts b/packages-user/client-modules/src/render/map/types.ts index 6572432..21b2928 100644 --- a/packages-user/client-modules/src/render/map/types.ts +++ b/packages-user/client-modules/src/render/map/types.ts @@ -1,10 +1,4 @@ -import { - IDirtyMark, - IDirtyTracker, - IHookable, - IHookBase, - IHookController -} from '@motajs/common'; +import { IDirtyMark, IDirtyTracker } from '@motajs/common'; import { ITextureRenderable } from '@motajs/render-assets'; import { Transform } from '@motajs/render-core'; import { @@ -153,7 +147,39 @@ export interface IContextData { vertexMark: IDirtyMark; } -export interface IMovingBlock { +export interface IBlockStatus { + /** 图块所属图层 */ + readonly layer: IMapLayer; + + /** + * 设置图块的不透明度 + * @param alpha 图块不透明度 + */ + setAlpha(alpha: number): void; + + /** + * 获取图块的不透明度 + */ + getAlpha(): number; + + /** + * 使用全局帧动画 + */ + useGlobalFrame(): void; + + /** + * 使用指定的动画帧数,传入第几帧图块就画第几帧,超过最大帧数会自动取模 + * @param frame 第几帧 + */ + useSpecifiedFrame(frame: number): void; + + /** + * 获取动画帧数,-1 表示使用全局帧动画,非负整数表示图块是第几帧 + */ + getFrame(): number; +} + +export interface IMovingBlock extends IBlockStatus { /** 移动图块的索引 */ readonly index: number; /** 图块数字 */ @@ -164,8 +190,13 @@ export interface IMovingBlock { readonly y: number; /** 图块使用的纹理 */ readonly texture: IMaterialFramedData; - /** 该图块所属的图层 */ - readonly layer: IMapLayer; + + /** + * 直接设置图块的位置,动画中设置无效 + * @param x 目标横坐标 + * @param y 目标纵坐标 + */ + setPos(x: number, y: number): void; /** * 沿直线移动到目标点 @@ -198,22 +229,6 @@ export interface IMovingBlock { timing?: TimingFn ): Promise; - /** - * 启用此图块的帧动画 - */ - enableFrameAnimate(): void; - - /** - * 禁用此图块的帧动画 - */ - disableFrameAnimate(): void; - - /** - * 设置此图块的不透明度 - * @param alpha 不透明度 - */ - setAlpha(alpha: number): void; - /** * 进行一步动画移动效果 * @param timestamp 当前时间戳 @@ -227,14 +242,7 @@ export interface IMovingBlock { destroy(): void; } -export interface IMapRendererHooks extends IHookBase { - /** - * 当需要更新画面时执行 - */ - onUpdate(controller: IHookController): void; -} - -export interface IMapRenderer extends IHookable { +export interface IMapRenderer { /** 地图渲染器使用的资源管理器 */ readonly manager: IMaterialManager; /** 画布渲染上下文 */ @@ -282,10 +290,38 @@ export interface IMapRenderer extends IHookable { destroy(): void; /** - * 渲染至目标画布 - * @param gl 渲染至的上下文 + * 渲染地图 */ - render(gl: WebGL2RenderingContext): void; + render(): HTMLCanvasElement; + + /** + * 设置地图的变换矩阵 + * @param transform 变换矩阵 + */ + setTransform(transform: Transform): void; + + /** + * 设置画布尺寸 + * @param width 画布宽度 + * @param height 画布高度 + */ + setCanvasSize(width: number, height: number): void; + + /** + * 设置渲染区域,等于 `gl.viewport` + * @param x 左上角横坐标 + * @param y 左上角纵坐标 + * @param width 区域宽度 + * @param height 区域高度 + */ + setViewport(x: number, y: number, width: number, height: number): void; + + /** + * 清空画布缓冲区 + * @param color 是否清空颜色缓冲区 + * @param depth 是否清空深度缓冲区 + */ + clear(color: boolean, depth: boolean): void; /** * 设置渲染器使用的地图状态 @@ -402,36 +438,24 @@ export interface IMapRenderer extends IHookable { */ getMovingBlockByIndex(index: number): Readonly | null; - /** - * 启用指定图块的帧动画效果 - * @param layer 图层 - * @param x 图块横坐标 - * @param y 图块纵坐标 - */ - enableTileFrameAnimate(layer: IMapLayer, x: number, y: number): void; - - /** - * 禁用指定图块的帧动画效果 - * @param layer 图层 - * @param x 图块横坐标 - * @param y 图块纵坐标 - */ - disableTileFrameAnimate(layer: IMapLayer, x: number, y: number): void; - - /** - * 设置指定图块的不透明度 - * @param layer 图层 - * @param alpha 图块不透明度 - * @param x 图块横坐标 - * @param y 图块纵坐标 - */ - setTileAlpha(layer: IMapLayer, alpha: number, x: number, y: number): void; - /** * 进行一帧更新 * @param timestamp 时间戳 */ tick(timestamp: number): void; + + /** + * 获取指定图层的指定图块的状态信息,可以设置与获取图块状态。多次调用的返回值不同引用。 + * @param layer 图层对象 + * @param x 图块横坐标 + * @param y 图块纵坐标 + */ + getBlockStatus(layer: IMapLayer, x: number, y: number): IBlockStatus | null; + + /** + * 当前地图状态是否发生改变,需要更新 + */ + needUpdate(): boolean; } export interface IMapVertexArray { @@ -710,11 +734,11 @@ export interface IMapVertexBlock extends IMapVertexData { readonly dirty: boolean; /** 渲染是否需要更新 */ readonly renderDirty: boolean; - /** 起始索引,即第一个元素索引 */ + /** 起始索引,即第一个元素索引,以实例为单位 */ readonly startIndex: number; - /** 终止索引,即最后一个元素索引+1 */ + /** 终止索引,即最后一个元素索引+1,以实例为单位 */ readonly endIndex: number; - /** 元素数量,即终止索引-起始索引 */ + /** 元素数量,即终止索引-起始索引,以实例为单位 */ readonly count: number; /** @@ -771,10 +795,74 @@ export interface IMapBlockUpdateObject { readonly y: number; } +export interface IMapVertexStatus { + /** + * 设置图块的不透明度 + * @param layer 图块所属图层 + * @param x 图块横坐标 + * @param y 图块纵坐标 + * @param alpha 目标不透明度 + */ + setStaticAlpha(layer: IMapLayer, x: number, y: number, alpha: number): void; + + /** + * 设置图块显示第几帧 + * @param layer 图块所属图层 + * @param x 图块横坐标 + * @param y 图块纵坐标 + * @param frame 图块的帧数,-1 表示使用全局帧数,非负整数表示画第几帧,超出最大帧数会自动取余 + */ + setStaticFrame(layer: IMapLayer, x: number, y: number, frame: number): void; + + /** + * 设置移动图块的不透明度 + * @param index 移动图块索引 + * @param alpha 目标不透明度 + */ + setDynamicAlpha(index: number, alpha: number): void; + + /** + * 设置移动图块显示第几帧 + * @param index 移动图块索引 + * @param frame 图块的帧数,-1 表示使用全局帧数,非负整数表示画第几帧,超出最大帧数会自动取余 + */ + setDynamicFrame(index: number, frame: number): void; + + /** + * 获取指定位置图块的不透明度,如果图块不在地图内则返回 0 + * @param layer 图块所属图层 + * @param x 图块横坐标 + * @param y 图块纵坐标 + */ + getStaticAlpha(layer: IMapLayer, x: number, y: number): number; + + /** + * 获取指定位置图块的帧数,-1 表示使用全局帧数,非负整数表示当前第几帧,不会超出最大帧数,如果图块不在地图内则返回 -1 + * @param layer 图块所属图层 + * @param x 图块横坐标 + * @param y 图块纵坐标 + */ + getStaticFrame(layer: IMapLayer, x: number, y: number): number; + + /** + * 获取移动图块的不透明度 + * @param index 移动图块索引 + */ + getDynamicAlpha(index: number): number; + + /** + * 获取移动图块的当前帧数,-1 表示使用全局帧数,非负整数表示当前第几帧 + * @param index 移动图块索引 + */ + getDynamicFrame(index: number): number; +} + /** * 脏标记表示顶点数组的长度是否发生变化 */ -export interface IMapVertexGenerator extends IDirtyTracker { +export interface IMapVertexGenerator + extends IDirtyTracker, + IMapVertexStatus { /** 地图渲染器 */ readonly renderer: IMapRenderer; /** 地图分块 */ @@ -782,8 +870,13 @@ export interface IMapVertexGenerator extends IDirtyTracker { /** 动态部分是否需要更新渲染缓冲区 */ readonly dynamicRenderDirty: boolean; + /** 动态内容起始索引,以实例为单位 */ + readonly dynamicStart: number; + /** 动态内容数量,以实例为单位 */ + readonly dynamicCount: number; + /** - * 取消渲染的脏标记 + * 取消动态内容渲染的脏标记 */ renderDynamic(): void; @@ -809,10 +902,8 @@ export interface IMapVertexGenerator extends IDirtyTracker { /** * 缩小移动图块数组尺寸 * @param targetSize 目标大小 - * @param indexMap 索引映射。由于缩小尺寸后,有可能有一些移动的图块位于较高的索引, - * 因此需要一个映射来指定之前的那些移动图块现在应该在哪个索引。 */ - reduceMoving(targetSize: number, indexMap: Map): void; + reduceMoving(targetSize: number): void; /** * 更新图层数组 @@ -886,59 +977,15 @@ export interface IMapVertexGenerator extends IDirtyTracker { * @param moving 移动图块对象 */ deleteMoving(moving: IMovingBlock): void; - - /** - * 启用静态内容的帧动画 - * @param layer 图层索引 - * @param x 图块横坐标 - * @param y 图块纵坐标 - */ - enableStaticFrameAnimate(layer: IMapLayer, x: number, y: number): void; - - /** - * 禁用静态内容的帧动画 - * @param layer 图层索引 - * @param x 图块横坐标 - * @param y 图块纵坐标 - */ - disableStaticFrameAnimate(layer: IMapLayer, x: number, y: number): void; - - /** - * 设置静态内容的不透明度 - * @param layer 图层索引 - * @param alpha 图块不透明度 - * @param x 图块横坐标 - * @param y 图块纵坐标 - */ - setStaticAlpha(layer: IMapLayer, alpha: number, x: number, y: number): void; - - /** - * 启用动态内容的帧动画 - * @param block 移动图块对象 - */ - enableDynamicFrameAnimate(block: IMovingBlock): void; - - /** - * 禁用动态内容的帧动画 - * @param block 移动图块对象 - */ - disableDynamicFrameAnimate(block: IMovingBlock): void; - - /** - * 设置动态内容的不透明度 - * @param block 移动图块对象 - * @param alpha 图块的不透明度 - */ - setDynamicAlpha(block: IMovingBlock, alpha: number): void; } export interface IMapRenderArea { /** 顶点起始索引,从哪个顶点开始处理 */ - readonly startIndex: number; + startIndex: number; /** 顶点终止索引,处理到哪个顶点 */ - readonly endIndex: number; + endIndex: number; /** 顶点数量,即终止索引减去起始索引 */ - readonly count: number; + count: number; } export interface IMapRenderData { @@ -965,3 +1012,5 @@ export interface IMapViewportController { */ bindTransform(transform: Transform): void; } + +export interface IMapCamera {} diff --git a/packages-user/client-modules/src/render/map/vertex.ts b/packages-user/client-modules/src/render/map/vertex.ts index 1360be8..c1e60f0 100644 --- a/packages-user/client-modules/src/render/map/vertex.ts +++ b/packages-user/client-modules/src/render/map/vertex.ts @@ -72,6 +72,15 @@ interface BlockIndex extends IndexedBlockMapPos { readonly mapIndex: number; } +interface VertexArrayOfBlock { + /** 图块在数组中的起始索引 */ + readonly index: number; + /** 分块顶点数组 */ + readonly array: Float32Array; + /** 分块数据 */ + readonly block: IBlockData; +} + const enum VertexUpdate { /** 更新顶点位置信息 */ Position = 0b01, @@ -92,6 +101,9 @@ export class MapVertexGenerator dynamicRenderDirty: boolean = true; + dynamicStart: number = 0; + dynamicCount: number = DYNAMIC_RESERVE; + /** 空顶点数组,因为空顶点很常用,所以直接定义一个全局常量 */ private static readonly EMPTY_VETREX: Float32Array = new Float32Array( INSTANCED_COUNT @@ -104,9 +116,6 @@ export class MapVertexGenerator /** 动态内容偏移数组 */ private dynamicInstancedArray: Float32Array = new Float32Array(); - /** 图层列表 */ - private layers: IMapLayer[] = []; - /** 分块宽度 */ private blockWidth: number = MAP_BLOCK_WIDTH; /** 分块高度 */ @@ -120,11 +129,6 @@ export class MapVertexGenerator /** 是否需要重建数组 */ private needRebuild: boolean = false; - /** 静态内容数组顶点数量 */ - private staticLength: number = 0; - /** 动态内容数组顶点数量。动态内容的数量无法预测,因此使用预留数量+动态扩充的方式 */ - private dynamicLength: number = DYNAMIC_RESERVE; - /** 更新图块性能检查防抖起始时刻 */ private updateCallDebounceTime: number = 0; /** 更新图块性能检查的调用次数 */ @@ -147,10 +151,10 @@ export class MapVertexGenerator // 顶点数组尺寸等于 地图大小 * 每个图块的顶点数量 * 每个顶点的数据量 const area = this.renderer.mapWidth * this.renderer.mapHeight; const staticCount = area * this.renderer.layerCount; - const count = staticCount + this.dynamicLength; + const count = staticCount + this.dynamicCount; const offsetSize = count * INSTANCED_COUNT; this.instancedArray = new Float32Array(offsetSize); - this.staticLength = staticCount; + this.dynamicStart = staticCount; this.dynamicInstancedArray = this.instancedArray.subarray( staticCount * INSTANCED_COUNT, count * INSTANCED_COUNT @@ -212,7 +216,7 @@ export class MapVertexGenerator expandMoving(targetSize: number): void { const beforeOffset = this.instancedArray; - this.dynamicLength = targetSize; + this.dynamicCount = targetSize; this.mallocVertexArray(); this.instancedArray.set(beforeOffset); const array: IMapVertexData = { @@ -224,22 +228,14 @@ export class MapVertexGenerator } } - reduceMoving(targetSize: number, indexMap: Map): void { + reduceMoving(targetSize: number): void { const beforeOffsetLength = this.instancedArray.length; - const deltaLength = this.dynamicLength - targetSize; - this.dynamicLength = targetSize; + const deltaLength = this.dynamicCount - targetSize; + this.dynamicCount = targetSize; this.instancedArray = this.instancedArray.subarray( 0, beforeOffsetLength - deltaLength * INSTANCED_COUNT ); - indexMap.forEach((target, from) => { - const next = from + 1; - this.dynamicInstancedArray.copyWithin( - target * INSTANCED_COUNT, - from * INSTANCED_COUNT, - next * INSTANCED_COUNT - ); - }); this.dynamicInstancedArray = this.dynamicInstancedArray.subarray( 0, targetSize * INSTANCED_COUNT @@ -248,13 +244,7 @@ export class MapVertexGenerator } updateLayerArray(): void { - const layers = this.renderer.getSortedLayer(); - if ( - layers.length !== this.layers.length || - this.layers.some((v, i) => layers[i] !== v) - ) { - this.needRebuild = true; - } + this.needRebuild = true; } checkRebuild() { @@ -812,56 +802,6 @@ export class MapVertexGenerator //#endregion - //#region 图块配置 - - enableStaticFrameAnimate(layer: IMapLayer, x: number, y: number): void { - const data = layer.getMapRef(); - const block = this.block.getBlockByDataLoc(x, y); - if (!block) return; - const vertexArray = block.data.getLayerInstanced(layer); - if (!vertexArray) return; - const mapIndex = y * this.mapWidth + x; - const num = data.array[mapIndex]; - const tile = this.renderer.manager.getIfBigImage(num); - if (!tile) return; - const bx = x - block.dataX; - const by = y - block.dataY; - const bIndex = by * block.width + bx; - vertexArray[bIndex * INSTANCED_COUNT + 13] = tile.frames; - block.data.markRenderDirty(); - } - - disableStaticFrameAnimate(layer: IMapLayer, x: number, y: number): void { - const block = this.block.getBlockByDataLoc(x, y); - if (!block) return; - const vertexArray = block.data.getLayerInstanced(layer); - if (!vertexArray) return; - const bx = x - block.dataX; - const by = y - block.dataY; - const bIndex = by * block.width + bx; - vertexArray[bIndex * INSTANCED_COUNT + 13] = 1; - block.data.markRenderDirty(); - } - - setStaticAlpha( - layer: IMapLayer, - alpha: number, - x: number, - y: number - ): void { - const block = this.block.getBlockByDataLoc(x, y); - if (!block) return; - const vertexArray = block.data.getLayerInstanced(layer); - if (!vertexArray) return; - const bx = x - block.dataX; - const by = y - block.dataY; - const bIndex = by * block.width + bx; - vertexArray[bIndex * INSTANCED_COUNT + 9] = alpha; - block.data.markRenderDirty(); - } - - //#endregion - //#region 动态图块 updateMoving(block: IMovingBlock, updateTexture: boolean): void { @@ -925,11 +865,9 @@ export class MapVertexGenerator } updateMovingList(moving: IMovingBlock[], updateTexture: boolean): void { - console.time('update-moving'); moving.forEach(v => { this.updateMoving(v, updateTexture); }); - console.timeEnd('update-moving'); } deleteMoving(moving: IMovingBlock): void { @@ -942,25 +880,85 @@ export class MapVertexGenerator this.dynamicRenderDirty = true; } - enableDynamicFrameAnimate(block: IMovingBlock): void { - if (!this.renderer.hasMoving(block)) return; - const instancedStart = block.index * INSTANCED_COUNT; - this.dynamicInstancedArray[instancedStart + 13] = 1; + //#endregion + + //#region 图块状态 + + /** + * 获取指定图层指定坐标的图块对应的分块信息 + * @param layer 图层对象 + * @param x 图块横坐标 + * @param y 图块纵坐标 + */ + private getIndexInBlock( + layer: IMapLayer, + x: number, + y: number + ): VertexArrayOfBlock | null { + const block = this.block.getBlockByDataLoc(x, y); + if (!block) return null; + const data = block?.data.getLayerInstanced(layer); + if (!data) return null; + const dx = x - block.x; + const dy = y - block.y; + const dIndex = dy * block.width + dx; + return { array: data, index: dIndex, block }; + } + + setStaticAlpha( + layer: IMapLayer, + x: number, + y: number, + alpha: number + ): void { + const index = this.getIndexInBlock(layer, x, y); + if (!index) return; + index.array[index.index * INSTANCED_COUNT + 9] = alpha; + index.block.data.markRenderDirty(); + } + + setStaticFrame( + layer: IMapLayer, + x: number, + y: number, + frame: number + ): void { + const index = this.getIndexInBlock(layer, x, y); + if (!index) return; + index.array[index.index * INSTANCED_COUNT + 12] = frame; + index.block.data.markRenderDirty(); + } + + getStaticAlpha(layer: IMapLayer, x: number, y: number): number { + const index = this.getIndexInBlock(layer, x, y); + if (!index) return 0; + return index.array[index.index * INSTANCED_COUNT + 9]; + } + + getStaticFrame(layer: IMapLayer, x: number, y: number): number { + const index = this.getIndexInBlock(layer, x, y); + if (!index) return -1; + return index.array[index.index * INSTANCED_COUNT + 12]; + } + + setDynamicAlpha(index: number, alpha: number): void { + this.dynamicInstancedArray[index * INSTANCED_COUNT + 9] = alpha; this.dynamicRenderDirty = true; } - disableDynamicFrameAnimate(block: IMovingBlock): void { - if (!this.renderer.hasMoving(block)) return; - const instancedStart = block.index * INSTANCED_COUNT; - this.dynamicInstancedArray[instancedStart + 13] = block.texture.frames; + setDynamicFrame(index: number, frame: number): void { + this.dynamicInstancedArray[index * INSTANCED_COUNT + 12] = frame; this.dynamicRenderDirty = true; } - setDynamicAlpha(block: IMovingBlock, alpha: number): void { - if (!this.renderer.hasMoving(block)) return; - const instancedStart = block.index * INSTANCED_COUNT; - this.dynamicInstancedArray[instancedStart + 9] = alpha; - this.dynamicRenderDirty = true; + getDynamicAlpha(index: number): number { + if (index > this.dynamicCount) return 0; + return this.dynamicInstancedArray[index * INSTANCED_COUNT + 9]; + } + + getDynamicFrame(index: number): number { + if (index > this.dynamicCount) return -1; + return this.dynamicInstancedArray[index * INSTANCED_COUNT + 12]; } //#endregion @@ -968,15 +966,14 @@ export class MapVertexGenerator //#region 其他接口 renderDynamic(): void { - // todo: vertex, offset, alpha 的脏标记分开 this.dynamicRenderDirty = false; } getVertexArray(): IMapVertexArray { this.checkRebuild(); return { - dynamicStart: this.staticLength, - dynamicCount: this.dynamicLength, + dynamicStart: this.dynamicStart, + dynamicCount: this.dynamicCount, tileInstanced: this.instancedArray }; } @@ -1040,7 +1037,6 @@ class MapVertexBlock implements IMapVertexBlock { } markRenderDirty() { - // todo: 潜在优化点:vertex, offset, alpha 的脏标记分开 this.renderDirty = true; } @@ -1051,7 +1047,6 @@ class MapVertexBlock implements IMapVertexBlock { right: number, bottom: number ): void { - // todo: 更细致的脏标记是否会更好? const data = this.layerDirty.get(layer); if (!data) return; const dl = clamp(left, 0, this.blockWidth); diff --git a/packages-user/client-modules/src/render/map/viewport.ts b/packages-user/client-modules/src/render/map/viewport.ts index 96b5624..9de51ef 100644 --- a/packages-user/client-modules/src/render/map/viewport.ts +++ b/packages-user/client-modules/src/render/map/viewport.ts @@ -33,6 +33,24 @@ export class MapViewport implements IMapViewportController { }); } + private checkDynamic( + list: IMapRenderArea[], + dynamicStart: number, + dynamicCount: number + ) { + const last = list[list.length - 1]; + if (!last || last.endIndex < dynamicStart) { + list.push({ + startIndex: dynamicStart, + endIndex: dynamicStart + dynamicCount, + count: dynamicCount + }); + } else { + last.endIndex = dynamicStart + dynamicCount; + last.count += dynamicCount; + } + } + getRenderArea(): IMapRenderData { const { cellWidth, cellHeight, renderWidth, renderHeight } = this.renderer; @@ -53,34 +71,12 @@ export class MapViewport implements IMapViewportController { const updateArea: IMapRenderArea[] = []; const blockList: IBlockData[] = []; - const widthOne = blockLeft === blockRight; - const heightOne = blockTop === blockBottom; - - if (widthOne && heightOne) { - // 只能看到一个分块 - const block = this.vertex.block.getBlockByLoc(blockLeft, blockTop)!; - blockList.push(block); - } else if (widthOne) { - // 看到的区域分块宽度是 1 - for (let ny = blockTop; ny <= blockBottom; ny++) { - const block = this.vertex.block.getBlockByLoc(blockLeft, ny)!; - blockList.push(block); - } - } else if (heightOne) { - // 看到的区域分块高度是 1 + // 内层横向外层纵向的话,索引在换行之前都是连续的,方便整合 + for (let ny = blockTop; ny <= blockBottom; ny++) { for (let nx = blockLeft; nx <= blockRight; nx++) { - const block = this.vertex.block.getBlockByLoc(nx, blockTop)!; + const block = this.vertex.block.getBlockByLoc(nx, ny)!; blockList.push(block); } - } else { - // 看到的区域分块宽高都不是 1 - // 使用这种方式的话,索引在换行之前都是连续的,方便整合 - for (let ny = blockTop; ny <= blockBottom; ny++) { - for (let nx = blockLeft; nx <= blockRight; nx++) { - const block = this.vertex.block.getBlockByLoc(nx, ny)!; - blockList.push(block); - } - } } if (blockList.length > 0) { @@ -122,7 +118,12 @@ export class MapViewport implements IMapViewportController { } } - // todo: 动态内容 + const dynamicStart = this.vertex.dynamicStart; + const dynamicCount = this.vertex.dynamicCount; + this.checkDynamic(renderArea, dynamicStart, dynamicCount); + if (this.vertex.dynamicRenderDirty) { + this.checkDynamic(updateArea, dynamicStart, dynamicCount); + } return { render: renderArea, diff --git a/packages/common/src/logger.json b/packages/common/src/logger.json index 4bb3a1e..80cd1ae 100644 --- a/packages/common/src/logger.json +++ b/packages/common/src/logger.json @@ -133,6 +133,7 @@ "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.", "85": "Hook to load does not belong to current hookable object.", + "86": "Cannot restore vertex data since delivered state does not belong to current vertex generator instance.", "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." }