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
} 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<IMapRendererHooks>;
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<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 { 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);
}

View File

@ -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<IMapRendererHooks>
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<IMovingBlock> = 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<number>;
@ -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<IMapRendererHooks>
): IHookController<IMapRendererHooks> {
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<IMapBackgroundConfig>): 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<IMapBackgroundConfig> {
@ -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<IMapRenderConfig>): 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<IMapRenderConfig> {
@ -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<number, number>();
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

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 {
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<this>;
/**
*
*/
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<this>): void;
}
export interface IMapRenderer extends IHookable<IMapRendererHooks> {
export interface IMapRenderer {
/** 地图渲染器使用的资源管理器 */
readonly manager: IMaterialManager;
/** 画布渲染上下文 */
@ -282,10 +290,38 @@ export interface IMapRenderer extends IHookable<IMapRendererHooks> {
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;
/**
*
* @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<boolean> {
export interface IMapVertexGenerator
extends IDirtyTracker<boolean>,
IMapVertexStatus {
/** 地图渲染器 */
readonly renderer: IMapRenderer;
/** 地图分块 */
@ -782,8 +870,13 @@ export interface IMapVertexGenerator extends IDirtyTracker<boolean> {
/** 动态部分是否需要更新渲染缓冲区 */
readonly dynamicRenderDirty: boolean;
/** 动态内容起始索引,以实例为单位 */
readonly dynamicStart: number;
/** 动态内容数量,以实例为单位 */
readonly dynamicCount: number;
/**
*
*
*/
renderDynamic(): void;
@ -809,10 +902,8 @@ export interface IMapVertexGenerator extends IDirtyTracker<boolean> {
/**
*
* @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
*/
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 {}

View File

@ -72,6 +72,15 @@ interface BlockIndex extends IndexedBlockMapPos {
readonly mapIndex: number;
}
interface VertexArrayOfBlock {
/** 图块在数组中的起始索引 */
readonly index: number;
/** 分块顶点数组 */
readonly array: Float32Array;
/** 分块数据 */
readonly block: IBlockData<MapVertexBlock>;
}
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<number, number>): 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);

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 {
const { cellWidth, cellHeight, renderWidth, renderHeight } =
this.renderer;
@ -53,34 +71,12 @@ export class MapViewport implements IMapViewportController {
const updateArea: IMapRenderArea[] = [];
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 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,

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