From 0b6cecda60a8a2020aa6fc684b7775517364461a Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Mon, 2 Mar 2026 21:35:17 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=9C=B0=E5=9B=BE=E6=96=87=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client-modules/src/render/commonIns.ts | 1 + .../src/render/elements/index.ts | 35 +-- .../src/render/elements/props.ts | 4 +- .../client-modules/src/render/map/element.ts | 42 ++- .../src/render/map/extension/manager.ts | 22 ++ .../src/render/map/extension/text.ts | 252 ++++++++++++++++-- .../src/render/map/extension/types.ts | 65 ++++- .../client-modules/src/render/map/renderer.ts | 11 +- .../client-modules/src/render/map/types.ts | 28 +- .../client-modules/src/render/map/vertex.ts | 17 ++ .../client-modules/src/render/ui/main.tsx | 33 +-- .../client-modules/src/render/ui/title.tsx | 2 +- packages/common/src/logger.json | 1 + packages/render-core/src/utils.ts | 8 +- src/types/declaration/control.d.ts | 2 +- 15 files changed, 442 insertions(+), 81 deletions(-) diff --git a/packages-user/client-modules/src/render/commonIns.ts b/packages-user/client-modules/src/render/commonIns.ts index acfd1c2..61799d4 100644 --- a/packages-user/client-modules/src/render/commonIns.ts +++ b/packages-user/client-modules/src/render/commonIns.ts @@ -19,4 +19,5 @@ export async function createMainExtension() { mainMapExtension.addHero(state.hero, layer); mainMapExtension.addDoor(layer); } + mainMapExtension.addText(); } diff --git a/packages-user/client-modules/src/render/elements/index.ts b/packages-user/client-modules/src/render/elements/index.ts index 4fd0cf5..5aaaa1b 100644 --- a/packages-user/client-modules/src/render/elements/index.ts +++ b/packages-user/client-modules/src/render/elements/index.ts @@ -7,7 +7,7 @@ import { Icon, Winskin } from './misc'; import { Animate } from './animate'; import { createItemDetail } from './itemDetail'; import { logger } from '@motajs/common'; -import { MapRender, MapRenderer } from '../map'; +import { MapExtensionManager, MapRender, MapRenderer } from '../map'; import { state } from '@user/data-state'; import { materials } from '@user/client-base'; @@ -72,28 +72,31 @@ export function createElements() { tagMap.register('icon', standardElementNoCache(Icon)); tagMap.register('map-render', (_0, _1, props) => { if (!props) { - logger.error(42); - return new MapRender( - state.layer, - new MapRenderer(materials, state.layer) - ); + logger.error(42, 'layerState, renderer, extenstion'); + const renderer = new MapRenderer(materials, state.layer); + const manager = new MapExtensionManager(renderer); + return new MapRender(state.layer, renderer, manager); } - const { layerState, renderer } = props; + const { layerState, renderer, extension } = props; if (!layerState) { logger.error(42, 'layerState'); - return new MapRender( - state.layer, - new MapRenderer(materials, state.layer) - ); + const renderer = new MapRenderer(materials, state.layer); + const manager = new MapExtensionManager(renderer); + return new MapRender(state.layer, renderer, manager); } if (!renderer) { logger.error(42, 'renderer'); - return new MapRender( - state.layer, - new MapRenderer(materials, state.layer) - ); + const renderer = new MapRenderer(materials, state.layer); + const manager = new MapExtensionManager(renderer); + return new MapRender(state.layer, renderer, manager); } - return new MapRender(layerState, renderer); + if (!extension) { + logger.error(42, 'extension'); + const renderer = new MapRenderer(materials, state.layer); + const manager = new MapExtensionManager(renderer); + return new MapRender(state.layer, renderer, manager); + } + return new MapRender(layerState, renderer, extension); }); } diff --git a/packages-user/client-modules/src/render/elements/props.ts b/packages-user/client-modules/src/render/elements/props.ts index fced0d4..ea2f51d 100644 --- a/packages-user/client-modules/src/render/elements/props.ts +++ b/packages-user/client-modules/src/render/elements/props.ts @@ -12,7 +12,7 @@ import { EAnimateEvent } from './animate'; import { EIconEvent, EWinskinEvent } from './misc'; import { IEnemyCollection } from '@motajs/types'; import { ILayerState } from '@user/data-state'; -import { IMapRenderer } from '../map'; +import { IMapExtensionManager, IMapRenderer, IOnMapTextRenderer } from '../map'; export interface AnimateProps extends BaseProps {} @@ -65,6 +65,8 @@ export interface LayerProps extends BaseProps { export interface MapRenderProps extends BaseProps { layerState: ILayerState; renderer: IMapRenderer; + extension: IMapExtensionManager; + textExtension?: IOnMapTextRenderer | null; } declare module 'vue/jsx-runtime' { diff --git a/packages-user/client-modules/src/render/map/element.ts b/packages-user/client-modules/src/render/map/element.ts index 2ce0093..cc691e7 100644 --- a/packages-user/client-modules/src/render/map/element.ts +++ b/packages-user/client-modules/src/render/map/element.ts @@ -3,6 +3,7 @@ import { ILayerState } from '@user/data-state'; import { IMapRenderer } from './types'; import { ElementNamespace, ComponentInternalInstance } from 'vue'; import { CELL_HEIGHT, CELL_WIDTH, MAP_HEIGHT, MAP_WIDTH } from '../shared'; +import { IMapExtensionManager } from './extension'; export class MapRender extends RenderItem { /** @@ -11,7 +12,8 @@ export class MapRender extends RenderItem { */ constructor( readonly layerState: ILayerState, - readonly renderer: IMapRenderer + readonly renderer: IMapRenderer, + readonly exManager: IMapExtensionManager ) { super('static', false, false); @@ -24,9 +26,33 @@ export class MapRender extends RenderItem { if (this.renderer.needUpdate()) { this.update(); } + const text = exManager.textRenderer; + if (text) { + if (text.needResize) { + this.resizeTextRenderer(this.width, this.height); + } + if (text.needUpdate()) { + this.update(); + } + } }); } + private resizeTextRenderer(width: number, height: number) { + const ex = this.exManager.textRenderer; + if (!ex) return; + const ratio = this.highResolution ? devicePixelRatio : 1; + const scale = ratio * this.scale; + const w = width * scale; + const h = height * scale; + ex.resize( + w, + h, + w / this.renderer.renderWidth, + h / this.renderer.renderHeight + ); + } + private sizeGL(width: number, height: number) { const ratio = this.highResolution ? devicePixelRatio : 1; const scale = ratio * this.scale; @@ -34,6 +60,14 @@ export class MapRender extends RenderItem { const h = height * scale; this.renderer.setCanvasSize(w, h); this.renderer.setViewport(0, 0, w, h); + if (this.exManager.textRenderer) { + this.exManager.textRenderer.resize( + w, + h, + w / this.renderer.renderWidth, + h / this.renderer.renderHeight + ); + } } onResize(scale: number): void { @@ -49,7 +83,11 @@ export class MapRender extends RenderItem { protected render(canvas: MotaOffscreenCanvas2D): void { this.renderer.clear(true, true); const map = this.renderer.render(); - canvas.ctx.drawImage(map, 0, 0, canvas.width, canvas.height); + canvas.ctx.drawImage(map.canvas, 0, 0, canvas.width, canvas.height); + if (this.exManager.textRenderer) { + const text = this.exManager.textRenderer.render(map); + canvas.ctx.drawImage(text, 0, 0, canvas.width, canvas.height); + } } patchProp( diff --git a/packages-user/client-modules/src/render/map/extension/manager.ts b/packages-user/client-modules/src/render/map/extension/manager.ts index 25f286e..aa5b341 100644 --- a/packages-user/client-modules/src/render/map/extension/manager.ts +++ b/packages-user/client-modules/src/render/map/extension/manager.ts @@ -8,12 +8,16 @@ import { IMapRenderer } from '../types'; import { MapHeroRenderer } from './hero'; import { logger } from '@motajs/common'; import { MapDoorRenderer } from './door'; +import { OnMapTextRenderer } from './text'; +import { IOnMapTextRenderer } from './types'; export class MapExtensionManager implements IMapExtensionManager { /** 勇士状态至勇士渲染器的映射 */ readonly heroMap: Map = new Map(); /** 地图图层到门渲染器的映射 */ readonly doorMap: Map = new Map(); + /** 单例的文字渲染拓展(独立图层) */ + textRenderer: IOnMapTextRenderer | null = null; constructor(readonly renderer: IMapRenderer) {} @@ -44,6 +48,22 @@ export class MapExtensionManager implements IMapExtensionManager { return doorRenderer; } + addText(): IOnMapTextRenderer | null { + if (this.textRenderer) { + logger.error(45, 'on-map text renderer'); + return null; + } + const r = new OnMapTextRenderer(this.renderer); + this.textRenderer = r; + return r; + } + + removeText(): void { + if (!this.textRenderer) return; + this.textRenderer.destroy(); + this.textRenderer = null; + } + removeDoor(layer: IMapLayer): void { const renderer = this.doorMap.get(layer); if (!renderer) return; @@ -56,5 +76,7 @@ export class MapExtensionManager implements IMapExtensionManager { this.doorMap.forEach(v => void v.destroy()); this.heroMap.clear(); this.doorMap.clear(); + this.textRenderer?.destroy(); + this.textRenderer = null; } } diff --git a/packages-user/client-modules/src/render/map/extension/text.ts b/packages-user/client-modules/src/render/map/extension/text.ts index a73d5aa..93827d4 100644 --- a/packages-user/client-modules/src/render/map/extension/text.ts +++ b/packages-user/client-modules/src/render/map/extension/text.ts @@ -1,63 +1,275 @@ -import { IMapRenderer } from '../types'; +import { logger } from '@motajs/common'; +import { + IBlockData, + IBlockSplitter, + IMapRenderer, + IMapRenderResult, + IMapVertexBlock +} from '../types'; import { IMapTextArea, IMapTextRenderable, IOnMapTextRenderer } from './types'; +import { ITransformUpdatable, Transform } from '@motajs/render-core'; -export class OnMapTextRenderer implements IOnMapTextRenderer { +export class OnMapTextRenderer + implements IOnMapTextRenderer, ITransformUpdatable +{ /** 画布元素 */ readonly canvas: HTMLCanvasElement; /** 画布 Canvas2D 上下文 */ readonly ctx: CanvasRenderingContext2D; - /** 图块索引到图块文本对象的映射 */ - readonly areaMap: Map = new Map(); + needResize: boolean = true; + + /** 分块上附着文本区域的标识符 */ + private readonly attachSymbol: symbol = Symbol('onMapTextAreas'); + + /** 是否有内容发生变化,需要更新 */ private dirty: boolean = false; + /** 分块对象 */ + private readonly block: IBlockSplitter; + /** 变换矩阵 */ + private readonly transform: Transform; + constructor(readonly renderer: IMapRenderer) { this.canvas = document.createElement('canvas'); this.ctx = this.canvas.getContext('2d')!; + this.block = renderer.vertex.block; + this.transform = renderer.transform; + this.ctx.lineWidth = 2; + this.transform.bind(this); } - render(): HTMLCanvasElement { + updateTransform() { + this.dirty = true; + } + + /** + * 标记为需要更新 + */ + markDirty(): void { + this.dirty = true; + } + + resize( + width: number, + height: number, + scaleX: number, + scaleY: number + ): void { + this.canvas.width = width; + this.canvas.height = height; + this.ctx.setTransform(1, 0, 0, 1, 0, 0); + this.ctx.translate(width / 2, height / 2); + this.ctx.scale(1, -1); + this.ctx.scale(scaleX, scaleY); + this.dirty = true; + this.needResize = false; + } + + getBlockByLoc(x: number, y: number): Readonly | null { + const index = y * this.renderer.mapWidth + x; + // 首先尝试使用分块系统获取对应分块并从分块附着数据读取 + const blockData = this.block.getBlockByDataIndex(index); + if (blockData) { + const map = blockData.data.getAttachedData< + Map + >(this.attachSymbol); + if (map) return map.get(index) ?? null; + } + return null; + } + + getBlockByIndex(index: number): Readonly | null { + const blockData = this.block.getBlockByDataIndex(index); + if (blockData) { + const map = blockData.data.getAttachedData< + Map + >(this.attachSymbol); + if (map) return map.get(index) || null; + } + return null; + } + + render(data: IMapRenderResult): HTMLCanvasElement { + if (!this.dirty) return this.canvas; + + const ctx = this.ctx; + const { renderWidth, renderHeight } = this.renderer; + + // clear + ctx.clearRect( + -renderWidth / 2, + -renderHeight / 2, + renderWidth, + renderHeight + ); + ctx.save(); + + // apply transform matrix + const [a, b, , c, d, , e, f] = this.transform.mat; + + ctx.transform( + a, + b, + c, + d, + (e * renderWidth) / 2, + (f * renderHeight) / 2 + ); + ctx.scale(1, -1); + + // draw text in each block + for (const blk of data.area.blockList) { + const map = blk.data.getAttachedData>( + this.attachSymbol + ); + if (!map) continue; + for (const area of map.values()) { + const baseX = area.mapX * this.renderer.cellWidth; + const baseY = area.mapY * this.renderer.cellHeight; + for (const renderable of area.getRenderables()) { + const x = baseX + renderable.px - renderWidth / 2; + const y = renderHeight / 2 - (baseY + renderable.py); + ctx.font = renderable.font.string(); + ctx.textAlign = renderable.textAlign; + ctx.textBaseline = renderable.textBaseline; + if (renderable.strokeStyle) { + ctx.strokeStyle = renderable.strokeStyle; + ctx.strokeText(renderable.text, x, -y); + } + if (renderable.fillStyle) { + ctx.fillStyle = renderable.fillStyle; + ctx.fillText(renderable.text, x, -y); + } + } + } + } + + this.dirty = false; + ctx.restore(); + return this.canvas; } - requireBlockArea(x: number, y: number): Readonly { + private getAttachedMap( + blockData: IBlockData + ): Map { + const map = blockData.data.getAttachedData>( + this.attachSymbol + ); + if (map) return map; + else { + const map = new Map(); + blockData.data.attach(this.attachSymbol, map); + return map; + } + } + + requireBlockArea(x: number, y: number): Readonly | null { const index = y * this.renderer.mapWidth + x; - const exist = this.areaMap.get(index); - if (exist) return exist; - const area = new MapTextArea(this, x, y); - this.areaMap.set(index, area); - return area; + // try to find corresponding block by data index + const blockData = this.block.getBlockByDataIndex(index); + if (blockData) { + const map = this.getAttachedMap(blockData); + const exist = map.get(index); + if (exist) return exist; + const area = new MapTextArea(this, x, y); + map.set(index, area); + this.markDirty(); + return area; + } else { + logger.error(47); + return null; + } } needUpdate(): boolean { return this.dirty; } - clear(): void {} + clear(): void { + // 清理所有附着在分块上的文本区域 + for (const b of this.block.iterateBlocks()) { + const blk = b.data; + const map = blk.getAttachedData>( + this.attachSymbol + ); + if (map) { + for (const area of map.values()) area.clear(); + blk.deleteAttachedData(this.attachSymbol); + } + } - destroy(): void {} + this.dirty = true; + } + + destroy(): void { + this.transform.unbind(this); + this.clear(); + // the canvas and context references are left intact; consumers may + // discard the renderer instance to allow GC. We don't detach the + // canvas from any DOM since ownership is external. + } } class MapTextArea implements IMapTextArea { - index: number; + readonly index: number; + // maintain both a set for quick membership checks and a map for index lookup + private renderableSet: Set = new Set(); + private renderableMap: Map = new Map(); + private reverseMap: Map = new Map(); + private nextRenderableIndex: number = 1; + + /** + * 获取本区域的所有可渲染对象,用于绘制阶段 + */ + getRenderables(): Iterable { + return this.renderableSet; + } constructor( readonly renderer: OnMapTextRenderer, - public mapX: number, - public mapY: number + public readonly mapX: number, + public readonly mapY: number ) { this.index = mapY * renderer.renderer.mapWidth + mapX; } - addTextRenderable(renderable: IMapTextRenderable): void { - throw new Error('Method not implemented.'); + addTextRenderable(renderable: IMapTextRenderable): number { + const idx = this.nextRenderableIndex++; + this.renderableSet.add(renderable); + this.renderableMap.set(idx, renderable); + this.reverseMap.set(renderable, idx); + this.renderer.markDirty(); + return idx; } removeTextRenderable(renderable: IMapTextRenderable): void { - throw new Error('Method not implemented.'); + const idx = this.reverseMap.get(renderable); + if (idx !== void 0) { + this.renderableSet.delete(renderable); + this.renderableMap.delete(idx); + this.reverseMap.delete(renderable); + this.renderer.markDirty(); + } + } + + removeTextRenderableByIndex(index: number): void { + const obj = this.renderableMap.get(index); + if (obj !== void 0) { + this.renderableMap.delete(index); + this.renderableSet.delete(obj); + this.reverseMap.delete(obj); + this.renderer.markDirty(); + } } clear(): void { - throw new Error('Method not implemented.'); + if (this.renderableSet.size > 0) { + this.renderableSet.clear(); + this.renderableMap.clear(); + this.reverseMap.clear(); + this.renderer.markDirty(); + } } } diff --git a/packages-user/client-modules/src/render/map/extension/types.ts b/packages-user/client-modules/src/render/map/extension/types.ts index 4617b77..cee94ed 100644 --- a/packages-user/client-modules/src/render/map/extension/types.ts +++ b/packages-user/client-modules/src/render/map/extension/types.ts @@ -6,8 +6,16 @@ import { IMapLayer } from '@user/data-state'; import { Font } from '@motajs/render-style'; +import { IMapRenderResult } from '../types'; export interface IMapExtensionManager { + /** 勇士状态至勇士渲染器的映射 */ + readonly heroMap: Map; + /** 地图图层到门渲染器的映射 */ + readonly doorMap: Map; + /** 单例的文字渲染拓展(独立图层) */ + readonly textRenderer: IOnMapTextRenderer | null; + /** * 添加勇士渲染拓展 * @param state 勇士状态 @@ -31,6 +39,16 @@ export interface IMapExtensionManager { */ removeDoor(layer: IMapLayer): void; + /** + * 添加文字渲染拓展 + */ + addText(): IOnMapTextRenderer | null; + + /** + * 移除文字渲染拓展 + */ + removeText(): void; + /** * 摧毁这个拓展管理对象,释放相关资源 */ @@ -192,17 +210,18 @@ export interface IMapTextRequested { export interface IMapTextArea { /** 图块在地图上的索引 */ - index: number; + readonly index: number; /** 图块横坐标 */ - mapX: number; + readonly mapX: number; /** 图块纵坐标 */ - mapY: number; + readonly mapY: number; /** * 添加文字可渲染对象。可渲染对象的坐标相对于图块,而非地图。 * @param renderable 可渲染对象 + * @returns 添加的可渲染对象的唯一索引标识符 */ - addTextRenderable(renderable: IMapTextRenderable): void; + addTextRenderable(renderable: IMapTextRenderable): number; /** * 移除指定的文字可渲染对象 @@ -210,6 +229,12 @@ export interface IMapTextArea { */ removeTextRenderable(renderable: IMapTextRenderable): void; + /** + * 根据可渲染对象的索引标识符移除文字的可渲染对象 + * @param index 可渲染对象对应的索引标识符 + */ + removeTextRenderableByIndex(index: number): void; + /** * 清除本图块的所有文字可渲染对象 */ @@ -217,17 +242,43 @@ export interface IMapTextArea { } export interface IOnMapTextRenderer { + /** 是否需要修改画布大小 */ + readonly needResize: boolean; + + /** + * 修改画布的缩放比例,传入的参数包含 `devicePixelRatio` 以及渲染器自身缩放比例 + * @param width 画布宽度 + * @param height 画布高度 + * @param scaleX 画布横向比例 + * @param scaleY 画布纵向比例 + */ + resize(width: number, height: number, scaleX: number, scaleY: number): void; + /** * 渲染地图文字,返回的画布就是文字画到的画布 */ - render(): HTMLCanvasElement; + render(data: IMapRenderResult): HTMLCanvasElement; /** - * 申请指定图块坐标的文字管理对象 + * 申请指定图块坐标的文字管理对象,如果该点已被申请,则会使用已申请的对象 + * @param x 图块横坐标 + * @param y 图块纵坐标 + * @returns 申请的文字管理对象,如果申请的坐标不在地图上,则会返回 `null` + */ + requireBlockArea(x: number, y: number): Readonly | null; + + /** + * 根据图块坐标获取已申请的文字管理对象,如果未申请则返回 `null` * @param x 图块横坐标 * @param y 图块纵坐标 */ - requireBlockArea(x: number, y: number): Readonly; + getBlockByLoc(x: number, y: number): Readonly | null; + + /** + * 根据索引获取已申请的文字管理对象,如果未申请则返回 `null` + * @param index 管理对象索引 + */ + getBlockByIndex(index: number): Readonly | null; /** * 判断是否需要更新 diff --git a/packages-user/client-modules/src/render/map/renderer.ts b/packages-user/client-modules/src/render/map/renderer.ts index 70468e4..6f80227 100644 --- a/packages-user/client-modules/src/render/map/renderer.ts +++ b/packages-user/client-modules/src/render/map/renderer.ts @@ -20,6 +20,7 @@ import { IMapRenderer, IMapRendererPostEffect, IMapRendererTicker, + IMapRenderResult, IMapVertexGenerator, IMapViewportController, IMovingBlock, @@ -217,6 +218,7 @@ export class MapRenderer this.canvas = document.createElement('canvas'); this.gl = this.canvas.getContext('webgl2')!; this.transform = new Transform(); + this.transform.bind(this); this.layerState = layerState; this.layerStateHook = layerState.addHook( new RendererLayerStateHook(this) @@ -1347,12 +1349,15 @@ export class MapRenderer this.updateRequired = true; } - render(): HTMLCanvasElement { + render(): IMapRenderResult { const gl = this.gl; const data = this.contextData; if (!this.assetData) { logger.error(31); - return this.canvas; + return { + canvas: this.canvas, + area: { blockList: [], dirty: [], render: [] } + }; } const { @@ -1512,7 +1517,7 @@ export class MapRenderer this.needUpdateOffsetPool = false; this.vertex.renderDynamic(); - return this.canvas; + return { canvas: this.canvas, area }; } //#endregion diff --git a/packages-user/client-modules/src/render/map/types.ts b/packages-user/client-modules/src/render/map/types.ts index 6571ff2..1aac847 100644 --- a/packages-user/client-modules/src/render/map/types.ts +++ b/packages-user/client-modules/src/render/map/types.ts @@ -317,6 +317,13 @@ export interface IMapRendererPostEffect { ): void; } +export interface IMapRenderResult { + /** 渲染结果所在的画布 */ + readonly canvas: HTMLCanvasElement; + /** 渲染内容所包含的分块 */ + readonly area: IMapRenderData; +} + export interface IMapRenderer { /** 地图渲染器使用的资源管理器 */ readonly manager: IMaterialManager; @@ -390,7 +397,7 @@ export interface IMapRenderer { /** * 渲染地图 */ - render(): HTMLCanvasElement; + render(): IMapRenderResult; /** * 设置地图的变换矩阵 @@ -894,6 +901,25 @@ export interface IMapVertexBlock extends IMapVertexData { * @param layer 图层对象 */ getLayerData(layer: IMapLayer): IMapVertexData | null; + + /** + * 在此分块上附着数据,一般用于拓展地图渲染,比如需要自定义分块渲染的场景 + * @param symbol 附着数据的标识符 + * @param data 附着数据内容 + */ + attach(symbol: symbol, data: T): void; + + /** + * 获取这个分块上指定的附着数据 + * @param symbol 附着数据的标识符 + */ + getAttachedData(symbol: symbol): T | undefined; + + /** + * 删除这个分块上指定的附着数据 + * @param symbol 附着数据的标识符 + */ + deleteAttachedData(symbol: symbol): void; } export interface IMapBlockUpdateObject { diff --git a/packages-user/client-modules/src/render/map/vertex.ts b/packages-user/client-modules/src/render/map/vertex.ts index 0a93d8d..8c06c11 100644 --- a/packages-user/client-modules/src/render/map/vertex.ts +++ b/packages-user/client-modules/src/render/map/vertex.ts @@ -1042,9 +1042,14 @@ class MapVertexBlock implements IMapVertexBlock { readonly instancedStart: number; + /** 每个图层的渲染偏移量 */ private readonly indexMap: Map = new Map(); + /** 每个图层对应的实例化数组 */ private readonly instancedMap: Map = new Map(); + /** 分块的附着数据 */ + private readonly attachedData: Map = new Map(); + /** * 创建分块的顶点数组对象,此对象不能动态扩展,如果地图变化,需要全部重建 * @param renderer 渲染器对象 @@ -1161,6 +1166,18 @@ class MapVertexBlock implements IMapVertexBlock { this.instancedStart + index * this.count * INSTANCED_COUNT }; } + + attach(symbol: symbol, data: T): void { + this.attachedData.set(symbol, data); + } + + getAttachedData(symbol: symbol): T | undefined { + return this.attachedData.get(symbol) as T; + } + + deleteAttachedData(symbol: symbol): void { + this.attachedData.delete(symbol); + } } //#endregion diff --git a/packages-user/client-modules/src/render/ui/main.tsx b/packages-user/client-modules/src/render/ui/main.tsx index af8059c..c2aedc4 100644 --- a/packages-user/client-modules/src/render/ui/main.tsx +++ b/packages-user/client-modules/src/render/ui/main.tsx @@ -4,18 +4,10 @@ import { IActionEvent, MotaOffscreenCanvas2D, Sprite, - onTick, - transformCanvas + onTick } from '@motajs/render'; -import { WeatherController } from '../weather'; -import { - defineComponent, - onMounted, - onUnmounted, - reactive, - ref, - shallowRef -} from 'vue'; +// import { WeatherController } from '../weather'; +import { defineComponent, onUnmounted, reactive, ref } from 'vue'; import { Textbox, Tip } from '../components'; import { GameUI } from '@motajs/system-ui'; import { @@ -39,9 +31,8 @@ import { getHeroStatusOn, state } from '@user/data-state'; import { hook } from '@user/data-base'; import { FloorChange } from '../legacy/fallback'; import { mainUIController } from './controller'; -import { LayerGroup } from '../elements'; import { isNil } from 'lodash-es'; -import { mainMapRenderer } from '../commonIns'; +import { mainMapExtension, mainMapRenderer } from '../commonIns'; const MainScene = defineComponent(() => { //#region 基本定义 @@ -60,17 +51,10 @@ const MainScene = defineComponent(() => { width: MAP_WIDTH }; - const map = shallowRef(); const hideStatus = ref(false); const locked = ref(false); - const weather = new WeatherController(); - weather.extern('main'); - - onMounted(() => { - if (map.value) { - weather.bind(map.value); - } - }); + // const weather = new WeatherController(); + // weather.extern('main'); const replayStatus: ReplayingStatus = reactive({ replaying: false, @@ -176,9 +160,7 @@ const MainScene = defineComponent(() => { const renderMapMisc = (canvas: MotaOffscreenCanvas2D) => { const step = core.status.stepPostfix; - const camera = map.value?.camera; - if (!step || !camera) return; - transformCanvas(canvas, camera); + if (!step) return; const ctx = canvas.ctx; ctx.fillStyle = '#fff'; step.forEach(({ x, y, direction }) => { @@ -260,6 +242,7 @@ const MainScene = defineComponent(() => { diff --git a/packages-user/client-modules/src/render/ui/title.tsx b/packages-user/client-modules/src/render/ui/title.tsx index 060b323..1ca906c 100644 --- a/packages-user/client-modules/src/render/ui/title.tsx +++ b/packages-user/client-modules/src/render/ui/title.tsx @@ -116,7 +116,7 @@ export const GameTitle = defineComponent(props => { const hard = main.levelChoose.map(v => { return { code: v.hard, - color: core.arrayToRGBA(v.color), + color: core.arrayToRGBA(v.color!), name: v.title, hard: v.name, colorTrans: transitionedColor('#fff', 400, hyper('sin', 'out'))!, diff --git a/packages/common/src/logger.json b/packages/common/src/logger.json index 9016c58..ce256ca 100644 --- a/packages/common/src/logger.json +++ b/packages/common/src/logger.json @@ -46,6 +46,7 @@ "44": "Cannot bind face direction to main block $1, since main direction cannot be override.", "45": "Cannot add $1 map renderer extension, since $1 already exists for the given state.", "46": "Cannot execute close door action on $1,$2, since the given position is not empty.", + "47": "Cannot require text area outside the target map.", "1201": "Floor-damage extension needs 'floor-binder' extension as dependency." }, "warn": { diff --git a/packages/render-core/src/utils.ts b/packages/render-core/src/utils.ts index 54350e8..f08cc04 100644 --- a/packages/render-core/src/utils.ts +++ b/packages/render-core/src/utils.ts @@ -12,10 +12,10 @@ export type Props< > = T extends keyof JSX.IntrinsicElements ? JSX.IntrinsicElements[T] : T extends DefineSetupFnComponent - ? InstanceType['$props'] & InstanceType['$emits'] - : T extends DefineComponent - ? InstanceType['$props'] & InstanceType['$emits'] - : unknown; + ? InstanceType['$props'] & InstanceType['$emits'] + : T extends DefineComponent + ? InstanceType['$props'] & InstanceType['$emits'] + : unknown; export type ElementLocator = [ x?: number, diff --git a/src/types/declaration/control.d.ts b/src/types/declaration/control.d.ts index 272aaee..d5f985b 100644 --- a/src/types/declaration/control.d.ts +++ b/src/types/declaration/control.d.ts @@ -1035,7 +1035,7 @@ interface Control { * @param bgm 背景音乐的文件名,支持全塔属性中映射前的中文名 * @param startTime 跳过前多少秒 */ - playBgm(bgm: BgmIds | NameMapIn, startTime?: number): void; + playBgm(bgm: BgmIds, startTime?: number): void; /** * @deprecated 可使用,考虑换用新的 `BgmController` 接口\