mirror of
https://github.com/motajs/template.git
synced 2026-04-12 15:11:10 +08:00
Compare commits
3 Commits
57108367b2
...
93fb788bef
| Author | SHA1 | Date | |
|---|---|---|---|
| 93fb788bef | |||
| 838f33347d | |||
| 0b6cecda60 |
@ -19,4 +19,5 @@ export async function createMainExtension() {
|
||||
mainMapExtension.addHero(state.hero, layer);
|
||||
mainMapExtension.addDoor(layer);
|
||||
}
|
||||
mainMapExtension.addText();
|
||||
}
|
||||
|
||||
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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' {
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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<IHeroState, IMapHeroRenderer> = new Map();
|
||||
/** 地图图层到门渲染器的映射 */
|
||||
readonly doorMap: Map<IMapLayer, IMapDoorRenderer> = 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,63 +1,279 @@
|
||||
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<Transform>
|
||||
{
|
||||
/** 画布元素 */
|
||||
readonly canvas: HTMLCanvasElement;
|
||||
/** 画布 Canvas2D 上下文 */
|
||||
readonly ctx: CanvasRenderingContext2D;
|
||||
|
||||
/** 图块索引到图块文本对象的映射 */
|
||||
readonly areaMap: Map<number, MapTextArea> = new Map();
|
||||
needResize: boolean = true;
|
||||
|
||||
/** 分块上附着文本区域的标识符 */
|
||||
private readonly attachSymbol: symbol = Symbol('onMapTextAreas');
|
||||
|
||||
/** 是否有内容发生变化,需要更新 */
|
||||
private dirty: boolean = false;
|
||||
|
||||
/** 分块对象 */
|
||||
private readonly block: IBlockSplitter<IMapVertexBlock>;
|
||||
/** 变换矩阵 */
|
||||
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<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 ?? 0) - renderWidth / 2;
|
||||
const y = renderHeight / 2 - (baseY + (renderable.py ?? 0));
|
||||
ctx.font = renderable.font.string();
|
||||
ctx.textAlign = renderable.textAlign ?? 'left';
|
||||
ctx.textBaseline = renderable.textBaseline ?? 'top';
|
||||
if (renderable.stroke) {
|
||||
ctx.strokeStyle = renderable.strokeStyle ?? 'black';
|
||||
ctx.strokeText(renderable.text, x, -y);
|
||||
}
|
||||
if (renderable.fill) {
|
||||
ctx.fillStyle = renderable.fillStyle ?? 'white';
|
||||
ctx.fillText(renderable.text, x, -y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.dirty = false;
|
||||
ctx.restore();
|
||||
|
||||
return this.canvas;
|
||||
}
|
||||
|
||||
requireBlockArea(x: number, y: number): Readonly<IMapTextArea> {
|
||||
/**
|
||||
* 获取分块所附着的文字数据
|
||||
* @param blockData 分块数据
|
||||
*/
|
||||
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 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;
|
||||
const area = new MapTextArea(this, x, y);
|
||||
this.areaMap.set(index, area);
|
||||
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<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 {
|
||||
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(
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<IHeroState, IMapHeroRenderer>;
|
||||
/** 地图图层到门渲染器的映射 */
|
||||
readonly doorMap: Map<IMapLayer, IMapDoorRenderer>;
|
||||
/** 单例的文字渲染拓展(独立图层) */
|
||||
readonly textRenderer: IOnMapTextRenderer | null;
|
||||
|
||||
/**
|
||||
* 添加勇士渲染拓展
|
||||
* @param state 勇士状态
|
||||
@ -31,6 +39,16 @@ export interface IMapExtensionManager {
|
||||
*/
|
||||
removeDoor(layer: IMapLayer): void;
|
||||
|
||||
/**
|
||||
* 添加文字渲染拓展
|
||||
*/
|
||||
addText(): IOnMapTextRenderer | null;
|
||||
|
||||
/**
|
||||
* 移除文字渲染拓展
|
||||
*/
|
||||
removeText(): void;
|
||||
|
||||
/**
|
||||
* 摧毁这个拓展管理对象,释放相关资源
|
||||
*/
|
||||
@ -168,18 +186,22 @@ export interface IMapTextRenderable {
|
||||
readonly text: string;
|
||||
/** 文本字体 */
|
||||
readonly font: Font;
|
||||
/** 是否填充 */
|
||||
readonly fill?: boolean;
|
||||
/** 是否描边 */
|
||||
readonly stroke?: boolean;
|
||||
/** 文本填充样式 */
|
||||
readonly fillStyle: CanvasStyle;
|
||||
readonly fillStyle?: CanvasStyle;
|
||||
/** 文本描边样式 */
|
||||
readonly strokeStyle: CanvasStyle;
|
||||
readonly strokeStyle?: CanvasStyle;
|
||||
/** 文本横坐标,注意 {@link IMapTextArea.addTextRenderable} 的相对关系 */
|
||||
readonly px: number;
|
||||
readonly px?: number;
|
||||
/** 文本纵坐标,注意 {@link IMapTextArea.addTextRenderable} 的相对关系 */
|
||||
readonly py: number;
|
||||
readonly py?: number;
|
||||
/** 文本横向对齐方式 */
|
||||
readonly textAlign: CanvasTextAlign;
|
||||
readonly textAlign?: CanvasTextAlign;
|
||||
/** 文本纵向对齐方式 */
|
||||
readonly textBaseline: CanvasTextBaseline;
|
||||
readonly textBaseline?: CanvasTextBaseline;
|
||||
}
|
||||
|
||||
export interface IMapTextRequested {
|
||||
@ -192,17 +214,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 +233,12 @@ export interface IMapTextArea {
|
||||
*/
|
||||
removeTextRenderable(renderable: IMapTextRenderable): void;
|
||||
|
||||
/**
|
||||
* 根据可渲染对象的索引标识符移除文字的可渲染对象
|
||||
* @param index 可渲染对象对应的索引标识符
|
||||
*/
|
||||
removeTextRenderableByIndex(index: number): void;
|
||||
|
||||
/**
|
||||
* 清除本图块的所有文字可渲染对象
|
||||
*/
|
||||
@ -217,17 +246,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<IMapTextArea> | null;
|
||||
|
||||
/**
|
||||
* 根据图块坐标获取已申请的文字管理对象,如果未申请则返回 `null`
|
||||
* @param x 图块横坐标
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* 判断是否需要更新
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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<T>(symbol: symbol, data: T): void;
|
||||
|
||||
/**
|
||||
* 获取这个分块上指定的附着数据
|
||||
* @param symbol 附着数据的标识符
|
||||
*/
|
||||
getAttachedData<T>(symbol: symbol): T | undefined;
|
||||
|
||||
/**
|
||||
* 删除这个分块上指定的附着数据
|
||||
* @param symbol 附着数据的标识符
|
||||
*/
|
||||
deleteAttachedData(symbol: symbol): void;
|
||||
}
|
||||
|
||||
export interface IMapBlockUpdateObject {
|
||||
|
||||
@ -1042,9 +1042,14 @@ class MapVertexBlock implements IMapVertexBlock {
|
||||
|
||||
readonly instancedStart: number;
|
||||
|
||||
/** 每个图层的渲染偏移量 */
|
||||
private readonly indexMap: Map<IMapLayer, number> = new Map();
|
||||
/** 每个图层对应的实例化数组 */
|
||||
private readonly instancedMap: Map<IMapLayer, Float32Array> = new Map();
|
||||
|
||||
/** 分块的附着数据 */
|
||||
private readonly attachedData: Map<symbol, unknown> = new Map();
|
||||
|
||||
/**
|
||||
* 创建分块的顶点数组对象,此对象不能动态扩展,如果地图变化,需要全部重建
|
||||
* @param renderer 渲染器对象
|
||||
@ -1161,6 +1166,18 @@ class MapVertexBlock implements IMapVertexBlock {
|
||||
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
|
||||
|
||||
@ -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<LayerGroup>();
|
||||
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(() => {
|
||||
<map-render
|
||||
renderer={mainMapRenderer}
|
||||
layerState={state.layer}
|
||||
extension={mainMapExtension}
|
||||
loc={[0, 0, MAP_WIDTH, MAP_HEIGHT]}
|
||||
/>
|
||||
<Textbox id="main-textbox" {...mainTextboxProps}></Textbox>
|
||||
|
||||
@ -116,7 +116,7 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
|
||||
const hard = main.levelChoose.map<ButtonOption>(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'))!,
|
||||
|
||||
@ -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": {
|
||||
|
||||
2
src/types/declaration/control.d.ts
vendored
2
src/types/declaration/control.d.ts
vendored
@ -1035,7 +1035,7 @@ interface Control {
|
||||
* @param bgm 背景音乐的文件名,支持全塔属性中映射前的中文名
|
||||
* @param startTime 跳过前多少秒
|
||||
*/
|
||||
playBgm(bgm: BgmIds | NameMapIn<BgmIds>, startTime?: number): void;
|
||||
playBgm(bgm: BgmIds, startTime?: number): void;
|
||||
|
||||
/**
|
||||
* @deprecated 可使用,考虑换用新的 `BgmController` 接口\
|
||||
|
||||
Loading…
Reference in New Issue
Block a user