refactor: 地图渲染无副作用化

This commit is contained in:
unanmed 2025-11-19 15:19:13 +08:00
parent a5ecc45d9b
commit bfccb33c88
8 changed files with 500 additions and 442 deletions

View File

@ -4,57 +4,41 @@ import {
Transform Transform
} from '@motajs/render-core'; } from '@motajs/render-core';
import { ILayerState, state } from '@user/data-state'; import { ILayerState, state } from '@user/data-state';
import { IMapRenderer, IMapRendererHooks } from './types'; import { IMapRenderer } 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 class MapRender extends RenderItem { export class MapRender extends RenderItem {
/** 地图渲染器 */ /** 地图渲染器 */
readonly renderer: IMapRenderer; readonly renderer: IMapRenderer;
/** 地图视角变换矩阵 */
readonly camera: Transform = new Transform();
/** 地图画布 */
readonly canvas: HTMLCanvasElement;
/** 画布上下文 */
readonly gl: WebGL2RenderingContext;
private rendererHook: IHookController<IMapRendererHooks>;
constructor(readonly layerState: ILayerState) { constructor(readonly layerState: ILayerState) {
super('static'); super('static');
this.canvas = document.createElement('canvas'); this.renderer = new MapRenderer(materials, state.layer);
const gl = this.canvas.getContext('webgl2')!;
this.gl = gl;
this.renderer = new MapRenderer(
materials,
this.gl,
this.camera,
state.layer
);
this.renderer.setLayerState(layerState); this.renderer.setLayerState(layerState);
this.renderer.useAsset(materials.trackedAsset); this.renderer.useAsset(materials.trackedAsset);
this.rendererHook = this.renderer.addHook(new RendererUpdateHook(this)); this.renderer.setCanvasSize(this.width, this.height);
this.rendererHook.load();
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);
this.delegateTicker(time => this.renderer.tick(time)); this.delegateTicker(time => {
this.renderer.tick(time);
gl.viewport(0, 0, this.canvas.width, this.canvas.height); if (this.renderer.needUpdate()) {
this.update();
}
});
} }
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;
this.canvas.width = width * scale; const w = width * scale;
this.canvas.height = height * scale; const h = height * scale;
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height); this.renderer.setCanvasSize(w, h);
this.renderer.setViewport(0, 0, w, h);
} }
onResize(scale: number): void { onResize(scale: number): void {
@ -75,11 +59,9 @@ export class MapRender extends RenderItem {
} }
protected render(canvas: MotaOffscreenCanvas2D): void { protected render(canvas: MotaOffscreenCanvas2D): void {
console.time('map-element-render'); this.renderer.clear(true, true);
this.renderer.render(this.gl); const map = this.renderer.render();
canvas.ctx.drawImage(map, 0, 0, canvas.width, canvas.height);
canvas.ctx.drawImage(this.canvas, 0, 0, canvas.width, canvas.height);
console.timeEnd('map-element-render');
} }
patchProp( patchProp(
@ -98,11 +80,3 @@ export class MapRender extends RenderItem {
super.patchProp(key, prevValue, nextValue, namespace, parentComponent); super.patchProp(key, prevValue, nextValue, namespace, parentComponent);
} }
} }
class RendererUpdateHook implements Partial<IMapRendererHooks> {
constructor(readonly element: MapRender) {}
onUpdate(): void {
this.element.update();
}
}

View File

