Compare commits

...

4 Commits

Author SHA1 Message Date
AncTe
8825792180
Merge a5ecc45d9b into 820dc5bf4c 2025-11-18 14:44:23 +00:00
a5ecc45d9b feat: 地图渲染动画效果 2025-11-18 22:44:07 +08:00
ebaa35ea5b refactor: 地图渲染器改为与 LayerState 绑定 2025-11-18 21:12:43 +08:00
6ae0d6ca5f feat: 地图渲染 2025-11-18 16:56:44 +08:00
38 changed files with 1469 additions and 1074 deletions

View File

@ -1,9 +1,10 @@
import { ITextureComposedData } from '@motajs/render-assets';
import { IMaterialAsset } from './types';
import { IDirtyMark } from '@motajs/common';
export class MaterialAsset implements IMaterialAsset {
/** 标记列表 */
private readonly marks: WeakMap<symbol, number> = new WeakMap();
private readonly marks: WeakMap<IDirtyMark, number> = new WeakMap();
/** 脏标记,所有值小于此标记的都视为需要更新 */
private dirtyFlag: number = 0;
@ -13,22 +14,22 @@ export class MaterialAsset implements IMaterialAsset {
this.dirtyFlag++;
}
mark(): symbol {
const symbol = Symbol();
mark(): IDirtyMark {
const symbol = {};
this.marks.set(symbol, this.dirtyFlag);
return symbol;
}
unmark(mark: symbol): void {
unmark(mark: IDirtyMark): void {
this.marks.delete(mark);
}
dirtySince(mark: symbol): boolean {
dirtySince(mark: IDirtyMark): boolean {
const value = this.marks.get(mark) ?? -1;
return value < this.dirtyFlag;
}
hasMark(symbol: symbol): boolean {
hasMark(symbol: IDirtyMark): boolean {
return this.marks.has(symbol);
}
}

View File

@ -71,21 +71,21 @@ export class AutotileProcessor implements IAutotileProcessor {
// 如果地图高度只有 1
if (length === width) {
if (index === 0) {
return 0b1100_0111;
return 0b1110_1111;
} else if (index === length - 1) {
return 0b0111_1100;
return 0b1111_1110;
} else {
return 0b0100_0100;
return 0b1110_1110;
}
}
// 如果地图宽度只有 1
if (width === 1) {
if (index === 0) {
return 0b1111_0001;
return 0b1111_1011;
} else if (index === length - 1) {
return 0b0001_1111;
return 0b1011_1111;
} else {
return 0b0001_0001;
return 0b1011_1011;
}
}
@ -96,23 +96,23 @@ export class AutotileProcessor implements IAutotileProcessor {
// 四个角,左上,右上,右下,左下
if (index === 0) {
return 0b1100_0001;
return 0b1110_0011;
} else if (index === width - 1) {
return 0b0111_0000;
return 0b1111_1000;
} else if (index === length - 1) {
return 0b0001_1100;
return 0b0011_1110;
} else if (index === lastLine) {
return 0b0000_0111;
return 0b1000_1111;
}
// 四条边,上,右,下,左
else if (index < width) {
return 0b0100_0000;
return 0b1110_0000;
} else if (x === width - 1) {
return 0b0001_0000;
return 0b0011_1000;
} else if (index > lastLine) {
return 0b0000_0100;
return 0b0000_1110;
} else if (x === 0) {
return 0b0000_0001;
return 0b1000_0011;
}
// 不在边缘
else {
@ -222,12 +222,13 @@ export class AutotileProcessor implements IAutotileProcessor {
connection: number
): ITextureRenderable | null {
const { texture } = tile;
const size = texture.height === 128 ? 32 : 48;
const size = texture.height === 32 * 48 ? 32 : 48;
const index = distinctConnectionMap.get(connection);
if (isNil(index)) return null;
const { rect } = texture.render();
return {
source: texture.source,
rect: { x: 0, y: size * index, w: size, h: size }
rect: { x: rect.x, y: rect.y + size * index, w: size, h: size }
};
}

View File

@ -3,10 +3,11 @@ import {
ITexture,
ITextureComposedData,
ITextureStreamComposer,
TextureMaxRectsStreamComposer
TextureMaxRectsStreamComposer,
SizedCanvasImageSource
} from '@motajs/render-assets';
import { IAssetBuilder } from './types';
import { logger } from '@motajs/common';
import { IAssetBuilder, IMaterialGetter, ITrackedAssetData } from './types';
import { logger, PrivateListDirtyTracker } from '@motajs/common';
export class AssetBuilder implements IAssetBuilder {
readonly composer: ITextureStreamComposer<void> =
@ -15,6 +16,18 @@ export class AssetBuilder implements IAssetBuilder {
private output: ITextureStore | null = null;
private started: boolean = false;
private readonly trackedData: TrackedAssetData;
/** 当前的索引 */
private index: number = -1;
/** 贴图更新的 promise */
private pending: Promise<void> = Promise.resolve();
constructor(readonly materials: IMaterialGetter) {
this.trackedData = new TrackedAssetData(materials, this);
}
pipe(store: ITextureStore): void {
if (this.started) {
logger.warn(76);
@ -29,29 +42,103 @@ export class AssetBuilder implements IAssetBuilder {
const data = res[0];
if (this.output) {
if (!this.output.getTexture(data.index)) {
if (data.index > this.index) {
this.output.addTexture(data.index, data.texture);
this.index = data.index;
}
}
this.pending = this.pending.then(() =>
this.trackedData.updateSource(data.index, data.texture.source)
);
return data;
}
private async updateSourceList(source: Set<ITextureComposedData>) {
for (const data of source) {
await this.trackedData.updateSource(
data.index,
data.texture.source
);
}
}
addTextureList(
texture: Iterable<ITexture>
): Iterable<ITextureComposedData> {
this.started = true;
const res = [...this.composer.add(texture)];
const toUpdate = new Set<ITextureComposedData>();
if (this.output) {
res.forEach(v => {
if (!this.output!.getTexture(v.index)) {
this.output!.addTexture(v.index, v.texture);
res.forEach(data => {
if (data.index > this.index) {
this.output!.addTexture(data.index, data.texture);
this.index = data.index;
toUpdate.add(data);
} else {
toUpdate.add(data);
}
});
}
this.pending = this.pending.then(() => this.updateSourceList(toUpdate));
return res;
}
tracked(): ITrackedAssetData {
return this.trackedData;
}
close(): void {
this.composer.close();
}
}
class TrackedAssetData
extends PrivateListDirtyTracker<number>
implements ITrackedAssetData
{
readonly sourceList: Map<number, ImageBitmap> = new Map();
readonly skipRef: Map<SizedCanvasImageSource, number> = new Map();
private originSourceMap: Map<number, SizedCanvasImageSource> = new Map();
constructor(
readonly materials: IMaterialGetter,
readonly builder: AssetBuilder
) {
super(0);
}
markDirty(index: number) {
this.dirty(index);
}
async updateSource(index: number, source: SizedCanvasImageSource) {
const origin = this.originSourceMap.get(index);
const prev = this.sourceList.get(index);
if (origin) {
this.skipRef.delete(origin);
}
if (prev) {
this.skipRef.delete(prev);
}
if (source instanceof ImageBitmap) {
if (this.skipRef.has(source)) return;
this.sourceList.set(index, source);
this.skipRef.set(source, index);
} else {
const bitmap = await createImageBitmap(source);
this.sourceList.set(index, bitmap);
this.skipRef.set(bitmap, index);
// 要把源也加到映射中,因为这里的 bitmap 与外部源并不同引用
this.skipRef.set(source, index);
this.originSourceMap.set(index, source);
}
this.dirty(index);
}
close(): void {}
}

View File

@ -36,7 +36,7 @@ function addAutotile(set: Set<number>, map?: readonly (readonly number[])[]) {
map.forEach(line => {
line.forEach(v => {
const id = core.maps.blocksInfo[v as keyof NumberToId];
if (id.cls === 'autotile') set.add(v);
if (id?.cls === 'autotile') set.add(v);
});
});
}
@ -118,7 +118,8 @@ export function fallbackLoad() {
addAutotile(autotileSet, floor.fg2map);
});
materials.cacheTilesetList(tilesetSet.union(autotileSet));
materials.buildAssets();
materials.cacheAutotileList(autotileSet);
materials.cacheTilesetList(tilesetSet);
}

View File

@ -19,16 +19,22 @@ import {
BlockCls,
IBigImageReturn,
IAssetBuilder,
IMaterialAsset,
IMaterialFramedData
IMaterialFramedData,
ITrackedAssetData
} from './types';
import { logger } from '@motajs/common';
import { getClsByString, getTextureFrame } from './utils';
import { isNil } from 'lodash-es';
import { AssetBuilder } from './builder';
import { MaterialAsset } from './asset';
import { AutotileProcessor } from './autotile';
interface TilesetCache {
/** 是否已经在贴图库中存在 */
readonly existed: boolean;
/** 贴图对象 */
readonly texture: ITexture;
}
export class MaterialManager implements IMaterialManager {
readonly tileStore: ITextureStore = new TextureStore();
readonly tilesetStore: ITextureStore = new TextureStore();
@ -40,16 +46,18 @@ export class MaterialManager implements IMaterialManager {
readonly autotileSource: Map<number, SizedCanvasImageSource> = new Map();
/** 图集信息存储 */
readonly assetDataStore: Map<number, IMaterialAsset> = new Map();
readonly assetDataStore: Map<number, ITextureComposedData> = new Map();
/** 贴图到图集索引的映射 */
readonly assetMap: Map<ITexture, number> = new Map();
/** 带有脏标记追踪的图集对象 */
readonly trackedAsset: ITrackedAssetData;
/** 大怪物数据 */
readonly bigImageData: Map<number, IMaterialFramedData> = new Map();
/** tileset 中 `Math.floor(id / 10000) + 1` 映射到 tileset 对应索引的映射,用于处理图块超出 10000 的 tileset */
readonly tilesetOffsetMap: Map<number, number> = new Map();
/** 图集打包器 */
readonly assetBuilder: IAssetBuilder = new AssetBuilder();
readonly assetBuilder: IAssetBuilder;
/** 图块 id 到图块数字的映射 */
readonly idNumMap: Map<string, number> = new Map();
@ -73,7 +81,9 @@ export class MaterialManager implements IMaterialManager {
private built: boolean = false;
constructor() {
this.assetBuilder = new AssetBuilder(this);
this.assetBuilder.pipe(this.assetStore);
this.trackedAsset = this.assetBuilder.tracked();
}
/**
@ -268,9 +278,9 @@ export class MaterialManager implements IMaterialManager {
return this.imageStore.fromAlias(alias);
}
private getTilesetOwnTexture(identifier: number) {
private getTilesetOwnTexture(identifier: number): TilesetCache | null {
const texture = this.tileStore.getTexture(identifier);
if (texture) return texture;
if (texture) return { existed: true, texture };
// 如果 tileset 不存在,那么执行缓存操作
const offset = Math.floor(identifier / 10000);
const index = this.tilesetOffsetMap.get(offset - 1);
@ -290,7 +300,7 @@ export class MaterialManager implements IMaterialManager {
const y = Math.floor(rest / tileWidth);
const newTexture = new Texture(tileset.source);
newTexture.clip(x * 32, y * 32, 32, 32);
return newTexture;
return { existed: false, texture: newTexture };
}
/**
@ -298,17 +308,13 @@ export class MaterialManager implements IMaterialManager {
* @param data
*/
private checkAssetDirty(data: ITextureComposedData) {
if (!this.built) return;
const asset = this.assetDataStore.get(data.index);
if (asset) {
// 如果不是新图集,需要标记为脏
asset.dirty();
} else {
if (!asset) {
// 如果有新图集,需要添加
const alias = `asset-${data.index}`;
const newAsset = new MaterialAsset(data);
newAsset.dirty();
this.assetStore.alias(data.index, alias);
this.assetDataStore.set(data.index, newAsset);
this.assetDataStore.set(data.index, data);
}
}
@ -335,14 +341,16 @@ export class MaterialManager implements IMaterialManager {
cacheTileset(identifier: number): ITexture | null {
const newTexture = this.getTilesetOwnTexture(identifier);
if (!newTexture) return null;
const { existed, texture } = newTexture;
if (existed) return texture;
// 缓存贴图
this.tileStore.addTexture(identifier, newTexture);
this.tileStore.addTexture(identifier, texture);
this.idNumMap.set(`X${identifier}`, identifier);
this.numIdMap.set(identifier, `X${identifier}`);
const data = this.assetBuilder.addTexture(newTexture);
newTexture.toAsset(data);
const data = this.assetBuilder.addTexture(texture);
texture.toAsset(data);
this.checkAssetDirty(data);
return newTexture;
return texture;
}
cacheTilesetList(
@ -354,8 +362,10 @@ export class MaterialManager implements IMaterialManager {
arr.forEach(v => {
const newTexture = this.getTilesetOwnTexture(v);
if (!newTexture) return;
toAdd.push(newTexture);
this.tileStore.addTexture(v, newTexture);
const { existed, texture } = newTexture;
if (existed) return;
toAdd.push(texture);
this.tileStore.addTexture(v, texture);
this.idNumMap.set(`X${v}`, v);
this.numIdMap.set(v, `X${v}`);
});
@ -429,7 +439,7 @@ export class MaterialManager implements IMaterialManager {
arr.forEach(v => {
const alias = `asset-${v.index}`;
this.assetStore.alias(v.index, alias);
this.assetDataStore.set(v.index, new MaterialAsset(v));
this.assetDataStore.set(v.index, v);
const data: IMaterialAssetData = {
data: v,
identifier: v.index,
@ -444,11 +454,11 @@ export class MaterialManager implements IMaterialManager {
return res;
}
getAsset(identifier: number): IMaterialAsset | null {
getAsset(identifier: number): ITextureComposedData | null {
return this.assetDataStore.get(identifier) ?? null;
}
getAssetByAlias(alias: string): IMaterialAsset | null {
getAssetByAlias(alias: string): ITextureComposedData | null {
const id = this.assetStore.identifierOf(alias);
if (isNil(id)) return null;
return this.assetDataStore.get(id) ?? null;

View File

@ -234,7 +234,7 @@ export interface IMaterialGetter {
*
* @param identifier
*/
getAsset(identifier: number): IMaterialAsset | null;
getAsset(identifier: number): ITextureComposedData | null;
/**
*
@ -272,7 +272,7 @@ export interface IMaterialAliasGetter {
*
* @param alias
*/
getAssetByAlias(alias: string): IMaterialAsset | null;
getAssetByAlias(alias: string): ITextureComposedData | null;
/**
*
@ -302,7 +302,9 @@ export interface IMaterialManager
readonly bigImageStore: ITextureStore;
/** 图集信息存储 */
readonly assetDataStore: Iterable<[number, IMaterialAsset]>;
readonly assetDataStore: Iterable<[number, ITextureComposedData]>;
/** 带有脏标记追踪的图集信息 */
readonly trackedAsset: ITrackedAssetData;
/** 图块类型映射 */
readonly clsMap: Map<number, BlockCls>;
@ -465,8 +467,30 @@ export interface IAssetBuilder {
*/
addTextureList(texture: Iterable<ITexture>): Iterable<ITextureComposedData>;
/**
*
*/
tracked(): ITrackedAssetData;
/**
*
*/
close(): void;
}
export interface ITrackedAssetData extends IDirtyTracker<Set<number>> {
/** 图像源列表 */
readonly sourceList: Map<number, ImageBitmap>;
/**
* `ImageBitmap` 使
* `ImageBitmap` `sourceList`
*/
readonly skipRef: Map<SizedCanvasImageSource, number>;
/** 贴图数据 */
readonly materials: IMaterialGetter;
/**
* 使
*/
close(): void;
}

View File

@ -8,6 +8,7 @@ import { Animate } from './animate';
import { createItemDetail } from './itemDetail';
import { logger } from '@motajs/common';
import { MapRender } from '../map/element';
import { state } from '@user/data-state';
export function createElements() {
createCache();
@ -71,14 +72,14 @@ export function createElements() {
tagMap.register('map-render', (_0, _1, props) => {
if (!props) {
logger.error(42);
return new MapRender([]);
return new MapRender(state.layer);
}
const { layerList } = props;
if (!layerList) {
const { layerState } = props;
if (!layerState) {
logger.error(42);
return new MapRender([]);
return new MapRender(state.layer);
}
return new MapRender(layerList);
return new MapRender(layerState);
});
}

View File

@ -1,5 +1,5 @@
import { BaseProps, TagDefine } from '@motajs/render-vue';
import { Transform } from '@motajs/render-core';
import { ERenderItemEvent, Transform } from '@motajs/render-core';
import { CanvasStyle } from '@motajs/render-assets';
import {
ILayerGroupRenderExtends,
@ -11,7 +11,7 @@ import {
import { EAnimateEvent } from './animate';
import { EIconEvent, EWinskinEvent } from './misc';
import { IEnemyCollection } from '@motajs/types';
import { IRenderLayerData } from '../map/element';
import { ILayerState } from '@user/data-state';
export interface AnimateProps extends BaseProps {}
@ -62,7 +62,7 @@ export interface LayerProps extends BaseProps {
}
export interface MapRenderProps extends BaseProps {
layerList: IRenderLayerData;
layerState: ILayerState;
}
declare module 'vue/jsx-runtime' {
@ -73,6 +73,7 @@ declare module 'vue/jsx-runtime' {
animation: TagDefine<AnimateProps, EAnimateEvent>;
icon: TagDefine<IconProps, EIconEvent>;
winskin: TagDefine<WinskinProps, EWinskinEvent>;
'map-render': TagDefine<MapRenderProps, ERenderItemEvent>;
}
}
}

View File

@ -1,31 +0,0 @@
import { SizedCanvasImageSource } from '@motajs/render-assets';
import {
IMaterialGetter,
IMaterialManager,
materials
} from '@user/client-base';
import { IMapAssetData, IMapAssetManager } from './types';
import { PrivateListDirtyTracker } from '@motajs/common';
export class MapAssetManager implements IMapAssetManager {
materials: IMaterialManager = materials;
generateAsset(): IMapAssetData {
const data = new MapAssetData();
return data;
}
}
class MapAssetData
extends PrivateListDirtyTracker<number>
implements IMapAssetData
{
sourceList: ImageBitmap[] = [];
skipRef: Map<SizedCanvasImageSource, number> = new Map();
materials: IMaterialGetter = materials;
constructor() {
super(0);
}
}

View File

@ -12,8 +12,8 @@ export class BlockSplitter<T> implements IBlockSplitter<T> {
blockHeight: number = 0;
dataWidth: number = 0;
dataHeight: number = 0;
width: number = 0;
height: number = 0;
width: number = 1;
height: number = 1;
/** 分块映射 */
readonly blockMap: Map<number, IBlockData<T>> = new Map();
@ -33,7 +33,7 @@ export class BlockSplitter<T> implements IBlockSplitter<T> {
* @param y
*/
private checkLocRange(x: number, y: number) {
return x > 0 && y > 0 && x < this.width && y < this.height;
return x >= 0 && y >= 0 && x < this.width && y < this.height;
}
getBlockByLoc(x: number, y: number): IBlockData<T> | null {
@ -221,10 +221,10 @@ export class BlockSplitter<T> implements IBlockSplitter<T> {
}
configSplitter(config: IBlockSplitterConfig): void {
this.splitDataWidth = config.dataWidth;
this.splitDataHeight = config.dataHeight;
this.splitBlockWidth = config.blockWidth;
this.splitBlockHeight = config.blockHeight;
this.splitDataWidth = config.dataWidth;
this.splitBlockHeight = config.dataHeight;
}
private mapBlock(
@ -252,6 +252,10 @@ export class BlockSplitter<T> implements IBlockSplitter<T> {
splitBlocks(mapFn: (block: IBlockInfo) => T): void {
this.blockMap.clear();
this.blockWidth = this.splitBlockWidth;
this.blockHeight = this.splitBlockHeight;
this.dataWidth = this.splitDataWidth;
this.dataHeight = this.splitDataHeight;
const restX = this.splitDataWidth % this.splitBlockWidth;
const restY = this.splitDataHeight % this.splitBlockHeight;
const width = Math.floor(this.splitDataWidth / this.splitBlockWidth);

View File

@ -0,0 +1,2 @@
/** 单个图块的实例化数据数量 */
export const INSTANCED_COUNT = 4 + 4 + 4 + 4;

View File

@ -3,20 +3,13 @@ import {
RenderItem,
Transform
} from '@motajs/render-core';
import { IMapLayer } from '@user/data-state';
import { IMapRenderer } from './types';
import { ILayerState, state } from '@user/data-state';
import { IMapRenderer, IMapRendererHooks } from './types';
import { MapRenderer } from './renderer';
import { materials } from '@user/client-base';
import { ElementNamespace, ComponentInternalInstance } from 'vue';
export interface IRenderLayerData {
/** 图层对象 */
readonly layer: IMapLayer;
/** 图层纵深 */
readonly zIndex: number;
/** 图层别名 */
readonly alias?: string;
}
import { CELL_HEIGHT, CELL_WIDTH, MAP_HEIGHT, MAP_WIDTH } from '../shared';
import { IHookController } from '@motajs/common';
export class MapRender extends RenderItem {
/** 地图渲染器 */
@ -29,32 +22,31 @@ export class MapRender extends RenderItem {
/** 画布上下文 */
readonly gl: WebGL2RenderingContext;
constructor(layerList: Iterable<IRenderLayerData>) {
private rendererHook: IHookController<IMapRendererHooks>;
constructor(readonly layerState: ILayerState) {
super('static');
this.canvas = document.createElement('canvas');
const gl = this.canvas.getContext('webgl2')!;
this.gl = gl;
this.renderer = new MapRenderer(materials, this.gl, this.camera);
for (const layer of layerList) {
this.renderer.addLayer(layer.layer, layer.alias);
this.renderer.setZIndex(layer.layer, layer.zIndex);
}
}
this.renderer = new MapRenderer(
materials,
this.gl,
this.camera,
state.layer
);
this.renderer.setLayerState(layerState);
this.renderer.useAsset(materials.trackedAsset);
this.rendererHook = this.renderer.addHook(new RendererUpdateHook(this));
this.rendererHook.load();
this.renderer.setCellSize(CELL_WIDTH, CELL_HEIGHT);
this.renderer.setRenderSize(MAP_WIDTH, MAP_HEIGHT);
/**
*
* @param layerList
*/
updateLayerList(layerList: Iterable<IRenderLayerData>) {
this.renderer
.getSortedLayer()
.forEach(v => this.renderer.removeLayer(v));
for (const layer of layerList) {
this.renderer.addLayer(layer.layer, layer.alias);
this.renderer.setZIndex(layer.layer, layer.zIndex);
}
this.delegateTicker(time => this.renderer.tick(time));
gl.viewport(0, 0, this.canvas.width, this.canvas.height);
}
private sizeGL(width: number, height: number) {
@ -86,13 +78,7 @@ export class MapRender extends RenderItem {
console.time('map-element-render');
this.renderer.render(this.gl);
canvas.ctx.drawImage(
this.canvas,
0,
0,
this.canvas.width,
this.canvas.height
);
canvas.ctx.drawImage(this.canvas, 0, 0, canvas.width, canvas.height);
console.timeEnd('map-element-render');
}
@ -104,11 +90,19 @@ export class MapRender extends RenderItem {
parentComponent?: ComponentInternalInstance | null
): void {
switch (key) {
case 'layerList': {
this.updateLayerList(nextValue);
case 'layerState': {
this.renderer.setLayerState(nextValue);
break;
}
}
super.patchProp(key, prevValue, nextValue, namespace, parentComponent);
}
}
class RendererUpdateHook implements Partial<IMapRendererHooks> {
constructor(readonly element: MapRender) {}
onUpdate(): void {
this.element.update();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,6 @@
#version 300 es
precision highp float;
precision highp sampler2DArray;
in vec3 v_texCoord;
out vec4 outColor;

View File

@ -1,8 +1,8 @@
#version 300 es
precision highp float;
in vec2 a_position;
in vec2 a_texCoord;
layout(location = 0) in vec2 a_position;
layout(location = 1) in vec2 a_texCoord;
out vec3 v_texCoord;
@ -10,9 +10,7 @@ uniform float u_nowFrame;
uniform mat3 u_transform;
void main() {
// 背景图永远是全图都画,因此变换矩阵应该作用于纹理坐标
vec3 texCoord = vec3(a_texCoord, 1.0);
vec3 transformed = u_transform * texCoord;
v_texCoord = vec3(transformed.xy, u_nowFrame);
gl_Position = vec4(a_position, 0.0, 1.0);
vec3 transformed = u_transform * vec3(a_position, 1.0);
v_texCoord = vec3(a_texCoord, u_nowFrame);
gl_Position = vec4(transformed.xy, 0.95, 1.0);
}

View File

@ -1,6 +1,6 @@
#version 300 es
precision highp float;
precision mediump uint;
precision highp sampler2DArray;
in vec4 v_texCoord;
@ -11,4 +11,5 @@ uniform sampler2DArray u_sampler;
void main() {
vec4 texColor = texture(u_sampler, v_texCoord.xyz);
outColor = vec4(texColor.rgb, texColor.a * v_texCoord.a);
// outColor = vec4(texColor.a * 0.001, v_texCoord.x * 6.0, v_texCoord.y * 0.0, v_texCoord.a);
}

View File

@ -1,26 +1,36 @@
#version 300 es
precision highp float;
precision mediump uint;
precision mediump int;
in vec3 a_position;
in vec3 a_texCoord;
// x: 最大帧数y: 偏移池索引,实例化绘制传入
in ivec2 a_offsetData;
// 不透明度,用于前景层虚化
in float a_alpha;
// 顶点坐标
layout(location = 0) in vec4 a_position;
// 实例化数据
// 图块坐标
layout(location = 1) in vec4 a_tilePos;
// 贴图坐标
layout(location = 2) in vec4 a_texCoord;
// x: 纵深y: 不透明度
layout(location = 3) in vec4 a_tileData;
// x: 当前帧数,负数表示使用 u_nowFramey: 最大帧数z: 偏移池索引w: 纹理数组索引
layout(location = 4) in vec4 a_texData;
// x,y,z: 纹理坐标w: 不透明度
out vec4 v_texCoord;
uniform vec2 u_offsetPool[$1];
uniform uint u_nowFrame;
uniform float u_offsetPool[$1];
uniform float u_nowFrame;
uniform mat3 u_transform;
void main() {
// 坐标
vec2 pos = a_position.xy * a_tilePos.zw + a_tilePos.xy;
vec2 texCoord = a_position.zw * a_texCoord.zw + a_texCoord.xy;
// 偏移量
uint offset = mod(u_nowFrame, a_offsetData.x);
float fOffset = float(offset);
// 贴图坐标
v_texCoord = vec4(a_texCoord.xy + u_offsetPool[a_offsetData.y] * fOffset, a_texCoord.z, a_alpha);
gl_Position = vec4(u_transform * a_position, 1.0);
float offset = a_texData.x < 0.0 ? mod(u_nowFrame, a_texData.y) : a_texData.x;
int offsetIndex = int(a_texData.z);
// 贴图偏移
texCoord.x += u_offsetPool[offsetIndex] * offset;
v_texCoord = vec4(texCoord.xy, a_texData.w, a_tileData.y);
vec3 transformed = u_transform * vec3(pos.xy, 1.0);
gl_Position = vec4(transformed.xy, a_tileData.x, 1.0);
}

View File

@ -1,16 +1,19 @@
import { IDirtyTracker } from '@motajs/common';
import {
ITextureRenderable,
SizedCanvasImageSource
} from '@motajs/render-assets';
IDirtyMark,
IDirtyTracker,
IHookable,
IHookBase,
IHookController
} from '@motajs/common';
import { ITextureRenderable } from '@motajs/render-assets';
import { Transform } from '@motajs/render-core';
import {
IAutotileProcessor,
IMaterialFramedData,
IMaterialGetter,
IMaterialManager
IMaterialManager,
ITrackedAssetData
} from '@user/client-base';
import { IMapLayer } from '@user/data-state';
import { ILayerState, IMapLayer } from '@user/data-state';
import { TimingFn } from 'mutate-animate';
export const enum MapBackgroundRepeat {
@ -45,18 +48,6 @@ export const enum MapTileAlign {
End
}
export interface IMapAssetData extends IDirtyTracker<Set<number>> {
/** 图像源列表 */
readonly sourceList: ImageBitmap[];
/**
* `ImageBitmap` 使
* `ImageBitmap` `sourceList`
*/
readonly skipRef: Map<SizedCanvasImageSource, number>;
/** 贴图数据 */
readonly materials: IMaterialGetter;
}
export interface IMapBackgroundConfig {
/** 是否使用图片大小作为背景图渲染大小,如果是 `false`,则使用 `renderWidth` `renderHeight` 作为渲染大小 */
readonly useImageSize: boolean;
@ -87,16 +78,6 @@ export interface IMapRenderConfig {
readonly frameSpeed: number;
}
export interface IMapAssetManager {
/** 素材管理对象 */
readonly materials: IMaterialManager;
/**
*
*/
generateAsset(): IMapAssetData;
}
export interface IContextData {
/** 图块程序 */
readonly tileProgram: WebGLProgram;
@ -126,22 +107,22 @@ export interface IContextData {
readonly backNowFrameLocation: WebGLUniformLocation;
/** 顶点数组输入 */
readonly vertexAttribLocation: number;
/** 纹理坐标输入输入 */
readonly texCoordAttribLocation: number;
/** 偏移数组输入 */
readonly offsetAttribLocation: number;
/** 不透明度数组输入 */
readonly alphaAttribLocation: number;
/** 图块坐标输入 */
readonly insTilePosAttribLocation: number;
/** 图块纹理坐标输入 */
readonly insTexCoordAttribLocation: number;
/** 图块数据输入 */
readonly insTileDataAttribLocation: number;
/** 图块当前帧数输入 */
readonly insTexDataAttribLocation: number;
/** 背景顶点数组输入 */
readonly backVertexAttribLocation: number;
/** 背景纹理数组输入 */
readonly backTexCoordAttribLocation: number;
/** 顶点数组 */
readonly vertexBuffer: WebGLBuffer;
/** 偏移数组 */
readonly offsetBuffer: WebGLBuffer;
/** 不透明度数组 */
readonly alphaBuffer: WebGLBuffer;
/** 实例化数据数组 */
readonly instancedBuffer: WebGLBuffer;
/** 背景顶点数组 */
readonly backgroundVertexBuffer: WebGLBuffer;
/** 图块纹理对象 */
@ -167,9 +148,9 @@ export interface IContextData {
backgroundDepth: number;
/** 图块纹理的脏标记 */
tileTextureMark: symbol;
tileTextureMark: IDirtyMark;
/** 顶点数组的脏标记 */
vertexMark: symbol;
vertexMark: IDirtyMark;
}
export interface IMovingBlock {
@ -246,7 +227,14 @@ export interface IMovingBlock {
destroy(): void;
}
export interface IMapRenderer {
export interface IMapRendererHooks extends IHookBase {
/**
*
*/
onUpdate(controller: IHookController<this>): void;
}
export interface IMapRenderer extends IHookable<IMapRendererHooks> {
/** 地图渲染器使用的资源管理器 */
readonly manager: IMaterialManager;
/** 画布渲染上下文 */
@ -260,6 +248,8 @@ export interface IMapRenderer {
readonly viewport: IMapViewportController;
/** 顶点数组生成器 */
readonly vertex: IMapVertexGenerator;
/** 使用的地图状态对象 */
readonly layerState: ILayerState;
/** 地图宽度 */
readonly mapWidth: number;
@ -275,12 +265,16 @@ export interface IMapRenderer {
readonly cellWidth: number;
/** 每个格子的高度 */
readonly cellHeight: number;
/** 图集宽度 */
readonly assetWidth: number;
/** 图集高度 */
readonly assetHeight: number;
/**
* 使
* @param asset 使
*/
useAsset(asset: IMapAssetData): void;
useAsset(asset: ITrackedAssetData): void;
/**
* 使
@ -294,17 +288,10 @@ export interface IMapRenderer {
render(gl: WebGL2RenderingContext): void;
/**
*
* @param layer
* @param identifier {@link getLayer}
* 使
* @param layerState
*/
addLayer(layer: IMapLayer, identifier?: string): void;
/**
*
* @param layer
*/
removeLayer(layer: IMapLayer): void;
setLayerState(layerState: ILayerState): void;
/**
*
@ -323,19 +310,6 @@ export interface IMapRenderer {
*/
getSortedLayer(): IMapLayer[];
/**
*
* @param layer
* @param zIndex
*/
setZIndex(layer: IMapLayer, zIndex: number): void;
/**
*
* @param layer
*/
getZIndex(layer: IMapLayer): number | undefined;
/**
*
* @param layer
@ -379,9 +353,9 @@ export interface IMapRenderer {
configRendering(config: Partial<IMapRenderConfig>): void;
/**
* 13 13
* @param width
* @param height
*
* @param width
* @param height
*/
setRenderSize(width: number, height: number): void;
@ -402,11 +376,6 @@ export interface IMapRenderer {
*/
setCellSize(width: number, height: number): void;
/**
*
*/
getTransform(): Transform;
/**
*
* @param layer
@ -459,28 +428,25 @@ export interface IMapRenderer {
setTileAlpha(layer: IMapLayer, alpha: number, x: number, y: number): void;
/**
* 使
*
* @param timestamp
*/
close(): void;
tick(timestamp: number): void;
}
export interface IMapVertexArray {
/**
* `[B, L, T]` `B` `L` `T`
* `x,y,z` `x,y,z`
* `[B, L, T]` `B` `L` `T`
*
*
*
* a_tilePos, a_texCoord, a_tileData, a_texData
*/
readonly tileVertex: Float32Array;
readonly tileInstanced: Float32Array;
/** 每个图块的偏移数据,使用实例化绘制,第一项表示这个图块的总帧数,第二项表示每帧的偏移量 */
readonly tileOffset: Int16Array;
/** 每个图块的不透明度,用于实现前景层虚化效果 */
readonly tileAlpha: Float32Array;
/** 动态内容顶点起始索引 */
/** 动态内容的起始索引,以实例为单位 */
readonly dynamicStart: number;
/** 动态内容顶点数量 */
/** 动态内容的数量,以实例为单位 */
readonly dynamicCount: number;
}
@ -717,21 +683,13 @@ export interface IBlockSplitter<T> extends IBlockSplitterConfig {
}
export interface IMapVertexData {
/** 这个分块的顶点数组 */
readonly vertexArray: Float32Array;
/** 这个分块的偏移数组 */
readonly offsetArray: Int16Array;
/** 这个分块的不透明度数组 */
readonly alphaArray: Float32Array;
/** 这个分块的实例化数据数组 */
readonly instancedArray: Float32Array;
}
export interface IIndexedMapVertexData extends IMapVertexData {
/** 这个分块的顶点数组的起始索引 */
readonly vertexStart: number;
/** 这个分块的偏移数组的起始索引 */
readonly offsetStart: number;
/** 这个分块的不透明度数组的起始索引 */
readonly alphaStart: number;
/** 这个分块的实例化数据的起始索引 */
readonly instancedStart: number;
}
export interface ILayerDirtyData {
@ -764,6 +722,11 @@ export interface IMapVertexBlock extends IMapVertexData {
*/
render(): void;
/**
*
*/
updated(): void;
/**
*
* @param layer
@ -787,22 +750,10 @@ export interface IMapVertexBlock extends IMapVertexData {
getDirtyArea(layer: IMapLayer): Readonly<ILayerDirtyData> | null;
/**
*
*
* @param layer
*/
getLayerVertex(layer: IMapLayer): Float32Array | null;
/**
*
* @param layer
*/
getLayerOffset(layer: IMapLayer): Int16Array | null;
/**
*
* @param layer
*/
getLayerAlpha(layer: IMapLayer): Float32Array | null;
getLayerInstanced(layer: IMapLayer): Float32Array | null;
/**
*
@ -820,6 +771,9 @@ export interface IMapBlockUpdateObject {
readonly y: number;
}
/**
*
*/
export interface IMapVertexGenerator extends IDirtyTracker<boolean> {
/** 地图渲染器 */
readonly renderer: IMapRenderer;

View File

@ -1,4 +1,4 @@
import { IMapLayer, IMapLayerData } from '@user/data-state';
import { IMapLayer } from '@user/data-state';
import {
IBlockData,
IBlockSplitter,
@ -22,8 +22,7 @@ import { BlockSplitter } from './block';
import { clamp, isNil } from 'lodash-es';
import { BlockCls, IMaterialFramedData } from '@user/client-base';
import { IRect, SizedCanvasImageSource } from '@motajs/render-assets';
// todo: 潜在优化点:顶点数组的 z 坐标以及纹理的 z 坐标可以换为实例化绘制
import { INSTANCED_COUNT } from './constant';
export interface IMapDataGetter {
/** 图块缩小行为,即图块比格子大时应该如何处理 */
@ -37,12 +36,6 @@ export interface IMapDataGetter {
/** 图块大小与格子大小判断方式 */
readonly tileTestMode: MapTileSizeTestMode;
/**
*
* @param layer
*/
getMapLayerData(layer: IMapLayer): Readonly<IMapLayerData> | null;
/**
*
* @param source
@ -79,20 +72,9 @@ interface BlockIndex extends IndexedBlockMapPos {
readonly mapIndex: number;
}
interface TilePosition {
/** 左边缘位置 */
readonly left: number;
/** 上边缘位置 */
readonly top: number;
/** 右边缘位置 */
readonly right: number;
/** 下边缘位置 */
readonly bottom: number;
}
const enum VertexUpdate {
/** 更新顶点信息 */
Vertex = 0b01,
/** 更新顶点位置信息 */
Position = 0b01,
/** 更新贴图信息 */
Texture = 0b10,
/** 全部更新 */
@ -112,29 +94,19 @@ export class MapVertexGenerator
/** 空顶点数组,因为空顶点很常用,所以直接定义一个全局常量 */
private static readonly EMPTY_VETREX: Float32Array = new Float32Array(
6 * 6
INSTANCED_COUNT
);
readonly block: IBlockSplitter<MapVertexBlock>;
/** 顶点数组 */
private vertexArray: Float32Array = new Float32Array();
/** 偏移数组 */
private offsetArray: Int16Array = new Int16Array();
/** 不透明度数组 */
private alphaArray: Float32Array = new Float32Array();
/** 动态内容顶点数组 */
private dynamicVertexArray: Float32Array = new Float32Array();
private instancedArray: Float32Array = new Float32Array();
/** 动态内容偏移数组 */
private dynamicOffsetArray: Int16Array = new Int16Array();
/** 动态内容不透明度数组 */
private dynamicAlphaArray: Float32Array = new Float32Array();
private dynamicInstancedArray: Float32Array = new Float32Array();
/** 图层列表 */
private layers: IMapLayer[] = [];
/** 对应分块的顶点数组 */
private blockList: Map<number, IMapVertexBlock> = new Map();
/** 分块宽度 */
private blockWidth: number = MAP_BLOCK_WIDTH;
/** 分块高度 */
@ -163,6 +135,7 @@ export class MapVertexGenerator
readonly data: IContextData
) {
super();
this.resizeMap();
this.block = new BlockSplitter();
}
@ -175,23 +148,13 @@ export class MapVertexGenerator
const area = this.renderer.mapWidth * this.renderer.mapHeight;
const staticCount = area * this.renderer.layerCount;
const count = staticCount + this.dynamicLength;
const vertexSize = count * 6 * 6;
const offsetSize = count * 2;
const alphaSize = count;
this.vertexArray = new Float32Array(vertexSize);
this.offsetArray = new Int16Array(offsetSize);
this.alphaArray = new Float32Array(alphaSize);
this.alphaArray.fill(1);
const offsetSize = count * INSTANCED_COUNT;
this.instancedArray = new Float32Array(offsetSize);
this.staticLength = staticCount;
this.dynamicVertexArray = this.vertexArray.subarray(
staticCount * 6 * 6,
count * 6 * 6
this.dynamicInstancedArray = this.instancedArray.subarray(
staticCount * INSTANCED_COUNT,
count * INSTANCED_COUNT
);
this.dynamicOffsetArray = this.offsetArray.subarray(
staticCount * 2,
count * 2
);
this.dynamicAlphaArray = this.alphaArray.subarray(staticCount, count);
}
private splitBlock() {
@ -206,19 +169,16 @@ export class MapVertexGenerator
const lastCount = (this.mapHeight % this.blockHeight) * this.blockWidth;
const bh = Math.floor(this.mapHeight / this.blockHeight);
const lastStart = bh * lineCount;
const layerCount = this.renderer.layerCount;
this.block.splitBlocks(block => {
// 最后一行的算法与其他行不同
const startIndex =
block.height < this.blockHeight
? lastStart + lastCount * block.x
: lineCount * block.y + blockCount * block.x;
const count = block.width * block.height * layerCount;
const count = block.width * block.height;
const origin: IMapVertexData = {
vertexArray: this.vertexArray,
offsetArray: this.offsetArray,
alphaArray: this.alphaArray
instancedArray: this.instancedArray
};
const data = new MapVertexBlock(
this.renderer,
@ -245,22 +205,18 @@ export class MapVertexGenerator
this.mapHeight !== this.renderer.mapHeight
) {
this.needRebuild = true;
this.mapWidth = this.renderer.mapWidth;
this.mapHeight = this.renderer.mapHeight;
}
}
expandMoving(targetSize: number): void {
const beforeVertex = this.vertexArray;
const beforeOffset = this.offsetArray;
const beforeAlpha = this.alphaArray;
const beforeOffset = this.instancedArray;
this.dynamicLength = targetSize;
this.mallocVertexArray();
this.vertexArray.set(beforeVertex);
this.offsetArray.set(beforeOffset);
this.alphaArray.set(beforeAlpha);
this.instancedArray.set(beforeOffset);
const array: IMapVertexData = {
vertexArray: this.vertexArray,
offsetArray: this.offsetArray,
alphaArray: this.alphaArray
instancedArray: this.instancedArray
};
// 重建一下对应分块就行了,不需要重新分块
for (const block of this.block.iterateBlocks()) {
@ -269,42 +225,25 @@ export class MapVertexGenerator
}
reduceMoving(targetSize: number, indexMap: Map<number, number>): void {
const beforeVertexLength = this.vertexArray.length;
const beforeOffsetLength = this.offsetArray.length;
const beforeAlphaLength = this.alphaArray.length;
const beforeOffsetLength = this.instancedArray.length;
const deltaLength = this.dynamicLength - targetSize;
this.dynamicLength = targetSize;
this.vertexArray = this.vertexArray.subarray(
this.instancedArray = this.instancedArray.subarray(
0,
beforeVertexLength - deltaLength * 6 * 6
);
this.offsetArray = this.offsetArray.subarray(
0,
beforeOffsetLength - deltaLength * 2
);
this.alphaArray = this.alphaArray.subarray(
0,
beforeAlphaLength - deltaLength
beforeOffsetLength - deltaLength * INSTANCED_COUNT
);
indexMap.forEach((target, from) => {
const next = from + 1;
this.dynamicVertexArray.copyWithin(
target * 6 * 6,
from * 6 * 6,
next * 6 * 6
this.dynamicInstancedArray.copyWithin(
target * INSTANCED_COUNT,
from * INSTANCED_COUNT,
next * INSTANCED_COUNT
);
this.dynamicOffsetArray.copyWithin(target * 2, from * 2, next * 2);
this.dynamicAlphaArray[target] = this.dynamicAlphaArray[from];
});
this.dynamicVertexArray = this.dynamicVertexArray.subarray(
this.dynamicInstancedArray = this.dynamicInstancedArray.subarray(
0,
targetSize * 6 * 6
targetSize * INSTANCED_COUNT
);
this.dynamicOffsetArray = this.dynamicOffsetArray.subarray(
0,
targetSize * 2
);
this.dynamicAlphaArray = this.dynamicAlphaArray.subarray(0, targetSize);
// 这个不需要重新分配内存,依然共用同一个 ArrayBuffer因此不需要重新分块
}
@ -323,6 +262,7 @@ export class MapVertexGenerator
this.needRebuild = false;
this.mallocVertexArray();
this.splitBlock();
this.dirty();
}
//#endregion
@ -339,7 +279,7 @@ export class MapVertexGenerator
pos: BlockMapPos,
width: number,
height: number
): TilePosition {
): Readonly<IRect> {
const {
renderWidth,
renderHeight,
@ -362,14 +302,14 @@ export class MapVertexGenerator
const cw = cwu * 2; // normalized cell width in range [-1, 1]
const ch = chu * 2; // normalized cell height in range [-1, 1]
const cl = pos.mapX * cw - 1; // cell left
const ct = pos.mapY * ch - 1; // cell top
const ct = 1 - pos.mapY * ch; // cell top
if (mode === MapTileBehavior.FitToSize) {
// 适应到格子大小
return {
left: cl,
top: ct,
right: cl + cw,
bottom: ct + ch
x: cl,
y: ct,
w: cw,
h: ch
};
} else {
// 维持大小,需要判断对齐
@ -382,26 +322,20 @@ export class MapVertexGenerator
const th = thu * 2; // normalized texture height in range [-1, 1]
let left = 0;
let top = 0;
let right = 0;
let bottom = 0;
switch (tileAlignX) {
case MapTileAlign.Start: {
// 左对齐
left = cl;
right = cl + tw;
break;
}
case MapTileAlign.Center: {
// 左右居中对齐
const center = cl + cwu;
left = center - twu;
right = center + twu;
left = cl + cwu - twu;
break;
}
case MapTileAlign.End: {
// 右对齐
right = cl + cw;
left = right - tw;
left = cl + cw - tw;
break;
}
}
@ -409,23 +343,19 @@ export class MapVertexGenerator
case MapTileAlign.Start: {
// 上对齐
top = ct;
bottom = ct + th;
break;
}
case MapTileAlign.Center: {
// 上下居中对齐
const center = ct + chu;
top = center - thu;
bottom = center + thu;
top = ct + chu - thu;
break;
}
case MapTileAlign.End: {
// 下对齐
bottom = ct + ch;
top = bottom - th;
top = ct + ch - th;
}
}
return { left, top, right, bottom };
return { x: left, y: top, w: tw, h: th };
}
}
@ -448,69 +378,44 @@ export class MapVertexGenerator
frames: number,
update: VertexUpdate
) {
const { offsetArray, vertexArray } = vertex;
const { instancedArray } = vertex;
// 顶点数组
const { renderWidth, renderHeight, layerCount } = this.renderer;
const { x, y, w, h } = rect;
const vertexStart = index.blockIndex * 6 * 6;
if (update & VertexUpdate.Texture) {
const texLeft = (x / renderWidth) * 2 - 1;
const texTop = (y / renderHeight) * 2 - 1;
const texRight = ((x + w) / renderWidth) * 2 - 1;
const texBottom = ((y + h) / renderHeight) * 2 - 1;
// 六个顶点分别是 左下,右下,左上,左上,右下,右上
vertexArray[vertexStart + 3] = texLeft;
vertexArray[vertexStart + 4] = texBottom;
vertexArray[vertexStart + 5] = assetIndex;
vertexArray[vertexStart + 9] = texRight;
vertexArray[vertexStart + 10] = texBottom;
vertexArray[vertexStart + 11] = assetIndex;
vertexArray[vertexStart + 15] = texLeft;
vertexArray[vertexStart + 16] = texTop;
vertexArray[vertexStart + 17] = assetIndex;
vertexArray[vertexStart + 21] = texLeft;
vertexArray[vertexStart + 22] = texTop;
vertexArray[vertexStart + 23] = assetIndex;
vertexArray[vertexStart + 27] = texRight;
vertexArray[vertexStart + 28] = texBottom;
vertexArray[vertexStart + 29] = assetIndex;
vertexArray[vertexStart + 33] = texRight;
vertexArray[vertexStart + 34] = texTop;
vertexArray[vertexStart + 35] = assetIndex;
}
if (update & VertexUpdate.Vertex) {
const { layerCount, assetWidth, assetHeight } = this.renderer;
const { x, y, w: width, h: height } = rect;
const startIndex = index.blockIndex * INSTANCED_COUNT;
if (update & VertexUpdate.Position) {
// 如果需要更新顶点坐标
const layerIndex = this.renderer.getLayerIndex(index.layer);
const layerStart = (layerIndex / layerCount) * 2 - 1;
const zIndex = layerStart + index.mapY / this.mapHeight;
const { left, top, right, bottom } = this.getTilePosition(
index,
w,
h
);
vertexArray[vertexStart] = left;
vertexArray[vertexStart + 1] = bottom;
vertexArray[vertexStart + 2] = zIndex;
vertexArray[vertexStart + 6] = right;
vertexArray[vertexStart + 7] = bottom;
vertexArray[vertexStart + 8] = zIndex;
vertexArray[vertexStart + 12] = left;
vertexArray[vertexStart + 13] = top;
vertexArray[vertexStart + 14] = zIndex;
vertexArray[vertexStart + 18] = left;
vertexArray[vertexStart + 19] = top;
vertexArray[vertexStart + 20] = zIndex;
vertexArray[vertexStart + 24] = right;
vertexArray[vertexStart + 25] = bottom;
vertexArray[vertexStart + 26] = zIndex;
vertexArray[vertexStart + 30] = right;
vertexArray[vertexStart + 31] = top;
vertexArray[vertexStart + 32] = zIndex;
// 避免 z 坐标是 1 的时候被裁剪,因此范围选择 [-0.9, 0.9]
const layerStart = (layerIndex / layerCount) * 1.8 - 0.9;
const zIndex = -layerStart - index.mapY / this.mapHeight;
const { x, y, w, h } = this.getTilePosition(index, width, height);
// 图块位置
instancedArray[startIndex] = x;
instancedArray[startIndex + 1] = y;
instancedArray[startIndex + 2] = w;
instancedArray[startIndex + 3] = h;
// 图块纵深
instancedArray[startIndex + 8] = zIndex;
}
if (update & VertexUpdate.Texture) {
const texX = x / assetWidth;
const texY = y / assetHeight;
const texWidth = width / assetWidth;
const texHeight = height / assetHeight;
// 纹理坐标
instancedArray[startIndex + 4] = texX;
instancedArray[startIndex + 5] = texY;
instancedArray[startIndex + 6] = texWidth;
instancedArray[startIndex + 7] = texHeight;
// 不透明度
instancedArray[startIndex + 9] = 1;
// 帧数、偏移、纹理索引
instancedArray[startIndex + 12] = -1;
instancedArray[startIndex + 13] = frames;
instancedArray[startIndex + 14] = offsetIndex;
instancedArray[startIndex + 15] = assetIndex;
}
// 偏移数组
const offsetStart = index.blockIndex * 2;
offsetArray[offsetStart] = frames;
offsetArray[offsetStart + 1] = offsetIndex;
}
/**
@ -615,13 +520,13 @@ export class MapVertexGenerator
if (!tile) {
// 不存在可渲染对象,认为是空图块
vertex.vertexArray.set(
MapVertexGenerator.EMPTY_VETREX,
index.blockIndex * 6 * 6
);
const offsetStart = index.blockIndex * 2;
vertex.offsetArray[offsetStart] = 0;
vertex.offsetArray[offsetStart + 1] = 0;
const { instancedArray } = vertex;
const instancedStart = index.blockIndex * INSTANCED_COUNT;
// 只把坐标改成 0 就可以了,其他的保留
instancedArray[instancedStart] = 0;
instancedArray[instancedStart + 1] = 0;
instancedArray[instancedStart + 2] = 0;
instancedArray[instancedStart + 3] = 0;
return;
}
@ -680,8 +585,8 @@ export class MapVertexGenerator
const block = this.block.getBlockByDataLoc(x, y);
if (!block) return;
const vertex = block.data.getLayerData(layer);
const data = this.renderer.getMapLayerData(layer);
if (!vertex || !data) return;
const data = layer.getMapRef();
if (!vertex) return;
const { array } = data;
const dx = x - block.dataX;
const dy = y - block.dataY;
@ -738,12 +643,15 @@ export class MapVertexGenerator
w: number,
h: number
): void {
if (!this.renderer.hasLayer(layer)) return;
this.checkRebuild();
// 这里多一圈是因为要更新这一圈的自动元件
const ax = x - 1;
const ay = y - 1;
const areaRight = x + w + 1;
const areaBottom = y + h + 1;
const blocks = this.block.iterateBlocksOfDataArea(ax, ay, w + 2, h + 2);
for (const block of blocks) {
const left = ax - block.dataX;
const top = ay - block.dataY;
@ -761,6 +669,7 @@ export class MapVertexGenerator
if (import.meta.env.DEV) {
this.checkUpdateCallPerformance('updateBlock');
}
this.checkRebuild();
this.updateBlockVertex(layer, num, x, y);
}
@ -769,6 +678,7 @@ export class MapVertexGenerator
if (import.meta.env.DEV) {
this.checkUpdateCallPerformance('updateBlockList');
}
this.checkRebuild();
if (blocks.length > 50) {
// 对于超出50个的更新操作使用懒更新
blocks.forEach(v => {
@ -864,14 +774,15 @@ export class MapVertexGenerator
}
updateBlockCache(block: Readonly<IBlockData<IMapVertexBlock>>): void {
console.time('update-block-cache');
if (!block.data.dirty) return;
const layers = this.renderer.getSortedLayer();
layers.forEach(layer => {
const dirty = block.data.getDirtyArea(layer);
if (!dirty || !dirty.dirty) return;
block.data.updated();
const vertex = block.data.getLayerData(layer);
const mapData = this.renderer.getMapLayerData(layer);
if (!vertex || !mapData) return;
const mapData = layer.getMapRef();
if (!vertex) return;
const { array } = mapData;
const { dirtyLeft, dirtyTop, dirtyRight, dirtyBottom } = dirty;
for (let nx = dirtyLeft; nx < dirtyRight; nx++) {
@ -897,7 +808,6 @@ export class MapVertexGenerator
}
}
});
console.timeEnd('update-block-cache');
}
//#endregion
@ -905,10 +815,10 @@ export class MapVertexGenerator
//#region 图块配置
enableStaticFrameAnimate(layer: IMapLayer, x: number, y: number): void {
const data = this.renderer.getMapLayerData(layer);
const data = layer.getMapRef();
const block = this.block.getBlockByDataLoc(x, y);
if (!data || !block) return;
const vertexArray = block.data.getLayerOffset(layer);
if (!block) return;
const vertexArray = block.data.getLayerInstanced(layer);
if (!vertexArray) return;
const mapIndex = y * this.mapWidth + x;
const num = data.array[mapIndex];
@ -917,19 +827,19 @@ export class MapVertexGenerator
const bx = x - block.dataX;
const by = y - block.dataY;
const bIndex = by * block.width + bx;
vertexArray[bIndex * 2] = tile.frames;
vertexArray[bIndex * INSTANCED_COUNT + 13] = tile.frames;
block.data.markRenderDirty();
}
disableStaticFrameAnimate(layer: IMapLayer, x: number, y: number): void {
const block = this.block.getBlockByDataLoc(x, y);
if (!block) return;
const vertexArray = block.data.getLayerOffset(layer);
const vertexArray = block.data.getLayerInstanced(layer);
if (!vertexArray) return;
const bx = x - block.dataX;
const by = y - block.dataY;
const bIndex = by * block.width + bx;
vertexArray[bIndex * 2] = 1;
vertexArray[bIndex * INSTANCED_COUNT + 13] = 1;
block.data.markRenderDirty();
}
@ -941,12 +851,12 @@ export class MapVertexGenerator
): void {
const block = this.block.getBlockByDataLoc(x, y);
if (!block) return;
const vertexArray = block.data.getLayerAlpha(layer);
const vertexArray = block.data.getLayerInstanced(layer);
if (!vertexArray) return;
const bx = x - block.dataX;
const by = y - block.dataY;
const bIndex = by * block.width + bx;
vertexArray[bIndex] = alpha;
vertexArray[bIndex * INSTANCED_COUNT + 9] = alpha;
block.data.markRenderDirty();
}
@ -958,9 +868,7 @@ export class MapVertexGenerator
if (!this.renderer.hasMoving(block)) return;
const { cls, frames, offset, texture } = block.texture;
const vertex: IMapVertexData = {
vertexArray: this.dynamicVertexArray,
offsetArray: this.dynamicOffsetArray,
alphaArray: this.dynamicAlphaArray
instancedArray: this.dynamicInstancedArray
};
const index: IndexedBlockMapPos = {
layer: block.layer,
@ -974,7 +882,7 @@ export class MapVertexGenerator
logger.error(40, block.tile.toString());
return;
}
const update = updateTexture ? VertexUpdate.All : VertexUpdate.Vertex;
const update = updateTexture ? VertexUpdate.All : VertexUpdate.Position;
if (cls === BlockCls.Autotile) {
// 自动元件使用全部不连接
const renderable = this.renderer.autotile.renderWithoutCheck(
@ -1025,32 +933,33 @@ export class MapVertexGenerator
}
deleteMoving(moving: IMovingBlock): void {
this.dynamicVertexArray.set(
const instancedStart = moving.index * INSTANCED_COUNT;
// 这个需要全部清空了,因为可能会复用
this.dynamicInstancedArray.set(
MapVertexGenerator.EMPTY_VETREX,
moving.index * 6 * 6
instancedStart
);
const offsetStart = moving.index * 2;
this.dynamicOffsetArray[offsetStart] = 0;
this.dynamicOffsetArray[offsetStart + 1] = 0;
this.dynamicAlphaArray[moving.index] = 0;
this.dynamicRenderDirty = true;
}
enableDynamicFrameAnimate(block: IMovingBlock): void {
if (!this.renderer.hasMoving(block)) return;
this.dynamicOffsetArray[block.index * 2] = 1;
const instancedStart = block.index * INSTANCED_COUNT;
this.dynamicInstancedArray[instancedStart + 13] = 1;
this.dynamicRenderDirty = true;
}
disableDynamicFrameAnimate(block: IMovingBlock): void {
if (!this.renderer.hasMoving(block)) return;
this.dynamicOffsetArray[block.index * 2] = block.texture.frames;
const instancedStart = block.index * INSTANCED_COUNT;
this.dynamicInstancedArray[instancedStart + 13] = block.texture.frames;
this.dynamicRenderDirty = true;
}
setDynamicAlpha(block: IMovingBlock, alpha: number): void {
if (!this.renderer.hasMoving(block)) return;
this.dynamicAlphaArray[block.index] = alpha;
const instancedStart = block.index * INSTANCED_COUNT;
this.dynamicInstancedArray[instancedStart + 9] = alpha;
this.dynamicRenderDirty = true;
}
@ -1066,11 +975,9 @@ export class MapVertexGenerator
getVertexArray(): IMapVertexArray {
this.checkRebuild();
return {
dynamicStart: this.staticLength * 6,
dynamicCount: this.dynamicLength * 6,
tileVertex: this.vertexArray,
tileOffset: this.offsetArray,
tileAlpha: this.alphaArray
dynamicStart: this.staticLength,
dynamicCount: this.dynamicLength,
tileInstanced: this.instancedArray
};
}
@ -1080,11 +987,9 @@ export class MapVertexGenerator
//#region 分块对象
class MapVertexBlock implements IMapVertexBlock {
vertexArray!: Float32Array;
offsetArray!: Int16Array;
alphaArray!: Float32Array;
instancedArray!: Float32Array;
dirty: boolean = false;
dirty: boolean = true;
renderDirty: boolean = true;
private readonly layerDirty: Map<IMapLayer, ILayerDirtyData> = new Map();
@ -1092,22 +997,21 @@ class MapVertexBlock implements IMapVertexBlock {
readonly startIndex: number;
readonly endIndex: number;
readonly count: number;
readonly layerCount: number;
readonly vertexStart: number;
readonly offsetStart: number;
readonly alphaStart: number;
readonly instancedStart: number;
private readonly indexMap: Map<IMapLayer, number> = new Map();
private readonly vertexMap: Map<IMapLayer, Float32Array> = new Map();
private readonly offsetMap: Map<IMapLayer, Int16Array> = new Map();
private readonly alphaMap: Map<IMapLayer, Float32Array> = new Map();
private readonly instancedMap: Map<IMapLayer, Float32Array> = new Map();
/**
*
* @param renderer
* @param originArray
* @param startIndex
* @param count
* @param count
* @param blockWidth
* @param blockHeight
*/
constructor(
readonly renderer: IMapRenderer,
@ -1117,17 +1021,13 @@ class MapVertexBlock implements IMapVertexBlock {
private readonly blockWidth: number,
private readonly blockHeight: number
) {
this.startIndex = startIndex;
this.endIndex = startIndex + count;
this.count = count;
const layerCount = renderer.layerCount;
const vertexStart = startIndex * layerCount * 6 * 6;
const offsetStart = startIndex * layerCount * 2;
const alphaStart = startIndex * layerCount;
this.vertexStart = vertexStart;
this.offsetStart = offsetStart;
this.alphaStart = alphaStart;
this.startIndex = startIndex * layerCount;
this.endIndex = (startIndex + count) * layerCount;
this.count = count;
const offsetStart = startIndex * layerCount * INSTANCED_COUNT;
this.instancedStart = offsetStart;
this.layerCount = layerCount;
this.rebuild(originArray);
}
@ -1135,6 +1035,10 @@ class MapVertexBlock implements IMapVertexBlock {
this.renderDirty = false;
}
updated(): void {
this.dirty = false;
}
markRenderDirty() {
// todo: 潜在优化点vertex, offset, alpha 的脏标记分开
this.renderDirty = true;
@ -1173,33 +1077,20 @@ class MapVertexBlock implements IMapVertexBlock {
}
rebuild(originArray: IMapVertexData) {
const vertexStart = this.vertexStart;
const offsetStart = this.offsetStart;
const alphaStart = this.alphaStart;
const offsetStart = this.instancedStart;
const count = this.count;
this.vertexArray = originArray.vertexArray.subarray(
vertexStart,
vertexStart + count * 6 * 6
);
this.offsetArray = originArray.offsetArray.subarray(
this.instancedArray = originArray.instancedArray.subarray(
offsetStart,
offsetStart + count * 2
);
this.alphaArray = originArray.alphaArray.subarray(
alphaStart,
alphaStart + count
offsetStart + count * INSTANCED_COUNT * this.layerCount
);
this.renderer.getSortedLayer().forEach((v, i) => {
const vs = vertexStart + i * count * 6 * 6;
const os = offsetStart + i * count * 2;
const as = alphaStart + i * count;
const va = this.vertexArray.subarray(vs, vs + count * 6 * 6);
const oa = this.offsetArray.subarray(os, os + count * 2);
const aa = this.alphaArray.subarray(as, as + count);
this.vertexMap.set(v, va);
this.offsetMap.set(v, oa);
this.alphaMap.set(v, aa);
const os = i * count * INSTANCED_COUNT;
const oa = this.instancedArray.subarray(
os,
os + count * INSTANCED_COUNT
);
this.instancedMap.set(v, oa);
this.indexMap.set(v, i);
this.layerDirty.set(v, {
dirty: true,
@ -1209,33 +1100,21 @@ class MapVertexBlock implements IMapVertexBlock {
dirtyBottom: this.blockHeight
});
});
this.dirty = true;
}
getLayerVertex(layer: IMapLayer): Float32Array | null {
return this.vertexMap.get(layer) ?? null;
}
getLayerOffset(layer: IMapLayer): Int16Array | null {
return this.offsetMap.get(layer) ?? null;
}
getLayerAlpha(layer: IMapLayer): Float32Array | null {
return this.alphaMap.get(layer) ?? null;
getLayerInstanced(layer: IMapLayer): Float32Array | null {
return this.instancedMap.get(layer) ?? null;
}
getLayerData(layer: IMapLayer): IIndexedMapVertexData | null {
const vertex = this.vertexMap.get(layer);
const offset = this.offsetMap.get(layer);
const alpha = this.alphaMap.get(layer);
const offset = this.instancedMap.get(layer);
const index = this.indexMap.get(layer);
if (!vertex || !offset || !alpha || isNil(index)) return null;
if (!offset || isNil(index)) return null;
return {
vertexArray: vertex,
offsetArray: offset,
alphaArray: alpha,
vertexStart: this.vertexStart + index * this.count * 6 * 6,
offsetStart: this.offsetStart + index * this.count * 2,
alphaStart: this.alphaStart + index * this.count
instancedArray: offset,
instancedStart:
this.instancedStart + index * this.count * INSTANCED_COUNT
};
}
}

View File

@ -19,20 +19,31 @@ export class MapViewport implements IMapViewportController {
this.vertex = renderer.vertex;
}
private pushBlock(
list: IMapRenderArea[],
start: IBlockData<IMapVertexBlock>,
end: IBlockData<IMapVertexBlock>
) {
const startIndex = start.data.startIndex;
const endIndex = end.data.endIndex;
list.push({
startIndex,
endIndex,
count: endIndex - startIndex
});
}
getRenderArea(): IMapRenderData {
const { cellWidth, cellHeight, renderWidth, renderHeight } =
this.renderer;
const { blockWidth, blockHeight, width, height } = this.vertex.block;
// 减一是因为第一个像素是 0所以最后一个像素就是宽度减一
const r = renderWidth - 1;
const b = renderHeight - 1;
// 其实只需要算左上角和右下角就行了
const [left, top] = this.transform.transformed(0, 0);
const [right, bottom] = this.transform.transformed(r, b);
const cl = left / cellWidth;
const ct = top / cellHeight;
const cr = right / cellWidth;
const cb = bottom / cellHeight;
const [left, top] = this.transform.untransformed(-1, -1);
const [right, bottom] = this.transform.untransformed(1, 1);
const cl = (left * renderWidth) / cellWidth;
const ct = (top * renderHeight) / cellHeight;
const cr = (right * renderWidth) / cellWidth;
const cb = (bottom * renderHeight) / cellHeight;
const blockLeft = clamp(Math.floor(cl / blockWidth), 0, width - 1);
const blockRight = clamp(Math.floor(cr / blockWidth), 0, width - 1);
const blockTop = clamp(Math.floor(ct / blockHeight), 0, height - 1);
@ -42,24 +53,31 @@ export class MapViewport implements IMapViewportController {
const updateArea: IMapRenderArea[] = [];
const blockList: IBlockData<IMapVertexBlock>[] = [];
// 使用这种方式的话,索引在换行之前都是连续的,方便整合
for (let ny = blockTop; ny <= blockBottom; ny++) {
const first = this.vertex.block.getBlockByLoc(blockLeft, ny)!;
const last = this.vertex.block.getBlockByLoc(blockRight, ny)!;
if (first.data.dirty) {
blockList.push(first);
const widthOne = blockLeft === blockRight;
const heightOne = blockTop === blockBottom;
if (widthOne && heightOne) {
// 只能看到一个分块
const block = this.vertex.block.getBlockByLoc(blockLeft, blockTop)!;
blockList.push(block);
} else if (widthOne) {
// 看到的区域分块宽度是 1
for (let ny = blockTop; ny <= blockBottom; ny++) {
const block = this.vertex.block.getBlockByLoc(blockLeft, ny)!;
blockList.push(block);
}
if (last.data.dirty) {
blockList.push(last);
} else if (heightOne) {
// 看到的区域分块高度是 1
for (let nx = blockLeft; nx <= blockRight; nx++) {
const block = this.vertex.block.getBlockByLoc(nx, blockTop)!;
blockList.push(block);
}
renderArea.push({
startIndex: first.data.startIndex,
endIndex: last.data.endIndex,
count: last.data.endIndex - first.data.startIndex
});
for (let nx = blockLeft + 1; nx < blockRight; nx++) {
const block = this.vertex.block.getBlockByLoc(nx, ny)!;
if (block.data.dirty) {
} else {
// 看到的区域分块宽高都不是 1
// 使用这种方式的话,索引在换行之前都是连续的,方便整合
for (let ny = blockTop; ny <= blockBottom; ny++) {
for (let nx = blockLeft; nx <= blockRight; nx++) {
const block = this.vertex.block.getBlockByLoc(nx, ny)!;
blockList.push(block);
}
}
@ -68,34 +86,48 @@ export class MapViewport implements IMapViewportController {
if (blockList.length > 0) {
if (blockList.length === 1) {
const block = blockList[0];
updateArea.push(block.data);
if (block.data.renderDirty) {
this.pushBlock(updateArea, block, block);
}
this.pushBlock(renderArea, block, block);
} else {
let continuousStart: IBlockData<IMapVertexBlock> = blockList[0];
let continuousLast: IBlockData<IMapVertexBlock> = blockList[0];
// 更新区域
let updateStart: IBlockData<IMapVertexBlock> = blockList[0];
let updateEnd: IBlockData<IMapVertexBlock> = blockList[0];
let renderStart: IBlockData<IMapVertexBlock> = blockList[0];
let renderEnd: IBlockData<IMapVertexBlock> = blockList[0];
for (let i = 1; i < blockList.length; i++) {
const block = blockList[i];
if (block.index === continuousLast.index + 1) {
// 连续则合并
continuousLast = block;
const { renderDirty } = block.data;
// 连续则合并
// 渲染区域
if (block.index === renderEnd.index + 1) {
renderEnd = block;
} else {
const start = continuousStart.data.startIndex;
const end = continuousLast.data.endIndex;
updateArea.push({
startIndex: start,
endIndex: end,
count: end - start
});
continuousStart = block;
continuousLast = block;
this.pushBlock(renderArea, renderStart, renderEnd);
renderStart = block;
renderEnd = block;
}
// 缓冲区更新区域
if (renderDirty && block.index === updateEnd.index + 1) {
updateEnd = block;
} else {
this.pushBlock(updateArea, updateStart, updateEnd);
updateStart = block;
updateEnd = block;
}
}
this.pushBlock(updateArea, updateStart, updateEnd);
this.pushBlock(renderArea, renderStart, renderEnd);
}
}
// todo: 动态内容
return {
render: renderArea,
dirty: updateArea,
blockList
blockList: blockList.filter(v => v.data.dirty)
};
}

View File

@ -5,6 +5,10 @@ import { Font } from '@motajs/render-style';
//#region 地图
/** 每个格子的默认宽度,现阶段用处不大 */
export const CELL_WIDTH = 32;
/** 每个格子的默认高度,现阶段用处不大 */
export const CELL_HEIGHT = 32;
/** 每个格子的宽高 */
export const CELL_SIZE = 32;
/** 地图格子宽度,此处仅影响画面,可能不影响游戏内的部分逻辑,游戏内逻辑地图大小请在 core.js 中修改 */
@ -29,10 +33,6 @@ export const DYNAMIC_RESERVE = 16;
*
*/
export const MOVING_TOLERANCE = 60;
/** 每个格子的默认宽度,现阶段用处不大 */
export const CELL_WIDTH = 32;
/** 每个格子的默认高度,现阶段用处不大 */
export const CELL_HEIGHT = 32;
//#region 状态栏

View File

@ -1,4 +1,3 @@
import { LayerShadowExtends } from '../legacy/shadow';
import {
Props,
Font,
@ -36,44 +35,23 @@ import {
RightStatusBar
} from './statusBar';
import { ReplayingStatus } from './toolbar';
import { getHeroStatusOn, HeroSkill, NightSpecial } from '@user/data-state';
import {
getHeroStatusOn,
HeroSkill,
NightSpecial,
state
} from '@user/data-state';
import { jumpIgnoreFloor } from '@user/legacy-plugin-data';
import { hook } from '@user/data-base';
import { FloorDamageExtends, FloorItemDetail } from '../elements';
import { LayerGroupPortal } from '../legacy/portal';
import { LayerGroupFilter } from '../legacy/gameCanvas';
import { LayerGroupHalo } from '../legacy/halo';
import { FloorChange } from '../legacy/fallback';
import { PopText } from '../legacy/pop';
import { mainUIController } from './controller';
import {
ILayerGroupRenderExtends,
LayerGroupAnimate,
FloorViewport,
ILayerRenderExtends,
HeroRenderer,
LayerDoorAnimate,
LayerGroup
} from '../elements';
import { LayerGroup } from '../elements';
import { isNil } from 'lodash-es';
import { materials } from '@user/client-base';
const MainScene = defineComponent(() => {
//#region 基本定义
const layerGroupExtends: ILayerGroupRenderExtends[] = [
new FloorDamageExtends(),
new FloorItemDetail(),
new LayerGroupFilter(),
new LayerGroupPortal(),
new LayerGroupHalo(),
new LayerGroupAnimate(),
new FloorViewport()
];
const eventExtends: ILayerRenderExtends[] = [
new HeroRenderer(),
new LayerDoorAnimate(),
new LayerShadowExtends()
];
const mainTextboxProps: Props<typeof Textbox> = {
text: '',
hidden: true,
@ -272,7 +250,7 @@ const MainScene = defineComponent(() => {
const tileset = materials.getAsset(0);
if (!tileset) return;
canvas.ctx.drawImage(
tileset.data.texture.source,
tileset.texture.source,
0,
0,
canvas.width,
@ -283,6 +261,7 @@ const MainScene = defineComponent(() => {
return () => (
<container id="main-scene" width={MAIN_WIDTH} height={MAIN_HEIGHT}>
<sprite
hidden
render={testRender}
loc={[180, 0, 480, 480]}
zIndex={1000}
@ -304,14 +283,10 @@ const MainScene = defineComponent(() => {
onDown={downMap}
onMove={moveMap}
>
<layer-group id="layer-main" ex={layerGroupExtends} ref={map}>
<layer layer="bg" zIndex={10}></layer>
<layer layer="bg2" zIndex={20}></layer>
<layer layer="event" zIndex={30} ex={eventExtends}></layer>
<layer layer="fg" zIndex={40}></layer>
<layer layer="fg2" zIndex={50}></layer>
<PopText id="pop-main" zIndex={80}></PopText>
</layer-group>
<map-render
layerState={state.layer}
loc={[0, 0, MAP_WIDTH, MAP_HEIGHT]}
/>
<Textbox id="main-textbox" {...mainTextboxProps}></Textbox>
<FloorChange id="floor-change" zIndex={50}></FloorChange>
<Tip

View File

@ -1,18 +1,43 @@
import { logger } from '@motajs/common';
import { IMapLayer, MapLayer } from '../map';
import { ILayerState } from './types';
import {
Hookable,
HookController,
IHookController,
logger
} from '@motajs/common';
import {
IMapLayer,
IMapLayerHookController,
IMapLayerHooks,
MapLayer
} from '../map';
import { ILayerState, ILayerStateHooks } from './types';
export class LayerState implements ILayerState {
readonly layerList: WeakSet<IMapLayer> = new WeakSet();
export class LayerState
extends Hookable<ILayerStateHooks>
implements ILayerState
{
readonly layerList: Set<IMapLayer> = new Set();
/** 图层到图层别名映射 */
readonly layerAliasMap: WeakMap<IMapLayer, string> = new WeakMap();
/** 图层别名到图层的映射 */
readonly aliasLayerMap: WeakMap<symbol, IMapLayer> = new WeakMap();
readonly aliasLayerMap: Map<symbol, IMapLayer> = new Map();
/** 背景图块 */
private backgroundTile: number = 0;
/** 图层钩子映射 */
private layerHookMap: Map<IMapLayer, IMapLayerHookController> = new Map();
addLayer(width: number, height: number): IMapLayer {
const array = new Uint32Array(width * height);
const layer = new MapLayer(array, width, height);
this.layerList.add(layer);
this.forEachHook((hook, controller) => {
hook.onUpdateLayer?.(controller, this.layerList);
});
const controller = layer.addHook(new StateMapLayerHook(this));
this.layerHookMap.set(layer, controller);
controller.load();
return layer;
}
@ -24,6 +49,17 @@ export class LayerState implements ILayerState {
this.aliasLayerMap.delete(symbol);
this.layerAliasMap.delete(layer);
}
this.forEachHook((hook, controller) => {
hook.onUpdateLayer?.(controller, this.layerList);
});
const controller = this.layerHookMap.get(layer);
if (!controller) return;
controller.unload();
this.layerHookMap.delete(layer);
}
hasLayer(layer: IMapLayer): boolean {
return this.layerList.has(layer);
}
setLayerAlias(layer: IMapLayer, alias: string): void {
@ -49,7 +85,7 @@ export class LayerState implements ILayerState {
layer: IMapLayer,
width: number,
height: number,
keepBlock?: boolean
keepBlock: boolean = false
): void {
if (keepBlock) {
layer.resize(width, height);
@ -57,4 +93,58 @@ export class LayerState implements ILayerState {
layer.resize2(width, height);
}
}
setBackground(tile: number): void {
this.backgroundTile = tile;
this.forEachHook((hook, controller) => {
hook.onChangeBackground?.(controller, tile);
});
}
getBackground(): number {
return this.backgroundTile;
}
protected createController(
hook: Partial<ILayerStateHooks>
): IHookController<ILayerStateHooks> {
return new HookController(this, hook);
}
}
class StateMapLayerHook implements Partial<IMapLayerHooks> {
constructor(readonly state: LayerState) {}
onUpdateArea(
controller: IMapLayerHookController,
x: number,
y: number,
width: number,
height: number
): void {
this.state.forEachHook((hook, c) => {
hook.onUpdateLayerArea?.(c, controller.layer, x, y, width, height);
});
}
onUpdateBlock(
controller: IMapLayerHookController,
block: number,
x: number,
y: number
): void {
this.state.forEachHook((hook, c) => {
hook.onUpdateLayerBlock?.(c, controller.layer, block, x, y);
});
}
onResize(
controller: IMapLayerHookController,
width: number,
height: number
): void {
this.state.forEachHook((hook, c) => {
hook.onResizeLayer?.(c, controller.layer, width, height);
});
}
}

View File

@ -1,8 +1,76 @@
import { IHookable, IHookBase, IHookController } from '@motajs/common';
import { IMapLayer } from '../map';
export interface ILayerState {
export interface ILayerStateHooks extends IHookBase {
/**
*
* @param controller
* @param tile
*/
onChangeBackground(controller: IHookController<this>, tile: number): void;
/**
*
* @param controller
* @param layerList
*/
onUpdateLayer(
controller: IHookController<this>,
layerList: Set<IMapLayer>
): void;
/**
*
* @param controller
* @param layer
* @param x
* @param y
* @param width
* @param height
*/
onUpdateLayerArea(
controller: IHookController<this>,
layer: IMapLayer,
x: number,
y: number,
width: number,
height: number
): void;
/**
*
* @param controller
* @param layer
* @param block
* @param x
* @param y
*/
onUpdateLayerBlock(
controller: IHookController<this>,
layer: IMapLayer,
block: number,
x: number,
y: number
): void;
/**
*
* @param controller
* @param layer
* @param width
* @param height
*/
onResizeLayer(
controller: IHookController<this>,
layer: IMapLayer,
width: number,
height: number
): void;
}
export interface ILayerState extends IHookable<ILayerStateHooks> {
/** 地图列表 */
readonly layerList: WeakSet<IMapLayer>;
readonly layerList: Set<IMapLayer>;
/**
*
@ -17,6 +85,12 @@ export interface ILayerState {
*/
removeLayer(layer: IMapLayer): void;
/**
*
* @param layer
*/
hasLayer(layer: IMapLayer): boolean;
/**
*
* @param layer
@ -49,6 +123,17 @@ export interface ILayerState {
height: number,
keepBlock?: boolean
): void;
/**
*
* @param tile
*/
setBackground(tile: number): void;
/**
* 0
*/
getBackground(): number;
}
export interface ICoreState {

View File

@ -2,25 +2,19 @@ import { isNil } from 'lodash-es';
import {
IMapLayer,
IMapLayerData,
IMapLayerExtends,
IMapLayerExtendsController
IMapLayerHookController,
IMapLayerHooks
} from './types';
import { logger } from '@motajs/common';
import { Hookable, HookController, logger } from '@motajs/common';
interface IExtendsData {
readonly ex: IMapLayerExtends;
readonly controller: IMapLayerExtendsController;
}
export class MapLayer implements IMapLayer {
export class MapLayer
extends Hookable<IMapLayerHooks, IMapLayerHookController>
implements IMapLayer
{
width: number;
height: number;
empty: boolean = true;
/** 已经加载完毕的图层拓展 */
private loadedExtends: Set<IExtendsData> = new Set();
/** 添加的图层拓展 */
private addedExtends: Map<string, IExtendsData> = new Map();
zIndex: number = 0;
/** 地图图块数组 */
private mapArray: Uint32Array;
@ -28,6 +22,7 @@ export class MapLayer implements IMapLayer {
private mapData: IMapLayerData;
constructor(array: Uint32Array, width: number, height: number) {
super();
this.width = width;
this.height = height;
const area = width * height;
@ -42,9 +37,6 @@ export class MapLayer implements IMapLayer {
resize(width: number, height: number): void {
if (this.width === width && this.height === height) {
this.loadedExtends.forEach(v => {
v.ex.onResize?.(v.controller, width, height);
});
return;
}
this.mapData.expired = true;
@ -78,17 +70,14 @@ export class MapLayer implements IMapLayer {
expired: false,
array: this.mapArray
};
this.loadedExtends.forEach(v => {
v.ex.onResize?.(v.controller, width, height);
this.forEachHook((hook, controller) => {
hook.onResize?.(controller, width, height);
});
}
resize2(width: number, height: number): void {
if (this.width === width && this.height === height) {
this.mapArray.fill(0);
this.loadedExtends.forEach(v => {
v.ex.onResize?.(v.controller, width, height);
});
return;
}
this.mapData.expired = true;
@ -99,17 +88,18 @@ export class MapLayer implements IMapLayer {
expired: false,
array: this.mapArray
};
this.loadedExtends.forEach(v => {
v.ex.onResize?.(v.controller, width, height);
});
this.empty = true;
this.forEachHook((hook, controller) => {
hook.onResize?.(controller, width, height);
});
}
setBlock(block: number, x: number, y: number): void {
const index = y * this.width + x;
if (block === this.mapArray[index]) return;
this.mapArray[index] = block;
this.loadedExtends.forEach(v => {
v.ex.onUpdateBlock?.(v.controller, block, x, y);
this.forEachHook((hook, controller) => {
hook.onUpdateBlock?.(controller, block, x, y);
});
if (block !== 0) {
this.empty = false;
@ -131,6 +121,9 @@ export class MapLayer implements IMapLayer {
const height = Math.ceil(array.length / width);
if (width === this.width && height === this.height) {
this.mapArray.set(array);
this.forEachHook((hook, controller) => {
hook.onUpdateArea?.(controller, x, y, width, height);
});
return;
}
const w = this.width;
@ -156,8 +149,8 @@ export class MapLayer implements IMapLayer {
}
this.mapArray.set(array.subarray(start, start + nw), offset);
}
this.loadedExtends.forEach(v => {
v.ex.onUpdateArea?.(v.controller, x, y, width, height);
this.forEachHook((hook, controller) => {
hook.onUpdateArea?.(controller, x, y, width, height);
});
this.empty &&= empty;
}
@ -210,51 +203,32 @@ export class MapLayer implements IMapLayer {
return this.mapData;
}
loadExtends(ex: IMapLayerExtends): boolean {
if (!this.addedExtends.has(ex.id)) return false;
ex.awake?.();
const data = this.addedExtends.get(ex.id)!;
this.loadedExtends.add(data);
return true;
protected createController(
hook: Partial<IMapLayerHooks>
): IMapLayerHookController {
return new MapLayerHookController(this, hook);
}
addExtends(ex: IMapLayerExtends): IMapLayerExtendsController {
const controller = new MapLayerExtendsController(this, ex);
this.addedExtends.set(ex.id, {
ex,
controller
});
return controller;
}
removeExtends(ex: IMapLayerExtends | string): void {
const id = typeof ex === 'string' ? ex : ex.id;
const data = this.addedExtends.get(id);
if (!data) return;
data.ex.destroy?.();
this.addedExtends.delete(id);
this.loadedExtends.delete(data);
setZIndex(zIndex: number): void {
this.zIndex = zIndex;
}
}
class MapLayerExtendsController implements IMapLayerExtendsController {
loaded: boolean = false;
class MapLayerHookController
extends HookController<IMapLayerHooks>
implements IMapLayerHookController
{
hookable: MapLayer;
constructor(
readonly layer: MapLayer,
readonly ex: IMapLayerExtends
) {}
load(): void {
this.loaded = this.layer.loadExtends(this.ex);
hook: Partial<IMapLayerHooks>
) {
super(layer, hook);
this.hookable = layer;
}
getMapData(): Readonly<IMapLayerData> {
return this.layer.getMapRef();
}
unload(): void {
this.layer.removeExtends(this.ex);
this.loaded = false;
}
}

View File

@ -1,3 +1,5 @@
import { IHookable, IHookBase, IHookController } from '@motajs/common';
export interface IMapLayerData {
/** 当前引用是否过期,当地图图层内部的地图数组引用更新时,此项会变为 `true` */
expired: boolean;
@ -5,26 +7,15 @@ export interface IMapLayerData {
array: Uint32Array;
}
export interface IMapLayerHooks {
export interface IMapLayerHooks extends IHookBase {
/**
*
* @param dependencies
*/
awake(): void;
/**
*
*/
destroy(): void;
/**
*
* `resize`
* @param controller
* @param width
* @param height
*/
onResize(
controller: IMapLayerExtendsController,
controller: IMapLayerHookController,
width: number,
height: number
): void;
@ -38,7 +29,7 @@ export interface IMapLayerHooks {
* @param height
*/
onUpdateArea(
controller: IMapLayerExtendsController,
controller: IMapLayerHookController,
x: number,
y: number,
width: number,
@ -53,47 +44,37 @@ export interface IMapLayerHooks {
* @param y
*/
onUpdateBlock(
controller: IMapLayerExtendsController,
controller: IMapLayerHookController,
block: number,
x: number,
y: number
): void;
}
export interface IMapLayerExtends extends Partial<IMapLayerHooks> {
/** 这个拓展对象的标识符 */
readonly id: string;
}
export interface IMapLayerExtendsController {
/** 当前图层拓展是否已经被加载 */
readonly loaded: boolean;
export interface IMapLayerHookController
extends IHookController<IMapLayerHooks> {
/** 拓展所属的图层对象 */
readonly layer: IMapLayer;
/**
*
*/
load(): void;
/**
*
*/
getMapData(): Readonly<IMapLayerData>;
/**
*
*/
unload(): void;
}
export interface IMapLayer {
export interface IMapLayer
extends IHookable<IMapLayerHooks, IMapLayerHookController> {
/** 地图宽度 */
readonly width: number;
/** 地图高度 */
readonly height: number;
/** 地图是否全部空白,此值具有保守性,即如果其为 `true`,则地图一定空白,但是如果其为 `false`,那么地图也有可能空白 */
/**
*
* `true` `false`
*/
readonly empty: boolean;
/** 图层纵深 */
readonly zIndex: number;
/**
*
@ -153,15 +134,13 @@ export interface IMapLayer {
): Uint32Array;
/**
* 使
* @param ex
* @returns
*
*/
addExtends(ex: IMapLayerExtends): IMapLayerExtendsController;
getMapRef(): IMapLayerData;
/**
*
* @param ex
*
* @param zIndex
*/
removeExtends(ex: IMapLayerExtends | string): void;
setZIndex(zIndex: number): void;
}

View File

@ -1,32 +1,32 @@
import { isNil } from 'lodash-es';
import { IDirtyTracker } from './types';
import { IDirtyMark, IDirtyTracker } from './types';
/**
* `dirtySince` `true`
*/
export class PrivateBooleanDirtyTracker implements IDirtyTracker<boolean> {
/** 标记映射 */
private markMap: WeakMap<symbol, number> = new WeakMap();
private markMap: WeakMap<IDirtyMark, number> = new WeakMap();
/** 脏标记 */
private dirtyFlag: number = 0;
mark(): symbol {
const symbol = Symbol();
mark(): IDirtyMark {
const symbol = {};
this.markMap.set(symbol, this.dirtyFlag);
return symbol;
}
unmark(mark: symbol): void {
unmark(mark: IDirtyMark): void {
this.markMap.delete(mark);
}
dirtySince(mark: symbol): boolean {
dirtySince(mark: IDirtyMark): boolean {
const num = this.markMap.get(mark);
if (isNil(num)) return true;
return num < this.dirtyFlag;
}
hasMark(symbol: symbol): boolean {
hasMark(symbol: IDirtyMark): boolean {
return this.markMap.has(symbol);
}
@ -47,24 +47,24 @@ export class PrivateListDirtyTracker<T extends number>
/** 标记映射,键表示在索引,值表示其对应的标记数字 */
private readonly markMap: Map<T, number> = new Map();
/** 标记 symbol 映射,值表示这个 symbol 对应的标记数字 */
private readonly symbolMap: WeakMap<symbol, number> = new WeakMap();
private readonly symbolMap: WeakMap<{}, number> = new WeakMap();
/** 脏标记数字 */
private dirtyFlag: number = 0;
constructor(protected length: number) {}
mark(): symbol {
const symbol = Symbol();
mark(): IDirtyMark {
const symbol = {};
this.symbolMap.set(symbol, this.dirtyFlag);
return symbol;
}
unmark(mark: symbol): void {
unmark(mark: IDirtyMark): void {
this.symbolMap.delete(mark);
}
dirtySince(mark: symbol): Set<T> {
dirtySince(mark: IDirtyMark): Set<T> {
const num = this.symbolMap.get(mark);
const res = new Set<T>();
if (isNil(num)) return res;
@ -74,7 +74,7 @@ export class PrivateListDirtyTracker<T extends number>
return res;
}
hasMark(symbol: symbol): boolean {
hasMark(symbol: IDirtyMark): boolean {
return this.symbolMap.has(symbol);
}
@ -95,24 +95,24 @@ export class PrivateMapDirtyTracker<T extends string>
/** 标记映射,键表示名称,值表示其对应的标记数字 */
private readonly markMap: Map<T, number> = new Map();
/** 标记 symbol 映射,值表示这个 symbol 对应的标记数字 */
private readonly symbolMap: WeakMap<symbol, number> = new WeakMap();
private readonly symbolMap: WeakMap<{}, number> = new WeakMap();
/** 脏标记数字 */
private dirtyFlag: number = 0;
constructor(protected length: number) {}
mark(): symbol {
const symbol = Symbol();
mark(): IDirtyMark {
const symbol = {};
this.symbolMap.set(symbol, this.dirtyFlag);
return symbol;
}
unmark(mark: symbol): void {
unmark(mark: IDirtyMark): void {
this.symbolMap.delete(mark);
}
dirtySince(mark: symbol): Record<T, boolean> {
dirtySince(mark: IDirtyMark): Record<T, boolean> {
const num = this.symbolMap.get(mark) ?? 0;
const obj: Partial<Record<T, boolean>> = {};
this.markMap.forEach((v, k) => {
@ -122,7 +122,7 @@ export class PrivateMapDirtyTracker<T extends string>
return obj as Record<T, boolean>;
}
hasMark(symbol: symbol): boolean {
hasMark(symbol: IDirtyMark): boolean {
return this.symbolMap.has(symbol);
}

View File

@ -0,0 +1,82 @@
import { logger } from './logger';
import { IHookable, IHookBase, IHookController, IHookObject } from './types';
export abstract class Hookable<
H extends IHookBase = IHookBase,
C extends IHookController<H> = IHookController<H>
> implements IHookable<H, C>
{
/** 加载完成的钩子列表 */
protected readonly loadedList: Set<IHookObject<H, C>> = new Set();
/** 钩子列表 */
private readonly hookList: Set<IHookObject<H, C>> = new Set();
/** 钩子对象到钩子存储对象的映射 */
private readonly hookMap: Map<Partial<H>, IHookObject<H, C>> = new Map();
/** 钩子控制器到钩子存储对象的映射 */
private readonly controllerMap: Map<C, IHookObject<H, C>> = new Map();
/**
*
* @param hook
*/
protected abstract createController(hook: Partial<H>): C;
addHook(hook: Partial<H>): C {
const controller = this.createController(hook);
const obj: IHookObject<H, C> = { hook, controller };
this.hookMap.set(hook, obj);
this.controllerMap.set(controller, obj);
this.hookList.add(obj);
return controller;
}
loadHook(hook: Partial<H>): void {
const obj = this.hookMap.get(hook);
if (!obj) {
logger.warn(85);
return;
}
hook.awake?.(obj.controller);
this.loadedList.add(obj);
}
removeHook(hook: Partial<H>): void {
const obj = this.hookMap.get(hook);
if (!obj) return;
obj.hook.destroy?.(obj.controller);
this.hookList.delete(obj);
this.loadedList.delete(obj);
this.hookMap.delete(hook);
this.controllerMap.delete(obj.controller);
}
removeHookByController(hook: C): void {
const obj = this.controllerMap.get(hook);
if (!obj) return;
obj.hook.destroy?.(obj.controller);
this.hookList.delete(obj);
this.loadedList.delete(obj);
this.controllerMap.delete(hook);
this.hookMap.delete(obj.hook);
}
forEachHook(fn: (hook: Partial<H>, controller: C) => void): void {
this.loadedList.forEach(v => fn(v.hook, v.controller));
}
}
export class HookController<H extends IHookBase> implements IHookController<H> {
constructor(
readonly hookable: IHookable<H, IHookController<H>>,
readonly hook: Partial<H>
) {}
load(): void {
this.hookable.loadHook(this.hook);
}
unload(): void {
this.hookable.removeHookByController(this);
}
}

View File

@ -1,4 +1,5 @@
export * from './dirtyTracker';
export * from './hook';
export * from './logger';
export * from './utils';
export * from './types';

View File

@ -41,7 +41,7 @@
"39": "Offset pool size exceeds WebGL2 limitation, ensure size type of your big image is less than $1.",
"40": "Material used by map block $1 is not found in built asset. Please ensure you have pack it into asset.",
"41": "You are trying to use a texture on moving block whose offset is not in the offset pool, please build it into asset after loading.",
"42": "The layerList property of map-render element is required.",
"42": "The layerState property of map-render element is required.",
"1101": "Shadow extension needs 'floor-hero' extension as dependency.",
"1201": "Floor-damage extension needs 'floor-binder' extension as dependency.",
"1301": "Portal extension need 'floor-binder' extension as dependency.",
@ -130,8 +130,9 @@
"80": "Parameter count of MapLayer.getMapData must be 0 or 4.",
"81": "Map data to get is partially (or totally) out of range. Overflowed area will be filled with zero.",
"82": "Big image offset size is larger than 64. Considier reduce big image offset bucket.",
"83": "It seems that you call 'updateBlock' too frequently. This will extremely affect game's performance, so considering integrate them into one 'updateArea' or 'updateBlockList' call.",
"83": "It seems that you call '$1' too frequently. This will extremely affect game's performance, so considering integrate them into one 'updateArea' or 'updateBlockList' call.",
"84": "Cannot set alias '$1' for layer, since '$1' is already an alias for another layer.",
"85": "Hook to load does not belong to current hookable object.",
"1001": "Item-detail extension needs 'floor-binder' and 'floor-damage' extension as dependency.",
"1101": "Cannot add new effect to point effect instance, for there's no more reserve space for it. Please increase the max count of the instance."
}

View File

@ -1,3 +1,5 @@
//#region 脏标记
export interface IDirtyMarker<T> {
/**
*
@ -6,27 +8,109 @@ export interface IDirtyMarker<T> {
dirty(data: T): void;
}
export interface IDirtyMark {}
export interface IDirtyTracker<T> {
/**
*
*/
mark(): symbol;
mark(): IDirtyMark;
/**
*
* @param mark
*/
unmark(mark: symbol): void;
unmark(mark: IDirtyMark): void;
/**
*
* @param mark
*/
dirtySince(mark: symbol): T;
dirtySince(mark: IDirtyMark): T;
/**
*
* @param symbol
*/
hasMark(symbol: symbol): boolean;
hasMark(symbol: IDirtyMark): boolean;
}
//#endregion
//#region 钩子
export interface IHookObject<
H extends IHookBase,
C extends IHookController<H>
> {
/** 钩子对象 */
readonly hook: Partial<H>;
/** 钩子控制器 */
readonly controller: C;
}
export interface IHookController<H extends IHookBase = IHookBase> {
/** 控制器的钩子对象 */
readonly hook: Partial<H>;
/**
*
*/
load(): void;
/**
*
*/
unload(): void;
}
export interface IHookBase {
/**
*
* @param controller
*/
awake(controller: IHookController<this>): void;
/**
*
* @param controller
*/
destroy(controller: IHookController<this>): void;
}
export interface IHookable<
H extends IHookBase = IHookBase,
C extends IHookController<H> = IHookController<H>
> {
/**
*
* @param hook
*/
addHook(hook: Partial<H>): C;
/**
*
* @param hook
*/
loadHook(hook: Partial<H>): void;
/**
* `destroy`
* @param hook
*/
removeHook(hook: Partial<H>): void;
/**
*
* @param hook
*/
removeHookByController(hook: C): void;
/**
*
* @param fn
*/
forEachHook(fn: (hook: Partial<H>, controller: C) => void): void;
}
//#endregion

View File

@ -45,8 +45,7 @@ export class TextureColumnAnimater implements ITextureAnimater<number> {
*once(texture: ITexture, frames: number): Generator<ITextureRenderable> {
if (frames <= 0) return;
const renderable = texture.render();
const { x: ox, y: oy } = renderable.rect;
const { width, height: h } = texture!;
const { x: ox, y: oy, w: width, h } = renderable.rect;
const w = width / frames;
for (let i = 0; i < frames; i++) {
const renderable: ITextureRenderable = {

View File

@ -64,6 +64,10 @@ export class TextureStore<T extends ITexture = ITexture>
}
}
hasTexture(identifier: number): boolean {
return this.texMap.has(identifier);
}
getTexture(identifier: number): T | null {
return this.texMap.get(identifier) ?? null;
}

View File

@ -7,6 +7,7 @@ import {
ITextureSplitter,
SizedCanvasImageSource
} from './types';
import { clamp } from 'lodash-es';
export class Texture implements ITexture {
source: SizedCanvasImageSource;
@ -91,11 +92,11 @@ export class Texture implements ITexture {
}
clampRect(rect: Readonly<IRect>): Readonly<IRect> {
const l = Math.max(this.cl, this.cl + rect.x);
const t = Math.max(this.ct, this.ct + rect.y);
const r = Math.min(l + rect.w, this.cr);
const b = Math.min(t + rect.h, this.cb);
return { x: l, y: t, w: r - b, h: b - t };
const l = clamp(rect.x, this.cl, this.cr);
const t = clamp(rect.y, this.ct, this.cb);
const r = clamp(rect.x + rect.w, this.cl, this.cr);
const b = clamp(rect.y + rect.h, this.ct, this.cb);
return { x: l, y: t, w: r - l, h: b - t };
}
clipped(rect: Readonly<IRect>): ITextureRenderable {

View File

@ -173,6 +173,12 @@ export interface ITextureStore<T extends ITexture = ITexture> {
*/
removeTexture(identifier: number | string | T): void;
/**
* id
* @param identifier id
*/
hasTexture(identifier: number): boolean;
/**
* id
* @param identifier id

View File

@ -1237,7 +1237,7 @@ export class GL2Program extends EventEmitter<ShaderProgramEvent> {
}
/**
* {@link Shader.DRAW_ARRAYS} {@link Shader.DRAW_INSTANCED}
* {@link GL2.DRAW_ARRAYS} {@link GL2.DRAW_INSTANCED}
*/
mode(mode: RenderMode) {
this.renderMode = mode;

View File

@ -67,7 +67,7 @@ main.floors.MT13=
],
"beforeBattle": {},
"weather": [
"sun",
"rain",
8
],
"cannotMoveIn": {},

View File

@ -161,6 +161,48 @@ var functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a = {
// ---------- 重绘新地图这一步将会设置core.status.floorId ---------- //
core.drawMap(floorId);
// 更新地图状态
const { state } = Mota.require('@user/data-state');
const bg = core.floors[floorId].bgmap ?? [];
const bg2 = core.floors[floorId].bg2map ?? [];
const event = core.floors[floorId].map ?? [];
const fg = core.floors[floorId].fgmap ?? [];
const fg2 = core.floors[floorId].fg2map ?? [];
const { width, height } = core.floors[floorId];
const bgLayer = state.layer.getLayerByAlias('bg');
const bg2Layer = state.layer.getLayerByAlias('bg2');
const eventLayer = state.layer.getLayerByAlias('event');
const fgLayer = state.layer.getLayerByAlias('fg');
const fg2Layer = state.layer.getLayerByAlias('fg2');
state.layer.resizeLayer(bgLayer, width, height);
state.layer.resizeLayer(bg2Layer, width, height);
state.layer.resizeLayer(eventLayer, width, height);
state.layer.resizeLayer(fgLayer, width, height);
state.layer.resizeLayer(fg2Layer, width, height);
if (bg.length > 0) {
const array = new Uint32Array(bg.flat());
bgLayer.putMapData(array, 0, 0, width);
}
if (bg2.length > 0) {
const array = new Uint32Array(bg2.flat());
bg2Layer.putMapData(array, 0, 0, width);
}
if (event.length > 0) {
const array = new Uint32Array(event.flat());
eventLayer.putMapData(array, 0, 0, width);
}
if (fg.length > 0) {
const array = new Uint32Array(fg.flat());
fgLayer.putMapData(array, 0, 0, width);
}
if (fg2.length > 0) {
const array = new Uint32Array(fg2.flat());
fg2Layer.putMapData(array, 0, 0, width);
}
const back = core.floors[floorId].defaultGround;
const id = core.maps.getNumberById(back);
state.layer.setBackground(id);
// 设置勇士的位置
heroLoc.direction = core.turnDirection(heroLoc.direction);
core.setHeroLoc('x', heroLoc.x);