mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-11-27 22:03:00 +08:00
refactor: 地图渲染无副作用化
This commit is contained in:
parent
a5ecc45d9b
commit
bfccb33c88
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
70
packages-user/client-modules/src/render/map/status.ts
Normal file
70
packages-user/client-modules/src/render/map/status.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -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 {}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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."
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user