@ -3,6 +3,7 @@ import { IMapRenderer, IMapVertexGenerator, IMovingBlock } from './types';
import { IMaterialFramedData, IMaterialManager } from '@user/client-base'; import { IMaterialFramedData, IMaterialManager } from '@user/client-base';
import { logger } from '@motajs/common'; import { logger } from '@motajs/common';
import { IMapLayer } from '@user/data-state'; import { IMapLayer } from '@user/data-state';
import { DynamicBlockStatus } from './status';
export interface IMovingRenderer { export interface IMovingRenderer {
/** 素材管理器 */ /** 素材管理器 */
@ -22,13 +23,13 @@ export interface IMovingRenderer {
deleteMoving(block: IMovingBlock): void; deleteMoving(block: IMovingBlock): void;
} }
export class MovingBlock implements IMovingBlock { export class MovingBlock extends DynamicBlockStatus implements IMovingBlock {
readonly texture: IMaterialFramedData; readonly texture: IMaterialFramedData;
readonly tile: number; readonly tile: number;
readonly renderer: IMovingRenderer; readonly renderer: IMovingRenderer;
readonly index: number;
readonly layer: IMapLayer; readonly layer: IMapLayer;
index: number;
x: number = 0; x: number = 0;
y: number = 0; y: number = 0;
@ -59,7 +60,10 @@ export class MovingBlock implements IMovingBlock {
/** 动画开始时纵坐标 */ /** 动画开始时纵坐标 */
private startY: number = 0; private startY: number = 0;
/** 当前动画是否已经结束 */ /** 当前动画是否已经结束 */
private end: boolean = false; private end: boolean = true;
/** 是否通过 `setPos` 设置了位置 */
private posUpdated: boolean = false;
/** 兑现函数 */ /** 兑现函数 */
private promiseFunc: () => void = () => {}; private promiseFunc: () => void = () => {};
@ -68,15 +72,12 @@ export class MovingBlock implements IMovingBlock {
renderer: IMovingRenderer & IMapRenderer, renderer: IMovingRenderer & IMapRenderer,
index: number, index: number,
layer: IMapLayer, layer: IMapLayer,
block: number | IMaterialFramedData, block: number | IMaterialFramedData
x: number,
y: number
) { ) {
super(layer, renderer.vertex, index);
this.renderer = renderer; this.renderer = renderer;
this.index = index; this.index = index;
this.layer = layer; this.layer = layer;
this.x = x;
this.y = y;
if (typeof block === 'number') { if (typeof block === 'number') {
this.texture = renderer.manager.getTile(block)!; this.texture = renderer.manager.getTile(block)!;
this.tile = 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( lineTo(
x: number, x: number,
y: number, y: number,
@ -167,7 +175,13 @@ export class MovingBlock implements IMovingBlock {
} }
stepMoving(timestamp: number): boolean { 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; const dt = timestamp - this.startTime;
if (this.line) { if (this.line) {
if (dt > this.time) { if (dt > this.time) {
@ -211,18 +225,6 @@ export class MovingBlock implements IMovingBlock {
return true; 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 { destroy(): void {
this.renderer.deleteMoving(this); this.renderer.deleteMoving(this);
} }

View File

@ -13,11 +13,11 @@ import {
ITrackedAssetData ITrackedAssetData
} from '@user/client-base'; } from '@user/client-base';
import { import {
IBlockStatus,
IContextData, IContextData,
IMapBackgroundConfig, IMapBackgroundConfig,
IMapRenderConfig, IMapRenderConfig,
IMapRenderer, IMapRenderer,
IMapRendererHooks,
IMapVertexGenerator, IMapVertexGenerator,
IMapViewportController, IMapViewportController,
IMovingBlock, IMovingBlock,
@ -27,12 +27,7 @@ import {
MapTileSizeTestMode MapTileSizeTestMode
} from './types'; } from './types';
import { ILayerState, ILayerStateHooks, IMapLayer } from '@user/data-state'; import { ILayerState, ILayerStateHooks, IMapLayer } from '@user/data-state';
import { import { IHookController, logger } from '@motajs/common';
Hookable,
HookController,
IHookController,
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';
@ -50,6 +45,7 @@ import {
import { ITransformUpdatable, Transform } from '@motajs/render-core'; import { ITransformUpdatable, Transform } from '@motajs/render-core';
import { MapViewport } from './viewport'; import { MapViewport } from './viewport';
import { INSTANCED_COUNT } from './constant'; import { INSTANCED_COUNT } from './constant';
import { StaticBlockStatus } from './status';
const enum BackgroundType { const enum BackgroundType {
Static, Static,
@ -58,7 +54,6 @@ const enum BackgroundType {
} }
export class MapRenderer export class MapRenderer
extends Hookable<IMapRendererHooks>
implements implements
IMapRenderer, IMapRenderer,
IMovingRenderer, IMovingRenderer,
@ -149,13 +144,6 @@ export class MapRenderer
/** 是否应该更新偏移池 uniform */ /** 是否应该更新偏移池 uniform */
private needUpdateOffsetPool: boolean = true; private needUpdateOffsetPool: boolean = true;
/** 顶点数组的图层列表是否需要更新 */
private layerListDirty: boolean = false;
/** 顶点数组的图层的尺寸是否需要更新 */
private layerSizeDirty: boolean = false;
/** 是否整个地图都需要更新,一般只有在地图尺寸等发生变动时才会执行 */
private layerAllDirty: boolean = false;
/** 所有正在移动的图块 */ /** 所有正在移动的图块 */
private movingBlock: Set<IMovingBlock> = new Set(); private movingBlock: Set<IMovingBlock> = new Set();
/** 移动图块对象索引池 */ /** 移动图块对象索引池 */
@ -178,10 +166,19 @@ export class MapRenderer
/** 帧动画速率 */ /** 帧动画速率 */
private frameSpeed: number = 300; private frameSpeed: number = 300;
/** 画布元素 */
readonly canvas: HTMLCanvasElement;
/** 画布 WebGL2 上下文 */
readonly gl: WebGL2RenderingContext;
/** 画布上下文数据 */ /** 画布上下文数据 */
private contextData: IContextData; private contextData: IContextData;
/** 地图变换矩阵 */
transform: Transform;
/** 是否需要更新变换矩阵 */ /** 是否需要更新变换矩阵 */
private needUpdateTransform: boolean = true; private needUpdateTransform: boolean = true;
/** 是否需要重新渲染 */
private updateRequired: boolean = true;
/** 图块动画器 */ /** 图块动画器 */
private readonly tileAnimater: ITextureAnimater<number>; private readonly tileAnimater: ITextureAnimater<number>;
@ -198,11 +195,11 @@ export class MapRenderer
*/ */
constructor( constructor(
readonly manager: IMaterialManager, readonly manager: IMaterialManager,
readonly gl: WebGL2RenderingContext,
readonly transform: Transform,
layerState: ILayerState layerState: ILayerState
) { ) {
super(); this.canvas = document.createElement('canvas');
this.gl = this.canvas.getContext('webgl2')!;
this.transform = new Transform();
this.layerState = layerState; this.layerState = layerState;
this.layerStateHook = layerState.addHook( this.layerStateHook = layerState.addHook(
new RendererLayerStateHook(this) new RendererLayerStateHook(this)
@ -219,12 +216,9 @@ export class MapRenderer
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);
this.transform.bind(this);
this.viewport = new MapViewport(this); this.viewport = new MapViewport(this);
this.viewport.bindTransform(this.transform);
this.tileAnimater = new TextureColumnAnimater(); this.tileAnimater = new TextureColumnAnimater();
this.initVertexPointer(gl, data); this.initVertexPointer(this.gl, data);
layerState.addHook(new RendererLayerStateHook(this));
} }
/** /**
@ -268,7 +262,7 @@ export class MapRenderer
new Float32Array([ new Float32Array([
// 左下,右下,左上,右上,前两个是顶点坐标,后两个是纹理坐标 // 左下,右下,左上,右上,前两个是顶点坐标,后两个是纹理坐标
// 因为我们已经在数据处理阶段将数据归一化到了 [-1, 1] 的范围,因此顶点坐标应该是 [0, 1] 的范围 // 因为我们已经在数据处理阶段将数据归一化到了 [-1, 1] 的范围,因此顶点坐标应该是 [0, 1] 的范围
// 同时又因为我们以左上角为原点,因此纵坐标需要取反 // 同时又因为我们以左上角为原点,纵坐标与 WebGL2 相反,因此纵坐标需要取反
0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0,
0, -1, 0, 1, 0, -1, 0, 1,
@ -297,10 +291,33 @@ export class MapRenderer
gl.bindVertexArray(null); gl.bindVertexArray(null);
} }
protected createController( //#endregion
hook: Partial<IMapRendererHooks>
): IHookController<IMapRendererHooks> { //#region 状态控制
return new HookController(this, hook);
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 //#endregion
@ -315,21 +332,13 @@ export class MapRenderer
return a.zIndex - b.zIndex; return a.zIndex - b.zIndex;
}); });
this.sortedLayers.forEach((v, i) => this.layerIndexMap.set(v, i)); 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() { updateLayerList() {
this.sortLayer(); this.sortLayer();
this.resizeLayer(); this.resizeLayer();
this.layerCount = this.layerState.layerList.size; this.layerCount = this.layerState.layerList.size;
this.layerAllDirty = true; this.vertex.updateLayerArray();
} }
setLayerState(layerState: ILayerState): void { setLayerState(layerState: ILayerState): void {
@ -342,7 +351,8 @@ export class MapRenderer
this.sortLayer(); this.sortLayer();
this.resizeLayer(); this.resizeLayer();
this.layerCount = layerState.layerList.size; this.layerCount = layerState.layerList.size;
this.layerAllDirty = true; this.vertex.updateLayerArray();
this.vertex.resizeMap();
} }
getLayer(identifier: string): IMapLayer | null { getLayer(identifier: string): IMapLayer | null {
@ -367,8 +377,8 @@ export class MapRenderer
resizeLayer() { resizeLayer() {
const maxWidth = maxBy(this.sortedLayers, v => v.width)?.width ?? 0; const maxWidth = maxBy(this.sortedLayers, v => v.width)?.width ?? 0;
const maxHeight = maxBy(this.sortedLayers, v => v.height)?.height ?? 0; const maxHeight = maxBy(this.sortedLayers, v => v.height)?.height ?? 0;
if (this.mapWidth !== maxWidth || this.mapHeight !== maxHeight) { if (this.mapWidth === maxWidth && this.mapHeight === maxHeight) {
this.layerSizeDirty = true; return;
} }
this.mapWidth = maxWidth; this.mapWidth = maxWidth;
this.mapHeight = maxHeight; this.mapHeight = maxHeight;
@ -378,8 +388,7 @@ export class MapRenderer
this.contextData.backgroundWidth, this.contextData.backgroundWidth,
this.contextData.backgroundHeight this.contextData.backgroundHeight
); );
this.layerAllDirty = true; this.vertex.resizeMap();
this.requestUpdate();
} }
//#endregion //#endregion
@ -420,25 +429,24 @@ export class MapRenderer
} }
configBackground(config: Partial<IMapBackgroundConfig>): void { configBackground(config: Partial<IMapBackgroundConfig>): void {
let needUpdate = false;
if (!isNil(config.renderWidth)) { if (!isNil(config.renderWidth)) {
needUpdate = true; this.updateRequired = true;
this.backRenderWidth = config.renderWidth; this.backRenderWidth = config.renderWidth;
} }
if (!isNil(config.renderHeight)) { if (!isNil(config.renderHeight)) {
needUpdate = true; this.updateRequired = true;
this.backRenderHeight = config.renderHeight; this.backRenderHeight = config.renderHeight;
} }
if (!isNil(config.repeatX)) { if (!isNil(config.repeatX)) {
needUpdate = true; this.updateRequired = true;
this.backRepeatModeX = config.repeatX; this.backRepeatModeX = config.repeatX;
} }
if (!isNil(config.repeatY)) { if (!isNil(config.repeatY)) {
needUpdate = true; this.updateRequired = true;
this.backRepeatModeY = config.repeatY; this.backRepeatModeY = config.repeatY;
} }
if (!isNil(config.useImageSize)) { if (!isNil(config.useImageSize)) {
needUpdate = true; this.updateRequired = true;
this.backUseImageSize = config.useImageSize; this.backUseImageSize = config.useImageSize;
} }
if (!isNil(config.frameSpeed)) { if (!isNil(config.frameSpeed)) {
@ -450,9 +458,6 @@ export class MapRenderer
this.contextData.backgroundWidth, this.contextData.backgroundWidth,
this.contextData.backgroundHeight this.contextData.backgroundHeight
); );
if (needUpdate) {
this.requestUpdate();
}
} }
getBackgroundConfig(): Readonly<IMapBackgroundConfig> { getBackgroundConfig(): Readonly<IMapBackgroundConfig> {
@ -483,7 +488,6 @@ export class MapRenderer
this.sortedLayers.forEach(v => { this.sortedLayers.forEach(v => {
this.vertex.updateArea(v, 0, 0, this.mapWidth, this.mapHeight); this.vertex.updateArea(v, 0, 0, this.mapWidth, this.mapHeight);
}); });
this.requestUpdate();
} }
setCellSize(width: number, height: number): void { setCellSize(width: number, height: number): void {
@ -492,37 +496,32 @@ export class MapRenderer
this.sortedLayers.forEach(v => { this.sortedLayers.forEach(v => {
this.vertex.updateArea(v, 0, 0, this.mapWidth, this.mapHeight); this.vertex.updateArea(v, 0, 0, this.mapWidth, this.mapHeight);
}); });
this.requestUpdate();
} }
configRendering(config: Partial<IMapRenderConfig>): void { configRendering(config: Partial<IMapRenderConfig>): void {
let needUpdate = false;
if (!isNil(config.minBehavior)) { if (!isNil(config.minBehavior)) {
this.tileMinifyBehavior = config.minBehavior; this.tileMinifyBehavior = config.minBehavior;
needUpdate = true; this.updateRequired = true;
} }
if (!isNil(config.magBehavior)) { if (!isNil(config.magBehavior)) {
this.tileMagnifyBehavior = config.magBehavior; this.tileMagnifyBehavior = config.magBehavior;
needUpdate = true; this.updateRequired = true;
} }
if (!isNil(config.tileAlignX)) { if (!isNil(config.tileAlignX)) {
this.tileAlignX = config.tileAlignX; this.tileAlignX = config.tileAlignX;
needUpdate = true; this.updateRequired = true;
} }
if (!isNil(config.tileAlignY)) { if (!isNil(config.tileAlignY)) {
this.tileAlignY = config.tileAlignY; this.tileAlignY = config.tileAlignY;
needUpdate = true; this.updateRequired = true;
} }
if (!isNil(config.tileTestMode)) { if (!isNil(config.tileTestMode)) {
this.tileTestMode = config.tileTestMode; this.tileTestMode = config.tileTestMode;
needUpdate = true; this.updateRequired = true;
} }
if (!isNil(config.frameSpeed)) { if (!isNil(config.frameSpeed)) {
this.frameSpeed = config.frameSpeed; this.frameSpeed = config.frameSpeed;
} }
if (needUpdate) {
this.requestUpdate();
}
} }
getRenderingConfig(): Readonly<IMapRenderConfig> { getRenderingConfig(): Readonly<IMapRenderConfig> {
@ -536,10 +535,6 @@ export class MapRenderer
}; };
} }
getTransform(): Transform {
return this.transform;
}
private getOffsetPool(): number[] { private getOffsetPool(): number[] {
const pool = new Set([32]); const pool = new Set([32]);
// 其他的都是 bigImage 了,直接遍历获取 // 其他的都是 bigImage 了,直接遍历获取
@ -1196,15 +1191,15 @@ export class MapRenderer
); );
this.backgroundPending = false; this.backgroundPending = false;
gl.bindTexture(gl.TEXTURE_2D_ARRAY, null); gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
this.requestUpdate(); this.updateRequired = true;
} }
render(): void { render(): HTMLCanvasElement {
const gl = this.gl; const gl = this.gl;
const data = this.contextData; const data = this.contextData;
if (!this.assetData) { if (!this.assetData) {
logger.error(31); logger.error(31);
return; return this.canvas;
} }
const { const {
@ -1227,18 +1222,6 @@ export class MapRenderer
} = data; } = 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(); this.vertex.checkRebuild();
// 数据检查 // 数据检查
@ -1272,7 +1255,6 @@ export class MapRenderer
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.useProgram(backProgram); gl.useProgram(backProgram);
if (this.needUpdateBackgroundFrame) { if (this.needUpdateBackgroundFrame) {
this.needUpdateBackgroundFrame = false;
gl.uniform1f(backNowFrameLocation, this.backgroundFrame); gl.uniform1f(backNowFrameLocation, this.backgroundFrame);
} }
if (this.needUpdateTransform) { if (this.needUpdateTransform) {
@ -1290,11 +1272,9 @@ export class MapRenderer
// 图块 // 图块
gl.useProgram(tileProgram); gl.useProgram(tileProgram);
if (this.needUpdateOffsetPool) { if (this.needUpdateOffsetPool) {
this.needUpdateOffsetPool = false;
gl.uniform1fv(offsetPoolLocation, this.normalizedOffsetPool); gl.uniform1fv(offsetPoolLocation, this.normalizedOffsetPool);
} }
if (this.needUpdateFrameCounter) { if (this.needUpdateFrameCounter) {
this.needUpdateFrameCounter = false;
gl.uniform1f(nowFrameLocation, this.frameCounter); gl.uniform1f(nowFrameLocation, this.frameCounter);
} }
if (this.needUpdateTransform) { if (this.needUpdateTransform) {
@ -1304,7 +1284,6 @@ export class MapRenderer
this.transform.mat this.transform.mat
); );
} }
this.needUpdateTransform = false;
gl.bindTexture(gl.TEXTURE_2D_ARRAY, tileTexture); gl.bindTexture(gl.TEXTURE_2D_ARRAY, tileTexture);
gl.bindVertexArray(tileVAO); gl.bindVertexArray(tileVAO);
@ -1325,6 +1304,16 @@ export class MapRenderer
}); });
gl.bindVertexArray(null); gl.bindVertexArray(null);
gl.bindTexture(gl.TEXTURE_2D_ARRAY, 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 //#endregion
@ -1347,7 +1336,7 @@ export class MapRenderer
h: number h: number
) { ) {
this.vertex.updateArea(layer, x, y, w, h); 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) { updateLayerBlock(layer: IMapLayer, block: number, x: number, y: number) {
this.vertex.updateBlock(layer, block, x, y); 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 //#endregion
@ -1385,22 +1385,10 @@ export class MapRenderer
private reduceMoving() { private reduceMoving() {
const half = Math.round(this.movingCount / 2); const half = Math.round(this.movingCount / 2);
if (half < DYNAMIC_RESERVE) return; if (half < DYNAMIC_RESERVE) return;
if (this.movingIndexPool.length < half) return; for (const moving of this.movingBlock) {
const needMap: number[] = []; if (moving.index >= half) return;
const restPool: number[] = []; }
this.movingIndexPool.forEach(v => { this.vertex.reduceMoving(half);
if (v < half) restPool.push(v);
else needMap.push(v);
});
// 这个判断理论上不可能成立,但是还是判断下吧
if (needMap.length > restPool.length) return;
const map = new Map<number, number>();
needMap.forEach(v => {
const item = restPool.pop()!;
map.set(v, item);
});
this.vertex.reduceMoving(half, map);
this.requestUpdate();
} }
/** /**
@ -1436,11 +1424,11 @@ export class MapRenderer
y: number y: number
): IMovingBlock { ): IMovingBlock {
const index = this.requireMovingIndex(); 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.movingBlock.add(moving);
this.movingIndexMap.set(index, moving); this.movingIndexMap.set(index, moving);
this.vertex.updateMoving(moving, true); this.vertex.updateMoving(moving, true);
this.requestUpdate();
return moving; return moving;
} }
@ -1457,7 +1445,6 @@ export class MapRenderer
this.movingBlock.delete(block); this.movingBlock.delete(block);
this.movingIndexMap.delete(block.index); this.movingIndexMap.delete(block.index);
this.vertex.deleteMoving(block); this.vertex.deleteMoving(block);
this.requestUpdate();
} }
hasMoving(moving: IMovingBlock): boolean { hasMoving(moving: IMovingBlock): boolean {
@ -1466,44 +1453,20 @@ export class MapRenderer
//#endregion //#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 其他方法 //#region 其他方法
private requestUpdate() {
this.forEachHook((hook, controller) => {
hook.onUpdate?.(controller);
});
}
getTimestamp(): number { getTimestamp(): number {
return this.timestamp; return this.timestamp;
} }
tick(timestamp: number) { tick(timestamp: number) {
this.timestamp = timestamp; this.timestamp = timestamp;
let update = false;
// 移动数组 // 移动数组
const expandDT = timestamp - this.lastExpandTime; const expandDT = timestamp - this.lastExpandTime;
if (expandDT > MOVING_TOLERANCE * 1000) { if (expandDT > MOVING_TOLERANCE * 1000) {
this.reduceMoving(); this.reduceMoving();
this.lastExpandTime = timestamp; this.lastExpandTime = timestamp;
update = true;
} }
// 背景 // 背景
@ -1513,7 +1476,6 @@ export class MapRenderer
this.backgroundFrame %= this.backgroundFrameCount; this.backgroundFrame %= this.backgroundFrameCount;
this.backLastFrame = timestamp; this.backLastFrame = timestamp;
this.needUpdateBackgroundFrame = true; this.needUpdateBackgroundFrame = true;
update = true;
} }
// 地图帧动画 // 地图帧动画
@ -1522,7 +1484,6 @@ export class MapRenderer
this.lastFrameTime = timestamp; this.lastFrameTime = timestamp;
this.frameCounter++; this.frameCounter++;
this.needUpdateFrameCounter = true; this.needUpdateFrameCounter = true;
update = true;
} }
// 图块移动 // 图块移动
@ -1533,17 +1494,22 @@ export class MapRenderer
if (move) toUpdate.push(v); if (move) toUpdate.push(v);
}); });
this.vertex.updateMovingList(toUpdate, false); this.vertex.updateMovingList(toUpdate, false);
update = true;
}
if (update) {
this.requestUpdate();
} }
} }
updateTransform(): void { updateTransform(): void {
this.needUpdateTransform = true; this.needUpdateTransform = true;
this.requestUpdate(); }
needUpdate(): boolean {
return (
this.updateRequired ||
this.needUpdateFrameCounter ||
this.needUpdateBackgroundFrame ||
this.needUpdateTransform ||
this.vertex.dynamicRenderDirty ||
this.needUpdateOffsetPool
);
} }
//#endregion //#endregion

View File

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

View File

@ -1,10 +1,4 @@
import { import { IDirtyMark, IDirtyTracker } from '@motajs/common';
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 {
@ -153,7 +147,39 @@ export interface IContextData {
vertexMark: IDirtyMark; 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; readonly index: number;
/** 图块数字 */ /** 图块数字 */
@ -164,8 +190,13 @@ export interface IMovingBlock {
readonly y: number; readonly y: number;
/** 图块使用的纹理 */ /** 图块使用的纹理 */
readonly texture: IMaterialFramedData; 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 timing?: TimingFn
): Promise<this>; ): Promise<this>;
/**
*
*/
enableFrameAnimate(): void;
/**
*
*/
disableFrameAnimate(): void;
/**
*
* @param alpha
*/
setAlpha(alpha: number): void;
/** /**
* *
* @param timestamp * @param timestamp
@ -227,14 +242,7 @@ export interface IMovingBlock {
destroy(): void; destroy(): void;
} }
export interface IMapRendererHooks extends IHookBase { export interface IMapRenderer {
/**
*
*/
onUpdate(controller: IHookController<this>): void;
}
export interface IMapRenderer extends IHookable<IMapRendererHooks> {
/** 地图渲染器使用的资源管理器 */ /** 地图渲染器使用的资源管理器 */
readonly manager: IMaterialManager; readonly manager: IMaterialManager;
/** 画布渲染上下文 */ /** 画布渲染上下文 */
@ -282,10 +290,38 @@ export interface IMapRenderer extends IHookable<IMapRendererHooks> {
destroy(): void; 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<IMapRendererHooks> {
*/ */
getMovingBlockByIndex(index: number): Readonly<IMovingBlock> | null; getMovingBlockByIndex(index: number): Readonly<IMovingBlock> | 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 * @param timestamp
*/ */
tick(timestamp: number): void; tick(timestamp: number): void;
/**
*
* @param layer
* @param x
* @param y
*/
getBlockStatus(layer: IMapLayer, x: number, y: number): IBlockStatus | null;
/**
*
*/
needUpdate(): boolean;
} }
export interface IMapVertexArray { export interface IMapVertexArray {
@ -710,11 +734,11 @@ export interface IMapVertexBlock extends IMapVertexData {
readonly dirty: boolean; readonly dirty: boolean;
/** 渲染是否需要更新 */ /** 渲染是否需要更新 */
readonly renderDirty: boolean; readonly renderDirty: boolean;
/** 起始索引,即第一个元素索引 */ /** 起始索引,即第一个元素索引,以实例为单位 */
readonly startIndex: number; readonly startIndex: number;
/** 终止索引,即最后一个元素索引+1 */ /** 终止索引,即最后一个元素索引+1,以实例为单位 */
readonly endIndex: number; readonly endIndex: number;
/** 元素数量,即终止索引-起始索引 */ /** 元素数量,即终止索引-起始索引,以实例为单位 */
readonly count: number; readonly count: number;
/** /**
@ -771,10 +795,74 @@ export interface IMapBlockUpdateObject {
readonly y: number; 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<boolean> { export interface IMapVertexGenerator
extends IDirtyTracker<boolean>,
IMapVertexStatus {
/** 地图渲染器 */ /** 地图渲染器 */
readonly renderer: IMapRenderer; readonly renderer: IMapRenderer;
/** 地图分块 */ /** 地图分块 */
@ -782,8 +870,13 @@ export interface IMapVertexGenerator extends IDirtyTracker<boolean> {
/** 动态部分是否需要更新渲染缓冲区 */ /** 动态部分是否需要更新渲染缓冲区 */
readonly dynamicRenderDirty: boolean; readonly dynamicRenderDirty: boolean;
/** 动态内容起始索引,以实例为单位 */
readonly dynamicStart: number;
/** 动态内容数量,以实例为单位 */
readonly dynamicCount: number;
/** /**
* *
*/ */
renderDynamic(): void; renderDynamic(): void;
@ -809,10 +902,8 @@ export interface IMapVertexGenerator extends IDirtyTracker<boolean> {
/** /**
* *
* @param targetSize * @param targetSize
* @param indexMap
*
*/ */
reduceMoving(targetSize: number, indexMap: Map<number, number>): void; reduceMoving(targetSize: number): void;
/** /**
* *
@ -886,59 +977,15 @@ export interface IMapVertexGenerator extends IDirtyTracker<boolean> {
* @param moving * @param moving
*/ */
deleteMoving(moving: IMovingBlock): void; 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 { export interface IMapRenderArea {
/** 顶点起始索引,从哪个顶点开始处理 */ /** 顶点起始索引,从哪个顶点开始处理 */
readonly startIndex: number; startIndex: number;
/** 顶点终止索引,处理到哪个顶点 */ /** 顶点终止索引,处理到哪个顶点 */
readonly endIndex: number; endIndex: number;
/** 顶点数量,即终止索引减去起始索引 */ /** 顶点数量,即终止索引减去起始索引 */
readonly count: number; count: number;
} }
export interface IMapRenderData { export interface IMapRenderData {
@ -965,3 +1012,5 @@ export interface IMapViewportController {
*/ */
bindTransform(transform: Transform): void; bindTransform(transform: Transform): void;
} }
export interface IMapCamera {}

View File

@ -72,6 +72,15 @@ interface BlockIndex extends IndexedBlockMapPos {
readonly mapIndex: number; readonly mapIndex: number;
} }
interface VertexArrayOfBlock {
/** 图块在数组中的起始索引 */
readonly index: number;
/** 分块顶点数组 */
readonly array: Float32Array;
/** 分块数据 */
readonly block: IBlockData<MapVertexBlock>;
}
const enum VertexUpdate { const enum VertexUpdate {
/** 更新顶点位置信息 */ /** 更新顶点位置信息 */
Position = 0b01, Position = 0b01,
@ -92,6 +101,9 @@ export class MapVertexGenerator
dynamicRenderDirty: boolean = true; dynamicRenderDirty: boolean = true;
dynamicStart: number = 0;
dynamicCount: number = DYNAMIC_RESERVE;
/** 空顶点数组,因为空顶点很常用,所以直接定义一个全局常量 */ /** 空顶点数组,因为空顶点很常用,所以直接定义一个全局常量 */
private static readonly EMPTY_VETREX: Float32Array = new Float32Array( private static readonly EMPTY_VETREX: Float32Array = new Float32Array(
INSTANCED_COUNT INSTANCED_COUNT
@ -104,9 +116,6 @@ export class MapVertexGenerator
/** 动态内容偏移数组 */ /** 动态内容偏移数组 */
private dynamicInstancedArray: Float32Array = new Float32Array(); private dynamicInstancedArray: Float32Array = new Float32Array();
/** 图层列表 */
private layers: IMapLayer[] = [];
/** 分块宽度 */ /** 分块宽度 */
private blockWidth: number = MAP_BLOCK_WIDTH; private blockWidth: number = MAP_BLOCK_WIDTH;
/** 分块高度 */ /** 分块高度 */
@ -120,11 +129,6 @@ export class MapVertexGenerator
/** 是否需要重建数组 */ /** 是否需要重建数组 */
private needRebuild: boolean = false; private needRebuild: boolean = false;
/** 静态内容数组顶点数量 */
private staticLength: number = 0;
/** 动态内容数组顶点数量。动态内容的数量无法预测,因此使用预留数量+动态扩充的方式 */
private dynamicLength: number = DYNAMIC_RESERVE;
/** 更新图块性能检查防抖起始时刻 */ /** 更新图块性能检查防抖起始时刻 */
private updateCallDebounceTime: number = 0; private updateCallDebounceTime: number = 0;
/** 更新图块性能检查的调用次数 */ /** 更新图块性能检查的调用次数 */
@ -147,10 +151,10 @@ export class MapVertexGenerator
// 顶点数组尺寸等于 地图大小 * 每个图块的顶点数量 * 每个顶点的数据量 // 顶点数组尺寸等于 地图大小 * 每个图块的顶点数量 * 每个顶点的数据量
const area = this.renderer.mapWidth * this.renderer.mapHeight; const area = this.renderer.mapWidth * this.renderer.mapHeight;
const staticCount = area * this.renderer.layerCount; const staticCount = area * this.renderer.layerCount;
const count = staticCount + this.dynamicLength; const count = staticCount + this.dynamicCount;
const offsetSize = count * INSTANCED_COUNT; const offsetSize = count * INSTANCED_COUNT;
this.instancedArray = new Float32Array(offsetSize); this.instancedArray = new Float32Array(offsetSize);
this.staticLength = staticCount; this.dynamicStart = staticCount;
this.dynamicInstancedArray = this.instancedArray.subarray( this.dynamicInstancedArray = this.instancedArray.subarray(
staticCount * INSTANCED_COUNT, staticCount * INSTANCED_COUNT,
count * INSTANCED_COUNT count * INSTANCED_COUNT
@ -212,7 +216,7 @@ export class MapVertexGenerator
expandMoving(targetSize: number): void { expandMoving(targetSize: number): void {
const beforeOffset = this.instancedArray; const beforeOffset = this.instancedArray;
this.dynamicLength = targetSize; this.dynamicCount = targetSize;
this.mallocVertexArray(); this.mallocVertexArray();
this.instancedArray.set(beforeOffset); this.instancedArray.set(beforeOffset);
const array: IMapVertexData = { const array: IMapVertexData = {
@ -224,22 +228,14 @@ export class MapVertexGenerator
} }
} }
reduceMoving(targetSize: number, indexMap: Map<number, number>): void { reduceMoving(targetSize: number): void {
const beforeOffsetLength = this.instancedArray.length; const beforeOffsetLength = this.instancedArray.length;
const deltaLength = this.dynamicLength - targetSize; const deltaLength = this.dynamicCount - targetSize;
this.dynamicLength = targetSize; this.dynamicCount = targetSize;
this.instancedArray = this.instancedArray.subarray( this.instancedArray = this.instancedArray.subarray(
0, 0,
beforeOffsetLength - deltaLength * INSTANCED_COUNT 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( this.dynamicInstancedArray = this.dynamicInstancedArray.subarray(
0, 0,
targetSize * INSTANCED_COUNT targetSize * INSTANCED_COUNT
@ -248,14 +244,8 @@ export class MapVertexGenerator
} }
updateLayerArray(): void { 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() { checkRebuild() {
if (!this.needRebuild) return; if (!this.needRebuild) return;
@ -812,56 +802,6 @@ export class MapVertexGenerator
//#endregion //#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 动态图块 //#region 动态图块
updateMoving(block: IMovingBlock, updateTexture: boolean): void { updateMoving(block: IMovingBlock, updateTexture: boolean): void {
@ -925,11 +865,9 @@ export class MapVertexGenerator
} }
updateMovingList(moving: IMovingBlock[], updateTexture: boolean): void { updateMovingList(moving: IMovingBlock[], updateTexture: boolean): void {
console.time('update-moving');
moving.forEach(v => { moving.forEach(v => {
this.updateMoving(v, updateTexture); this.updateMoving(v, updateTexture);
}); });
console.timeEnd('update-moving');
} }
deleteMoving(moving: IMovingBlock): void { deleteMoving(moving: IMovingBlock): void {
@ -942,25 +880,85 @@ export class MapVertexGenerator
this.dynamicRenderDirty = true; this.dynamicRenderDirty = true;
} }
enableDynamicFrameAnimate(block: IMovingBlock): void { //#endregion
if (!this.renderer.hasMoving(block)) return;
const instancedStart = block.index * INSTANCED_COUNT; //#region 图块状态
this.dynamicInstancedArray[instancedStart + 13] = 1;
/**
*
* @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; this.dynamicRenderDirty = true;
} }
disableDynamicFrameAnimate(block: IMovingBlock): void { setDynamicFrame(index: number, frame: number): void {
if (!this.renderer.hasMoving(block)) return; this.dynamicInstancedArray[index * INSTANCED_COUNT + 12] = frame;
const instancedStart = block.index * INSTANCED_COUNT;
this.dynamicInstancedArray[instancedStart + 13] = block.texture.frames;
this.dynamicRenderDirty = true; this.dynamicRenderDirty = true;
} }
setDynamicAlpha(block: IMovingBlock, alpha: number): void { getDynamicAlpha(index: number): number {
if (!this.renderer.hasMoving(block)) return; if (index > this.dynamicCount) return 0;
const instancedStart = block.index * INSTANCED_COUNT; return this.dynamicInstancedArray[index * INSTANCED_COUNT + 9];
this.dynamicInstancedArray[instancedStart + 9] = alpha; }
this.dynamicRenderDirty = true;
getDynamicFrame(index: number): number {
if (index > this.dynamicCount) return -1;
return this.dynamicInstancedArray[index * INSTANCED_COUNT + 12];
} }
//#endregion //#endregion
@ -968,15 +966,14 @@ export class MapVertexGenerator
//#region 其他接口 //#region 其他接口
renderDynamic(): void { renderDynamic(): void {
// todo: vertex, offset, alpha 的脏标记分开
this.dynamicRenderDirty = false; this.dynamicRenderDirty = false;
} }
getVertexArray(): IMapVertexArray { getVertexArray(): IMapVertexArray {
this.checkRebuild(); this.checkRebuild();
return { return {
dynamicStart: this.staticLength, dynamicStart: this.dynamicStart,
dynamicCount: this.dynamicLength, dynamicCount: this.dynamicCount,
tileInstanced: this.instancedArray tileInstanced: this.instancedArray
}; };
} }
@ -1040,7 +1037,6 @@ class MapVertexBlock implements IMapVertexBlock {
} }
markRenderDirty() { markRenderDirty() {
// todo: 潜在优化点vertex, offset, alpha 的脏标记分开
this.renderDirty = true; this.renderDirty = true;
} }
@ -1051,7 +1047,6 @@ class MapVertexBlock implements IMapVertexBlock {
right: number, right: number,
bottom: number bottom: number
): void { ): void {
// todo: 更细致的脏标记是否会更好?
const data = this.layerDirty.get(layer); const data = this.layerDirty.get(layer);
if (!data) return; if (!data) return;
const dl = clamp(left, 0, this.blockWidth); const dl = clamp(left, 0, this.blockWidth);

View File

@ -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 { getRenderArea(): IMapRenderData {
const { cellWidth, cellHeight, renderWidth, renderHeight } = const { cellWidth, cellHeight, renderWidth, renderHeight } =
this.renderer; this.renderer;
@ -53,35 +71,13 @@ export class MapViewport implements IMapViewportController {
const updateArea: IMapRenderArea[] = []; const updateArea: IMapRenderArea[] = [];
const blockList: IBlockData<IMapVertexBlock>[] = []; const blockList: IBlockData<IMapVertexBlock>[] = [];
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 nx = blockLeft; nx <= blockRight; nx++) {
const block = this.vertex.block.getBlockByLoc(nx, blockTop)!;
blockList.push(block);
}
} else {
// 看到的区域分块宽高都不是 1
// 使用这种方式的话,索引在换行之前都是连续的,方便整合
for (let ny = blockTop; ny <= blockBottom; ny++) { for (let ny = blockTop; ny <= blockBottom; ny++) {
for (let nx = blockLeft; nx <= blockRight; nx++) { for (let nx = blockLeft; nx <= blockRight; nx++) {
const block = this.vertex.block.getBlockByLoc(nx, ny)!; const block = this.vertex.block.getBlockByLoc(nx, ny)!;
blockList.push(block); blockList.push(block);
} }
} }
}
if (blockList.length > 0) { if (blockList.length > 0) {
if (blockList.length === 1) { if (blockList.length === 1) {
@ -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 { return {
render: renderArea, render: renderArea,

View File

@ -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.", "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.", "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.", "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."
} }