mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-11-29 07:02:58 +08:00
Compare commits
1 Commits
8825792180
...
967e3d498d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
967e3d498d |
@ -1,10 +1,9 @@
|
|||||||
import { ITextureComposedData } from '@motajs/render-assets';
|
import { ITextureComposedData } from '@motajs/render-assets';
|
||||||
import { IMaterialAsset } from './types';
|
import { IMaterialAsset } from './types';
|
||||||
import { IDirtyMark } from '@motajs/common';
|
|
||||||
|
|
||||||
export class MaterialAsset implements IMaterialAsset {
|
export class MaterialAsset implements IMaterialAsset {
|
||||||
/** 标记列表 */
|
/** 标记列表 */
|
||||||
private readonly marks: WeakMap<IDirtyMark, number> = new WeakMap();
|
private readonly marks: WeakMap<symbol, number> = new WeakMap();
|
||||||
/** 脏标记,所有值小于此标记的都视为需要更新 */
|
/** 脏标记,所有值小于此标记的都视为需要更新 */
|
||||||
private dirtyFlag: number = 0;
|
private dirtyFlag: number = 0;
|
||||||
|
|
||||||
@ -14,22 +13,22 @@ export class MaterialAsset implements IMaterialAsset {
|
|||||||
this.dirtyFlag++;
|
this.dirtyFlag++;
|
||||||
}
|
}
|
||||||
|
|
||||||
mark(): IDirtyMark {
|
mark(): symbol {
|
||||||
const symbol = {};
|
const symbol = Symbol();
|
||||||
this.marks.set(symbol, this.dirtyFlag);
|
this.marks.set(symbol, this.dirtyFlag);
|
||||||
return symbol;
|
return symbol;
|
||||||
}
|
}
|
||||||
|
|
||||||
unmark(mark: IDirtyMark): void {
|
unmark(mark: symbol): void {
|
||||||
this.marks.delete(mark);
|
this.marks.delete(mark);
|
||||||
}
|
}
|
||||||
|
|
||||||
dirtySince(mark: IDirtyMark): boolean {
|
dirtySince(mark: symbol): boolean {
|
||||||
const value = this.marks.get(mark) ?? -1;
|
const value = this.marks.get(mark) ?? -1;
|
||||||
return value < this.dirtyFlag;
|
return value < this.dirtyFlag;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasMark(symbol: IDirtyMark): boolean {
|
hasMark(symbol: symbol): boolean {
|
||||||
return this.marks.has(symbol);
|
return this.marks.has(symbol);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -71,21 +71,21 @@ export class AutotileProcessor implements IAutotileProcessor {
|
|||||||
// 如果地图高度只有 1
|
// 如果地图高度只有 1
|
||||||
if (length === width) {
|
if (length === width) {
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
return 0b1110_1111;
|
return 0b1100_0111;
|
||||||
} else if (index === length - 1) {
|
} else if (index === length - 1) {
|
||||||
return 0b1111_1110;
|
return 0b0111_1100;
|
||||||
} else {
|
} else {
|
||||||
return 0b1110_1110;
|
return 0b0100_0100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 如果地图宽度只有 1
|
// 如果地图宽度只有 1
|
||||||
if (width === 1) {
|
if (width === 1) {
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
return 0b1111_1011;
|
return 0b1111_0001;
|
||||||
} else if (index === length - 1) {
|
} else if (index === length - 1) {
|
||||||
return 0b1011_1111;
|
return 0b0001_1111;
|
||||||
} else {
|
} else {
|
||||||
return 0b1011_1011;
|
return 0b0001_0001;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,23 +96,23 @@ export class AutotileProcessor implements IAutotileProcessor {
|
|||||||
|
|
||||||
// 四个角,左上,右上,右下,左下
|
// 四个角,左上,右上,右下,左下
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
return 0b1110_0011;
|
return 0b1100_0001;
|
||||||
} else if (index === width - 1) {
|
} else if (index === width - 1) {
|
||||||
return 0b1111_1000;
|
return 0b0111_0000;
|
||||||
} else if (index === length - 1) {
|
} else if (index === length - 1) {
|
||||||
return 0b0011_1110;
|
return 0b0001_1100;
|
||||||
} else if (index === lastLine) {
|
} else if (index === lastLine) {
|
||||||
return 0b1000_1111;
|
return 0b0000_0111;
|
||||||
}
|
}
|
||||||
// 四条边,上,右,下,左
|
// 四条边,上,右,下,左
|
||||||
else if (index < width) {
|
else if (index < width) {
|
||||||
return 0b1110_0000;
|
return 0b0100_0000;
|
||||||
} else if (x === width - 1) {
|
} else if (x === width - 1) {
|
||||||
return 0b0011_1000;
|
return 0b0001_0000;
|
||||||
} else if (index > lastLine) {
|
} else if (index > lastLine) {
|
||||||
return 0b0000_1110;
|
return 0b0000_0100;
|
||||||
} else if (x === 0) {
|
} else if (x === 0) {
|
||||||
return 0b1000_0011;
|
return 0b0000_0001;
|
||||||
}
|
}
|
||||||
// 不在边缘
|
// 不在边缘
|
||||||
else {
|
else {
|
||||||
@ -222,13 +222,12 @@ export class AutotileProcessor implements IAutotileProcessor {
|
|||||||
connection: number
|
connection: number
|
||||||
): ITextureRenderable | null {
|
): ITextureRenderable | null {
|
||||||
const { texture } = tile;
|
const { texture } = tile;
|
||||||
const size = texture.height === 32 * 48 ? 32 : 48;
|
const size = texture.height === 128 ? 32 : 48;
|
||||||
const index = distinctConnectionMap.get(connection);
|
const index = distinctConnectionMap.get(connection);
|
||||||
if (isNil(index)) return null;
|
if (isNil(index)) return null;
|
||||||
const { rect } = texture.render();
|
|
||||||
return {
|
return {
|
||||||
source: texture.source,
|
source: texture.source,
|
||||||
rect: { x: rect.x, y: rect.y + size * index, w: size, h: size }
|
rect: { x: 0, y: size * index, w: size, h: size }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,11 +3,10 @@ import {
|
|||||||
ITexture,
|
ITexture,
|
||||||
ITextureComposedData,
|
ITextureComposedData,
|
||||||
ITextureStreamComposer,
|
ITextureStreamComposer,
|
||||||
TextureMaxRectsStreamComposer,
|
TextureMaxRectsStreamComposer
|
||||||
SizedCanvasImageSource
|
|
||||||
} from '@motajs/render-assets';
|
} from '@motajs/render-assets';
|
||||||
import { IAssetBuilder, IMaterialGetter, ITrackedAssetData } from './types';
|
import { IAssetBuilder } from './types';
|
||||||
import { logger, PrivateListDirtyTracker } from '@motajs/common';
|
import { logger } from '@motajs/common';
|
||||||
|
|
||||||
export class AssetBuilder implements IAssetBuilder {
|
export class AssetBuilder implements IAssetBuilder {
|
||||||
readonly composer: ITextureStreamComposer<void> =
|
readonly composer: ITextureStreamComposer<void> =
|
||||||
@ -16,18 +15,6 @@ export class AssetBuilder implements IAssetBuilder {
|
|||||||
private output: ITextureStore | null = null;
|
private output: ITextureStore | null = null;
|
||||||
private started: boolean = false;
|
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 {
|
pipe(store: ITextureStore): void {
|
||||||
if (this.started) {
|
if (this.started) {
|
||||||
logger.warn(76);
|
logger.warn(76);
|
||||||
@ -42,103 +29,29 @@ export class AssetBuilder implements IAssetBuilder {
|
|||||||
const data = res[0];
|
const data = res[0];
|
||||||
|
|
||||||
if (this.output) {
|
if (this.output) {
|
||||||
if (data.index > this.index) {
|
if (!this.output.getTexture(data.index)) {
|
||||||
this.output.addTexture(data.index, data.texture);
|
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;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateSourceList(source: Set<ITextureComposedData>) {
|
|
||||||
for (const data of source) {
|
|
||||||
await this.trackedData.updateSource(
|
|
||||||
data.index,
|
|
||||||
data.texture.source
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addTextureList(
|
addTextureList(
|
||||||
texture: Iterable<ITexture>
|
texture: Iterable<ITexture>
|
||||||
): Iterable<ITextureComposedData> {
|
): Iterable<ITextureComposedData> {
|
||||||
this.started = true;
|
this.started = true;
|
||||||
const res = [...this.composer.add(texture)];
|
const res = [...this.composer.add(texture)];
|
||||||
const toUpdate = new Set<ITextureComposedData>();
|
|
||||||
if (this.output) {
|
if (this.output) {
|
||||||
res.forEach(data => {
|
res.forEach(v => {
|
||||||
if (data.index > this.index) {
|
if (!this.output!.getTexture(v.index)) {
|
||||||
this.output!.addTexture(data.index, data.texture);
|
this.output!.addTexture(v.index, v.texture);
|
||||||
this.index = data.index;
|
|
||||||
toUpdate.add(data);
|
|
||||||
} else {
|
|
||||||
toUpdate.add(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.pending = this.pending.then(() => this.updateSourceList(toUpdate));
|
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
tracked(): ITrackedAssetData {
|
|
||||||
return this.trackedData;
|
|
||||||
}
|
|
||||||
|
|
||||||
close(): void {
|
close(): void {
|
||||||
this.composer.close();
|
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 {}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -36,7 +36,7 @@ function addAutotile(set: Set<number>, map?: readonly (readonly number[])[]) {
|
|||||||
map.forEach(line => {
|
map.forEach(line => {
|
||||||
line.forEach(v => {
|
line.forEach(v => {
|
||||||
const id = core.maps.blocksInfo[v as keyof NumberToId];
|
const id = core.maps.blocksInfo[v as keyof NumberToId];
|
||||||
if (id?.cls === 'autotile') set.add(v);
|
if (id.cls === 'autotile') set.add(v);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -118,8 +118,7 @@ export function fallbackLoad() {
|
|||||||
addAutotile(autotileSet, floor.fg2map);
|
addAutotile(autotileSet, floor.fg2map);
|
||||||
});
|
});
|
||||||
|
|
||||||
materials.buildAssets();
|
materials.cacheTilesetList(tilesetSet.union(autotileSet));
|
||||||
|
|
||||||
materials.cacheAutotileList(autotileSet);
|
materials.buildAssets();
|
||||||
materials.cacheTilesetList(tilesetSet);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,22 +19,16 @@ import {
|
|||||||
BlockCls,
|
BlockCls,
|
||||||
IBigImageReturn,
|
IBigImageReturn,
|
||||||
IAssetBuilder,
|
IAssetBuilder,
|
||||||
IMaterialFramedData,
|
IMaterialAsset,
|
||||||
ITrackedAssetData
|
IMaterialFramedData
|
||||||
} from './types';
|
} from './types';
|
||||||
import { logger } from '@motajs/common';
|
import { logger } from '@motajs/common';
|
||||||
import { getClsByString, getTextureFrame } from './utils';
|
import { getClsByString, getTextureFrame } from './utils';
|
||||||
import { isNil } from 'lodash-es';
|
import { isNil } from 'lodash-es';
|
||||||
import { AssetBuilder } from './builder';
|
import { AssetBuilder } from './builder';
|
||||||
|
import { MaterialAsset } from './asset';
|
||||||
import { AutotileProcessor } from './autotile';
|
import { AutotileProcessor } from './autotile';
|
||||||
|
|
||||||
interface TilesetCache {
|
|
||||||
/** 是否已经在贴图库中存在 */
|
|
||||||
readonly existed: boolean;
|
|
||||||
/** 贴图对象 */
|
|
||||||
readonly texture: ITexture;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class MaterialManager implements IMaterialManager {
|
export class MaterialManager implements IMaterialManager {
|
||||||
readonly tileStore: ITextureStore = new TextureStore();
|
readonly tileStore: ITextureStore = new TextureStore();
|
||||||
readonly tilesetStore: ITextureStore = new TextureStore();
|
readonly tilesetStore: ITextureStore = new TextureStore();
|
||||||
@ -46,18 +40,16 @@ export class MaterialManager implements IMaterialManager {
|
|||||||
readonly autotileSource: Map<number, SizedCanvasImageSource> = new Map();
|
readonly autotileSource: Map<number, SizedCanvasImageSource> = new Map();
|
||||||
|
|
||||||
/** 图集信息存储 */
|
/** 图集信息存储 */
|
||||||
readonly assetDataStore: Map<number, ITextureComposedData> = new Map();
|
readonly assetDataStore: Map<number, IMaterialAsset> = new Map();
|
||||||
/** 贴图到图集索引的映射 */
|
/** 贴图到图集索引的映射 */
|
||||||
readonly assetMap: Map<ITexture, number> = new Map();
|
readonly assetMap: Map<ITexture, number> = new Map();
|
||||||
/** 带有脏标记追踪的图集对象 */
|
|
||||||
readonly trackedAsset: ITrackedAssetData;
|
|
||||||
|
|
||||||
/** 大怪物数据 */
|
/** 大怪物数据 */
|
||||||
readonly bigImageData: Map<number, IMaterialFramedData> = new Map();
|
readonly bigImageData: Map<number, IMaterialFramedData> = new Map();
|
||||||
/** tileset 中 `Math.floor(id / 10000) + 1` 映射到 tileset 对应索引的映射,用于处理图块超出 10000 的 tileset */
|
/** tileset 中 `Math.floor(id / 10000) + 1` 映射到 tileset 对应索引的映射,用于处理图块超出 10000 的 tileset */
|
||||||
readonly tilesetOffsetMap: Map<number, number> = new Map();
|
readonly tilesetOffsetMap: Map<number, number> = new Map();
|
||||||
/** 图集打包器 */
|
/** 图集打包器 */
|
||||||
readonly assetBuilder: IAssetBuilder;
|
readonly assetBuilder: IAssetBuilder = new AssetBuilder();
|
||||||
|
|
||||||
/** 图块 id 到图块数字的映射 */
|
/** 图块 id 到图块数字的映射 */
|
||||||
readonly idNumMap: Map<string, number> = new Map();
|
readonly idNumMap: Map<string, number> = new Map();
|
||||||
@ -81,9 +73,7 @@ export class MaterialManager implements IMaterialManager {
|
|||||||
private built: boolean = false;
|
private built: boolean = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.assetBuilder = new AssetBuilder(this);
|
|
||||||
this.assetBuilder.pipe(this.assetStore);
|
this.assetBuilder.pipe(this.assetStore);
|
||||||
this.trackedAsset = this.assetBuilder.tracked();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -278,9 +268,9 @@ export class MaterialManager implements IMaterialManager {
|
|||||||
return this.imageStore.fromAlias(alias);
|
return this.imageStore.fromAlias(alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getTilesetOwnTexture(identifier: number): TilesetCache | null {
|
private getTilesetOwnTexture(identifier: number) {
|
||||||
const texture = this.tileStore.getTexture(identifier);
|
const texture = this.tileStore.getTexture(identifier);
|
||||||
if (texture) return { existed: true, texture };
|
if (texture) return texture;
|
||||||
// 如果 tileset 不存在,那么执行缓存操作
|
// 如果 tileset 不存在,那么执行缓存操作
|
||||||
const offset = Math.floor(identifier / 10000);
|
const offset = Math.floor(identifier / 10000);
|
||||||
const index = this.tilesetOffsetMap.get(offset - 1);
|
const index = this.tilesetOffsetMap.get(offset - 1);
|
||||||
@ -300,7 +290,7 @@ export class MaterialManager implements IMaterialManager {
|
|||||||
const y = Math.floor(rest / tileWidth);
|
const y = Math.floor(rest / tileWidth);
|
||||||
const newTexture = new Texture(tileset.source);
|
const newTexture = new Texture(tileset.source);
|
||||||
newTexture.clip(x * 32, y * 32, 32, 32);
|
newTexture.clip(x * 32, y * 32, 32, 32);
|
||||||
return { existed: false, texture: newTexture };
|
return newTexture;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -308,13 +298,17 @@ export class MaterialManager implements IMaterialManager {
|
|||||||
* @param data 图集数据
|
* @param data 图集数据
|
||||||
*/
|
*/
|
||||||
private checkAssetDirty(data: ITextureComposedData) {
|
private checkAssetDirty(data: ITextureComposedData) {
|
||||||
if (!this.built) return;
|
|
||||||
const asset = this.assetDataStore.get(data.index);
|
const asset = this.assetDataStore.get(data.index);
|
||||||
if (!asset) {
|
if (asset) {
|
||||||
|
// 如果不是新图集,需要标记为脏
|
||||||
|
asset.dirty();
|
||||||
|
} else {
|
||||||
// 如果有新图集,需要添加
|
// 如果有新图集,需要添加
|
||||||
const alias = `asset-${data.index}`;
|
const alias = `asset-${data.index}`;
|
||||||
|
const newAsset = new MaterialAsset(data);
|
||||||
|
newAsset.dirty();
|
||||||
this.assetStore.alias(data.index, alias);
|
this.assetStore.alias(data.index, alias);
|
||||||
this.assetDataStore.set(data.index, data);
|
this.assetDataStore.set(data.index, newAsset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,16 +335,14 @@ export class MaterialManager implements IMaterialManager {
|
|||||||
cacheTileset(identifier: number): ITexture | null {
|
cacheTileset(identifier: number): ITexture | null {
|
||||||
const newTexture = this.getTilesetOwnTexture(identifier);
|
const newTexture = this.getTilesetOwnTexture(identifier);
|
||||||
if (!newTexture) return null;
|
if (!newTexture) return null;
|
||||||
const { existed, texture } = newTexture;
|
|
||||||
if (existed) return texture;
|
|
||||||
// 缓存贴图
|
// 缓存贴图
|
||||||
this.tileStore.addTexture(identifier, texture);
|
this.tileStore.addTexture(identifier, newTexture);
|
||||||
this.idNumMap.set(`X${identifier}`, identifier);
|
this.idNumMap.set(`X${identifier}`, identifier);
|
||||||
this.numIdMap.set(identifier, `X${identifier}`);
|
this.numIdMap.set(identifier, `X${identifier}`);
|
||||||
const data = this.assetBuilder.addTexture(texture);
|
const data = this.assetBuilder.addTexture(newTexture);
|
||||||
texture.toAsset(data);
|
newTexture.toAsset(data);
|
||||||
this.checkAssetDirty(data);
|
this.checkAssetDirty(data);
|
||||||
return texture;
|
return newTexture;
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheTilesetList(
|
cacheTilesetList(
|
||||||
@ -362,10 +354,8 @@ export class MaterialManager implements IMaterialManager {
|
|||||||
arr.forEach(v => {
|
arr.forEach(v => {
|
||||||
const newTexture = this.getTilesetOwnTexture(v);
|
const newTexture = this.getTilesetOwnTexture(v);
|
||||||
if (!newTexture) return;
|
if (!newTexture) return;
|
||||||
const { existed, texture } = newTexture;
|
toAdd.push(newTexture);
|
||||||
if (existed) return;
|
this.tileStore.addTexture(v, newTexture);
|
||||||
toAdd.push(texture);
|
|
||||||
this.tileStore.addTexture(v, texture);
|
|
||||||
this.idNumMap.set(`X${v}`, v);
|
this.idNumMap.set(`X${v}`, v);
|
||||||
this.numIdMap.set(v, `X${v}`);
|
this.numIdMap.set(v, `X${v}`);
|
||||||
});
|
});
|
||||||
@ -439,7 +429,7 @@ export class MaterialManager implements IMaterialManager {
|
|||||||
arr.forEach(v => {
|
arr.forEach(v => {
|
||||||
const alias = `asset-${v.index}`;
|
const alias = `asset-${v.index}`;
|
||||||
this.assetStore.alias(v.index, alias);
|
this.assetStore.alias(v.index, alias);
|
||||||
this.assetDataStore.set(v.index, v);
|
this.assetDataStore.set(v.index, new MaterialAsset(v));
|
||||||
const data: IMaterialAssetData = {
|
const data: IMaterialAssetData = {
|
||||||
data: v,
|
data: v,
|
||||||
identifier: v.index,
|
identifier: v.index,
|
||||||
@ -454,11 +444,11 @@ export class MaterialManager implements IMaterialManager {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAsset(identifier: number): ITextureComposedData | null {
|
getAsset(identifier: number): IMaterialAsset | null {
|
||||||
return this.assetDataStore.get(identifier) ?? null;
|
return this.assetDataStore.get(identifier) ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAssetByAlias(alias: string): ITextureComposedData | null {
|
getAssetByAlias(alias: string): IMaterialAsset | null {
|
||||||
const id = this.assetStore.identifierOf(alias);
|
const id = this.assetStore.identifierOf(alias);
|
||||||
if (isNil(id)) return null;
|
if (isNil(id)) return null;
|
||||||
return this.assetDataStore.get(id) ?? null;
|
return this.assetDataStore.get(id) ?? null;
|
||||||
|
|||||||
@ -234,7 +234,7 @@ export interface IMaterialGetter {
|
|||||||
* 根据标识符获取图集信息
|
* 根据标识符获取图集信息
|
||||||
* @param identifier 图集的标识符
|
* @param identifier 图集的标识符
|
||||||
*/
|
*/
|
||||||
getAsset(identifier: number): ITextureComposedData | null;
|
getAsset(identifier: number): IMaterialAsset | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据额外素材索引获取额外素材
|
* 根据额外素材索引获取额外素材
|
||||||
@ -272,7 +272,7 @@ export interface IMaterialAliasGetter {
|
|||||||
* 根据别名获取图集信息
|
* 根据别名获取图集信息
|
||||||
* @param alias 图集的别名
|
* @param alias 图集的别名
|
||||||
*/
|
*/
|
||||||
getAssetByAlias(alias: string): ITextureComposedData | null;
|
getAssetByAlias(alias: string): IMaterialAsset | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据图块别名获取图块类型
|
* 根据图块别名获取图块类型
|
||||||
@ -302,9 +302,7 @@ export interface IMaterialManager
|
|||||||
readonly bigImageStore: ITextureStore;
|
readonly bigImageStore: ITextureStore;
|
||||||
|
|
||||||
/** 图集信息存储 */
|
/** 图集信息存储 */
|
||||||
readonly assetDataStore: Iterable<[number, ITextureComposedData]>;
|
readonly assetDataStore: Iterable<[number, IMaterialAsset]>;
|
||||||
/** 带有脏标记追踪的图集信息 */
|
|
||||||
readonly trackedAsset: ITrackedAssetData;
|
|
||||||
|
|
||||||
/** 图块类型映射 */
|
/** 图块类型映射 */
|
||||||
readonly clsMap: Map<number, BlockCls>;
|
readonly clsMap: Map<number, BlockCls>;
|
||||||
@ -467,30 +465,8 @@ export interface IAssetBuilder {
|
|||||||
*/
|
*/
|
||||||
addTextureList(texture: Iterable<ITexture>): Iterable<ITextureComposedData>;
|
addTextureList(texture: Iterable<ITexture>): Iterable<ITextureComposedData>;
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取可追踪贴图对象
|
|
||||||
*/
|
|
||||||
tracked(): ITrackedAssetData;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 结束此打包器
|
* 结束此打包器
|
||||||
*/
|
*/
|
||||||
close(): void;
|
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;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import { Animate } from './animate';
|
|||||||
import { createItemDetail } from './itemDetail';
|
import { createItemDetail } from './itemDetail';
|
||||||
import { logger } from '@motajs/common';
|
import { logger } from '@motajs/common';
|
||||||
import { MapRender } from '../map/element';
|
import { MapRender } from '../map/element';
|
||||||
import { state } from '@user/data-state';
|
|
||||||
|
|
||||||
export function createElements() {
|
export function createElements() {
|
||||||
createCache();
|
createCache();
|
||||||
@ -72,14 +71,14 @@ export function createElements() {
|
|||||||
tagMap.register('map-render', (_0, _1, props) => {
|
tagMap.register('map-render', (_0, _1, props) => {
|
||||||
if (!props) {
|
if (!props) {
|
||||||
logger.error(42);
|
logger.error(42);
|
||||||
return new MapRender(state.layer);
|
return new MapRender([]);
|
||||||
}
|
}
|
||||||
const { layerState } = props;
|
const { layerList } = props;
|
||||||
if (!layerState) {
|
if (!layerList) {
|
||||||
logger.error(42);
|
logger.error(42);
|
||||||
return new MapRender(state.layer);
|
return new MapRender([]);
|
||||||
}
|
}
|
||||||
return new MapRender(layerState);
|
return new MapRender(layerList);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { BaseProps, TagDefine } from '@motajs/render-vue';
|
import { BaseProps, TagDefine } from '@motajs/render-vue';
|
||||||
import { ERenderItemEvent, Transform } from '@motajs/render-core';
|
import { Transform } from '@motajs/render-core';
|
||||||
import { CanvasStyle } from '@motajs/render-assets';
|
import { CanvasStyle } from '@motajs/render-assets';
|
||||||
import {
|
import {
|
||||||
ILayerGroupRenderExtends,
|
ILayerGroupRenderExtends,
|
||||||
@ -11,7 +11,7 @@ import {
|
|||||||
import { EAnimateEvent } from './animate';
|
import { EAnimateEvent } from './animate';
|
||||||
import { EIconEvent, EWinskinEvent } from './misc';
|
import { EIconEvent, EWinskinEvent } from './misc';
|
||||||
import { IEnemyCollection } from '@motajs/types';
|
import { IEnemyCollection } from '@motajs/types';
|
||||||
import { ILayerState } from '@user/data-state';
|
import { IRenderLayerData } from '../map/element';
|
||||||
|
|
||||||
export interface AnimateProps extends BaseProps {}
|
export interface AnimateProps extends BaseProps {}
|
||||||
|
|
||||||
@ -62,7 +62,7 @@ export interface LayerProps extends BaseProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface MapRenderProps extends BaseProps {
|
export interface MapRenderProps extends BaseProps {
|
||||||
layerState: ILayerState;
|
layerList: IRenderLayerData;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module 'vue/jsx-runtime' {
|
declare module 'vue/jsx-runtime' {
|
||||||
@ -73,7 +73,6 @@ declare module 'vue/jsx-runtime' {
|
|||||||
animation: TagDefine<AnimateProps, EAnimateEvent>;
|
animation: TagDefine<AnimateProps, EAnimateEvent>;
|
||||||
icon: TagDefine<IconProps, EIconEvent>;
|
icon: TagDefine<IconProps, EIconEvent>;
|
||||||
winskin: TagDefine<WinskinProps, EWinskinEvent>;
|
winskin: TagDefine<WinskinProps, EWinskinEvent>;
|
||||||
'map-render': TagDefine<MapRenderProps, ERenderItemEvent>;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
31
packages-user/client-modules/src/render/map/asset.ts
Normal file
31
packages-user/client-modules/src/render/map/asset.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,8 +12,8 @@ export class BlockSplitter<T> implements IBlockSplitter<T> {
|
|||||||
blockHeight: number = 0;
|
blockHeight: number = 0;
|
||||||
dataWidth: number = 0;
|
dataWidth: number = 0;
|
||||||
dataHeight: number = 0;
|
dataHeight: number = 0;
|
||||||
width: number = 1;
|
width: number = 0;
|
||||||
height: number = 1;
|
height: number = 0;
|
||||||
|
|
||||||
/** 分块映射 */
|
/** 分块映射 */
|
||||||
readonly blockMap: Map<number, IBlockData<T>> = new Map();
|
readonly blockMap: Map<number, IBlockData<T>> = new Map();
|
||||||
@ -33,7 +33,7 @@ export class BlockSplitter<T> implements IBlockSplitter<T> {
|
|||||||
* @param y 分块纵坐标
|
* @param y 分块纵坐标
|
||||||
*/
|
*/
|
||||||
private checkLocRange(x: number, y: number) {
|
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 {
|
getBlockByLoc(x: number, y: number): IBlockData<T> | null {
|
||||||
@ -221,10 +221,10 @@ export class BlockSplitter<T> implements IBlockSplitter<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
configSplitter(config: IBlockSplitterConfig): void {
|
configSplitter(config: IBlockSplitterConfig): void {
|
||||||
this.splitDataWidth = config.dataWidth;
|
|
||||||
this.splitDataHeight = config.dataHeight;
|
|
||||||
this.splitBlockWidth = config.blockWidth;
|
this.splitBlockWidth = config.blockWidth;
|
||||||
this.splitBlockHeight = config.blockHeight;
|
this.splitBlockHeight = config.blockHeight;
|
||||||
|
this.splitDataWidth = config.dataWidth;
|
||||||
|
this.splitBlockHeight = config.dataHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
private mapBlock(
|
private mapBlock(
|
||||||
@ -252,10 +252,6 @@ export class BlockSplitter<T> implements IBlockSplitter<T> {
|
|||||||
|
|
||||||
splitBlocks(mapFn: (block: IBlockInfo) => T): void {
|
splitBlocks(mapFn: (block: IBlockInfo) => T): void {
|
||||||
this.blockMap.clear();
|
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 restX = this.splitDataWidth % this.splitBlockWidth;
|
||||||
const restY = this.splitDataHeight % this.splitBlockHeight;
|
const restY = this.splitDataHeight % this.splitBlockHeight;
|
||||||
const width = Math.floor(this.splitDataWidth / this.splitBlockWidth);
|
const width = Math.floor(this.splitDataWidth / this.splitBlockWidth);
|
||||||
|
|||||||
@ -1,2 +0,0 @@
|
|||||||
/** 单个图块的实例化数据数量 */
|
|
||||||
export const INSTANCED_COUNT = 4 + 4 + 4 + 4;
|
|
||||||
@ -3,13 +3,20 @@ import {
|
|||||||
RenderItem,
|
RenderItem,
|
||||||
Transform
|
Transform
|
||||||
} from '@motajs/render-core';
|
} from '@motajs/render-core';
|
||||||
import { ILayerState, state } from '@user/data-state';
|
import { IMapLayer } from '@user/data-state';
|
||||||
import { IMapRenderer, IMapRendererHooks } from './types';
|
import { IMapRenderer } from './types';
|
||||||
import { MapRenderer } from './renderer';
|
import { MapRenderer } from './renderer';
|
||||||
import { materials } from '@user/client-base';
|
import { materials } from '@user/client-base';
|
||||||
import { ElementNamespace, ComponentInternalInstance } from 'vue';
|
import { ElementNamespace, ComponentInternalInstance } from 'vue';
|
||||||
import { CELL_HEIGHT, CELL_WIDTH, MAP_HEIGHT, MAP_WIDTH } from '../shared';
|
|
||||||
import { IHookController } from '@motajs/common';
|
export interface IRenderLayerData {
|
||||||
|
/** 图层对象 */
|
||||||
|
readonly layer: IMapLayer;
|
||||||
|
/** 图层纵深 */
|
||||||
|
readonly zIndex: number;
|
||||||
|
/** 图层别名 */
|
||||||
|
readonly alias?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class MapRender extends RenderItem {
|
export class MapRender extends RenderItem {
|
||||||
/** 地图渲染器 */
|
/** 地图渲染器 */
|
||||||
@ -22,31 +29,32 @@ export class MapRender extends RenderItem {
|
|||||||
/** 画布上下文 */
|
/** 画布上下文 */
|
||||||
readonly gl: WebGL2RenderingContext;
|
readonly gl: WebGL2RenderingContext;
|
||||||
|
|
||||||
private rendererHook: IHookController<IMapRendererHooks>;
|
constructor(layerList: Iterable<IRenderLayerData>) {
|
||||||
|
|
||||||
constructor(readonly layerState: ILayerState) {
|
|
||||||
super('static');
|
super('static');
|
||||||
|
|
||||||
this.canvas = document.createElement('canvas');
|
this.canvas = document.createElement('canvas');
|
||||||
const gl = this.canvas.getContext('webgl2')!;
|
const gl = this.canvas.getContext('webgl2')!;
|
||||||
this.gl = gl;
|
this.gl = gl;
|
||||||
|
|
||||||
this.renderer = new MapRenderer(
|
this.renderer = new MapRenderer(materials, this.gl, this.camera);
|
||||||
materials,
|
for (const layer of layerList) {
|
||||||
this.gl,
|
this.renderer.addLayer(layer.layer, layer.alias);
|
||||||
this.camera,
|
this.renderer.setZIndex(layer.layer, layer.zIndex);
|
||||||
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);
|
|
||||||
|
|
||||||
this.delegateTicker(time => this.renderer.tick(time));
|
/**
|
||||||
|
* 更新图层列表
|
||||||
gl.viewport(0, 0, this.canvas.width, this.canvas.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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sizeGL(width: number, height: number) {
|
private sizeGL(width: number, height: number) {
|
||||||
@ -78,7 +86,13 @@ export class MapRender extends RenderItem {
|
|||||||
console.time('map-element-render');
|
console.time('map-element-render');
|
||||||
this.renderer.render(this.gl);
|
this.renderer.render(this.gl);
|
||||||
|
|
||||||
canvas.ctx.drawImage(this.canvas, 0, 0, canvas.width, canvas.height);
|
canvas.ctx.drawImage(
|
||||||
|
this.canvas,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
this.canvas.width,
|
||||||
|
this.canvas.height
|
||||||
|
);
|
||||||
console.timeEnd('map-element-render');
|
console.timeEnd('map-element-render');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,19 +104,11 @@ export class MapRender extends RenderItem {
|
|||||||
parentComponent?: ComponentInternalInstance | null
|
parentComponent?: ComponentInternalInstance | null
|
||||||
): void {
|
): void {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'layerState': {
|
case 'layerList': {
|
||||||
this.renderer.setLayerState(nextValue);
|
this.updateLayerList(nextValue);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
super.patchProp(key, prevValue, nextValue, namespace, parentComponent);
|
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
@ -1,6 +1,5 @@
|
|||||||
#version 300 es
|
#version 300 es
|
||||||
precision highp float;
|
precision highp float;
|
||||||
precision highp sampler2DArray;
|
|
||||||
|
|
||||||
in vec3 v_texCoord;
|
in vec3 v_texCoord;
|
||||||
out vec4 outColor;
|
out vec4 outColor;
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
#version 300 es
|
#version 300 es
|
||||||
precision highp float;
|
precision highp float;
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
in vec2 a_position;
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
in vec2 a_texCoord;
|
||||||
|
|
||||||
out vec3 v_texCoord;
|
out vec3 v_texCoord;
|
||||||
|
|
||||||
@ -10,7 +10,9 @@ uniform float u_nowFrame;
|
|||||||
uniform mat3 u_transform;
|
uniform mat3 u_transform;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec3 transformed = u_transform * vec3(a_position, 1.0);
|
// 背景图永远是全图都画,因此变换矩阵应该作用于纹理坐标
|
||||||
v_texCoord = vec3(a_texCoord, u_nowFrame);
|
vec3 texCoord = vec3(a_texCoord, 1.0);
|
||||||
gl_Position = vec4(transformed.xy, 0.95, 1.0);
|
vec3 transformed = u_transform * texCoord;
|
||||||
|
v_texCoord = vec3(transformed.xy, u_nowFrame);
|
||||||
|
gl_Position = vec4(a_position, 0.0, 1.0);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
#version 300 es
|
#version 300 es
|
||||||
precision highp float;
|
precision highp float;
|
||||||
precision highp sampler2DArray;
|
precision mediump uint;
|
||||||
|
|
||||||
in vec4 v_texCoord;
|
in vec4 v_texCoord;
|
||||||
|
|
||||||
@ -11,5 +11,4 @@ uniform sampler2DArray u_sampler;
|
|||||||
void main() {
|
void main() {
|
||||||
vec4 texColor = texture(u_sampler, v_texCoord.xyz);
|
vec4 texColor = texture(u_sampler, v_texCoord.xyz);
|
||||||
outColor = vec4(texColor.rgb, texColor.a * v_texCoord.a);
|
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);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,36 +1,26 @@
|
|||||||
#version 300 es
|
#version 300 es
|
||||||
precision highp float;
|
precision highp float;
|
||||||
precision mediump int;
|
precision mediump uint;
|
||||||
|
|
||||||
// 顶点坐标
|
in vec3 a_position;
|
||||||
layout(location = 0) in vec4 a_position;
|
in vec3 a_texCoord;
|
||||||
// 实例化数据
|
// x: 最大帧数,y: 偏移池索引,实例化绘制传入
|
||||||
// 图块坐标
|
in ivec2 a_offsetData;
|
||||||
layout(location = 1) in vec4 a_tilePos;
|
// 不透明度,用于前景层虚化
|
||||||
// 贴图坐标
|
in float a_alpha;
|
||||||
layout(location = 2) in vec4 a_texCoord;
|
|
||||||
// x: 纵深,y: 不透明度
|
|
||||||
layout(location = 3) in vec4 a_tileData;
|
|
||||||
// x: 当前帧数,负数表示使用 u_nowFrame,y: 最大帧数,z: 偏移池索引,w: 纹理数组索引
|
|
||||||
layout(location = 4) in vec4 a_texData;
|
|
||||||
|
|
||||||
// x,y,z: 纹理坐标,w: 不透明度
|
// x,y,z: 纹理坐标,w: 不透明度
|
||||||
out vec4 v_texCoord;
|
out vec4 v_texCoord;
|
||||||
|
|
||||||
uniform float u_offsetPool[$1];
|
uniform vec2 u_offsetPool[$1];
|
||||||
uniform float u_nowFrame;
|
uniform uint u_nowFrame;
|
||||||
uniform mat3 u_transform;
|
uniform mat3 u_transform;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
// 坐标
|
|
||||||
vec2 pos = a_position.xy * a_tilePos.zw + a_tilePos.xy;
|
|
||||||
vec2 texCoord = a_position.zw * a_texCoord.zw + a_texCoord.xy;
|
|
||||||
// 偏移量
|
// 偏移量
|
||||||
float offset = a_texData.x < 0.0 ? mod(u_nowFrame, a_texData.y) : a_texData.x;
|
uint offset = mod(u_nowFrame, a_offsetData.x);
|
||||||
int offsetIndex = int(a_texData.z);
|
float fOffset = float(offset);
|
||||||
// 贴图偏移
|
// 贴图坐标
|
||||||
texCoord.x += u_offsetPool[offsetIndex] * offset;
|
v_texCoord = vec4(a_texCoord.xy + u_offsetPool[a_offsetData.y] * fOffset, a_texCoord.z, a_alpha);
|
||||||
v_texCoord = vec4(texCoord.xy, a_texData.w, a_tileData.y);
|
gl_Position = vec4(u_transform * a_position, 1.0);
|
||||||
vec3 transformed = u_transform * vec3(pos.xy, 1.0);
|
|
||||||
gl_Position = vec4(transformed.xy, a_tileData.x, 1.0);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +1,16 @@
|
|||||||
|
import { IDirtyTracker } from '@motajs/common';
|
||||||
import {
|
import {
|
||||||
IDirtyMark,
|
ITextureRenderable,
|
||||||
IDirtyTracker,
|
SizedCanvasImageSource
|
||||||
IHookable,
|
} from '@motajs/render-assets';
|
||||||
IHookBase,
|
|
||||||
IHookController
|
|
||||||
} from '@motajs/common';
|
|
||||||
import { ITextureRenderable } from '@motajs/render-assets';
|
|
||||||
import { Transform } from '@motajs/render-core';
|
import { Transform } from '@motajs/render-core';
|
||||||
import {
|
import {
|
||||||
IAutotileProcessor,
|
IAutotileProcessor,
|
||||||
IMaterialFramedData,
|
IMaterialFramedData,
|
||||||
IMaterialManager,
|
IMaterialGetter,
|
||||||
ITrackedAssetData
|
IMaterialManager
|
||||||
} from '@user/client-base';
|
} from '@user/client-base';
|
||||||
import { ILayerState, IMapLayer } from '@user/data-state';
|
import { IMapLayer } from '@user/data-state';
|
||||||
import { TimingFn } from 'mutate-animate';
|
import { TimingFn } from 'mutate-animate';
|
||||||
|
|
||||||
export const enum MapBackgroundRepeat {
|
export const enum MapBackgroundRepeat {
|
||||||
@ -48,6 +45,18 @@ export const enum MapTileAlign {
|
|||||||
End
|
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 {
|
export interface IMapBackgroundConfig {
|
||||||
/** 是否使用图片大小作为背景图渲染大小,如果是 `false`,则使用 `renderWidth` `renderHeight` 作为渲染大小 */
|
/** 是否使用图片大小作为背景图渲染大小,如果是 `false`,则使用 `renderWidth` `renderHeight` 作为渲染大小 */
|
||||||
readonly useImageSize: boolean;
|
readonly useImageSize: boolean;
|
||||||
@ -78,6 +87,16 @@ export interface IMapRenderConfig {
|
|||||||
readonly frameSpeed: number;
|
readonly frameSpeed: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IMapAssetManager {
|
||||||
|
/** 素材管理对象 */
|
||||||
|
readonly materials: IMaterialManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成地图渲染图集数据
|
||||||
|
*/
|
||||||
|
generateAsset(): IMapAssetData;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IContextData {
|
export interface IContextData {
|
||||||
/** 图块程序 */
|
/** 图块程序 */
|
||||||
readonly tileProgram: WebGLProgram;
|
readonly tileProgram: WebGLProgram;
|
||||||
@ -107,22 +126,22 @@ export interface IContextData {
|
|||||||
readonly backNowFrameLocation: WebGLUniformLocation;
|
readonly backNowFrameLocation: WebGLUniformLocation;
|
||||||
/** 顶点数组输入 */
|
/** 顶点数组输入 */
|
||||||
readonly vertexAttribLocation: number;
|
readonly vertexAttribLocation: number;
|
||||||
/** 图块坐标输入 */
|
/** 纹理坐标输入输入 */
|
||||||
readonly insTilePosAttribLocation: number;
|
readonly texCoordAttribLocation: number;
|
||||||
/** 图块纹理坐标输入 */
|
/** 偏移数组输入 */
|
||||||
readonly insTexCoordAttribLocation: number;
|
readonly offsetAttribLocation: number;
|
||||||
/** 图块数据输入 */
|
/** 不透明度数组输入 */
|
||||||
readonly insTileDataAttribLocation: number;
|
readonly alphaAttribLocation: number;
|
||||||
/** 图块当前帧数输入 */
|
|
||||||
readonly insTexDataAttribLocation: number;
|
|
||||||
/** 背景顶点数组输入 */
|
/** 背景顶点数组输入 */
|
||||||
readonly backVertexAttribLocation: number;
|
readonly backVertexAttribLocation: number;
|
||||||
/** 背景纹理数组输入 */
|
/** 背景纹理数组输入 */
|
||||||
readonly backTexCoordAttribLocation: number;
|
readonly backTexCoordAttribLocation: number;
|
||||||
/** 顶点数组 */
|
/** 顶点数组 */
|
||||||
readonly vertexBuffer: WebGLBuffer;
|
readonly vertexBuffer: WebGLBuffer;
|
||||||
/** 实例化数据数组 */
|
/** 偏移数组 */
|
||||||
readonly instancedBuffer: WebGLBuffer;
|
readonly offsetBuffer: WebGLBuffer;
|
||||||
|
/** 不透明度数组 */
|
||||||
|
readonly alphaBuffer: WebGLBuffer;
|
||||||
/** 背景顶点数组 */
|
/** 背景顶点数组 */
|
||||||
readonly backgroundVertexBuffer: WebGLBuffer;
|
readonly backgroundVertexBuffer: WebGLBuffer;
|
||||||
/** 图块纹理对象 */
|
/** 图块纹理对象 */
|
||||||
@ -148,9 +167,9 @@ export interface IContextData {
|
|||||||
backgroundDepth: number;
|
backgroundDepth: number;
|
||||||
|
|
||||||
/** 图块纹理的脏标记 */
|
/** 图块纹理的脏标记 */
|
||||||
tileTextureMark: IDirtyMark;
|
tileTextureMark: symbol;
|
||||||
/** 顶点数组的脏标记 */
|
/** 顶点数组的脏标记 */
|
||||||
vertexMark: IDirtyMark;
|
vertexMark: symbol;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMovingBlock {
|
export interface IMovingBlock {
|
||||||
@ -227,14 +246,7 @@ export interface IMovingBlock {
|
|||||||
destroy(): void;
|
destroy(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMapRendererHooks extends IHookBase {
|
export interface IMapRenderer {
|
||||||
/**
|
|
||||||
* 当需要更新画面时执行
|
|
||||||
*/
|
|
||||||
onUpdate(controller: IHookController<this>): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IMapRenderer extends IHookable<IMapRendererHooks> {
|
|
||||||
/** 地图渲染器使用的资源管理器 */
|
/** 地图渲染器使用的资源管理器 */
|
||||||
readonly manager: IMaterialManager;
|
readonly manager: IMaterialManager;
|
||||||
/** 画布渲染上下文 */
|
/** 画布渲染上下文 */
|
||||||
@ -248,8 +260,6 @@ export interface IMapRenderer extends IHookable<IMapRendererHooks> {
|
|||||||
readonly viewport: IMapViewportController;
|
readonly viewport: IMapViewportController;
|
||||||
/** 顶点数组生成器 */
|
/** 顶点数组生成器 */
|
||||||
readonly vertex: IMapVertexGenerator;
|
readonly vertex: IMapVertexGenerator;
|
||||||
/** 使用的地图状态对象 */
|
|
||||||
readonly layerState: ILayerState;
|
|
||||||
|
|
||||||
/** 地图宽度 */
|
/** 地图宽度 */
|
||||||
readonly mapWidth: number;
|
readonly mapWidth: number;
|
||||||
@ -265,16 +275,12 @@ export interface IMapRenderer extends IHookable<IMapRendererHooks> {
|
|||||||
readonly cellWidth: number;
|
readonly cellWidth: number;
|
||||||
/** 每个格子的高度 */
|
/** 每个格子的高度 */
|
||||||
readonly cellHeight: number;
|
readonly cellHeight: number;
|
||||||
/** 图集宽度 */
|
|
||||||
readonly assetWidth: number;
|
|
||||||
/** 图集高度 */
|
|
||||||
readonly assetHeight: number;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 使用指定图集对象
|
* 使用指定图集对象
|
||||||
* @param asset 要使用的缓存对象
|
* @param asset 要使用的缓存对象
|
||||||
*/
|
*/
|
||||||
useAsset(asset: ITrackedAssetData): void;
|
useAsset(asset: IMapAssetData): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 摧毁此地图渲染器,表示当前渲染器不会再被使用到
|
* 摧毁此地图渲染器,表示当前渲染器不会再被使用到
|
||||||
@ -288,10 +294,17 @@ export interface IMapRenderer extends IHookable<IMapRendererHooks> {
|
|||||||
render(gl: WebGL2RenderingContext): void;
|
render(gl: WebGL2RenderingContext): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置渲染器使用的地图状态
|
* 添加地图图层
|
||||||
* @param layerState 地图状态
|
* @param layer 地图图层
|
||||||
|
* @param identifier 图层的标识符,可以用于 {@link getLayer} 获取图层
|
||||||
*/
|
*/
|
||||||
setLayerState(layerState: ILayerState): void;
|
addLayer(layer: IMapLayer, identifier?: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除指定图层
|
||||||
|
* @param layer 要移除的图层
|
||||||
|
*/
|
||||||
|
removeLayer(layer: IMapLayer): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据标识符获取图层
|
* 根据标识符获取图层
|
||||||
@ -310,6 +323,19 @@ export interface IMapRenderer extends IHookable<IMapRendererHooks> {
|
|||||||
*/
|
*/
|
||||||
getSortedLayer(): IMapLayer[];
|
getSortedLayer(): IMapLayer[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置当前渲染器中指定图层的纵深
|
||||||
|
* @param layer 要设置的图层
|
||||||
|
* @param zIndex 目标纵深
|
||||||
|
*/
|
||||||
|
setZIndex(layer: IMapLayer, zIndex: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定图层的纵深
|
||||||
|
* @param layer 要获取的图层
|
||||||
|
*/
|
||||||
|
getZIndex(layer: IMapLayer): number | undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取指定图层排序后的索引位置
|
* 获取指定图层排序后的索引位置
|
||||||
* @param layer 要获取的图层
|
* @param layer 要获取的图层
|
||||||
@ -353,9 +379,9 @@ export interface IMapRenderer extends IHookable<IMapRendererHooks> {
|
|||||||
configRendering(config: Partial<IMapRenderConfig>): void;
|
configRendering(config: Partial<IMapRenderConfig>): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置渲染的宽高,单位像素
|
* 设置渲染的宽高,单位格子。例如填 13 就表示渲染宽度或高度是 13 个格子。
|
||||||
* @param width 渲染的像素宽度
|
* @param width 渲染的格子宽度
|
||||||
* @param height 渲染的像素高度
|
* @param height 渲染的格子高度
|
||||||
*/
|
*/
|
||||||
setRenderSize(width: number, height: number): void;
|
setRenderSize(width: number, height: number): void;
|
||||||
|
|
||||||
@ -376,6 +402,11 @@ export interface IMapRenderer extends IHookable<IMapRendererHooks> {
|
|||||||
*/
|
*/
|
||||||
setCellSize(width: number, height: number): void;
|
setCellSize(width: number, height: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取变换矩阵
|
||||||
|
*/
|
||||||
|
getTransform(): Transform;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加一个移动图块
|
* 添加一个移动图块
|
||||||
* @param layer 图块所属的图层
|
* @param layer 图块所属的图层
|
||||||
@ -428,25 +459,28 @@ export interface IMapRenderer extends IHookable<IMapRendererHooks> {
|
|||||||
setTileAlpha(layer: IMapLayer, alpha: number, x: number, y: number): void;
|
setTileAlpha(layer: IMapLayer, alpha: number, x: number, y: number): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 进行一帧更新
|
* 结束此渲染器的使用,释放所有相关资源
|
||||||
* @param timestamp 时间戳
|
|
||||||
*/
|
*/
|
||||||
tick(timestamp: number): void;
|
close(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMapVertexArray {
|
export interface IMapVertexArray {
|
||||||
/**
|
/**
|
||||||
* 地图渲染实例化数组,结构是一个三维张量 `[B, L, T]`,其中 `B` 代表分块,`L` 代表图层,`T` 代表图块,并按照结构顺序平铺存储。
|
* 地图渲染顶点数组,结构是一个三维张量 `[B, L, T]`,其中 `B` 代表分块,`L` 代表图层,`T` 代表图块,并按照结构顺序平铺存储。
|
||||||
|
* 每个顶点包含两个数据,顶点的 `x,y,z` 坐标,顶点的贴图坐标 `x,y,z`。
|
||||||
*
|
*
|
||||||
* 语义解释就是,最内层存储图块,再外面一层存储图层,最外层存储分块。这样的话可以一次性将一个分块的所有图层渲染完毕。
|
* 语义解释就是,最内层存储图块,再外面一层存储图层,最外层存储分块。这样的话可以一次性将一个分块的所有图层渲染完毕。
|
||||||
*
|
|
||||||
* 依次存储 a_tilePos, a_texCoord, a_tileData, a_texData
|
|
||||||
*/
|
*/
|
||||||
readonly tileInstanced: Float32Array;
|
readonly tileVertex: Float32Array;
|
||||||
|
|
||||||
/** 动态内容的起始索引,以实例为单位 */
|
/** 每个图块的偏移数据,使用实例化绘制,第一项表示这个图块的总帧数,第二项表示每帧的偏移量 */
|
||||||
|
readonly tileOffset: Int16Array;
|
||||||
|
/** 每个图块的不透明度,用于实现前景层虚化效果 */
|
||||||
|
readonly tileAlpha: Float32Array;
|
||||||
|
|
||||||
|
/** 动态内容顶点起始索引 */
|
||||||
readonly dynamicStart: number;
|
readonly dynamicStart: number;
|
||||||
/** 动态内容的数量,以实例为单位 */
|
/** 动态内容顶点数量 */
|
||||||
readonly dynamicCount: number;
|
readonly dynamicCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -683,13 +717,21 @@ export interface IBlockSplitter<T> extends IBlockSplitterConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IMapVertexData {
|
export interface IMapVertexData {
|
||||||
/** 这个分块的实例化数据数组 */
|
/** 这个分块的顶点数组 */
|
||||||
readonly instancedArray: Float32Array;
|
readonly vertexArray: Float32Array;
|
||||||
|
/** 这个分块的偏移数组 */
|
||||||
|
readonly offsetArray: Int16Array;
|
||||||
|
/** 这个分块的不透明度数组 */
|
||||||
|
readonly alphaArray: Float32Array;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IIndexedMapVertexData extends IMapVertexData {
|
export interface IIndexedMapVertexData extends IMapVertexData {
|
||||||
/** 这个分块的实例化数据的起始索引 */
|
/** 这个分块的顶点数组的起始索引 */
|
||||||
readonly instancedStart: number;
|
readonly vertexStart: number;
|
||||||
|
/** 这个分块的偏移数组的起始索引 */
|
||||||
|
readonly offsetStart: number;
|
||||||
|
/** 这个分块的不透明度数组的起始索引 */
|
||||||
|
readonly alphaStart: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ILayerDirtyData {
|
export interface ILayerDirtyData {
|
||||||
@ -722,11 +764,6 @@ export interface IMapVertexBlock extends IMapVertexData {
|
|||||||
*/
|
*/
|
||||||
render(): void;
|
render(): void;
|
||||||
|
|
||||||
/**
|
|
||||||
* 取消数据脏标记
|
|
||||||
*/
|
|
||||||
updated(): void;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 标记指定区域为脏,需要更新
|
* 标记指定区域为脏,需要更新
|
||||||
* @param layer 图层对象
|
* @param layer 图层对象
|
||||||
@ -750,10 +787,22 @@ export interface IMapVertexBlock extends IMapVertexData {
|
|||||||
getDirtyArea(layer: IMapLayer): Readonly<ILayerDirtyData> | null;
|
getDirtyArea(layer: IMapLayer): Readonly<ILayerDirtyData> | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取指定图层的实例化数据数组,是对内部存储的直接引用
|
* 获取指定图层的顶点数组,是对内部存储的直接引用
|
||||||
* @param layer 图层对象
|
* @param layer 图层对象
|
||||||
*/
|
*/
|
||||||
getLayerInstanced(layer: IMapLayer): Float32Array | null;
|
getLayerVertex(layer: IMapLayer): Float32Array | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定图层的偏移数组,是对内部存储的直接引用
|
||||||
|
* @param layer 图层对象
|
||||||
|
*/
|
||||||
|
getLayerOffset(layer: IMapLayer): Int16Array | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定图层的不透明度数组,是对内部存储的直接引用
|
||||||
|
* @param layer 图层对象
|
||||||
|
*/
|
||||||
|
getLayerAlpha(layer: IMapLayer): Float32Array | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取指定图层的所有顶点数组数据,是对内部存储的直接引用
|
* 获取指定图层的所有顶点数组数据,是对内部存储的直接引用
|
||||||
@ -771,9 +820,6 @@ export interface IMapBlockUpdateObject {
|
|||||||
readonly y: number;
|
readonly y: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 脏标记表示顶点数组的长度是否发生变化
|
|
||||||
*/
|
|
||||||
export interface IMapVertexGenerator extends IDirtyTracker<boolean> {
|
export interface IMapVertexGenerator extends IDirtyTracker<boolean> {
|
||||||
/** 地图渲染器 */
|
/** 地图渲染器 */
|
||||||
readonly renderer: IMapRenderer;
|
readonly renderer: IMapRenderer;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { IMapLayer } from '@user/data-state';
|
import { IMapLayer, IMapLayerData } from '@user/data-state';
|
||||||
import {
|
import {
|
||||||
IBlockData,
|
IBlockData,
|
||||||
IBlockSplitter,
|
IBlockSplitter,
|
||||||
@ -22,7 +22,8 @@ import { BlockSplitter } from './block';
|
|||||||
import { clamp, isNil } from 'lodash-es';
|
import { clamp, isNil } from 'lodash-es';
|
||||||
import { BlockCls, IMaterialFramedData } from '@user/client-base';
|
import { BlockCls, IMaterialFramedData } from '@user/client-base';
|
||||||
import { IRect, SizedCanvasImageSource } from '@motajs/render-assets';
|
import { IRect, SizedCanvasImageSource } from '@motajs/render-assets';
|
||||||
import { INSTANCED_COUNT } from './constant';
|
|
||||||
|
// todo: 潜在优化点:顶点数组的 z 坐标以及纹理的 z 坐标可以换为实例化绘制
|
||||||
|
|
||||||
export interface IMapDataGetter {
|
export interface IMapDataGetter {
|
||||||
/** 图块缩小行为,即图块比格子大时应该如何处理 */
|
/** 图块缩小行为,即图块比格子大时应该如何处理 */
|
||||||
@ -36,6 +37,12 @@ export interface IMapDataGetter {
|
|||||||
/** 图块大小与格子大小判断方式 */
|
/** 图块大小与格子大小判断方式 */
|
||||||
readonly tileTestMode: MapTileSizeTestMode;
|
readonly tileTestMode: MapTileSizeTestMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定图层的图块信息,是对内部存储的直接引用
|
||||||
|
* @param layer 地图图层
|
||||||
|
*/
|
||||||
|
getMapLayerData(layer: IMapLayer): Readonly<IMapLayerData> | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据图集的图像源获取其索引
|
* 根据图集的图像源获取其索引
|
||||||
* @param source 图像源
|
* @param source 图像源
|
||||||
@ -72,9 +79,20 @@ interface BlockIndex extends IndexedBlockMapPos {
|
|||||||
readonly mapIndex: number;
|
readonly mapIndex: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface TilePosition {
|
||||||
|
/** 左边缘位置 */
|
||||||
|
readonly left: number;
|
||||||
|
/** 上边缘位置 */
|
||||||
|
readonly top: number;
|
||||||
|
/** 右边缘位置 */
|
||||||
|
readonly right: number;
|
||||||
|
/** 下边缘位置 */
|
||||||
|
readonly bottom: number;
|
||||||
|
}
|
||||||
|
|
||||||
const enum VertexUpdate {
|
const enum VertexUpdate {
|
||||||
/** 更新顶点位置信息 */
|
/** 更新顶点信息 */
|
||||||
Position = 0b01,
|
Vertex = 0b01,
|
||||||
/** 更新贴图信息 */
|
/** 更新贴图信息 */
|
||||||
Texture = 0b10,
|
Texture = 0b10,
|
||||||
/** 全部更新 */
|
/** 全部更新 */
|
||||||
@ -94,19 +112,29 @@ export class MapVertexGenerator
|
|||||||
|
|
||||||
/** 空顶点数组,因为空顶点很常用,所以直接定义一个全局常量 */
|
/** 空顶点数组,因为空顶点很常用,所以直接定义一个全局常量 */
|
||||||
private static readonly EMPTY_VETREX: Float32Array = new Float32Array(
|
private static readonly EMPTY_VETREX: Float32Array = new Float32Array(
|
||||||
INSTANCED_COUNT
|
6 * 6
|
||||||
);
|
);
|
||||||
|
|
||||||
readonly block: IBlockSplitter<MapVertexBlock>;
|
readonly block: IBlockSplitter<MapVertexBlock>;
|
||||||
|
|
||||||
|
/** 顶点数组 */
|
||||||
|
private vertexArray: Float32Array = new Float32Array();
|
||||||
/** 偏移数组 */
|
/** 偏移数组 */
|
||||||
private instancedArray: Float32Array = new Float32Array();
|
private offsetArray: Int16Array = new Int16Array();
|
||||||
|
/** 不透明度数组 */
|
||||||
|
private alphaArray: Float32Array = new Float32Array();
|
||||||
|
/** 动态内容顶点数组 */
|
||||||
|
private dynamicVertexArray: Float32Array = new Float32Array();
|
||||||
/** 动态内容偏移数组 */
|
/** 动态内容偏移数组 */
|
||||||
private dynamicInstancedArray: Float32Array = new Float32Array();
|
private dynamicOffsetArray: Int16Array = new Int16Array();
|
||||||
|
/** 动态内容不透明度数组 */
|
||||||
|
private dynamicAlphaArray: Float32Array = new Float32Array();
|
||||||
|
|
||||||
/** 图层列表 */
|
/** 图层列表 */
|
||||||
private layers: IMapLayer[] = [];
|
private layers: IMapLayer[] = [];
|
||||||
|
|
||||||
|
/** 对应分块的顶点数组 */
|
||||||
|
private blockList: Map<number, IMapVertexBlock> = new Map();
|
||||||
/** 分块宽度 */
|
/** 分块宽度 */
|
||||||
private blockWidth: number = MAP_BLOCK_WIDTH;
|
private blockWidth: number = MAP_BLOCK_WIDTH;
|
||||||
/** 分块高度 */
|
/** 分块高度 */
|
||||||
@ -135,7 +163,6 @@ export class MapVertexGenerator
|
|||||||
readonly data: IContextData
|
readonly data: IContextData
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.resizeMap();
|
|
||||||
this.block = new BlockSplitter();
|
this.block = new BlockSplitter();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,13 +175,23 @@ export class MapVertexGenerator
|
|||||||
const area = this.renderer.mapWidth * this.renderer.mapHeight;
|
const area = this.renderer.mapWidth * this.renderer.mapHeight;
|
||||||
const staticCount = area * this.renderer.layerCount;
|
const staticCount = area * this.renderer.layerCount;
|
||||||
const count = staticCount + this.dynamicLength;
|
const count = staticCount + this.dynamicLength;
|
||||||
const offsetSize = count * INSTANCED_COUNT;
|
const vertexSize = count * 6 * 6;
|
||||||
this.instancedArray = new Float32Array(offsetSize);
|
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);
|
||||||
this.staticLength = staticCount;
|
this.staticLength = staticCount;
|
||||||
this.dynamicInstancedArray = this.instancedArray.subarray(
|
this.dynamicVertexArray = this.vertexArray.subarray(
|
||||||
staticCount * INSTANCED_COUNT,
|
staticCount * 6 * 6,
|
||||||
count * INSTANCED_COUNT
|
count * 6 * 6
|
||||||
);
|
);
|
||||||
|
this.dynamicOffsetArray = this.offsetArray.subarray(
|
||||||
|
staticCount * 2,
|
||||||
|
count * 2
|
||||||
|
);
|
||||||
|
this.dynamicAlphaArray = this.alphaArray.subarray(staticCount, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
private splitBlock() {
|
private splitBlock() {
|
||||||
@ -169,16 +206,19 @@ export class MapVertexGenerator
|
|||||||
const lastCount = (this.mapHeight % this.blockHeight) * this.blockWidth;
|
const lastCount = (this.mapHeight % this.blockHeight) * this.blockWidth;
|
||||||
const bh = Math.floor(this.mapHeight / this.blockHeight);
|
const bh = Math.floor(this.mapHeight / this.blockHeight);
|
||||||
const lastStart = bh * lineCount;
|
const lastStart = bh * lineCount;
|
||||||
|
const layerCount = this.renderer.layerCount;
|
||||||
this.block.splitBlocks(block => {
|
this.block.splitBlocks(block => {
|
||||||
// 最后一行的算法与其他行不同
|
// 最后一行的算法与其他行不同
|
||||||
const startIndex =
|
const startIndex =
|
||||||
block.height < this.blockHeight
|
block.height < this.blockHeight
|
||||||
? lastStart + lastCount * block.x
|
? lastStart + lastCount * block.x
|
||||||
: lineCount * block.y + blockCount * block.x;
|
: lineCount * block.y + blockCount * block.x;
|
||||||
const count = block.width * block.height;
|
const count = block.width * block.height * layerCount;
|
||||||
|
|
||||||
const origin: IMapVertexData = {
|
const origin: IMapVertexData = {
|
||||||
instancedArray: this.instancedArray
|
vertexArray: this.vertexArray,
|
||||||
|
offsetArray: this.offsetArray,
|
||||||
|
alphaArray: this.alphaArray
|
||||||
};
|
};
|
||||||
const data = new MapVertexBlock(
|
const data = new MapVertexBlock(
|
||||||
this.renderer,
|
this.renderer,
|
||||||
@ -205,18 +245,22 @@ export class MapVertexGenerator
|
|||||||
this.mapHeight !== this.renderer.mapHeight
|
this.mapHeight !== this.renderer.mapHeight
|
||||||
) {
|
) {
|
||||||
this.needRebuild = true;
|
this.needRebuild = true;
|
||||||
this.mapWidth = this.renderer.mapWidth;
|
|
||||||
this.mapHeight = this.renderer.mapHeight;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
expandMoving(targetSize: number): void {
|
expandMoving(targetSize: number): void {
|
||||||
const beforeOffset = this.instancedArray;
|
const beforeVertex = this.vertexArray;
|
||||||
|
const beforeOffset = this.offsetArray;
|
||||||
|
const beforeAlpha = this.alphaArray;
|
||||||
this.dynamicLength = targetSize;
|
this.dynamicLength = targetSize;
|
||||||
this.mallocVertexArray();
|
this.mallocVertexArray();
|
||||||
this.instancedArray.set(beforeOffset);
|
this.vertexArray.set(beforeVertex);
|
||||||
|
this.offsetArray.set(beforeOffset);
|
||||||
|
this.alphaArray.set(beforeAlpha);
|
||||||
const array: IMapVertexData = {
|
const array: IMapVertexData = {
|
||||||
instancedArray: this.instancedArray
|
vertexArray: this.vertexArray,
|
||||||
|
offsetArray: this.offsetArray,
|
||||||
|
alphaArray: this.alphaArray
|
||||||
};
|
};
|
||||||
// 重建一下对应分块就行了,不需要重新分块
|
// 重建一下对应分块就行了,不需要重新分块
|
||||||
for (const block of this.block.iterateBlocks()) {
|
for (const block of this.block.iterateBlocks()) {
|
||||||
@ -225,25 +269,42 @@ export class MapVertexGenerator
|
|||||||
}
|
}
|
||||||
|
|
||||||
reduceMoving(targetSize: number, indexMap: Map<number, number>): void {
|
reduceMoving(targetSize: number, indexMap: Map<number, number>): void {
|
||||||
const beforeOffsetLength = this.instancedArray.length;
|
const beforeVertexLength = this.vertexArray.length;
|
||||||
|
const beforeOffsetLength = this.offsetArray.length;
|
||||||
|
const beforeAlphaLength = this.alphaArray.length;
|
||||||
const deltaLength = this.dynamicLength - targetSize;
|
const deltaLength = this.dynamicLength - targetSize;
|
||||||
this.dynamicLength = targetSize;
|
this.dynamicLength = targetSize;
|
||||||
this.instancedArray = this.instancedArray.subarray(
|
this.vertexArray = this.vertexArray.subarray(
|
||||||
0,
|
0,
|
||||||
beforeOffsetLength - deltaLength * INSTANCED_COUNT
|
beforeVertexLength - deltaLength * 6 * 6
|
||||||
|
);
|
||||||
|
this.offsetArray = this.offsetArray.subarray(
|
||||||
|
0,
|
||||||
|
beforeOffsetLength - deltaLength * 2
|
||||||
|
);
|
||||||
|
this.alphaArray = this.alphaArray.subarray(
|
||||||
|
0,
|
||||||
|
beforeAlphaLength - deltaLength
|
||||||
);
|
);
|
||||||
indexMap.forEach((target, from) => {
|
indexMap.forEach((target, from) => {
|
||||||
const next = from + 1;
|
const next = from + 1;
|
||||||
this.dynamicInstancedArray.copyWithin(
|
this.dynamicVertexArray.copyWithin(
|
||||||
target * INSTANCED_COUNT,
|
target * 6 * 6,
|
||||||
from * INSTANCED_COUNT,
|
from * 6 * 6,
|
||||||
next * INSTANCED_COUNT
|
next * 6 * 6
|
||||||
);
|
);
|
||||||
|
this.dynamicOffsetArray.copyWithin(target * 2, from * 2, next * 2);
|
||||||
|
this.dynamicAlphaArray[target] = this.dynamicAlphaArray[from];
|
||||||
});
|
});
|
||||||
this.dynamicInstancedArray = this.dynamicInstancedArray.subarray(
|
this.dynamicVertexArray = this.dynamicVertexArray.subarray(
|
||||||
0,
|
0,
|
||||||
targetSize * INSTANCED_COUNT
|
targetSize * 6 * 6
|
||||||
);
|
);
|
||||||
|
this.dynamicOffsetArray = this.dynamicOffsetArray.subarray(
|
||||||
|
0,
|
||||||
|
targetSize * 2
|
||||||
|
);
|
||||||
|
this.dynamicAlphaArray = this.dynamicAlphaArray.subarray(0, targetSize);
|
||||||
// 这个不需要重新分配内存,依然共用同一个 ArrayBuffer,因此不需要重新分块
|
// 这个不需要重新分配内存,依然共用同一个 ArrayBuffer,因此不需要重新分块
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,7 +323,6 @@ export class MapVertexGenerator
|
|||||||
this.needRebuild = false;
|
this.needRebuild = false;
|
||||||
this.mallocVertexArray();
|
this.mallocVertexArray();
|
||||||
this.splitBlock();
|
this.splitBlock();
|
||||||
this.dirty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
@ -279,7 +339,7 @@ export class MapVertexGenerator
|
|||||||
pos: BlockMapPos,
|
pos: BlockMapPos,
|
||||||
width: number,
|
width: number,
|
||||||
height: number
|
height: number
|
||||||
): Readonly<IRect> {
|
): TilePosition {
|
||||||
const {
|
const {
|
||||||
renderWidth,
|
renderWidth,
|
||||||
renderHeight,
|
renderHeight,
|
||||||
@ -302,14 +362,14 @@ export class MapVertexGenerator
|
|||||||
const cw = cwu * 2; // normalized cell width in range [-1, 1]
|
const cw = cwu * 2; // normalized cell width in range [-1, 1]
|
||||||
const ch = chu * 2; // normalized cell height in range [-1, 1]
|
const ch = chu * 2; // normalized cell height in range [-1, 1]
|
||||||
const cl = pos.mapX * cw - 1; // cell left
|
const cl = pos.mapX * cw - 1; // cell left
|
||||||
const ct = 1 - pos.mapY * ch; // cell top
|
const ct = pos.mapY * ch - 1; // cell top
|
||||||
if (mode === MapTileBehavior.FitToSize) {
|
if (mode === MapTileBehavior.FitToSize) {
|
||||||
// 适应到格子大小
|
// 适应到格子大小
|
||||||
return {
|
return {
|
||||||
x: cl,
|
left: cl,
|
||||||
y: ct,
|
top: ct,
|
||||||
w: cw,
|
right: cl + cw,
|
||||||
h: ch
|
bottom: ct + ch
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// 维持大小,需要判断对齐
|
// 维持大小,需要判断对齐
|
||||||
@ -322,20 +382,26 @@ export class MapVertexGenerator
|
|||||||
const th = thu * 2; // normalized texture height in range [-1, 1]
|
const th = thu * 2; // normalized texture height in range [-1, 1]
|
||||||
let left = 0;
|
let left = 0;
|
||||||
let top = 0;
|
let top = 0;
|
||||||
|
let right = 0;
|
||||||
|
let bottom = 0;
|
||||||
switch (tileAlignX) {
|
switch (tileAlignX) {
|
||||||
case MapTileAlign.Start: {
|
case MapTileAlign.Start: {
|
||||||
// 左对齐
|
// 左对齐
|
||||||
left = cl;
|
left = cl;
|
||||||
|
right = cl + tw;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MapTileAlign.Center: {
|
case MapTileAlign.Center: {
|
||||||
// 左右居中对齐
|
// 左右居中对齐
|
||||||
left = cl + cwu - twu;
|
const center = cl + cwu;
|
||||||
|
left = center - twu;
|
||||||
|
right = center + twu;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MapTileAlign.End: {
|
case MapTileAlign.End: {
|
||||||
// 右对齐
|
// 右对齐
|
||||||
left = cl + cw - tw;
|
right = cl + cw;
|
||||||
|
left = right - tw;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -343,19 +409,23 @@ export class MapVertexGenerator
|
|||||||
case MapTileAlign.Start: {
|
case MapTileAlign.Start: {
|
||||||
// 上对齐
|
// 上对齐
|
||||||
top = ct;
|
top = ct;
|
||||||
|
bottom = ct + th;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MapTileAlign.Center: {
|
case MapTileAlign.Center: {
|
||||||
// 上下居中对齐
|
// 上下居中对齐
|
||||||
top = ct + chu - thu;
|
const center = ct + chu;
|
||||||
|
top = center - thu;
|
||||||
|
bottom = center + thu;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MapTileAlign.End: {
|
case MapTileAlign.End: {
|
||||||
// 下对齐
|
// 下对齐
|
||||||
top = ct + ch - th;
|
bottom = ct + ch;
|
||||||
|
top = bottom - th;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { x: left, y: top, w: tw, h: th };
|
return { left, top, right, bottom };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -378,44 +448,69 @@ export class MapVertexGenerator
|
|||||||
frames: number,
|
frames: number,
|
||||||
update: VertexUpdate
|
update: VertexUpdate
|
||||||
) {
|
) {
|
||||||
const { instancedArray } = vertex;
|
const { offsetArray, vertexArray } = vertex;
|
||||||
// 顶点数组
|
// 顶点数组
|
||||||
const { layerCount, assetWidth, assetHeight } = this.renderer;
|
const { renderWidth, renderHeight, layerCount } = this.renderer;
|
||||||
const { x, y, w: width, h: height } = rect;
|
const { x, y, w, h } = rect;
|
||||||
const startIndex = index.blockIndex * INSTANCED_COUNT;
|
const vertexStart = index.blockIndex * 6 * 6;
|
||||||
if (update & VertexUpdate.Position) {
|
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 layerIndex = this.renderer.getLayerIndex(index.layer);
|
const layerIndex = this.renderer.getLayerIndex(index.layer);
|
||||||
// 避免 z 坐标是 1 的时候被裁剪,因此范围选择 [-0.9, 0.9]
|
const layerStart = (layerIndex / layerCount) * 2 - 1;
|
||||||
const layerStart = (layerIndex / layerCount) * 1.8 - 0.9;
|
const zIndex = layerStart + index.mapY / this.mapHeight;
|
||||||
const zIndex = -layerStart - index.mapY / this.mapHeight;
|
const { left, top, right, bottom } = this.getTilePosition(
|
||||||
const { x, y, w, h } = this.getTilePosition(index, width, height);
|
index,
|
||||||
// 图块位置
|
w,
|
||||||
instancedArray[startIndex] = x;
|
h
|
||||||
instancedArray[startIndex + 1] = y;
|
);
|
||||||
instancedArray[startIndex + 2] = w;
|
vertexArray[vertexStart] = left;
|
||||||
instancedArray[startIndex + 3] = h;
|
vertexArray[vertexStart + 1] = bottom;
|
||||||
// 图块纵深
|
vertexArray[vertexStart + 2] = zIndex;
|
||||||
instancedArray[startIndex + 8] = zIndex;
|
vertexArray[vertexStart + 6] = right;
|
||||||
}
|
vertexArray[vertexStart + 7] = bottom;
|
||||||
if (update & VertexUpdate.Texture) {
|
vertexArray[vertexStart + 8] = zIndex;
|
||||||
const texX = x / assetWidth;
|
vertexArray[vertexStart + 12] = left;
|
||||||
const texY = y / assetHeight;
|
vertexArray[vertexStart + 13] = top;
|
||||||
const texWidth = width / assetWidth;
|
vertexArray[vertexStart + 14] = zIndex;
|
||||||
const texHeight = height / assetHeight;
|
vertexArray[vertexStart + 18] = left;
|
||||||
// 纹理坐标
|
vertexArray[vertexStart + 19] = top;
|
||||||
instancedArray[startIndex + 4] = texX;
|
vertexArray[vertexStart + 20] = zIndex;
|
||||||
instancedArray[startIndex + 5] = texY;
|
vertexArray[vertexStart + 24] = right;
|
||||||
instancedArray[startIndex + 6] = texWidth;
|
vertexArray[vertexStart + 25] = bottom;
|
||||||
instancedArray[startIndex + 7] = texHeight;
|
vertexArray[vertexStart + 26] = zIndex;
|
||||||
// 不透明度
|
vertexArray[vertexStart + 30] = right;
|
||||||
instancedArray[startIndex + 9] = 1;
|
vertexArray[vertexStart + 31] = top;
|
||||||
// 帧数、偏移、纹理索引
|
vertexArray[vertexStart + 32] = zIndex;
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -520,13 +615,13 @@ export class MapVertexGenerator
|
|||||||
|
|
||||||
if (!tile) {
|
if (!tile) {
|
||||||
// 不存在可渲染对象,认为是空图块
|
// 不存在可渲染对象,认为是空图块
|
||||||
const { instancedArray } = vertex;
|
vertex.vertexArray.set(
|
||||||
const instancedStart = index.blockIndex * INSTANCED_COUNT;
|
MapVertexGenerator.EMPTY_VETREX,
|
||||||
// 只把坐标改成 0 就可以了,其他的保留
|
index.blockIndex * 6 * 6
|
||||||
instancedArray[instancedStart] = 0;
|
);
|
||||||
instancedArray[instancedStart + 1] = 0;
|
const offsetStart = index.blockIndex * 2;
|
||||||
instancedArray[instancedStart + 2] = 0;
|
vertex.offsetArray[offsetStart] = 0;
|
||||||
instancedArray[instancedStart + 3] = 0;
|
vertex.offsetArray[offsetStart + 1] = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -585,8 +680,8 @@ export class MapVertexGenerator
|
|||||||
const block = this.block.getBlockByDataLoc(x, y);
|
const block = this.block.getBlockByDataLoc(x, y);
|
||||||
if (!block) return;
|
if (!block) return;
|
||||||
const vertex = block.data.getLayerData(layer);
|
const vertex = block.data.getLayerData(layer);
|
||||||
const data = layer.getMapRef();
|
const data = this.renderer.getMapLayerData(layer);
|
||||||
if (!vertex) return;
|
if (!vertex || !data) return;
|
||||||
const { array } = data;
|
const { array } = data;
|
||||||
const dx = x - block.dataX;
|
const dx = x - block.dataX;
|
||||||
const dy = y - block.dataY;
|
const dy = y - block.dataY;
|
||||||
@ -643,15 +738,12 @@ export class MapVertexGenerator
|
|||||||
w: number,
|
w: number,
|
||||||
h: number
|
h: number
|
||||||
): void {
|
): void {
|
||||||
if (!this.renderer.hasLayer(layer)) return;
|
|
||||||
this.checkRebuild();
|
|
||||||
// 这里多一圈是因为要更新这一圈的自动元件
|
// 这里多一圈是因为要更新这一圈的自动元件
|
||||||
const ax = x - 1;
|
const ax = x - 1;
|
||||||
const ay = y - 1;
|
const ay = y - 1;
|
||||||
const areaRight = x + w + 1;
|
const areaRight = x + w + 1;
|
||||||
const areaBottom = y + h + 1;
|
const areaBottom = y + h + 1;
|
||||||
const blocks = this.block.iterateBlocksOfDataArea(ax, ay, w + 2, h + 2);
|
const blocks = this.block.iterateBlocksOfDataArea(ax, ay, w + 2, h + 2);
|
||||||
|
|
||||||
for (const block of blocks) {
|
for (const block of blocks) {
|
||||||
const left = ax - block.dataX;
|
const left = ax - block.dataX;
|
||||||
const top = ay - block.dataY;
|
const top = ay - block.dataY;
|
||||||
@ -669,7 +761,6 @@ export class MapVertexGenerator
|
|||||||
if (import.meta.env.DEV) {
|
if (import.meta.env.DEV) {
|
||||||
this.checkUpdateCallPerformance('updateBlock');
|
this.checkUpdateCallPerformance('updateBlock');
|
||||||
}
|
}
|
||||||
this.checkRebuild();
|
|
||||||
this.updateBlockVertex(layer, num, x, y);
|
this.updateBlockVertex(layer, num, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -678,7 +769,6 @@ export class MapVertexGenerator
|
|||||||
if (import.meta.env.DEV) {
|
if (import.meta.env.DEV) {
|
||||||
this.checkUpdateCallPerformance('updateBlockList');
|
this.checkUpdateCallPerformance('updateBlockList');
|
||||||
}
|
}
|
||||||
this.checkRebuild();
|
|
||||||
if (blocks.length > 50) {
|
if (blocks.length > 50) {
|
||||||
// 对于超出50个的更新操作使用懒更新
|
// 对于超出50个的更新操作使用懒更新
|
||||||
blocks.forEach(v => {
|
blocks.forEach(v => {
|
||||||
@ -774,15 +864,14 @@ export class MapVertexGenerator
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateBlockCache(block: Readonly<IBlockData<IMapVertexBlock>>): void {
|
updateBlockCache(block: Readonly<IBlockData<IMapVertexBlock>>): void {
|
||||||
if (!block.data.dirty) return;
|
console.time('update-block-cache');
|
||||||
const layers = this.renderer.getSortedLayer();
|
const layers = this.renderer.getSortedLayer();
|
||||||
layers.forEach(layer => {
|
layers.forEach(layer => {
|
||||||
const dirty = block.data.getDirtyArea(layer);
|
const dirty = block.data.getDirtyArea(layer);
|
||||||
if (!dirty || !dirty.dirty) return;
|
if (!dirty || !dirty.dirty) return;
|
||||||
block.data.updated();
|
|
||||||
const vertex = block.data.getLayerData(layer);
|
const vertex = block.data.getLayerData(layer);
|
||||||
const mapData = layer.getMapRef();
|
const mapData = this.renderer.getMapLayerData(layer);
|
||||||
if (!vertex) return;
|
if (!vertex || !mapData) return;
|
||||||
const { array } = mapData;
|
const { array } = mapData;
|
||||||
const { dirtyLeft, dirtyTop, dirtyRight, dirtyBottom } = dirty;
|
const { dirtyLeft, dirtyTop, dirtyRight, dirtyBottom } = dirty;
|
||||||
for (let nx = dirtyLeft; nx < dirtyRight; nx++) {
|
for (let nx = dirtyLeft; nx < dirtyRight; nx++) {
|
||||||
@ -808,6 +897,7 @@ export class MapVertexGenerator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
console.timeEnd('update-block-cache');
|
||||||
}
|
}
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
@ -815,10 +905,10 @@ export class MapVertexGenerator
|
|||||||
//#region 图块配置
|
//#region 图块配置
|
||||||
|
|
||||||
enableStaticFrameAnimate(layer: IMapLayer, x: number, y: number): void {
|
enableStaticFrameAnimate(layer: IMapLayer, x: number, y: number): void {
|
||||||
const data = layer.getMapRef();
|
const data = this.renderer.getMapLayerData(layer);
|
||||||
const block = this.block.getBlockByDataLoc(x, y);
|
const block = this.block.getBlockByDataLoc(x, y);
|
||||||
if (!block) return;
|
if (!data || !block) return;
|
||||||
const vertexArray = block.data.getLayerInstanced(layer);
|
const vertexArray = block.data.getLayerOffset(layer);
|
||||||
if (!vertexArray) return;
|
if (!vertexArray) return;
|
||||||
const mapIndex = y * this.mapWidth + x;
|
const mapIndex = y * this.mapWidth + x;
|
||||||
const num = data.array[mapIndex];
|
const num = data.array[mapIndex];
|
||||||
@ -827,19 +917,19 @@ export class MapVertexGenerator
|
|||||||
const bx = x - block.dataX;
|
const bx = x - block.dataX;
|
||||||
const by = y - block.dataY;
|
const by = y - block.dataY;
|
||||||
const bIndex = by * block.width + bx;
|
const bIndex = by * block.width + bx;
|
||||||
vertexArray[bIndex * INSTANCED_COUNT + 13] = tile.frames;
|
vertexArray[bIndex * 2] = tile.frames;
|
||||||
block.data.markRenderDirty();
|
block.data.markRenderDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
disableStaticFrameAnimate(layer: IMapLayer, x: number, y: number): void {
|
disableStaticFrameAnimate(layer: IMapLayer, x: number, y: number): void {
|
||||||
const block = this.block.getBlockByDataLoc(x, y);
|
const block = this.block.getBlockByDataLoc(x, y);
|
||||||
if (!block) return;
|
if (!block) return;
|
||||||
const vertexArray = block.data.getLayerInstanced(layer);
|
const vertexArray = block.data.getLayerOffset(layer);
|
||||||
if (!vertexArray) return;
|
if (!vertexArray) return;
|
||||||
const bx = x - block.dataX;
|
const bx = x - block.dataX;
|
||||||
const by = y - block.dataY;
|
const by = y - block.dataY;
|
||||||
const bIndex = by * block.width + bx;
|
const bIndex = by * block.width + bx;
|
||||||
vertexArray[bIndex * INSTANCED_COUNT + 13] = 1;
|
vertexArray[bIndex * 2] = 1;
|
||||||
block.data.markRenderDirty();
|
block.data.markRenderDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -851,12 +941,12 @@ export class MapVertexGenerator
|
|||||||
): void {
|
): void {
|
||||||
const block = this.block.getBlockByDataLoc(x, y);
|
const block = this.block.getBlockByDataLoc(x, y);
|
||||||
if (!block) return;
|
if (!block) return;
|
||||||
const vertexArray = block.data.getLayerInstanced(layer);
|
const vertexArray = block.data.getLayerAlpha(layer);
|
||||||
if (!vertexArray) return;
|
if (!vertexArray) return;
|
||||||
const bx = x - block.dataX;
|
const bx = x - block.dataX;
|
||||||
const by = y - block.dataY;
|
const by = y - block.dataY;
|
||||||
const bIndex = by * block.width + bx;
|
const bIndex = by * block.width + bx;
|
||||||
vertexArray[bIndex * INSTANCED_COUNT + 9] = alpha;
|
vertexArray[bIndex] = alpha;
|
||||||
block.data.markRenderDirty();
|
block.data.markRenderDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -868,7 +958,9 @@ export class MapVertexGenerator
|
|||||||
if (!this.renderer.hasMoving(block)) return;
|
if (!this.renderer.hasMoving(block)) return;
|
||||||
const { cls, frames, offset, texture } = block.texture;
|
const { cls, frames, offset, texture } = block.texture;
|
||||||
const vertex: IMapVertexData = {
|
const vertex: IMapVertexData = {
|
||||||
instancedArray: this.dynamicInstancedArray
|
vertexArray: this.dynamicVertexArray,
|
||||||
|
offsetArray: this.dynamicOffsetArray,
|
||||||
|
alphaArray: this.dynamicAlphaArray
|
||||||
};
|
};
|
||||||
const index: IndexedBlockMapPos = {
|
const index: IndexedBlockMapPos = {
|
||||||
layer: block.layer,
|
layer: block.layer,
|
||||||
@ -882,7 +974,7 @@ export class MapVertexGenerator
|
|||||||
logger.error(40, block.tile.toString());
|
logger.error(40, block.tile.toString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const update = updateTexture ? VertexUpdate.All : VertexUpdate.Position;
|
const update = updateTexture ? VertexUpdate.All : VertexUpdate.Vertex;
|
||||||
if (cls === BlockCls.Autotile) {
|
if (cls === BlockCls.Autotile) {
|
||||||
// 自动元件使用全部不连接
|
// 自动元件使用全部不连接
|
||||||
const renderable = this.renderer.autotile.renderWithoutCheck(
|
const renderable = this.renderer.autotile.renderWithoutCheck(
|
||||||
@ -933,33 +1025,32 @@ export class MapVertexGenerator
|
|||||||
}
|
}
|
||||||
|
|
||||||
deleteMoving(moving: IMovingBlock): void {
|
deleteMoving(moving: IMovingBlock): void {
|
||||||
const instancedStart = moving.index * INSTANCED_COUNT;
|
this.dynamicVertexArray.set(
|
||||||
// 这个需要全部清空了,因为可能会复用
|
|
||||||
this.dynamicInstancedArray.set(
|
|
||||||
MapVertexGenerator.EMPTY_VETREX,
|
MapVertexGenerator.EMPTY_VETREX,
|
||||||
instancedStart
|
moving.index * 6 * 6
|
||||||
);
|
);
|
||||||
|
const offsetStart = moving.index * 2;
|
||||||
|
this.dynamicOffsetArray[offsetStart] = 0;
|
||||||
|
this.dynamicOffsetArray[offsetStart + 1] = 0;
|
||||||
|
this.dynamicAlphaArray[moving.index] = 0;
|
||||||
this.dynamicRenderDirty = true;
|
this.dynamicRenderDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
enableDynamicFrameAnimate(block: IMovingBlock): void {
|
enableDynamicFrameAnimate(block: IMovingBlock): void {
|
||||||
if (!this.renderer.hasMoving(block)) return;
|
if (!this.renderer.hasMoving(block)) return;
|
||||||
const instancedStart = block.index * INSTANCED_COUNT;
|
this.dynamicOffsetArray[block.index * 2] = 1;
|
||||||
this.dynamicInstancedArray[instancedStart + 13] = 1;
|
|
||||||
this.dynamicRenderDirty = true;
|
this.dynamicRenderDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
disableDynamicFrameAnimate(block: IMovingBlock): void {
|
disableDynamicFrameAnimate(block: IMovingBlock): void {
|
||||||
if (!this.renderer.hasMoving(block)) return;
|
if (!this.renderer.hasMoving(block)) return;
|
||||||
const instancedStart = block.index * INSTANCED_COUNT;
|
this.dynamicOffsetArray[block.index * 2] = block.texture.frames;
|
||||||
this.dynamicInstancedArray[instancedStart + 13] = block.texture.frames;
|
|
||||||
this.dynamicRenderDirty = true;
|
this.dynamicRenderDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
setDynamicAlpha(block: IMovingBlock, alpha: number): void {
|
setDynamicAlpha(block: IMovingBlock, alpha: number): void {
|
||||||
if (!this.renderer.hasMoving(block)) return;
|
if (!this.renderer.hasMoving(block)) return;
|
||||||
const instancedStart = block.index * INSTANCED_COUNT;
|
this.dynamicAlphaArray[block.index] = alpha;
|
||||||
this.dynamicInstancedArray[instancedStart + 9] = alpha;
|
|
||||||
this.dynamicRenderDirty = true;
|
this.dynamicRenderDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -975,9 +1066,11 @@ export class MapVertexGenerator
|
|||||||
getVertexArray(): IMapVertexArray {
|
getVertexArray(): IMapVertexArray {
|
||||||
this.checkRebuild();
|
this.checkRebuild();
|
||||||
return {
|
return {
|
||||||
dynamicStart: this.staticLength,
|
dynamicStart: this.staticLength * 6,
|
||||||
dynamicCount: this.dynamicLength,
|
dynamicCount: this.dynamicLength * 6,
|
||||||
tileInstanced: this.instancedArray
|
tileVertex: this.vertexArray,
|
||||||
|
tileOffset: this.offsetArray,
|
||||||
|
tileAlpha: this.alphaArray
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -987,9 +1080,11 @@ export class MapVertexGenerator
|
|||||||
//#region 分块对象
|
//#region 分块对象
|
||||||
|
|
||||||
class MapVertexBlock implements IMapVertexBlock {
|
class MapVertexBlock implements IMapVertexBlock {
|
||||||
instancedArray!: Float32Array;
|
vertexArray!: Float32Array;
|
||||||
|
offsetArray!: Int16Array;
|
||||||
|
alphaArray!: Float32Array;
|
||||||
|
|
||||||
dirty: boolean = true;
|
dirty: boolean = false;
|
||||||
renderDirty: boolean = true;
|
renderDirty: boolean = true;
|
||||||
|
|
||||||
private readonly layerDirty: Map<IMapLayer, ILayerDirtyData> = new Map();
|
private readonly layerDirty: Map<IMapLayer, ILayerDirtyData> = new Map();
|
||||||
@ -997,21 +1092,22 @@ class MapVertexBlock implements IMapVertexBlock {
|
|||||||
readonly startIndex: number;
|
readonly startIndex: number;
|
||||||
readonly endIndex: number;
|
readonly endIndex: number;
|
||||||
readonly count: number;
|
readonly count: number;
|
||||||
readonly layerCount: number;
|
|
||||||
|
|
||||||
readonly instancedStart: number;
|
readonly vertexStart: number;
|
||||||
|
readonly offsetStart: number;
|
||||||
|
readonly alphaStart: number;
|
||||||
|
|
||||||
private readonly indexMap: Map<IMapLayer, number> = new Map();
|
private readonly indexMap: Map<IMapLayer, number> = new Map();
|
||||||
private readonly instancedMap: Map<IMapLayer, Float32Array> = new Map();
|
private readonly vertexMap: Map<IMapLayer, Float32Array> = new Map();
|
||||||
|
private readonly offsetMap: Map<IMapLayer, Int16Array> = new Map();
|
||||||
|
private readonly alphaMap: Map<IMapLayer, Float32Array> = new Map();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建分块的顶点数组对象,此对象不能动态扩展,如果地图变化,需要全部重建
|
* 创建分块的顶点数组对象,此对象不能动态扩展,如果地图变化,需要全部重建
|
||||||
* @param renderer 渲染器对象
|
* @param renderer 渲染器对象
|
||||||
* @param originArray 原始顶点数组
|
* @param originArray 原始顶点数组
|
||||||
* @param startIndex 起始网格索引
|
* @param startIndex 起始网格索引
|
||||||
* @param count 单个图层的图块数量
|
* @param count 分块数量
|
||||||
* @param blockWidth 分块宽度
|
|
||||||
* @param blockHeight 分块高度
|
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
readonly renderer: IMapRenderer,
|
readonly renderer: IMapRenderer,
|
||||||
@ -1021,13 +1117,17 @@ class MapVertexBlock implements IMapVertexBlock {
|
|||||||
private readonly blockWidth: number,
|
private readonly blockWidth: number,
|
||||||
private readonly blockHeight: number
|
private readonly blockHeight: number
|
||||||
) {
|
) {
|
||||||
const layerCount = renderer.layerCount;
|
this.startIndex = startIndex;
|
||||||
this.startIndex = startIndex * layerCount;
|
this.endIndex = startIndex + count;
|
||||||
this.endIndex = (startIndex + count) * layerCount;
|
|
||||||
this.count = count;
|
this.count = count;
|
||||||
const offsetStart = startIndex * layerCount * INSTANCED_COUNT;
|
const layerCount = renderer.layerCount;
|
||||||
this.instancedStart = offsetStart;
|
const vertexStart = startIndex * layerCount * 6 * 6;
|
||||||
this.layerCount = layerCount;
|
const offsetStart = startIndex * layerCount * 2;
|
||||||
|
const alphaStart = startIndex * layerCount;
|
||||||
|
this.vertexStart = vertexStart;
|
||||||
|
this.offsetStart = offsetStart;
|
||||||
|
this.alphaStart = alphaStart;
|
||||||
|
|
||||||
this.rebuild(originArray);
|
this.rebuild(originArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1035,10 +1135,6 @@ class MapVertexBlock implements IMapVertexBlock {
|
|||||||
this.renderDirty = false;
|
this.renderDirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
updated(): void {
|
|
||||||
this.dirty = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
markRenderDirty() {
|
markRenderDirty() {
|
||||||
// todo: 潜在优化点:vertex, offset, alpha 的脏标记分开
|
// todo: 潜在优化点:vertex, offset, alpha 的脏标记分开
|
||||||
this.renderDirty = true;
|
this.renderDirty = true;
|
||||||
@ -1077,20 +1173,33 @@ class MapVertexBlock implements IMapVertexBlock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rebuild(originArray: IMapVertexData) {
|
rebuild(originArray: IMapVertexData) {
|
||||||
const offsetStart = this.instancedStart;
|
const vertexStart = this.vertexStart;
|
||||||
|
const offsetStart = this.offsetStart;
|
||||||
|
const alphaStart = this.alphaStart;
|
||||||
const count = this.count;
|
const count = this.count;
|
||||||
this.instancedArray = originArray.instancedArray.subarray(
|
this.vertexArray = originArray.vertexArray.subarray(
|
||||||
|
vertexStart,
|
||||||
|
vertexStart + count * 6 * 6
|
||||||
|
);
|
||||||
|
this.offsetArray = originArray.offsetArray.subarray(
|
||||||
offsetStart,
|
offsetStart,
|
||||||
offsetStart + count * INSTANCED_COUNT * this.layerCount
|
offsetStart + count * 2
|
||||||
|
);
|
||||||
|
this.alphaArray = originArray.alphaArray.subarray(
|
||||||
|
alphaStart,
|
||||||
|
alphaStart + count
|
||||||
);
|
);
|
||||||
|
|
||||||
this.renderer.getSortedLayer().forEach((v, i) => {
|
this.renderer.getSortedLayer().forEach((v, i) => {
|
||||||
const os = i * count * INSTANCED_COUNT;
|
const vs = vertexStart + i * count * 6 * 6;
|
||||||
const oa = this.instancedArray.subarray(
|
const os = offsetStart + i * count * 2;
|
||||||
os,
|
const as = alphaStart + i * count;
|
||||||
os + count * INSTANCED_COUNT
|
const va = this.vertexArray.subarray(vs, vs + count * 6 * 6);
|
||||||
);
|
const oa = this.offsetArray.subarray(os, os + count * 2);
|
||||||
this.instancedMap.set(v, oa);
|
const aa = this.alphaArray.subarray(as, as + count);
|
||||||
|
this.vertexMap.set(v, va);
|
||||||
|
this.offsetMap.set(v, oa);
|
||||||
|
this.alphaMap.set(v, aa);
|
||||||
this.indexMap.set(v, i);
|
this.indexMap.set(v, i);
|
||||||
this.layerDirty.set(v, {
|
this.layerDirty.set(v, {
|
||||||
dirty: true,
|
dirty: true,
|
||||||
@ -1100,21 +1209,33 @@ class MapVertexBlock implements IMapVertexBlock {
|
|||||||
dirtyBottom: this.blockHeight
|
dirtyBottom: this.blockHeight
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.dirty = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getLayerInstanced(layer: IMapLayer): Float32Array | null {
|
getLayerVertex(layer: IMapLayer): Float32Array | null {
|
||||||
return this.instancedMap.get(layer) ?? 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
getLayerData(layer: IMapLayer): IIndexedMapVertexData | null {
|
getLayerData(layer: IMapLayer): IIndexedMapVertexData | null {
|
||||||
const offset = this.instancedMap.get(layer);
|
const vertex = this.vertexMap.get(layer);
|
||||||
|
const offset = this.offsetMap.get(layer);
|
||||||
|
const alpha = this.alphaMap.get(layer);
|
||||||
const index = this.indexMap.get(layer);
|
const index = this.indexMap.get(layer);
|
||||||
if (!offset || isNil(index)) return null;
|
if (!vertex || !offset || !alpha || isNil(index)) return null;
|
||||||
return {
|
return {
|
||||||
instancedArray: offset,
|
vertexArray: vertex,
|
||||||
instancedStart:
|
offsetArray: offset,
|
||||||
this.instancedStart + index * this.count * INSTANCED_COUNT
|
alphaArray: alpha,
|
||||||
|
vertexStart: this.vertexStart + index * this.count * 6 * 6,
|
||||||
|
offsetStart: this.offsetStart + index * this.count * 2,
|
||||||
|
alphaStart: this.alphaStart + index * this.count
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,31 +19,20 @@ export class MapViewport implements IMapViewportController {
|
|||||||
this.vertex = renderer.vertex;
|
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 {
|
getRenderArea(): IMapRenderData {
|
||||||
const { cellWidth, cellHeight, renderWidth, renderHeight } =
|
const { cellWidth, cellHeight, renderWidth, renderHeight } =
|
||||||
this.renderer;
|
this.renderer;
|
||||||
const { blockWidth, blockHeight, width, height } = this.vertex.block;
|
const { blockWidth, blockHeight, width, height } = this.vertex.block;
|
||||||
|
// 减一是因为第一个像素是 0,所以最后一个像素就是宽度减一
|
||||||
|
const r = renderWidth - 1;
|
||||||
|
const b = renderHeight - 1;
|
||||||
// 其实只需要算左上角和右下角就行了
|
// 其实只需要算左上角和右下角就行了
|
||||||
const [left, top] = this.transform.untransformed(-1, -1);
|
const [left, top] = this.transform.transformed(0, 0);
|
||||||
const [right, bottom] = this.transform.untransformed(1, 1);
|
const [right, bottom] = this.transform.transformed(r, b);
|
||||||
const cl = (left * renderWidth) / cellWidth;
|
const cl = left / cellWidth;
|
||||||
const ct = (top * renderHeight) / cellHeight;
|
const ct = top / cellHeight;
|
||||||
const cr = (right * renderWidth) / cellWidth;
|
const cr = right / cellWidth;
|
||||||
const cb = (bottom * renderHeight) / cellHeight;
|
const cb = bottom / cellHeight;
|
||||||
const blockLeft = clamp(Math.floor(cl / blockWidth), 0, width - 1);
|
const blockLeft = clamp(Math.floor(cl / blockWidth), 0, width - 1);
|
||||||
const blockRight = clamp(Math.floor(cr / blockWidth), 0, width - 1);
|
const blockRight = clamp(Math.floor(cr / blockWidth), 0, width - 1);
|
||||||
const blockTop = clamp(Math.floor(ct / blockHeight), 0, height - 1);
|
const blockTop = clamp(Math.floor(ct / blockHeight), 0, height - 1);
|
||||||
@ -53,31 +42,24 @@ export class MapViewport implements IMapViewportController {
|
|||||||
const updateArea: IMapRenderArea[] = [];
|
const updateArea: IMapRenderArea[] = [];
|
||||||
const blockList: IBlockData<IMapVertexBlock>[] = [];
|
const blockList: IBlockData<IMapVertexBlock>[] = [];
|
||||||
|
|
||||||
const widthOne = blockLeft === blockRight;
|
// 使用这种方式的话,索引在换行之前都是连续的,方便整合
|
||||||
const heightOne = blockTop === blockBottom;
|
for (let ny = blockTop; ny <= blockBottom; ny++) {
|
||||||
|
const first = this.vertex.block.getBlockByLoc(blockLeft, ny)!;
|
||||||
if (widthOne && heightOne) {
|
const last = this.vertex.block.getBlockByLoc(blockRight, ny)!;
|
||||||
// 只能看到一个分块
|
if (first.data.dirty) {
|
||||||
const block = this.vertex.block.getBlockByLoc(blockLeft, blockTop)!;
|
blockList.push(first);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
} else if (heightOne) {
|
if (last.data.dirty) {
|
||||||
// 看到的区域分块高度是 1
|
blockList.push(last);
|
||||||
for (let nx = blockLeft; nx <= blockRight; nx++) {
|
|
||||||
const block = this.vertex.block.getBlockByLoc(nx, blockTop)!;
|
|
||||||
blockList.push(block);
|
|
||||||
}
|
}
|
||||||
} else {
|
renderArea.push({
|
||||||
// 看到的区域分块宽高都不是 1
|
startIndex: first.data.startIndex,
|
||||||
// 使用这种方式的话,索引在换行之前都是连续的,方便整合
|
endIndex: last.data.endIndex,
|
||||||
for (let ny = blockTop; ny <= blockBottom; ny++) {
|
count: last.data.endIndex - first.data.startIndex
|
||||||
for (let nx = blockLeft; nx <= blockRight; nx++) {
|
});
|
||||||
const block = this.vertex.block.getBlockByLoc(nx, ny)!;
|
for (let nx = blockLeft + 1; nx < blockRight; nx++) {
|
||||||
|
const block = this.vertex.block.getBlockByLoc(nx, ny)!;
|
||||||
|
if (block.data.dirty) {
|
||||||
blockList.push(block);
|
blockList.push(block);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,48 +68,34 @@ export class MapViewport implements IMapViewportController {
|
|||||||
if (blockList.length > 0) {
|
if (blockList.length > 0) {
|
||||||
if (blockList.length === 1) {
|
if (blockList.length === 1) {
|
||||||
const block = blockList[0];
|
const block = blockList[0];
|
||||||
if (block.data.renderDirty) {
|
updateArea.push(block.data);
|
||||||
this.pushBlock(updateArea, block, block);
|
|
||||||
}
|
|
||||||
this.pushBlock(renderArea, block, block);
|
|
||||||
} else {
|
} else {
|
||||||
// 更新区域
|
let continuousStart: IBlockData<IMapVertexBlock> = blockList[0];
|
||||||
let updateStart: IBlockData<IMapVertexBlock> = blockList[0];
|
let continuousLast: 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++) {
|
for (let i = 1; i < blockList.length; i++) {
|
||||||
const block = blockList[i];
|
const block = blockList[i];
|
||||||
const { renderDirty } = block.data;
|
if (block.index === continuousLast.index + 1) {
|
||||||
// 连续则合并
|
// 连续则合并
|
||||||
// 渲染区域
|
continuousLast = block;
|
||||||
if (block.index === renderEnd.index + 1) {
|
|
||||||
renderEnd = block;
|
|
||||||
} else {
|
} else {
|
||||||
this.pushBlock(renderArea, renderStart, renderEnd);
|
const start = continuousStart.data.startIndex;
|
||||||
renderStart = block;
|
const end = continuousLast.data.endIndex;
|
||||||
renderEnd = block;
|
updateArea.push({
|
||||||
}
|
startIndex: start,
|
||||||
// 缓冲区更新区域
|
endIndex: end,
|
||||||
if (renderDirty && block.index === updateEnd.index + 1) {
|
count: end - start
|
||||||
updateEnd = block;
|
});
|
||||||
} else {
|
continuousStart = block;
|
||||||
this.pushBlock(updateArea, updateStart, updateEnd);
|
continuousLast = block;
|
||||||
updateStart = block;
|
|
||||||
updateEnd = block;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.pushBlock(updateArea, updateStart, updateEnd);
|
|
||||||
this.pushBlock(renderArea, renderStart, renderEnd);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: 动态内容
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
render: renderArea,
|
render: renderArea,
|
||||||
dirty: updateArea,
|
dirty: updateArea,
|
||||||
blockList: blockList.filter(v => v.data.dirty)
|
blockList
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,10 +5,6 @@ import { Font } from '@motajs/render-style';
|
|||||||
|
|
||||||
//#region 地图
|
//#region 地图
|
||||||
|
|
||||||
/** 每个格子的默认宽度,现阶段用处不大 */
|
|
||||||
export const CELL_WIDTH = 32;
|
|
||||||
/** 每个格子的默认高度,现阶段用处不大 */
|
|
||||||
export const CELL_HEIGHT = 32;
|
|
||||||
/** 每个格子的宽高 */
|
/** 每个格子的宽高 */
|
||||||
export const CELL_SIZE = 32;
|
export const CELL_SIZE = 32;
|
||||||
/** 地图格子宽度,此处仅影响画面,可能不影响游戏内的部分逻辑,游戏内逻辑地图大小请在 core.js 中修改 */
|
/** 地图格子宽度,此处仅影响画面,可能不影响游戏内的部分逻辑,游戏内逻辑地图大小请在 core.js 中修改 */
|
||||||
@ -33,6 +29,10 @@ export const DYNAMIC_RESERVE = 16;
|
|||||||
* 调整此值可以调整频率,值越大,越不容易因为数量小于预留数量而减小预留。
|
* 调整此值可以调整频率,值越大,越不容易因为数量小于预留数量而减小预留。
|
||||||
*/
|
*/
|
||||||
export const MOVING_TOLERANCE = 60;
|
export const MOVING_TOLERANCE = 60;
|
||||||
|
/** 每个格子的默认宽度,现阶段用处不大 */
|
||||||
|
export const CELL_WIDTH = 32;
|
||||||
|
/** 每个格子的默认高度,现阶段用处不大 */
|
||||||
|
export const CELL_HEIGHT = 32;
|
||||||
|
|
||||||
//#region 状态栏
|
//#region 状态栏
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { LayerShadowExtends } from '../legacy/shadow';
|
||||||
import {
|
import {
|
||||||
Props,
|
Props,
|
||||||
Font,
|
Font,
|
||||||
@ -35,23 +36,44 @@ import {
|
|||||||
RightStatusBar
|
RightStatusBar
|
||||||
} from './statusBar';
|
} from './statusBar';
|
||||||
import { ReplayingStatus } from './toolbar';
|
import { ReplayingStatus } from './toolbar';
|
||||||
import {
|
import { getHeroStatusOn, HeroSkill, NightSpecial } from '@user/data-state';
|
||||||
getHeroStatusOn,
|
|
||||||
HeroSkill,
|
|
||||||
NightSpecial,
|
|
||||||
state
|
|
||||||
} from '@user/data-state';
|
|
||||||
import { jumpIgnoreFloor } from '@user/legacy-plugin-data';
|
import { jumpIgnoreFloor } from '@user/legacy-plugin-data';
|
||||||
import { hook } from '@user/data-base';
|
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 { FloorChange } from '../legacy/fallback';
|
||||||
|
import { PopText } from '../legacy/pop';
|
||||||
import { mainUIController } from './controller';
|
import { mainUIController } from './controller';
|
||||||
import { LayerGroup } from '../elements';
|
import {
|
||||||
|
ILayerGroupRenderExtends,
|
||||||
|
LayerGroupAnimate,
|
||||||
|
FloorViewport,
|
||||||
|
ILayerRenderExtends,
|
||||||
|
HeroRenderer,
|
||||||
|
LayerDoorAnimate,
|
||||||
|
LayerGroup
|
||||||
|
} from '../elements';
|
||||||
import { isNil } from 'lodash-es';
|
import { isNil } from 'lodash-es';
|
||||||
import { materials } from '@user/client-base';
|
import { materials } from '@user/client-base';
|
||||||
|
|
||||||
const MainScene = defineComponent(() => {
|
const MainScene = defineComponent(() => {
|
||||||
//#region 基本定义
|
//#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> = {
|
const mainTextboxProps: Props<typeof Textbox> = {
|
||||||
text: '',
|
text: '',
|
||||||
hidden: true,
|
hidden: true,
|
||||||
@ -250,7 +272,7 @@ const MainScene = defineComponent(() => {
|
|||||||
const tileset = materials.getAsset(0);
|
const tileset = materials.getAsset(0);
|
||||||
if (!tileset) return;
|
if (!tileset) return;
|
||||||
canvas.ctx.drawImage(
|
canvas.ctx.drawImage(
|
||||||
tileset.texture.source,
|
tileset.data.texture.source,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
canvas.width,
|
canvas.width,
|
||||||
@ -261,7 +283,6 @@ const MainScene = defineComponent(() => {
|
|||||||
return () => (
|
return () => (
|
||||||
<container id="main-scene" width={MAIN_WIDTH} height={MAIN_HEIGHT}>
|
<container id="main-scene" width={MAIN_WIDTH} height={MAIN_HEIGHT}>
|
||||||
<sprite
|
<sprite
|
||||||
hidden
|
|
||||||
render={testRender}
|
render={testRender}
|
||||||
loc={[180, 0, 480, 480]}
|
loc={[180, 0, 480, 480]}
|
||||||
zIndex={1000}
|
zIndex={1000}
|
||||||
@ -283,10 +304,14 @@ const MainScene = defineComponent(() => {
|
|||||||
onDown={downMap}
|
onDown={downMap}
|
||||||
onMove={moveMap}
|
onMove={moveMap}
|
||||||
>
|
>
|
||||||
<map-render
|
<layer-group id="layer-main" ex={layerGroupExtends} ref={map}>
|
||||||
layerState={state.layer}
|
<layer layer="bg" zIndex={10}></layer>
|
||||||
loc={[0, 0, MAP_WIDTH, MAP_HEIGHT]}
|
<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>
|
||||||
<Textbox id="main-textbox" {...mainTextboxProps}></Textbox>
|
<Textbox id="main-textbox" {...mainTextboxProps}></Textbox>
|
||||||
<FloorChange id="floor-change" zIndex={50}></FloorChange>
|
<FloorChange id="floor-change" zIndex={50}></FloorChange>
|
||||||
<Tip
|
<Tip
|
||||||
|
|||||||
@ -1,43 +1,18 @@
|
|||||||
import {
|
import { logger } from '@motajs/common';
|
||||||
Hookable,
|
import { IMapLayer, MapLayer } from '../map';
|
||||||
HookController,
|
import { ILayerState } from './types';
|
||||||
IHookController,
|
|
||||||
logger
|
|
||||||
} from '@motajs/common';
|
|
||||||
import {
|
|
||||||
IMapLayer,
|
|
||||||
IMapLayerHookController,
|
|
||||||
IMapLayerHooks,
|
|
||||||
MapLayer
|
|
||||||
} from '../map';
|
|
||||||
import { ILayerState, ILayerStateHooks } from './types';
|
|
||||||
|
|
||||||
export class LayerState
|
export class LayerState implements ILayerState {
|
||||||
extends Hookable<ILayerStateHooks>
|
readonly layerList: WeakSet<IMapLayer> = new WeakSet();
|
||||||
implements ILayerState
|
|
||||||
{
|
|
||||||
readonly layerList: Set<IMapLayer> = new Set();
|
|
||||||
/** 图层到图层别名映射 */
|
/** 图层到图层别名映射 */
|
||||||
readonly layerAliasMap: WeakMap<IMapLayer, string> = new WeakMap();
|
readonly layerAliasMap: WeakMap<IMapLayer, string> = new WeakMap();
|
||||||
/** 图层别名到图层的映射 */
|
/** 图层别名到图层的映射 */
|
||||||
readonly aliasLayerMap: Map<symbol, IMapLayer> = new Map();
|
readonly aliasLayerMap: WeakMap<symbol, IMapLayer> = new WeakMap();
|
||||||
|
|
||||||
/** 背景图块 */
|
|
||||||
private backgroundTile: number = 0;
|
|
||||||
|
|
||||||
/** 图层钩子映射 */
|
|
||||||
private layerHookMap: Map<IMapLayer, IMapLayerHookController> = new Map();
|
|
||||||
|
|
||||||
addLayer(width: number, height: number): IMapLayer {
|
addLayer(width: number, height: number): IMapLayer {
|
||||||
const array = new Uint32Array(width * height);
|
const array = new Uint32Array(width * height);
|
||||||
const layer = new MapLayer(array, width, height);
|
const layer = new MapLayer(array, width, height);
|
||||||
this.layerList.add(layer);
|
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;
|
return layer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,17 +24,6 @@ export class LayerState
|
|||||||
this.aliasLayerMap.delete(symbol);
|
this.aliasLayerMap.delete(symbol);
|
||||||
this.layerAliasMap.delete(layer);
|
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 {
|
setLayerAlias(layer: IMapLayer, alias: string): void {
|
||||||
@ -85,7 +49,7 @@ export class LayerState
|
|||||||
layer: IMapLayer,
|
layer: IMapLayer,
|
||||||
width: number,
|
width: number,
|
||||||
height: number,
|
height: number,
|
||||||
keepBlock: boolean = false
|
keepBlock?: boolean
|
||||||
): void {
|
): void {
|
||||||
if (keepBlock) {
|
if (keepBlock) {
|
||||||
layer.resize(width, height);
|
layer.resize(width, height);
|
||||||
@ -93,58 +57,4 @@ export class LayerState
|
|||||||
layer.resize2(width, height);
|
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,76 +1,8 @@
|
|||||||
import { IHookable, IHookBase, IHookController } from '@motajs/common';
|
|
||||||
import { IMapLayer } from '../map';
|
import { IMapLayer } from '../map';
|
||||||
|
|
||||||
export interface ILayerStateHooks extends IHookBase {
|
export interface ILayerState {
|
||||||
/**
|
|
||||||
* 当设置背景图块时执行,如果设置的背景图块与原先一样,则不会执行
|
|
||||||
* @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: Set<IMapLayer>;
|
readonly layerList: WeakSet<IMapLayer>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加图层
|
* 添加图层
|
||||||
@ -85,12 +17,6 @@ export interface ILayerState extends IHookable<ILayerStateHooks> {
|
|||||||
*/
|
*/
|
||||||
removeLayer(layer: IMapLayer): void;
|
removeLayer(layer: IMapLayer): void;
|
||||||
|
|
||||||
/**
|
|
||||||
* 当前地图状态对象是否包含指定图层对象
|
|
||||||
* @param layer 图层对象
|
|
||||||
*/
|
|
||||||
hasLayer(layer: IMapLayer): boolean;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置图层别名
|
* 设置图层别名
|
||||||
* @param layer 图层对象
|
* @param layer 图层对象
|
||||||
@ -123,17 +49,6 @@ export interface ILayerState extends IHookable<ILayerStateHooks> {
|
|||||||
height: number,
|
height: number,
|
||||||
keepBlock?: boolean
|
keepBlock?: boolean
|
||||||
): void;
|
): void;
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置背景图块
|
|
||||||
* @param tile 背景图块数字
|
|
||||||
*/
|
|
||||||
setBackground(tile: number): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取背景图块数字,如果没有设置过,则返回 0
|
|
||||||
*/
|
|
||||||
getBackground(): number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICoreState {
|
export interface ICoreState {
|
||||||
|
|||||||
@ -2,19 +2,25 @@ import { isNil } from 'lodash-es';
|
|||||||
import {
|
import {
|
||||||
IMapLayer,
|
IMapLayer,
|
||||||
IMapLayerData,
|
IMapLayerData,
|
||||||
IMapLayerHookController,
|
IMapLayerExtends,
|
||||||
IMapLayerHooks
|
IMapLayerExtendsController
|
||||||
} from './types';
|
} from './types';
|
||||||
import { Hookable, HookController, logger } from '@motajs/common';
|
import { logger } from '@motajs/common';
|
||||||
|
|
||||||
export class MapLayer
|
interface IExtendsData {
|
||||||
extends Hookable<IMapLayerHooks, IMapLayerHookController>
|
readonly ex: IMapLayerExtends;
|
||||||
implements IMapLayer
|
readonly controller: IMapLayerExtendsController;
|
||||||
{
|
}
|
||||||
|
|
||||||
|
export class MapLayer implements IMapLayer {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
empty: boolean = true;
|
empty: boolean = true;
|
||||||
zIndex: number = 0;
|
|
||||||
|
/** 已经加载完毕的图层拓展 */
|
||||||
|
private loadedExtends: Set<IExtendsData> = new Set();
|
||||||
|
/** 添加的图层拓展 */
|
||||||
|
private addedExtends: Map<string, IExtendsData> = new Map();
|
||||||
|
|
||||||
/** 地图图块数组 */
|
/** 地图图块数组 */
|
||||||
private mapArray: Uint32Array;
|
private mapArray: Uint32Array;
|
||||||
@ -22,7 +28,6 @@ export class MapLayer
|
|||||||
private mapData: IMapLayerData;
|
private mapData: IMapLayerData;
|
||||||
|
|
||||||
constructor(array: Uint32Array, width: number, height: number) {
|
constructor(array: Uint32Array, width: number, height: number) {
|
||||||
super();
|
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
const area = width * height;
|
const area = width * height;
|
||||||
@ -37,6 +42,9 @@ export class MapLayer
|
|||||||
|
|
||||||
resize(width: number, height: number): void {
|
resize(width: number, height: number): void {
|
||||||
if (this.width === width && this.height === height) {
|
if (this.width === width && this.height === height) {
|
||||||
|
this.loadedExtends.forEach(v => {
|
||||||
|
v.ex.onResize?.(v.controller, width, height);
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.mapData.expired = true;
|
this.mapData.expired = true;
|
||||||
@ -70,14 +78,17 @@ export class MapLayer
|
|||||||
expired: false,
|
expired: false,
|
||||||
array: this.mapArray
|
array: this.mapArray
|
||||||
};
|
};
|
||||||
this.forEachHook((hook, controller) => {
|
this.loadedExtends.forEach(v => {
|
||||||
hook.onResize?.(controller, width, height);
|
v.ex.onResize?.(v.controller, width, height);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
resize2(width: number, height: number): void {
|
resize2(width: number, height: number): void {
|
||||||
if (this.width === width && this.height === height) {
|
if (this.width === width && this.height === height) {
|
||||||
this.mapArray.fill(0);
|
this.mapArray.fill(0);
|
||||||
|
this.loadedExtends.forEach(v => {
|
||||||
|
v.ex.onResize?.(v.controller, width, height);
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.mapData.expired = true;
|
this.mapData.expired = true;
|
||||||
@ -88,18 +99,17 @@ export class MapLayer
|
|||||||
expired: false,
|
expired: false,
|
||||||
array: this.mapArray
|
array: this.mapArray
|
||||||
};
|
};
|
||||||
this.empty = true;
|
this.loadedExtends.forEach(v => {
|
||||||
this.forEachHook((hook, controller) => {
|
v.ex.onResize?.(v.controller, width, height);
|
||||||
hook.onResize?.(controller, width, height);
|
|
||||||
});
|
});
|
||||||
|
this.empty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
setBlock(block: number, x: number, y: number): void {
|
setBlock(block: number, x: number, y: number): void {
|
||||||
const index = y * this.width + x;
|
const index = y * this.width + x;
|
||||||
if (block === this.mapArray[index]) return;
|
|
||||||
this.mapArray[index] = block;
|
this.mapArray[index] = block;
|
||||||
this.forEachHook((hook, controller) => {
|
this.loadedExtends.forEach(v => {
|
||||||
hook.onUpdateBlock?.(controller, block, x, y);
|
v.ex.onUpdateBlock?.(v.controller, block, x, y);
|
||||||
});
|
});
|
||||||
if (block !== 0) {
|
if (block !== 0) {
|
||||||
this.empty = false;
|
this.empty = false;
|
||||||
@ -121,9 +131,6 @@ export class MapLayer
|
|||||||
const height = Math.ceil(array.length / width);
|
const height = Math.ceil(array.length / width);
|
||||||
if (width === this.width && height === this.height) {
|
if (width === this.width && height === this.height) {
|
||||||
this.mapArray.set(array);
|
this.mapArray.set(array);
|
||||||
this.forEachHook((hook, controller) => {
|
|
||||||
hook.onUpdateArea?.(controller, x, y, width, height);
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const w = this.width;
|
const w = this.width;
|
||||||
@ -149,8 +156,8 @@ export class MapLayer
|
|||||||
}
|
}
|
||||||
this.mapArray.set(array.subarray(start, start + nw), offset);
|
this.mapArray.set(array.subarray(start, start + nw), offset);
|
||||||
}
|
}
|
||||||
this.forEachHook((hook, controller) => {
|
this.loadedExtends.forEach(v => {
|
||||||
hook.onUpdateArea?.(controller, x, y, width, height);
|
v.ex.onUpdateArea?.(v.controller, x, y, width, height);
|
||||||
});
|
});
|
||||||
this.empty &&= empty;
|
this.empty &&= empty;
|
||||||
}
|
}
|
||||||
@ -203,32 +210,51 @@ export class MapLayer
|
|||||||
return this.mapData;
|
return this.mapData;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected createController(
|
loadExtends(ex: IMapLayerExtends): boolean {
|
||||||
hook: Partial<IMapLayerHooks>
|
if (!this.addedExtends.has(ex.id)) return false;
|
||||||
): IMapLayerHookController {
|
ex.awake?.();
|
||||||
return new MapLayerHookController(this, hook);
|
const data = this.addedExtends.get(ex.id)!;
|
||||||
|
this.loadedExtends.add(data);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
setZIndex(zIndex: number): void {
|
addExtends(ex: IMapLayerExtends): IMapLayerExtendsController {
|
||||||
this.zIndex = zIndex;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MapLayerHookController
|
class MapLayerExtendsController implements IMapLayerExtendsController {
|
||||||
extends HookController<IMapLayerHooks>
|
loaded: boolean = false;
|
||||||
implements IMapLayerHookController
|
|
||||||
{
|
|
||||||
hookable: MapLayer;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly layer: MapLayer,
|
readonly layer: MapLayer,
|
||||||
hook: Partial<IMapLayerHooks>
|
readonly ex: IMapLayerExtends
|
||||||
) {
|
) {}
|
||||||
super(layer, hook);
|
|
||||||
this.hookable = layer;
|
load(): void {
|
||||||
|
this.loaded = this.layer.loadExtends(this.ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
getMapData(): Readonly<IMapLayerData> {
|
getMapData(): Readonly<IMapLayerData> {
|
||||||
return this.layer.getMapRef();
|
return this.layer.getMapRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unload(): void {
|
||||||
|
this.layer.removeExtends(this.ex);
|
||||||
|
this.loaded = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
import { IHookable, IHookBase, IHookController } from '@motajs/common';
|
|
||||||
|
|
||||||
export interface IMapLayerData {
|
export interface IMapLayerData {
|
||||||
/** 当前引用是否过期,当地图图层内部的地图数组引用更新时,此项会变为 `true` */
|
/** 当前引用是否过期,当地图图层内部的地图数组引用更新时,此项会变为 `true` */
|
||||||
expired: boolean;
|
expired: boolean;
|
||||||
@ -7,15 +5,26 @@ export interface IMapLayerData {
|
|||||||
array: Uint32Array;
|
array: Uint32Array;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMapLayerHooks extends IHookBase {
|
export interface IMapLayerHooks {
|
||||||
/**
|
/**
|
||||||
* 当地图大小发生变化时执行,如果调用了地图的 `resize` 方法,但是地图大小没变,则不会触发
|
* 当钩子准备完毕时执行,会自动分析依赖,并把依赖实例作为参数传入,遵循依赖列表的顺序
|
||||||
|
* @param dependencies 依赖列表
|
||||||
|
*/
|
||||||
|
awake(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当拓展被移除之前执行,可以用来清理相关内容
|
||||||
|
*/
|
||||||
|
destroy(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当地图大小发生变化时执行
|
||||||
* @param controller 拓展控制器
|
* @param controller 拓展控制器
|
||||||
* @param width 地图宽度
|
* @param width 地图宽度
|
||||||
* @param height 地图高度
|
* @param height 地图高度
|
||||||
*/
|
*/
|
||||||
onResize(
|
onResize(
|
||||||
controller: IMapLayerHookController,
|
controller: IMapLayerExtendsController,
|
||||||
width: number,
|
width: number,
|
||||||
height: number
|
height: number
|
||||||
): void;
|
): void;
|
||||||
@ -29,7 +38,7 @@ export interface IMapLayerHooks extends IHookBase {
|
|||||||
* @param height 更新区域高度
|
* @param height 更新区域高度
|
||||||
*/
|
*/
|
||||||
onUpdateArea(
|
onUpdateArea(
|
||||||
controller: IMapLayerHookController,
|
controller: IMapLayerExtendsController,
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
width: number,
|
width: number,
|
||||||
@ -44,37 +53,47 @@ export interface IMapLayerHooks extends IHookBase {
|
|||||||
* @param y 更新点纵坐标
|
* @param y 更新点纵坐标
|
||||||
*/
|
*/
|
||||||
onUpdateBlock(
|
onUpdateBlock(
|
||||||
controller: IMapLayerHookController,
|
controller: IMapLayerExtendsController,
|
||||||
block: number,
|
block: number,
|
||||||
x: number,
|
x: number,
|
||||||
y: number
|
y: number
|
||||||
): void;
|
): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMapLayerHookController
|
export interface IMapLayerExtends extends Partial<IMapLayerHooks> {
|
||||||
extends IHookController<IMapLayerHooks> {
|
/** 这个拓展对象的标识符 */
|
||||||
|
readonly id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMapLayerExtendsController {
|
||||||
|
/** 当前图层拓展是否已经被加载 */
|
||||||
|
readonly loaded: boolean;
|
||||||
/** 拓展所属的图层对象 */
|
/** 拓展所属的图层对象 */
|
||||||
readonly layer: IMapLayer;
|
readonly layer: IMapLayer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载此图层拓展,如果拓展依赖了其他拓展并且已经添加,将会自动加载其他拓展
|
||||||
|
*/
|
||||||
|
load(): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取地图数据,是对内部存储的直接引用
|
* 获取地图数据,是对内部存储的直接引用
|
||||||
*/
|
*/
|
||||||
getMapData(): Readonly<IMapLayerData>;
|
getMapData(): Readonly<IMapLayerData>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 结束此拓展的生命周期,释放相关资源
|
||||||
|
*/
|
||||||
|
unload(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMapLayer
|
export interface IMapLayer {
|
||||||
extends IHookable<IMapLayerHooks, IMapLayerHookController> {
|
|
||||||
/** 地图宽度 */
|
/** 地图宽度 */
|
||||||
readonly width: number;
|
readonly width: number;
|
||||||
/** 地图高度 */
|
/** 地图高度 */
|
||||||
readonly height: number;
|
readonly height: number;
|
||||||
/**
|
/** 地图是否全部空白,此值具有保守性,即如果其为 `true`,则地图一定空白,但是如果其为 `false`,那么地图也有可能空白 */
|
||||||
* 地图是否全部空白,此值具有充分性,但不具有必要性,
|
|
||||||
* 即如果其为 `true`,则地图一定空白,但是如果其为 `false`,那么地图也有可能空白
|
|
||||||
*/
|
|
||||||
readonly empty: boolean;
|
readonly empty: boolean;
|
||||||
/** 图层纵深 */
|
|
||||||
readonly zIndex: number;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 调整地图尺寸,维持原有图块。如果尺寸变大,那么会补零,如果尺寸变小,那么会将当前数组裁剪
|
* 调整地图尺寸,维持原有图块。如果尺寸变大,那么会补零,如果尺寸变小,那么会将当前数组裁剪
|
||||||
@ -134,13 +153,15 @@ export interface IMapLayer
|
|||||||
): Uint32Array;
|
): Uint32Array;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取整个地图的地图数组,是对内部数组的直接引用
|
* 添加图层拓展,使用一系列钩子与图层本身通讯。不同图层拓展没有顺序关系。
|
||||||
|
* @param ex 图层拓展对象
|
||||||
|
* @returns 图层拓展控制对象,可以通过它来控制拓展的生命周期,也可以用于获取图层内的一些数据
|
||||||
*/
|
*/
|
||||||
getMapRef(): IMapLayerData;
|
addExtends(ex: IMapLayerExtends): IMapLayerExtendsController;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置地图纵深,会影响渲染的遮挡顺序
|
* 移除指定的图层拓展对象
|
||||||
* @param zIndex 纵深
|
* @param ex 要移除的图层拓展对象,也可以填拓展对象的标识符
|
||||||
*/
|
*/
|
||||||
setZIndex(zIndex: number): void;
|
removeExtends(ex: IMapLayerExtends | string): void;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,32 +1,32 @@
|
|||||||
import { isNil } from 'lodash-es';
|
import { isNil } from 'lodash-es';
|
||||||
import { IDirtyMark, IDirtyTracker } from './types';
|
import { IDirtyTracker } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 布尔类型的脏标记追踪器。当传入 `dirtySince` 的标记不属于当前的追踪器时,会返回 `true`
|
* 布尔类型的脏标记追踪器。当传入 `dirtySince` 的标记不属于当前的追踪器时,会返回 `true`
|
||||||
*/
|
*/
|
||||||
export class PrivateBooleanDirtyTracker implements IDirtyTracker<boolean> {
|
export class PrivateBooleanDirtyTracker implements IDirtyTracker<boolean> {
|
||||||
/** 标记映射 */
|
/** 标记映射 */
|
||||||
private markMap: WeakMap<IDirtyMark, number> = new WeakMap();
|
private markMap: WeakMap<symbol, number> = new WeakMap();
|
||||||
/** 脏标记 */
|
/** 脏标记 */
|
||||||
private dirtyFlag: number = 0;
|
private dirtyFlag: number = 0;
|
||||||
|
|
||||||
mark(): IDirtyMark {
|
mark(): symbol {
|
||||||
const symbol = {};
|
const symbol = Symbol();
|
||||||
this.markMap.set(symbol, this.dirtyFlag);
|
this.markMap.set(symbol, this.dirtyFlag);
|
||||||
return symbol;
|
return symbol;
|
||||||
}
|
}
|
||||||
|
|
||||||
unmark(mark: IDirtyMark): void {
|
unmark(mark: symbol): void {
|
||||||
this.markMap.delete(mark);
|
this.markMap.delete(mark);
|
||||||
}
|
}
|
||||||
|
|
||||||
dirtySince(mark: IDirtyMark): boolean {
|
dirtySince(mark: symbol): boolean {
|
||||||
const num = this.markMap.get(mark);
|
const num = this.markMap.get(mark);
|
||||||
if (isNil(num)) return true;
|
if (isNil(num)) return true;
|
||||||
return num < this.dirtyFlag;
|
return num < this.dirtyFlag;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasMark(symbol: IDirtyMark): boolean {
|
hasMark(symbol: symbol): boolean {
|
||||||
return this.markMap.has(symbol);
|
return this.markMap.has(symbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,24 +47,24 @@ export class PrivateListDirtyTracker<T extends number>
|
|||||||
/** 标记映射,键表示在索引,值表示其对应的标记数字 */
|
/** 标记映射,键表示在索引,值表示其对应的标记数字 */
|
||||||
private readonly markMap: Map<T, number> = new Map();
|
private readonly markMap: Map<T, number> = new Map();
|
||||||
/** 标记 symbol 映射,值表示这个 symbol 对应的标记数字 */
|
/** 标记 symbol 映射,值表示这个 symbol 对应的标记数字 */
|
||||||
private readonly symbolMap: WeakMap<{}, number> = new WeakMap();
|
private readonly symbolMap: WeakMap<symbol, number> = new WeakMap();
|
||||||
|
|
||||||
/** 脏标记数字 */
|
/** 脏标记数字 */
|
||||||
private dirtyFlag: number = 0;
|
private dirtyFlag: number = 0;
|
||||||
|
|
||||||
constructor(protected length: number) {}
|
constructor(protected length: number) {}
|
||||||
|
|
||||||
mark(): IDirtyMark {
|
mark(): symbol {
|
||||||
const symbol = {};
|
const symbol = Symbol();
|
||||||
this.symbolMap.set(symbol, this.dirtyFlag);
|
this.symbolMap.set(symbol, this.dirtyFlag);
|
||||||
return symbol;
|
return symbol;
|
||||||
}
|
}
|
||||||
|
|
||||||
unmark(mark: IDirtyMark): void {
|
unmark(mark: symbol): void {
|
||||||
this.symbolMap.delete(mark);
|
this.symbolMap.delete(mark);
|
||||||
}
|
}
|
||||||
|
|
||||||
dirtySince(mark: IDirtyMark): Set<T> {
|
dirtySince(mark: symbol): Set<T> {
|
||||||
const num = this.symbolMap.get(mark);
|
const num = this.symbolMap.get(mark);
|
||||||
const res = new Set<T>();
|
const res = new Set<T>();
|
||||||
if (isNil(num)) return res;
|
if (isNil(num)) return res;
|
||||||
@ -74,7 +74,7 @@ export class PrivateListDirtyTracker<T extends number>
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasMark(symbol: IDirtyMark): boolean {
|
hasMark(symbol: symbol): boolean {
|
||||||
return this.symbolMap.has(symbol);
|
return this.symbolMap.has(symbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,24 +95,24 @@ export class PrivateMapDirtyTracker<T extends string>
|
|||||||
/** 标记映射,键表示名称,值表示其对应的标记数字 */
|
/** 标记映射,键表示名称,值表示其对应的标记数字 */
|
||||||
private readonly markMap: Map<T, number> = new Map();
|
private readonly markMap: Map<T, number> = new Map();
|
||||||
/** 标记 symbol 映射,值表示这个 symbol 对应的标记数字 */
|
/** 标记 symbol 映射,值表示这个 symbol 对应的标记数字 */
|
||||||
private readonly symbolMap: WeakMap<{}, number> = new WeakMap();
|
private readonly symbolMap: WeakMap<symbol, number> = new WeakMap();
|
||||||
|
|
||||||
/** 脏标记数字 */
|
/** 脏标记数字 */
|
||||||
private dirtyFlag: number = 0;
|
private dirtyFlag: number = 0;
|
||||||
|
|
||||||
constructor(protected length: number) {}
|
constructor(protected length: number) {}
|
||||||
|
|
||||||
mark(): IDirtyMark {
|
mark(): symbol {
|
||||||
const symbol = {};
|
const symbol = Symbol();
|
||||||
this.symbolMap.set(symbol, this.dirtyFlag);
|
this.symbolMap.set(symbol, this.dirtyFlag);
|
||||||
return symbol;
|
return symbol;
|
||||||
}
|
}
|
||||||
|
|
||||||
unmark(mark: IDirtyMark): void {
|
unmark(mark: symbol): void {
|
||||||
this.symbolMap.delete(mark);
|
this.symbolMap.delete(mark);
|
||||||
}
|
}
|
||||||
|
|
||||||
dirtySince(mark: IDirtyMark): Record<T, boolean> {
|
dirtySince(mark: symbol): Record<T, boolean> {
|
||||||
const num = this.symbolMap.get(mark) ?? 0;
|
const num = this.symbolMap.get(mark) ?? 0;
|
||||||
const obj: Partial<Record<T, boolean>> = {};
|
const obj: Partial<Record<T, boolean>> = {};
|
||||||
this.markMap.forEach((v, k) => {
|
this.markMap.forEach((v, k) => {
|
||||||
@ -122,7 +122,7 @@ export class PrivateMapDirtyTracker<T extends string>
|
|||||||
return obj as Record<T, boolean>;
|
return obj as Record<T, boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasMark(symbol: IDirtyMark): boolean {
|
hasMark(symbol: symbol): boolean {
|
||||||
return this.symbolMap.has(symbol);
|
return this.symbolMap.has(symbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,82 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,5 +1,4 @@
|
|||||||
export * from './dirtyTracker';
|
export * from './dirtyTracker';
|
||||||
export * from './hook';
|
|
||||||
export * from './logger';
|
export * from './logger';
|
||||||
export * from './utils';
|
export * from './utils';
|
||||||
export * from './types';
|
export * from './types';
|
||||||
|
|||||||
@ -41,7 +41,7 @@
|
|||||||
"39": "Offset pool size exceeds WebGL2 limitation, ensure size type of your big image is less than $1.",
|
"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.",
|
"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.",
|
"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 layerState property of map-render element is required.",
|
"42": "The layerList property of map-render element is required.",
|
||||||
"1101": "Shadow extension needs 'floor-hero' extension as dependency.",
|
"1101": "Shadow extension needs 'floor-hero' extension as dependency.",
|
||||||
"1201": "Floor-damage extension needs 'floor-binder' extension as dependency.",
|
"1201": "Floor-damage extension needs 'floor-binder' extension as dependency.",
|
||||||
"1301": "Portal extension need 'floor-binder' extension as dependency.",
|
"1301": "Portal extension need 'floor-binder' extension as dependency.",
|
||||||
@ -130,9 +130,8 @@
|
|||||||
"80": "Parameter count of MapLayer.getMapData must be 0 or 4.",
|
"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.",
|
"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.",
|
"82": "Big image offset size is larger than 64. Considier reduce big image offset bucket.",
|
||||||
"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.",
|
"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.",
|
||||||
"84": "Cannot set alias '$1' for layer, since '$1' is already an alias for another layer.",
|
"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.",
|
"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."
|
"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."
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
//#region 脏标记
|
|
||||||
|
|
||||||
export interface IDirtyMarker<T> {
|
export interface IDirtyMarker<T> {
|
||||||
/**
|
/**
|
||||||
* 标记为脏,即进行了一次更新
|
* 标记为脏,即进行了一次更新
|
||||||
@ -8,109 +6,27 @@ export interface IDirtyMarker<T> {
|
|||||||
dirty(data: T): void;
|
dirty(data: T): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IDirtyMark {}
|
|
||||||
|
|
||||||
export interface IDirtyTracker<T> {
|
export interface IDirtyTracker<T> {
|
||||||
/**
|
/**
|
||||||
* 对状态进行标记
|
* 对状态进行标记
|
||||||
*/
|
*/
|
||||||
mark(): IDirtyMark;
|
mark(): symbol;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 取消指定标记符号
|
* 取消指定标记符号
|
||||||
* @param mark 标记符号
|
* @param mark 标记符号
|
||||||
*/
|
*/
|
||||||
unmark(mark: IDirtyMark): void;
|
unmark(mark: symbol): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从指定标记符号开始,数据是否发生了变动
|
* 从指定标记符号开始,数据是否发生了变动
|
||||||
* @param mark 标记符号
|
* @param mark 标记符号
|
||||||
*/
|
*/
|
||||||
dirtySince(mark: IDirtyMark): T;
|
dirtySince(mark: symbol): T;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 当前追踪器是否包含指定标记符号
|
* 当前追踪器是否包含指定标记符号
|
||||||
* @param symbol 标记符号
|
* @param symbol 标记符号
|
||||||
*/
|
*/
|
||||||
hasMark(symbol: IDirtyMark): boolean;
|
hasMark(symbol: symbol): 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
|
|
||||||
|
|||||||
@ -45,7 +45,8 @@ export class TextureColumnAnimater implements ITextureAnimater<number> {
|
|||||||
*once(texture: ITexture, frames: number): Generator<ITextureRenderable> {
|
*once(texture: ITexture, frames: number): Generator<ITextureRenderable> {
|
||||||
if (frames <= 0) return;
|
if (frames <= 0) return;
|
||||||
const renderable = texture.render();
|
const renderable = texture.render();
|
||||||
const { x: ox, y: oy, w: width, h } = renderable.rect;
|
const { x: ox, y: oy } = renderable.rect;
|
||||||
|
const { width, height: h } = texture!;
|
||||||
const w = width / frames;
|
const w = width / frames;
|
||||||
for (let i = 0; i < frames; i++) {
|
for (let i = 0; i < frames; i++) {
|
||||||
const renderable: ITextureRenderable = {
|
const renderable: ITextureRenderable = {
|
||||||
|
|||||||
@ -64,10 +64,6 @@ export class TextureStore<T extends ITexture = ITexture>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hasTexture(identifier: number): boolean {
|
|
||||||
return this.texMap.has(identifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
getTexture(identifier: number): T | null {
|
getTexture(identifier: number): T | null {
|
||||||
return this.texMap.get(identifier) ?? null;
|
return this.texMap.get(identifier) ?? null;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import {
|
|||||||
ITextureSplitter,
|
ITextureSplitter,
|
||||||
SizedCanvasImageSource
|
SizedCanvasImageSource
|
||||||
} from './types';
|
} from './types';
|
||||||
import { clamp } from 'lodash-es';
|
|
||||||
|
|
||||||
export class Texture implements ITexture {
|
export class Texture implements ITexture {
|
||||||
source: SizedCanvasImageSource;
|
source: SizedCanvasImageSource;
|
||||||
@ -92,11 +91,11 @@ export class Texture implements ITexture {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clampRect(rect: Readonly<IRect>): Readonly<IRect> {
|
clampRect(rect: Readonly<IRect>): Readonly<IRect> {
|
||||||
const l = clamp(rect.x, this.cl, this.cr);
|
const l = Math.max(this.cl, this.cl + rect.x);
|
||||||
const t = clamp(rect.y, this.ct, this.cb);
|
const t = Math.max(this.ct, this.ct + rect.y);
|
||||||
const r = clamp(rect.x + rect.w, this.cl, this.cr);
|
const r = Math.min(l + rect.w, this.cr);
|
||||||
const b = clamp(rect.y + rect.h, this.ct, this.cb);
|
const b = Math.min(t + rect.h, this.cb);
|
||||||
return { x: l, y: t, w: r - l, h: b - t };
|
return { x: l, y: t, w: r - b, h: b - t };
|
||||||
}
|
}
|
||||||
|
|
||||||
clipped(rect: Readonly<IRect>): ITextureRenderable {
|
clipped(rect: Readonly<IRect>): ITextureRenderable {
|
||||||
|
|||||||
@ -173,12 +173,6 @@ export interface ITextureStore<T extends ITexture = ITexture> {
|
|||||||
*/
|
*/
|
||||||
removeTexture(identifier: number | string | T): void;
|
removeTexture(identifier: number | string | T): void;
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断当前贴图存储对象是否包含指定贴图 id
|
|
||||||
* @param identifier 贴图对象 id
|
|
||||||
*/
|
|
||||||
hasTexture(identifier: number): boolean;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据贴图对象 id 获取贴图
|
* 根据贴图对象 id 获取贴图
|
||||||
* @param identifier 贴图对象 id
|
* @param identifier 贴图对象 id
|
||||||
|
|||||||
@ -1237,7 +1237,7 @@ export class GL2Program extends EventEmitter<ShaderProgramEvent> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置渲染模式,目前可选 {@link GL2.DRAW_ARRAYS} 至 {@link GL2.DRAW_INSTANCED}
|
* 设置渲染模式,目前可选 {@link Shader.DRAW_ARRAYS} 至 {@link Shader.DRAW_INSTANCED}
|
||||||
*/
|
*/
|
||||||
mode(mode: RenderMode) {
|
mode(mode: RenderMode) {
|
||||||
this.renderMode = mode;
|
this.renderMode = mode;
|
||||||
|
|||||||
@ -67,7 +67,7 @@ main.floors.MT13=
|
|||||||
],
|
],
|
||||||
"beforeBattle": {},
|
"beforeBattle": {},
|
||||||
"weather": [
|
"weather": [
|
||||||
"rain",
|
"sun",
|
||||||
8
|
8
|
||||||
],
|
],
|
||||||
"cannotMoveIn": {},
|
"cannotMoveIn": {},
|
||||||
|
|||||||
@ -161,48 +161,6 @@ var functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a = {
|
|||||||
// ---------- 重绘新地图;这一步将会设置core.status.floorId ---------- //
|
// ---------- 重绘新地图;这一步将会设置core.status.floorId ---------- //
|
||||||
core.drawMap(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);
|
heroLoc.direction = core.turnDirection(heroLoc.direction);
|
||||||
core.setHeroLoc('x', heroLoc.x);
|
core.setHeroLoc('x', heroLoc.x);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user