feat: 地图渲染动画效果

This commit is contained in:
unanmed 2025-11-18 22:44:07 +08:00
parent ebaa35ea5b
commit a5ecc45d9b
4 changed files with 51 additions and 58 deletions

View File

@ -44,6 +44,8 @@ export class MapRender extends RenderItem {
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));
gl.viewport(0, 0, this.canvas.width, this.canvas.height); gl.viewport(0, 0, this.canvas.width, this.canvas.height);
} }
@ -73,8 +75,6 @@ export class MapRender extends RenderItem {
} }
protected render(canvas: MotaOffscreenCanvas2D): void { protected render(canvas: MotaOffscreenCanvas2D): void {
console.log('----- render start -----');
console.time('map-element-render'); console.time('map-element-render');
this.renderer.render(this.gl); this.renderer.render(this.gl);

View File

@ -315,10 +315,8 @@ 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.forEachHook((hook, controller) => {
hook.onUpdate?.(controller);
});
this.layerListDirty = true; this.layerListDirty = true;
this.requestUpdate();
} }
private updateAllLayers() { private updateAllLayers() {
@ -381,6 +379,7 @@ export class MapRenderer
this.contextData.backgroundHeight this.contextData.backgroundHeight
); );
this.layerAllDirty = true; this.layerAllDirty = true;
this.requestUpdate();
} }
//#endregion //#endregion
@ -421,19 +420,25 @@ 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.backRenderWidth = config.renderWidth; this.backRenderWidth = config.renderWidth;
} }
if (!isNil(config.renderHeight)) { if (!isNil(config.renderHeight)) {
needUpdate = true;
this.backRenderHeight = config.renderHeight; this.backRenderHeight = config.renderHeight;
} }
if (!isNil(config.repeatX)) { if (!isNil(config.repeatX)) {
needUpdate = true;
this.backRepeatModeX = config.repeatX; this.backRepeatModeX = config.repeatX;
} }
if (!isNil(config.repeatY)) { if (!isNil(config.repeatY)) {
needUpdate = true;
this.backRepeatModeY = config.repeatY; this.backRepeatModeY = config.repeatY;
} }
if (!isNil(config.useImageSize)) { if (!isNil(config.useImageSize)) {
needUpdate = true;
this.backUseImageSize = config.useImageSize; this.backUseImageSize = config.useImageSize;
} }
if (!isNil(config.frameSpeed)) { if (!isNil(config.frameSpeed)) {
@ -445,6 +450,9 @@ 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> {
@ -475,6 +483,7 @@ 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 {
@ -483,6 +492,7 @@ 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 {
@ -511,9 +521,7 @@ export class MapRenderer
this.frameSpeed = config.frameSpeed; this.frameSpeed = config.frameSpeed;
} }
if (needUpdate) { if (needUpdate) {
this.sortedLayers.forEach(v => { this.requestUpdate();
this.vertex.updateArea(v, 0, 0, this.mapWidth, this.mapHeight);
});
} }
} }
@ -1188,9 +1196,7 @@ export class MapRenderer
); );
this.backgroundPending = false; this.backgroundPending = false;
gl.bindTexture(gl.TEXTURE_2D_ARRAY, null); gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
this.forEachHook((hook, controller) => { this.requestUpdate();
hook.onUpdate?.(controller);
});
} }
render(): void { render(): void {
@ -1200,7 +1206,6 @@ export class MapRenderer
logger.error(31); logger.error(31);
return; return;
} }
console.time('render-map');
const { const {
backVAO, backVAO,
@ -1221,7 +1226,6 @@ export class MapRenderer
insTexDataAttribLocation: texData insTexDataAttribLocation: texData
} = data; } = data;
console.time('layer-check');
// 图层检查 // 图层检查
if (this.layerSizeDirty) { if (this.layerSizeDirty) {
this.vertex.resizeMap(); this.vertex.resizeMap();
@ -1236,28 +1240,18 @@ export class MapRenderer
this.layerAllDirty = false; this.layerAllDirty = false;
} }
this.vertex.checkRebuild(); this.vertex.checkRebuild();
console.timeEnd('layer-check');
console.time('texture-check');
// 数据检查 // 数据检查
this.checkTexture(gl, data); this.checkTexture(gl, data);
this.checkTileVertexArray(gl, data); this.checkTileVertexArray(gl, data);
console.timeEnd('texture-check');
console.time('update-block-cache');
const area = this.viewport.getRenderArea(); const area = this.viewport.getRenderArea();
area.blockList.forEach(v => { area.blockList.forEach(v => {
this.vertex.updateBlockCache(v); this.vertex.updateBlockCache(v);
v.data.render(); v.data.render();
}); });
console.timeEnd('update-block-cache');
if (area.dirty.length > 0) { if (area.dirty.length > 0) {
console.time('upload-block-buffer');
// 如果需要更新顶点数组... // 如果需要更新顶点数组...
const array = this.vertex.getVertexArray(); const array = this.vertex.getVertexArray();
gl.bindBuffer(gl.ARRAY_BUFFER, instancedBuffer); gl.bindBuffer(gl.ARRAY_BUFFER, instancedBuffer);
@ -1272,11 +1266,9 @@ export class MapRenderer
); );
}); });
gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.bindBuffer(gl.ARRAY_BUFFER, null);
console.timeEnd('upload-block-buffer');
} }
// 背景 // 背景
console.time('render-call');
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) {
@ -1333,8 +1325,6 @@ export class MapRenderer
}); });
gl.bindVertexArray(null); gl.bindVertexArray(null);
gl.bindTexture(gl.TEXTURE_2D_ARRAY, null); gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
console.timeEnd('render-call');
console.timeEnd('render-map');
} }
//#endregion //#endregion
@ -1357,9 +1347,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.forEachHook((hook, controller) => { this.requestUpdate();
hook.onUpdate?.(controller);
});
} }
/** /**
@ -1371,9 +1359,7 @@ 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.forEachHook((hook, controller) => { this.requestUpdate();
hook.onUpdate?.(controller);
});
} }
//#endregion //#endregion
@ -1414,6 +1400,7 @@ export class MapRenderer
map.set(v, item); map.set(v, item);
}); });
this.vertex.reduceMoving(half, map); this.vertex.reduceMoving(half, map);
this.requestUpdate();
} }
/** /**
@ -1453,6 +1440,7 @@ export class MapRenderer
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;
} }
@ -1469,6 +1457,7 @@ 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 {
@ -1495,18 +1484,26 @@ export class MapRenderer
//#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;
} }
// 背景 // 背景
@ -1516,13 +1513,16 @@ 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;
} }
// 地图帧动画 // 地图帧动画
const frameDT = timestamp - this.lastFrameTime; const frameDT = timestamp - this.lastFrameTime;
if (frameDT > this.frameSpeed) { if (frameDT > this.frameSpeed) {
this.lastFrameTime = timestamp;
this.frameCounter++; this.frameCounter++;
this.needUpdateFrameCounter = true; this.needUpdateFrameCounter = true;
update = true;
} }
// 图块移动 // 图块移动
@ -1533,14 +1533,17 @@ 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.forEachHook((hook, controller) => { this.requestUpdate();
hook.onUpdate?.(controller);
});
} }
//#endregion //#endregion

View File

@ -426,6 +426,12 @@ export interface IMapRenderer extends IHookable<IMapRendererHooks> {
* @param y * @param y
*/ */
setTileAlpha(layer: IMapLayer, alpha: number, x: number, y: number): void; setTileAlpha(layer: IMapLayer, alpha: number, x: number, y: number): void;
/**
*
* @param timestamp
*/
tick(timestamp: number): void;
} }
export interface IMapVertexArray { export interface IMapVertexArray {

View File

@ -59,42 +59,26 @@ export class MapViewport implements IMapViewportController {
if (widthOne && heightOne) { if (widthOne && heightOne) {
// 只能看到一个分块 // 只能看到一个分块
const block = this.vertex.block.getBlockByLoc(blockLeft, blockTop)!; const block = this.vertex.block.getBlockByLoc(blockLeft, blockTop)!;
if (block.data.dirty || block.data.renderDirty) { blockList.push(block);
blockList.push(block);
}
} else if (widthOne) { } else if (widthOne) {
// 看到的区域分块宽度是 1 // 看到的区域分块宽度是 1
for (let ny = blockTop; ny <= blockBottom; ny++) { for (let ny = blockTop; ny <= blockBottom; ny++) {
const block = this.vertex.block.getBlockByLoc(blockLeft, ny)!; const block = this.vertex.block.getBlockByLoc(blockLeft, ny)!;
if (block.data.dirty || block.data.renderDirty) { blockList.push(block);
blockList.push(block);
}
} }
} else if (heightOne) { } else if (heightOne) {
// 看到的区域分块高度是 1 // 看到的区域分块高度是 1
for (let nx = blockLeft; nx <= blockRight; nx++) { for (let nx = blockLeft; nx <= blockRight; nx++) {
const block = this.vertex.block.getBlockByLoc(nx, blockTop)!; const block = this.vertex.block.getBlockByLoc(nx, blockTop)!;
if (block.data.dirty || block.data.renderDirty) { blockList.push(block);
blockList.push(block);
}
} }
} else { } else {
// 看到的区域分块宽高都不是 1 // 看到的区域分块宽高都不是 1
// 使用这种方式的话,索引在换行之前都是连续的,方便整合 // 使用这种方式的话,索引在换行之前都是连续的,方便整合
for (let ny = blockTop; ny <= blockBottom; ny++) { for (let ny = blockTop; ny <= blockBottom; ny++) {
const first = this.vertex.block.getBlockByLoc(blockLeft, ny)!; for (let nx = blockLeft; nx <= blockRight; nx++) {
const last = this.vertex.block.getBlockByLoc(blockRight, ny)!;
if (first.data.dirty) {
blockList.push(first);
}
if (last.data.dirty && first !== last) {
blockList.push(last);
}
for (let nx = blockLeft + 1; nx < blockRight; nx++) {
const block = this.vertex.block.getBlockByLoc(nx, ny)!; const block = this.vertex.block.getBlockByLoc(nx, ny)!;
if (block.data.dirty) { blockList.push(block);
blockList.push(block);
}
} }
} }
} }
@ -143,7 +127,7 @@ export class MapViewport implements IMapViewportController {
return { return {
render: renderArea, render: renderArea,
dirty: updateArea, dirty: updateArea,
blockList blockList: blockList.filter(v => v.data.dirty)
}; };
} }