feat: 地图文字

This commit is contained in:
unanmed 2026-03-02 21:35:17 +08:00
parent 57108367b2
commit 0b6cecda60
15 changed files with 442 additions and 81 deletions

View File

@ -19,4 +19,5 @@ export async function createMainExtension() {
mainMapExtension.addHero(state.hero, layer); mainMapExtension.addHero(state.hero, layer);
mainMapExtension.addDoor(layer); mainMapExtension.addDoor(layer);
} }
mainMapExtension.addText();
} }

View File

@ -7,7 +7,7 @@ import { Icon, Winskin } from './misc';
import { Animate } from './animate'; import { Animate } from './animate';
import { createItemDetail } from './itemDetail'; import { createItemDetail } from './itemDetail';
import { logger } from '@motajs/common'; import { logger } from '@motajs/common';
import { MapRender, MapRenderer } from '../map'; import { MapExtensionManager, MapRender, MapRenderer } from '../map';
import { state } from '@user/data-state'; import { state } from '@user/data-state';
import { materials } from '@user/client-base'; import { materials } from '@user/client-base';
@ -72,28 +72,31 @@ export function createElements() {
tagMap.register('icon', standardElementNoCache(Icon)); tagMap.register('icon', standardElementNoCache(Icon));
tagMap.register('map-render', (_0, _1, props) => { tagMap.register('map-render', (_0, _1, props) => {
if (!props) { if (!props) {
logger.error(42); logger.error(42, 'layerState, renderer, extenstion');
return new MapRender( const renderer = new MapRenderer(materials, state.layer);
state.layer, const manager = new MapExtensionManager(renderer);
new MapRenderer(materials, state.layer) return new MapRender(state.layer, renderer, manager);
);
} }
const { layerState, renderer } = props; const { layerState, renderer, extension } = props;
if (!layerState) { if (!layerState) {
logger.error(42, 'layerState'); logger.error(42, 'layerState');
return new MapRender( const renderer = new MapRenderer(materials, state.layer);
state.layer, const manager = new MapExtensionManager(renderer);
new MapRenderer(materials, state.layer) return new MapRender(state.layer, renderer, manager);
);
} }
if (!renderer) { if (!renderer) {
logger.error(42, 'renderer'); logger.error(42, 'renderer');
return new MapRender( const renderer = new MapRenderer(materials, state.layer);
state.layer, const manager = new MapExtensionManager(renderer);
new MapRenderer(materials, state.layer) 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);
}); });
} }

View File

@ -12,7 +12,7 @@ import { EAnimateEvent } from './animate';
import { EIconEvent, EWinskinEvent } from './misc'; import { EIconEvent, EWinskinEvent } from './misc';
import { IEnemyCollection } from '@motajs/types'; import { IEnemyCollection } from '@motajs/types';
import { ILayerState } from '@user/data-state'; import { ILayerState } from '@user/data-state';
import { IMapRenderer } from '../map'; import { IMapExtensionManager, IMapRenderer, IOnMapTextRenderer } from '../map';
export interface AnimateProps extends BaseProps {} export interface AnimateProps extends BaseProps {}
@ -65,6 +65,8 @@ export interface LayerProps extends BaseProps {
export interface MapRenderProps extends BaseProps { export interface MapRenderProps extends BaseProps {
layerState: ILayerState; layerState: ILayerState;
renderer: IMapRenderer; renderer: IMapRenderer;
extension: IMapExtensionManager;
textExtension?: IOnMapTextRenderer | null;
} }
declare module 'vue/jsx-runtime' { declare module 'vue/jsx-runtime' {

View File

@ -3,6 +3,7 @@ import { ILayerState } from '@user/data-state';
import { IMapRenderer } from './types'; import { IMapRenderer } from './types';
import { ElementNamespace, ComponentInternalInstance } from 'vue'; import { ElementNamespace, ComponentInternalInstance } from 'vue';
import { CELL_HEIGHT, CELL_WIDTH, MAP_HEIGHT, MAP_WIDTH } from '../shared'; import { CELL_HEIGHT, CELL_WIDTH, MAP_HEIGHT, MAP_WIDTH } from '../shared';
import { IMapExtensionManager } from './extension';
export class MapRender extends RenderItem { export class MapRender extends RenderItem {
/** /**
@ -11,7 +12,8 @@ export class MapRender extends RenderItem {
*/ */
constructor( constructor(
readonly layerState: ILayerState, readonly layerState: ILayerState,
readonly renderer: IMapRenderer readonly renderer: IMapRenderer,
readonly exManager: IMapExtensionManager
) { ) {
super('static', false, false); super('static', false, false);
@ -24,9 +26,33 @@ export class MapRender extends RenderItem {
if (this.renderer.needUpdate()) { if (this.renderer.needUpdate()) {
this.update(); 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) { private sizeGL(width: number, height: number) {
const ratio = this.highResolution ? devicePixelRatio : 1; const ratio = this.highResolution ? devicePixelRatio : 1;
const scale = ratio * this.scale; const scale = ratio * this.scale;
@ -34,6 +60,14 @@ export class MapRender extends RenderItem {
const h = height * scale; const h = height * scale;
this.renderer.setCanvasSize(w, h); this.renderer.setCanvasSize(w, h);
this.renderer.setViewport(0, 0, 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 { onResize(scale: number): void {
@ -49,7 +83,11 @@ export class MapRender extends RenderItem {
protected render(canvas: MotaOffscreenCanvas2D): void { protected render(canvas: MotaOffscreenCanvas2D): void {
this.renderer.clear(true, true); this.renderer.clear(true, true);
const map = this.renderer.render(); 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( patchProp(

View File

@ -8,12 +8,16 @@ import { IMapRenderer } from '../types';
import { MapHeroRenderer } from './hero'; import { MapHeroRenderer } from './hero';
import { logger } from '@motajs/common'; import { logger } from '@motajs/common';
import { MapDoorRenderer } from './door'; import { MapDoorRenderer } from './door';
import { OnMapTextRenderer } from './text';
import { IOnMapTextRenderer } from './types';
export class MapExtensionManager implements IMapExtensionManager { export class MapExtensionManager implements IMapExtensionManager {
/** 勇士状态至勇士渲染器的映射 */ /** 勇士状态至勇士渲染器的映射 */
readonly heroMap: Map<IHeroState, IMapHeroRenderer> = new Map(); readonly heroMap: Map<IHeroState, IMapHeroRenderer> = new Map();
/** 地图图层到门渲染器的映射 */ /** 地图图层到门渲染器的映射 */
readonly doorMap: Map<IMapLayer, IMapDoorRenderer> = new Map(); readonly doorMap: Map<IMapLayer, IMapDoorRenderer> = new Map();
/** 单例的文字渲染拓展(独立图层) */
textRenderer: IOnMapTextRenderer | null = null;
constructor(readonly renderer: IMapRenderer) {} constructor(readonly renderer: IMapRenderer) {}
@ -44,6 +48,22 @@ export class MapExtensionManager implements IMapExtensionManager {
return doorRenderer; 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 { removeDoor(layer: IMapLayer): void {
const renderer = this.doorMap.get(layer); const renderer = this.doorMap.get(layer);
if (!renderer) return; if (!renderer) return;
@ -56,5 +76,7 @@ export class MapExtensionManager implements IMapExtensionManager {
this.doorMap.forEach(v => void v.destroy()); this.doorMap.forEach(v => void v.destroy());
this.heroMap.clear(); this.heroMap.clear();
this.doorMap.clear(); this.doorMap.clear();
this.textRenderer?.destroy();
this.textRenderer = null;
} }
} }

View File

@ -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 { IMapTextArea, IMapTextRenderable, IOnMapTextRenderer } from './types';
import { ITransformUpdatable, Transform } from '@motajs/render-core';
export class OnMapTextRenderer implements IOnMapTextRenderer { export class OnMapTextRenderer
implements IOnMapTextRenderer, ITransformUpdatable<Transform>
{
/** 画布元素 */ /** 画布元素 */
readonly canvas: HTMLCanvasElement; readonly canvas: HTMLCanvasElement;
/** 画布 Canvas2D 上下文 */ /** 画布 Canvas2D 上下文 */
readonly ctx: CanvasRenderingContext2D; readonly ctx: CanvasRenderingContext2D;
/** 图块索引到图块文本对象的映射 */ needResize: boolean = true;
readonly areaMap: Map<number, MapTextArea> = new Map();
/** 分块上附着文本区域的标识符 */
private readonly attachSymbol: symbol = Symbol('onMapTextAreas');
/** 是否有内容发生变化,需要更新 */
private dirty: boolean = false; private dirty: boolean = false;
/** 分块对象 */
private readonly block: IBlockSplitter<IMapVertexBlock>;
/** 变换矩阵 */
private readonly transform: Transform;
constructor(readonly renderer: IMapRenderer) { constructor(readonly renderer: IMapRenderer) {
this.canvas = document.createElement('canvas'); this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d')!; 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<IMapTextArea> | null {
const index = y * this.renderer.mapWidth + x;
// 首先尝试使用分块系统获取对应分块并从分块附着数据读取
const blockData = this.block.getBlockByDataIndex(index);
if (blockData) {
const map = blockData.data.getAttachedData<
Map<number, MapTextArea>
>(this.attachSymbol);
if (map) return map.get(index) ?? null;
}
return null;
}
getBlockByIndex(index: number): Readonly<IMapTextArea> | null {
const blockData = this.block.getBlockByDataIndex(index);
if (blockData) {
const map = blockData.data.getAttachedData<
Map<number, MapTextArea>
>(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<Map<number, MapTextArea>>(
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; return this.canvas;
} }
requireBlockArea(x: number, y: number): Readonly<IMapTextArea> { private getAttachedMap(
blockData: IBlockData<IMapVertexBlock>
): Map<number, MapTextArea> {
const map = blockData.data.getAttachedData<Map<number, MapTextArea>>(
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<IMapTextArea> | null {
const index = y * this.renderer.mapWidth + x; const index = y * this.renderer.mapWidth + x;
const exist = this.areaMap.get(index); // 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; if (exist) return exist;
const area = new MapTextArea(this, x, y); const area = new MapTextArea(this, x, y);
this.areaMap.set(index, area); map.set(index, area);
this.markDirty();
return area; return area;
} else {
logger.error(47);
return null;
}
} }
needUpdate(): boolean { needUpdate(): boolean {
return this.dirty; return this.dirty;
} }
clear(): void {} clear(): void {
// 清理所有附着在分块上的文本区域
for (const b of this.block.iterateBlocks()) {
const blk = b.data;
const map = blk.getAttachedData<Map<number, MapTextArea>>(
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 { 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<IMapTextRenderable> = new Set();
private renderableMap: Map<number, IMapTextRenderable> = new Map();
private reverseMap: Map<IMapTextRenderable, number> = new Map();
private nextRenderableIndex: number = 1;
/**
*
*/
getRenderables(): Iterable<IMapTextRenderable> {
return this.renderableSet;
}
constructor( constructor(
readonly renderer: OnMapTextRenderer, readonly renderer: OnMapTextRenderer,
public mapX: number, public readonly mapX: number,
public mapY: number public readonly mapY: number
) { ) {
this.index = mapY * renderer.renderer.mapWidth + mapX; this.index = mapY * renderer.renderer.mapWidth + mapX;
} }
addTextRenderable(renderable: IMapTextRenderable): void { addTextRenderable(renderable: IMapTextRenderable): number {
throw new Error('Method not implemented.'); 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 { 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 { 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();
}
} }
} }

View File

@ -6,8 +6,16 @@ import {
IMapLayer IMapLayer
} from '@user/data-state'; } from '@user/data-state';
import { Font } from '@motajs/render-style'; import { Font } from '@motajs/render-style';
import { IMapRenderResult } from '../types';
export interface IMapExtensionManager { export interface IMapExtensionManager {
/** 勇士状态至勇士渲染器的映射 */
readonly heroMap: Map<IHeroState, IMapHeroRenderer>;
/** 地图图层到门渲染器的映射 */
readonly doorMap: Map<IMapLayer, IMapDoorRenderer>;
/** 单例的文字渲染拓展(独立图层) */
readonly textRenderer: IOnMapTextRenderer | null;
/** /**
* *
* @param state * @param state
@ -31,6 +39,16 @@ export interface IMapExtensionManager {
*/ */
removeDoor(layer: IMapLayer): void; removeDoor(layer: IMapLayer): void;
/**
*
*/
addText(): IOnMapTextRenderer | null;
/**
*
*/
removeText(): void;
/** /**
* *
*/ */
@ -192,17 +210,18 @@ export interface IMapTextRequested {
export interface IMapTextArea { export interface IMapTextArea {
/** 图块在地图上的索引 */ /** 图块在地图上的索引 */
index: number; readonly index: number;
/** 图块横坐标 */ /** 图块横坐标 */
mapX: number; readonly mapX: number;
/** 图块纵坐标 */ /** 图块纵坐标 */
mapY: number; readonly mapY: number;
/** /**
* *
* @param renderable * @param renderable
* @returns
*/ */
addTextRenderable(renderable: IMapTextRenderable): void; addTextRenderable(renderable: IMapTextRenderable): number;
/** /**
* *
@ -210,6 +229,12 @@ export interface IMapTextArea {
*/ */
removeTextRenderable(renderable: IMapTextRenderable): void; removeTextRenderable(renderable: IMapTextRenderable): void;
/**
*
* @param index
*/
removeTextRenderableByIndex(index: number): void;
/** /**
* *
*/ */
@ -217,17 +242,43 @@ export interface IMapTextArea {
} }
export interface IOnMapTextRenderer { 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<IMapTextArea> | null;
/**
* `null`
* @param x * @param x
* @param y * @param y
*/ */
requireBlockArea(x: number, y: number): Readonly<IMapTextArea>; getBlockByLoc(x: number, y: number): Readonly<IMapTextArea> | null;
/**
* `null`
* @param index
*/
getBlockByIndex(index: number): Readonly<IMapTextArea> | null;
/** /**
* *

View File

@ -20,6 +20,7 @@ import {
IMapRenderer, IMapRenderer,
IMapRendererPostEffect, IMapRendererPostEffect,
IMapRendererTicker, IMapRendererTicker,
IMapRenderResult,
IMapVertexGenerator, IMapVertexGenerator,
IMapViewportController, IMapViewportController,
IMovingBlock, IMovingBlock,
@ -217,6 +218,7 @@ export class MapRenderer
this.canvas = document.createElement('canvas'); this.canvas = document.createElement('canvas');
this.gl = this.canvas.getContext('webgl2')!; this.gl = this.canvas.getContext('webgl2')!;
this.transform = new Transform(); this.transform = new Transform();
this.transform.bind(this);
this.layerState = layerState; this.layerState = layerState;
this.layerStateHook = layerState.addHook( this.layerStateHook = layerState.addHook(
new RendererLayerStateHook(this) new RendererLayerStateHook(this)
@ -1347,12 +1349,15 @@ export class MapRenderer
this.updateRequired = true; this.updateRequired = true;
} }
render(): HTMLCanvasElement { render(): IMapRenderResult {
const gl = this.gl; const gl = this.gl;
const data = this.contextData; const data = this.contextData;
if (!this.assetData) { if (!this.assetData) {
logger.error(31); logger.error(31);
return this.canvas; return {
canvas: this.canvas,
area: { blockList: [], dirty: [], render: [] }
};
} }
const { const {
@ -1512,7 +1517,7 @@ export class MapRenderer
this.needUpdateOffsetPool = false; this.needUpdateOffsetPool = false;
this.vertex.renderDynamic(); this.vertex.renderDynamic();
return this.canvas; return { canvas: this.canvas, area };
} }
//#endregion //#endregion

View File

@ -317,6 +317,13 @@ export interface IMapRendererPostEffect {
): void; ): void;
} }
export interface IMapRenderResult {
/** 渲染结果所在的画布 */
readonly canvas: HTMLCanvasElement;
/** 渲染内容所包含的分块 */
readonly area: IMapRenderData;
}
export interface IMapRenderer { export interface IMapRenderer {
/** 地图渲染器使用的资源管理器 */ /** 地图渲染器使用的资源管理器 */
readonly manager: IMaterialManager; 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 * @param layer
*/ */
getLayerData(layer: IMapLayer): IMapVertexData | null; getLayerData(layer: IMapLayer): IMapVertexData | null;
/**
*
* @param symbol
* @param data
*/
attach<T>(symbol: symbol, data: T): void;
/**
*
* @param symbol
*/
getAttachedData<T>(symbol: symbol): T | undefined;
/**
*
* @param symbol
*/
deleteAttachedData(symbol: symbol): void;
} }
export interface IMapBlockUpdateObject { export interface IMapBlockUpdateObject {

View File

@ -1042,9 +1042,14 @@ class MapVertexBlock implements IMapVertexBlock {
readonly instancedStart: number; readonly instancedStart: number;
/** 每个图层的渲染偏移量 */
private readonly indexMap: Map<IMapLayer, number> = new Map(); private readonly indexMap: Map<IMapLayer, number> = new Map();
/** 每个图层对应的实例化数组 */
private readonly instancedMap: Map<IMapLayer, Float32Array> = new Map(); private readonly instancedMap: Map<IMapLayer, Float32Array> = new Map();
/** 分块的附着数据 */
private readonly attachedData: Map<symbol, unknown> = new Map();
/** /**
* *
* @param renderer * @param renderer
@ -1161,6 +1166,18 @@ class MapVertexBlock implements IMapVertexBlock {
this.instancedStart + index * this.count * INSTANCED_COUNT this.instancedStart + index * this.count * INSTANCED_COUNT
}; };
} }
attach<T>(symbol: symbol, data: T): void {
this.attachedData.set(symbol, data);
}
getAttachedData<T>(symbol: symbol): T | undefined {
return this.attachedData.get(symbol) as T;
}
deleteAttachedData(symbol: symbol): void {
this.attachedData.delete(symbol);
}
} }
//#endregion //#endregion

View File

@ -4,18 +4,10 @@ import {
IActionEvent, IActionEvent,
MotaOffscreenCanvas2D, MotaOffscreenCanvas2D,
Sprite, Sprite,
onTick, onTick
transformCanvas
} from '@motajs/render'; } from '@motajs/render';
import { WeatherController } from '../weather'; // import { WeatherController } from '../weather';
import { import { defineComponent, onUnmounted, reactive, ref } from 'vue';
defineComponent,
onMounted,
onUnmounted,
reactive,
ref,
shallowRef
} from 'vue';
import { Textbox, Tip } from '../components'; import { Textbox, Tip } from '../components';
import { GameUI } from '@motajs/system-ui'; import { GameUI } from '@motajs/system-ui';
import { import {
@ -39,9 +31,8 @@ import { getHeroStatusOn, state } from '@user/data-state';
import { hook } from '@user/data-base'; import { hook } from '@user/data-base';
import { FloorChange } from '../legacy/fallback'; import { FloorChange } from '../legacy/fallback';
import { mainUIController } from './controller'; import { mainUIController } from './controller';
import { LayerGroup } from '../elements';
import { isNil } from 'lodash-es'; import { isNil } from 'lodash-es';
import { mainMapRenderer } from '../commonIns'; import { mainMapExtension, mainMapRenderer } from '../commonIns';
const MainScene = defineComponent(() => { const MainScene = defineComponent(() => {
//#region 基本定义 //#region 基本定义
@ -60,17 +51,10 @@ const MainScene = defineComponent(() => {
width: MAP_WIDTH width: MAP_WIDTH
}; };
const map = shallowRef<LayerGroup>();
const hideStatus = ref(false); const hideStatus = ref(false);
const locked = ref(false); const locked = ref(false);
const weather = new WeatherController(); // const weather = new WeatherController();
weather.extern('main'); // weather.extern('main');
onMounted(() => {
if (map.value) {
weather.bind(map.value);
}
});
const replayStatus: ReplayingStatus = reactive({ const replayStatus: ReplayingStatus = reactive({
replaying: false, replaying: false,
@ -176,9 +160,7 @@ const MainScene = defineComponent(() => {
const renderMapMisc = (canvas: MotaOffscreenCanvas2D) => { const renderMapMisc = (canvas: MotaOffscreenCanvas2D) => {
const step = core.status.stepPostfix; const step = core.status.stepPostfix;
const camera = map.value?.camera; if (!step) return;
if (!step || !camera) return;
transformCanvas(canvas, camera);
const ctx = canvas.ctx; const ctx = canvas.ctx;
ctx.fillStyle = '#fff'; ctx.fillStyle = '#fff';
step.forEach(({ x, y, direction }) => { step.forEach(({ x, y, direction }) => {
@ -260,6 +242,7 @@ const MainScene = defineComponent(() => {
<map-render <map-render
renderer={mainMapRenderer} renderer={mainMapRenderer}
layerState={state.layer} layerState={state.layer}
extension={mainMapExtension}
loc={[0, 0, MAP_WIDTH, MAP_HEIGHT]} loc={[0, 0, MAP_WIDTH, MAP_HEIGHT]}
/> />
<Textbox id="main-textbox" {...mainTextboxProps}></Textbox> <Textbox id="main-textbox" {...mainTextboxProps}></Textbox>

View File

@ -116,7 +116,7 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
const hard = main.levelChoose.map<ButtonOption>(v => { const hard = main.levelChoose.map<ButtonOption>(v => {
return { return {
code: v.hard, code: v.hard,
color: core.arrayToRGBA(v.color), color: core.arrayToRGBA(v.color!),
name: v.title, name: v.title,
hard: v.name, hard: v.name,
colorTrans: transitionedColor('#fff', 400, hyper('sin', 'out'))!, colorTrans: transitionedColor('#fff', 400, hyper('sin', 'out'))!,

View File

@ -46,6 +46,7 @@
"44": "Cannot bind face direction to main block $1, since main direction cannot be override.", "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.", "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.", "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." "1201": "Floor-damage extension needs 'floor-binder' extension as dependency."
}, },
"warn": { "warn": {

View File

@ -1035,7 +1035,7 @@ interface Control {
* @param bgm * @param bgm
* @param startTime * @param startTime
*/ */
playBgm(bgm: BgmIds | NameMapIn<BgmIds>, startTime?: number): void; playBgm(bgm: BgmIds, startTime?: number): void;
/** /**
* @deprecated 使 `BgmController` \ * @deprecated 使 `BgmController` \