mirror of
https://github.com/motajs/template.git
synced 2026-11-01 06:04:30 +08:00
feat: 将 HumanBreak 新内容移植入该样板
This commit is contained in:
1 parent
6bf32174d8
commit
bea2725d6a
128 files changed
+11984
-573
No files matched your search
@@ -0,0 +1,7 @@
|
||||
import { createMaterial } from './material';
|
||||
|
||||
export function create() {
|
||||
createMaterial();
|
||||
}
|
||||
|
||||
export * from './material';
|
||||
@@ -0,0 +1,454 @@
|
||||
import {
|
||||
IRect,
|
||||
ITextureRenderable,
|
||||
SizedCanvasImageSource
|
||||
} from '@motajs/render-assets';
|
||||
import {
|
||||
AutotileConnection,
|
||||
AutotileType,
|
||||
BlockCls,
|
||||
IAutotileConnection,
|
||||
IAutotileProcessor,
|
||||
IMaterialFramedData,
|
||||
IMaterialManager
|
||||
} from './types';
|
||||
import { isNil } from 'lodash-es';
|
||||
|
||||
interface ConnectedAutotile {
|
||||
readonly lt: Readonly<IRect>;
|
||||
readonly rt: Readonly<IRect>;
|
||||
readonly rb: Readonly<IRect>;
|
||||
readonly lb: Readonly<IRect>;
|
||||
}
|
||||
|
||||
export interface IAutotileData {
|
||||
/** 图像源 */
|
||||
readonly source: SizedCanvasImageSource;
|
||||
/** 自动元件帧数 */
|
||||
readonly frames: number;
|
||||
}
|
||||
|
||||
/** 3x4 自动元件的连接映射,元组表示将对应大小的自动元件按照格子 1/4 大小切分后对应的索引位置 */
|
||||
const connectionMap3x4 = new Map<number, [number, number, number, number]>();
|
||||
/** 2x3 自动元件的连接映射,元组表示将对应大小的自动元件按照格子 1/4 大小切分后对应的索引位置 */
|
||||
const connectionMap2x3 = new Map<number, [number, number, number, number]>();
|
||||
/** 3x4 自动元件各方向连接的矩形映射 */
|
||||
const rectMap3x4 = new Map<number, ConnectedAutotile>();
|
||||
/** 2x3 自动元件各方向连接的矩形映射 */
|
||||
const rectMap2x3 = new Map<number, ConnectedAutotile>();
|
||||
/** 不重复连接映射,用于平铺自动元件,一共 48 种 */
|
||||
const distinctConnectionMap = new Map<number, number>();
|
||||
|
||||
export class AutotileProcessor implements IAutotileProcessor {
|
||||
/** 自动元件父子关系映射,子元件 -> 父元件 */
|
||||
readonly parentMap: Map<number, number> = new Map();
|
||||
/** 自动元件父子关系映射,父元件 -> 子元件列表 */
|
||||
readonly childMap: Map<number, Set<number>> = new Map();
|
||||
|
||||
constructor(readonly manager: IMaterialManager) {}
|
||||
|
||||
private ensureChildSet(num: number) {
|
||||
const set = this.childMap.get(num);
|
||||
if (set) return set;
|
||||
const ensure = new Set<number>();
|
||||
this.childMap.set(num, ensure);
|
||||
return ensure;
|
||||
}
|
||||
|
||||
setConnection(autotile: number, parent: number): void {
|
||||
this.parentMap.set(autotile, parent);
|
||||
const child = this.ensureChildSet(parent);
|
||||
child.add(autotile);
|
||||
}
|
||||
|
||||
private connectEdge(length: number, index: number, width: number): number {
|
||||
// 最高位表示左上,低位依次顺时针旋转
|
||||
|
||||
// 如果地图大小只有 1
|
||||
if (length === 1) {
|
||||
return 0b1111_1111;
|
||||
}
|
||||
// 如果地图高度只有 1
|
||||
if (length === width) {
|
||||
if (index === 0) {
|
||||
return 0b1110_1111;
|
||||
} else if (index === length - 1) {
|
||||
return 0b1111_1110;
|
||||
} else {
|
||||
return 0b1110_1110;
|
||||
}
|
||||
}
|
||||
// 如果地图宽度只有 1
|
||||
if (width === 1) {
|
||||
if (index === 0) {
|
||||
return 0b1111_1011;
|
||||
} else if (index === length - 1) {
|
||||
return 0b1011_1111;
|
||||
} else {
|
||||
return 0b1011_1011;
|
||||
}
|
||||
}
|
||||
|
||||
// 正常地图
|
||||
|
||||
const lastLine = length - width;
|
||||
const x = index % width;
|
||||
|
||||
// 四个角,左上,右上,右下,左下
|
||||
if (index === 0) {
|
||||
return 0b1110_0011;
|
||||
} else if (index === width - 1) {
|
||||
return 0b1111_1000;
|
||||
} else if (index === length - 1) {
|
||||
return 0b0011_1110;
|
||||
} else if (index === lastLine) {
|
||||
return 0b1000_1111;
|
||||
}
|
||||
// 四条边,上,右,下,左
|
||||
else if (index < width) {
|
||||
return 0b1110_0000;
|
||||
} else if (x === width - 1) {
|
||||
return 0b0011_1000;
|
||||
} else if (index > lastLine) {
|
||||
return 0b0000_1110;
|
||||
} else if (x === 0) {
|
||||
return 0b1000_0011;
|
||||
}
|
||||
// 不在边缘
|
||||
else {
|
||||
return 0b0000_0000;
|
||||
}
|
||||
}
|
||||
|
||||
connect(
|
||||
array: Uint32Array,
|
||||
index: number,
|
||||
width: number
|
||||
): IAutotileConnection {
|
||||
const block = array[index];
|
||||
if (block === 0) {
|
||||
return {
|
||||
connection: 0,
|
||||
center: 0
|
||||
};
|
||||
}
|
||||
let res: number = this.connectEdge(array.length, index, width);
|
||||
const childList = this.childMap.get(block);
|
||||
|
||||
// 最高位表示左上,低位依次顺时针旋转
|
||||
const a7 = array[index - width - 1] ?? 0;
|
||||
const a6 = array[index - width] ?? 0;
|
||||
const a5 = array[index - width + 1] ?? 0;
|
||||
const a4 = array[index + 1] ?? 0;
|
||||
const a3 = array[index + width + 1] ?? 0;
|
||||
const a2 = array[index + width] ?? 0;
|
||||
const a1 = array[index + width - 1] ?? 0;
|
||||
const a0 = array[index - 1] ?? 0;
|
||||
|
||||
// Benchmark https://www.measurethat.net/Benchmarks/Show/35271/0/convert-boolean-to-number
|
||||
|
||||
if (!childList || childList.size === 0) {
|
||||
// 不包含子元件,那么直接跟相同的连接
|
||||
res |=
|
||||
+(a0 === block) |
|
||||
(+(a1 === block) << 1) |
|
||||
(+(a2 === block) << 2) |
|
||||
(+(a3 === block) << 3) |
|
||||
(+(a4 === block) << 4) |
|
||||
(+(a5 === block) << 5) |
|
||||
(+(a6 === block) << 6) |
|
||||
(+(a7 === block) << 7);
|
||||
} else {
|
||||
res |=
|
||||
+childList.has(a0) |
|
||||
(+childList.has(a1) << 1) |
|
||||
(+childList.has(a2) << 2) |
|
||||
(+childList.has(a3) << 3) |
|
||||
(+childList.has(a4) << 4) |
|
||||
(+childList.has(a5) << 5) |
|
||||
(+childList.has(a6) << 6) |
|
||||
(+childList.has(a7) << 7);
|
||||
}
|
||||
|
||||
return {
|
||||
connection: res,
|
||||
center: block
|
||||
};
|
||||
}
|
||||
|
||||
updateConnectionFor(
|
||||
connection: number,
|
||||
center: number,
|
||||
target: number,
|
||||
direction: AutotileConnection
|
||||
): number {
|
||||
const childList = this.childMap.get(center);
|
||||
if (!childList || !childList.has(target)) {
|
||||
return connection & ~direction;
|
||||
} else {
|
||||
return connection | direction;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查贴图是否是一个自动元件
|
||||
* @param tile 贴图数据
|
||||
*/
|
||||
private checkAutotile(tile: IMaterialFramedData) {
|
||||
if (tile.cls !== BlockCls.Autotile) return false;
|
||||
const { texture, frames } = tile;
|
||||
if (texture.width !== 96 * frames) return false;
|
||||
if (texture.height === 128 || texture.height === 144) return true;
|
||||
else return false;
|
||||
}
|
||||
|
||||
render(autotile: number, connection: number): ITextureRenderable | null {
|
||||
const tile = this.manager.getTile(autotile);
|
||||
if (!tile) return null;
|
||||
if (!this.checkAutotile(tile)) return null;
|
||||
return this.renderWithoutCheck(tile, connection);
|
||||
}
|
||||
|
||||
renderWith(
|
||||
tile: IMaterialFramedData,
|
||||
connection: number
|
||||
): ITextureRenderable | null {
|
||||
if (!this.checkAutotile(tile)) return null;
|
||||
return this.renderWithoutCheck(tile, connection);
|
||||
}
|
||||
|
||||
renderWithoutCheck(
|
||||
tile: IMaterialFramedData,
|
||||
connection: number
|
||||
): ITextureRenderable | null {
|
||||
const { texture } = tile;
|
||||
const size = texture.height === 32 * 48 ? 32 : 48;
|
||||
const index = distinctConnectionMap.get(connection);
|
||||
if (isNil(index)) return null;
|
||||
const { rect } = texture.render();
|
||||
return {
|
||||
source: texture.source,
|
||||
rect: { x: rect.x, y: rect.y + size * index, w: size, h: size }
|
||||
};
|
||||
}
|
||||
|
||||
*renderAnimated(
|
||||
autotile: number,
|
||||
connection: number
|
||||
): Generator<ITextureRenderable, void> {
|
||||
const tile = this.manager.getTile(autotile);
|
||||
if (!tile) return;
|
||||
yield* this.renderAnimatedWith(tile, connection);
|
||||
}
|
||||
|
||||
*renderAnimatedWith(
|
||||
tile: IMaterialFramedData,
|
||||
connection: number
|
||||
): Generator<ITextureRenderable, void> {
|
||||
if (!this.checkAutotile(tile)) return;
|
||||
const { texture, frames } = tile;
|
||||
const size = texture.height === 128 ? 32 : 48;
|
||||
const index = distinctConnectionMap.get(connection);
|
||||
if (isNil(index)) return;
|
||||
for (let i = 0; i < frames; i++) {
|
||||
yield {
|
||||
source: texture.source,
|
||||
rect: { x: i * size, y: size * index, w: size, h: size }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将自动元件图片展平,平铺存储 48 种样式,此时可以只通过一次绘制来绘制出自动元件,不需要四次绘制
|
||||
* @param image 原始自动元件图片
|
||||
*/
|
||||
static flatten(image: IAutotileData): SizedCanvasImageSource | null {
|
||||
const { source, frames } = image;
|
||||
if (source.width !== frames * 96) return null;
|
||||
if (source.height !== 128 && source.height !== 144) return null;
|
||||
const type =
|
||||
source.height === 128 ? AutotileType.Big3x4 : AutotileType.Small2x3;
|
||||
const size = type === AutotileType.Big3x4 ? 32 : 48;
|
||||
const width = frames * size;
|
||||
const height = 48 * size;
|
||||
// 画到画布上
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return null;
|
||||
const half = size / 2;
|
||||
const map = type === AutotileType.Big3x4 ? rectMap3x4 : rectMap2x3;
|
||||
const used = new Set<number>();
|
||||
// 遍历每个组合
|
||||
distinctConnectionMap.forEach((index, conn) => {
|
||||
if (used.has(conn)) return;
|
||||
used.add(conn);
|
||||
const { lt, rt, rb, lb } = map.get(conn)!;
|
||||
const y = index * size;
|
||||
for (let i = 0; i < frames; i++) {
|
||||
const x = i * size;
|
||||
// prettier-ignore
|
||||
ctx.drawImage(source, lt.x + i * 96, lt.y, lt.w, lt.h, x, y, half, half);
|
||||
// prettier-ignore
|
||||
ctx.drawImage(source, rt.x + i * 96, rt.y, rt.w, rt.h, x + half, y, half, half);
|
||||
// prettier-ignore
|
||||
ctx.drawImage(source, rb.x + i * 96, rb.y, rb.w, rb.h, x + half, y + half, half, half);
|
||||
// prettier-ignore
|
||||
ctx.drawImage(source, lb.x + i * 96, lb.y, lb.w, lb.h, x, y + half, half, half);
|
||||
}
|
||||
});
|
||||
|
||||
return canvas;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 映射自动元件连接
|
||||
* @param target 输出映射对象
|
||||
* @param mode 自动元件类型,1 表示 3x4,2 表示 2x3
|
||||
*/
|
||||
function mapAutotile(
|
||||
target: Map<number, [number, number, number, number]>,
|
||||
mode: 1 | 2
|
||||
) {
|
||||
const h = mode === 1 ? 2 : 1; // 横向偏移因子
|
||||
const v = mode === 1 ? 12 : 4; // 纵向偏移因子
|
||||
const luo = mode === 1 ? 12 : 8; // leftup origin
|
||||
const ruo = mode === 1 ? 17 : 11; // rightup origin
|
||||
const ldo = mode === 1 ? 42 : 20; // leftdown origin
|
||||
const rdo = mode === 1 ? 47 : 23; // rightdown origin
|
||||
const luc = mode === 1 ? 4 : 2; // leftup corner
|
||||
const ruc = mode === 1 ? 5 : 3; // rightup corner
|
||||
const rdc = mode === 1 ? 11 : 7; // rightdown corner
|
||||
const ldc = mode === 1 ? 10 : 6; // leftdown corner
|
||||
|
||||
for (let i = 0; i <= 0b1111_1111; i++) {
|
||||
// 自动元件由四个更小的矩形组合而成
|
||||
// 初始状态下,四个矩形分别处在四个角的位置
|
||||
// 而且对应角落的矩形只可能出现在每个大区块的对应角落
|
||||
|
||||
let lu = luo; // leftup
|
||||
let ru = ruo; // rightup
|
||||
let ld = ldo; // leftdown
|
||||
let rd = rdo; // rightdown
|
||||
|
||||
// 先看四个方向,最后看斜角方向
|
||||
if (i & 0b0000_0001) {
|
||||
// 左侧有连接,左侧两个矩形向右偏移两个因子
|
||||
lu += h * 2;
|
||||
ld += h * 2;
|
||||
// 如果右侧还有连接,那么右侧矩形和左侧矩形需要移动至中间
|
||||
// 但是由于后面还处理了先右侧再左侧的情况,因此需要先向右偏移一个因子
|
||||
// 结果就是先向右移动了一个因子,在后面又向左移动了两个因子,因此相当于向左移动了一个因子
|
||||
if (i & 0b0001_0000) {
|
||||
ru += h;
|
||||
rd += h;
|
||||
}
|
||||
}
|
||||
if (i & 0b0000_0100) {
|
||||
// 下侧有连接,下侧两个矩形向上偏移两个因子
|
||||
ld -= v * 2;
|
||||
rd -= v * 2;
|
||||
if (i & 0b0100_0000) {
|
||||
lu -= v;
|
||||
ru -= v;
|
||||
}
|
||||
}
|
||||
if (i & 0b0001_0000) {
|
||||
// 右侧有连接,右侧矩形向左移动两个因子
|
||||
ru -= h * 2;
|
||||
rd -= h * 2;
|
||||
if (i & 0b0000_0001) {
|
||||
lu -= h;
|
||||
ld -= h;
|
||||
}
|
||||
}
|
||||
if (i & 0b0100_0000) {
|
||||
// 上侧有链接,上侧矩形向下移动两个因子
|
||||
lu += v * 2;
|
||||
ru += v * 2;
|
||||
if (i & 0b0000_0100) {
|
||||
ld += v;
|
||||
rd += v;
|
||||
}
|
||||
}
|
||||
// 斜角
|
||||
// 如果左上仅与上和左连接
|
||||
if ((i & 0b1100_0001) === 0b0100_0001) {
|
||||
lu = luc;
|
||||
}
|
||||
// 如果右上仅与上和右连接
|
||||
if ((i & 0b0111_0000) === 0b0101_0000) {
|
||||
ru = ruc;
|
||||
}
|
||||
// 如果右下仅与右和下连接
|
||||
if ((i & 0b0001_1100) === 0b0001_0100) {
|
||||
rd = rdc;
|
||||
}
|
||||
// 如果左下仅与左和下连接
|
||||
if ((i & 0b0000_0111) === 0b0000_0101) {
|
||||
ld = ldc;
|
||||
}
|
||||
target.set(i, [lu, ru, rd, ld]);
|
||||
}
|
||||
}
|
||||
|
||||
export function createAutotile() {
|
||||
mapAutotile(connectionMap3x4, 1);
|
||||
mapAutotile(connectionMap2x3, 2);
|
||||
|
||||
connectionMap3x4.forEach((data, connection) => {
|
||||
const [ltd, rtd, rbd, lbd] = data;
|
||||
const ltx = (ltd % 6) * 16;
|
||||
const lty = Math.floor(ltd / 6) * 16;
|
||||
const rtx = (rtd % 6) * 16;
|
||||
const rty = Math.floor(rtd / 6) * 16;
|
||||
const rbx = (rbd % 6) * 16;
|
||||
const rby = Math.floor(rbd / 6) * 16;
|
||||
const lbx = (lbd % 6) * 16;
|
||||
const lby = Math.floor(lbd / 6) * 16;
|
||||
rectMap3x4.set(connection, {
|
||||
lt: { x: ltx, y: lty, w: 16, h: 16 },
|
||||
rt: { x: rtx, y: rty, w: 16, h: 16 },
|
||||
rb: { x: rbx, y: rby, w: 16, h: 16 },
|
||||
lb: { x: lbx, y: lby, w: 16, h: 16 }
|
||||
});
|
||||
});
|
||||
connectionMap2x3.forEach((data, connection) => {
|
||||
const [ltd, rtd, rbd, lbd] = data;
|
||||
const ltx = (ltd % 4) * 24;
|
||||
const lty = Math.floor(ltd / 4) * 24;
|
||||
const rtx = (rtd % 4) * 24;
|
||||
const rty = Math.floor(rtd / 4) * 24;
|
||||
const rbx = (rbd % 4) * 24;
|
||||
const rby = Math.floor(rbd / 4) * 24;
|
||||
const lbx = (lbd % 4) * 24;
|
||||
const lby = Math.floor(lbd / 4) * 24;
|
||||
rectMap2x3.set(connection, {
|
||||
lt: { x: ltx, y: lty, w: 24, h: 24 },
|
||||
rt: { x: rtx, y: rty, w: 24, h: 24 },
|
||||
rb: { x: rbx, y: rby, w: 24, h: 24 },
|
||||
lb: { x: lbx, y: lby, w: 24, h: 24 }
|
||||
});
|
||||
});
|
||||
const usedRect: [number, number, number, number][] = [];
|
||||
let flag = 0;
|
||||
// 2x3 和 3x4 的自动元件连接方式一样,因此没必要映射两次
|
||||
connectionMap2x3.forEach((conn, num) => {
|
||||
const index = usedRect.findIndex(
|
||||
used =>
|
||||
used[0] === conn[0] &&
|
||||
used[1] === conn[1] &&
|
||||
used[2] === conn[2] &&
|
||||
used[3] === conn[3]
|
||||
);
|
||||
if (index === -1) {
|
||||
distinctConnectionMap.set(num, flag);
|
||||
usedRect.push(conn.slice() as [number, number, number, number]);
|
||||
flag++;
|
||||
} else {
|
||||
distinctConnectionMap.set(num, index);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
import {
|
||||
ITextureStore,
|
||||
ITexture,
|
||||
ITextureComposedData,
|
||||
ITextureStreamComposer,
|
||||
TextureMaxRectsStreamComposer,
|
||||
SizedCanvasImageSource
|
||||
} from '@motajs/render-assets';
|
||||
import { IAssetBuilder, IMaterialGetter, ITrackedAssetData } from './types';
|
||||
import { logger, PrivateListDirtyTracker } from '@motajs/common';
|
||||
|
||||
export class AssetBuilder implements IAssetBuilder {
|
||||
readonly composer: ITextureStreamComposer<void> =
|
||||
new TextureMaxRectsStreamComposer(4096, 4096, 0);
|
||||
|
||||
private output: ITextureStore | null = null;
|
||||
private started: boolean = false;
|
||||
|
||||
private readonly trackedData: TrackedAssetData;
|
||||
|
||||
/** 当前的索引 */
|
||||
private index: number = -1;
|
||||
|
||||
/** 贴图更新的 promise */
|
||||
private pending: Promise<void> = Promise.resolve();
|
||||
|
||||
constructor(readonly materials: IMaterialGetter) {
|
||||
this.trackedData = new TrackedAssetData(materials, this);
|
||||
}
|
||||
|
||||
pipe(store: ITextureStore): void {
|
||||
if (this.started) {
|
||||
logger.warn(76);
|
||||
return;
|
||||
}
|
||||
this.output = store;
|
||||
}
|
||||
|
||||
addTexture(texture: ITexture): ITextureComposedData {
|
||||
this.started = true;
|
||||
const res = [...this.composer.add([texture])];
|
||||
const data = res[0];
|
||||
|
||||
if (this.output) {
|
||||
if (data.index > this.index) {
|
||||
this.output.addTexture(data.index, data.texture);
|
||||
this.index = data.index;
|
||||
}
|
||||
}
|
||||
|
||||
this.pending = this.pending.then(() =>
|
||||
this.trackedData.updateSource(data.index, data.texture.source)
|
||||
);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private async updateSourceList(source: Set<ITextureComposedData>) {
|
||||
for (const data of source) {
|
||||
await this.trackedData.updateSource(
|
||||
data.index,
|
||||
data.texture.source
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
addTextureList(
|
||||
texture: Iterable<ITexture>
|
||||
): Iterable<ITextureComposedData> {
|
||||
this.started = true;
|
||||
const res = [...this.composer.add(texture)];
|
||||
const toUpdate = new Set<ITextureComposedData>();
|
||||
if (this.output) {
|
||||
res.forEach(data => {
|
||||
if (data.index > this.index) {
|
||||
this.output!.addTexture(data.index, data.texture);
|
||||
this.index = data.index;
|
||||
toUpdate.add(data);
|
||||
} else {
|
||||
toUpdate.add(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.pending = this.pending.then(() => this.updateSourceList(toUpdate));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
tracked(): ITrackedAssetData {
|
||||
return this.trackedData;
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.composer.close();
|
||||
}
|
||||
}
|
||||
|
||||
class TrackedAssetData
|
||||
extends PrivateListDirtyTracker<number>
|
||||
implements ITrackedAssetData
|
||||
{
|
||||
readonly sourceList: Map<number, ImageBitmap> = new Map();
|
||||
readonly skipRef: Map<SizedCanvasImageSource, number> = new Map();
|
||||
|
||||
private originSourceMap: Map<number, SizedCanvasImageSource> = new Map();
|
||||
|
||||
private promises: Set<Promise<ImageBitmap>> = new Set();
|
||||
|
||||
constructor(
|
||||
readonly materials: IMaterialGetter,
|
||||
readonly builder: AssetBuilder
|
||||
) {
|
||||
super(0);
|
||||
}
|
||||
|
||||
markDirty(index: number) {
|
||||
if (index >= this.length) {
|
||||
this.updateLength(index + 1);
|
||||
}
|
||||
this.dirty(index);
|
||||
}
|
||||
|
||||
async updateSource(index: number, source: SizedCanvasImageSource) {
|
||||
if (index >= this.length) {
|
||||
this.updateLength(this.length + 1);
|
||||
}
|
||||
const origin = this.originSourceMap.get(index);
|
||||
const prev = this.sourceList.get(index);
|
||||
if (origin && origin !== source) {
|
||||
this.skipRef.delete(origin);
|
||||
}
|
||||
if (prev) {
|
||||
this.skipRef.delete(prev);
|
||||
}
|
||||
this.originSourceMap.set(index, source);
|
||||
if (source instanceof ImageBitmap) {
|
||||
if (this.skipRef.has(source)) return;
|
||||
this.sourceList.set(index, source);
|
||||
this.skipRef.set(source, index);
|
||||
} else {
|
||||
const promise = createImageBitmap(source);
|
||||
this.promises.add(promise);
|
||||
const bitmap = await promise;
|
||||
this.promises.delete(promise);
|
||||
this.sourceList.set(index, bitmap);
|
||||
this.skipRef.set(bitmap, index);
|
||||
// 要把源也加到映射中,因为这里的 bitmap 与外部源并不同引用
|
||||
this.skipRef.set(source, index);
|
||||
}
|
||||
this.dirty(index);
|
||||
}
|
||||
|
||||
async then(): Promise<void> {
|
||||
await Promise.all([...this.promises]);
|
||||
}
|
||||
|
||||
close(): void {}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
import { ITexture } from '@motajs/render-assets';
|
||||
import { materials } from './ins';
|
||||
import { IBlockIdentifier, IIndexedIdentifier } from './types';
|
||||
import { isNil } from 'lodash-es';
|
||||
|
||||
function extractClsBlocks<C extends Exclude<Cls, 'tileset'>>(
|
||||
cls: C,
|
||||
map: Record<string, number>,
|
||||
icons: Record<string, number>
|
||||
): IBlockIdentifier[] {
|
||||
const max = Math.max(...Object.values(icons));
|
||||
const arr = Array(max).fill(void 0);
|
||||
for (const [key, value] of Object.entries(icons)) {
|
||||
// 样板编辑器 bug 可能会导致多个 id 使用一个偏移,因此要判断下
|
||||
if (!(key in map) || !isNil(arr[value])) continue;
|
||||
const id = key as AllIdsOf<C>;
|
||||
const num = map[id] as keyof NumberToId;
|
||||
const identifier: IBlockIdentifier = {
|
||||
id: id as string,
|
||||
cls,
|
||||
num
|
||||
};
|
||||
arr[value] = identifier;
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
function addTileset(set: Set<number>, map?: readonly (readonly number[])[]) {
|
||||
if (!map) return;
|
||||
map.forEach(line => {
|
||||
line.forEach(v => {
|
||||
if (v >= 10000) set.add(v);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function addAutotile(set: Set<number>, map?: readonly (readonly number[])[]) {
|
||||
if (!map) return;
|
||||
map.forEach(line => {
|
||||
line.forEach(v => {
|
||||
const id = core.maps.blocksInfo[v as keyof NumberToId];
|
||||
if (id?.cls === 'autotile') set.add(v);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容旧版加载
|
||||
*/
|
||||
export function fallbackLoad() {
|
||||
// 基本素材
|
||||
const icons = core.icons.icons;
|
||||
const images = core.material.images;
|
||||
const idNumMap: Record<string, number> = {};
|
||||
|
||||
for (const [key, value] of Object.entries(core.maps.blocksInfo)) {
|
||||
const num = Number(key);
|
||||
idNumMap[value.id] = Number(num);
|
||||
if (!isNil(value.animate)) {
|
||||
materials.setDefaultFrame(num, value.animate - 1);
|
||||
}
|
||||
}
|
||||
|
||||
const terrains = extractClsBlocks('terrains', idNumMap, icons.terrains);
|
||||
const animates = extractClsBlocks('animates', idNumMap, icons.animates);
|
||||
const items = extractClsBlocks('items', idNumMap, icons.items);
|
||||
const enemys = extractClsBlocks('enemys', idNumMap, icons.enemys);
|
||||
const npcs = extractClsBlocks('npcs', idNumMap, icons.npcs);
|
||||
const enemy48 = extractClsBlocks('enemy48', idNumMap, icons.enemy48);
|
||||
const npc48 = extractClsBlocks('npc48', idNumMap, icons.npc48);
|
||||
|
||||
// Grid
|
||||
materials.addGrid(images.terrains, terrains);
|
||||
materials.addGrid(images.items, items);
|
||||
|
||||
// Row Animates
|
||||
materials.addRowAnimate(images.animates, animates, 32);
|
||||
materials.addRowAnimate(images.enemys, enemys, 32);
|
||||
materials.addRowAnimate(images.npcs, npcs, 32);
|
||||
materials.addRowAnimate(images.enemy48, enemy48, 48);
|
||||
materials.addRowAnimate(images.npc48, npc48, 48);
|
||||
|
||||
// Autotile
|
||||
for (const key of Object.keys(icons.autotile)) {
|
||||
const id = key as AllIdsOf<'autotile'>;
|
||||
const img = images.autotile[id];
|
||||
const identifier: IBlockIdentifier = {
|
||||
id,
|
||||
num: idNumMap[id],
|
||||
cls: 'autotile'
|
||||
};
|
||||
materials.addAutotile(img, identifier);
|
||||
}
|
||||
|
||||
// Tilesets
|
||||
core.tilesets.forEach((v, i) => {
|
||||
const img = images.tilesets[v];
|
||||
const identifier: IIndexedIdentifier = {
|
||||
index: i,
|
||||
alias: v
|
||||
};
|
||||
materials.addTileset(img, identifier);
|
||||
});
|
||||
|
||||
// Images
|
||||
core.images.forEach((v, i) => {
|
||||
const img = core.material.images.images[v];
|
||||
materials.addImage(img, { index: i, alias: v });
|
||||
});
|
||||
|
||||
// 地图上出现过的 tileset
|
||||
const tilesetSet = new Set<number>();
|
||||
const autotileSet = new Set<number>();
|
||||
core.floorIds.forEach(v => {
|
||||
const floor = core.floors[v];
|
||||
addTileset(tilesetSet, floor.bgmap);
|
||||
addTileset(tilesetSet, floor.bg2map);
|
||||
addTileset(tilesetSet, floor.map);
|
||||
addTileset(tilesetSet, floor.fgmap);
|
||||
addTileset(tilesetSet, floor.fg2map);
|
||||
addAutotile(autotileSet, floor.bgmap);
|
||||
addAutotile(autotileSet, floor.bg2map);
|
||||
addAutotile(autotileSet, floor.map);
|
||||
addAutotile(autotileSet, floor.fgmap);
|
||||
addAutotile(autotileSet, floor.fg2map);
|
||||
});
|
||||
|
||||
const heroTextures: ITexture[] = [];
|
||||
|
||||
data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d.main.heroImages.forEach(v => {
|
||||
const tex = materials.getImageByAlias(v);
|
||||
if (tex) heroTextures.push(tex);
|
||||
});
|
||||
|
||||
materials.buildAssets();
|
||||
|
||||
materials.cacheAutotileList(autotileSet);
|
||||
materials.cacheTilesetList(tilesetSet);
|
||||
materials.buildListToAsset(heroTextures);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { loading } from '@user/data-base';
|
||||
import { fallbackLoad } from './fallback';
|
||||
import { createAutotile } from './autotile';
|
||||
|
||||
export function createMaterial() {
|
||||
createAutotile();
|
||||
loading.once('loaded', () => {
|
||||
fallbackLoad();
|
||||
loading.emit('assetBuilt');
|
||||
});
|
||||
}
|
||||
|
||||
export * from './autotile';
|
||||
export * from './builder';
|
||||
export * from './fallback';
|
||||
export * from './ins';
|
||||
export * from './manager';
|
||||
export * from './types';
|
||||
export * from './utils';
|
||||
@@ -0,0 +1,5 @@
|
||||
import { AutotileProcessor } from './autotile';
|
||||
import { MaterialManager } from './manager';
|
||||
|
||||
export const materials = new MaterialManager();
|
||||
export const autotile = new AutotileProcessor(materials);
|
||||
@@ -0,0 +1,602 @@
|
||||
import {
|
||||
ITexture,
|
||||
ITextureComposedData,
|
||||
ITextureRenderable,
|
||||
ITextureSplitter,
|
||||
ITextureStore,
|
||||
SizedCanvasImageSource,
|
||||
Texture,
|
||||
TextureGridSplitter,
|
||||
TextureRowSplitter,
|
||||
TextureStore
|
||||
} from '@motajs/render-assets';
|
||||
import {
|
||||
IBlockIdentifier,
|
||||
IMaterialData,
|
||||
IMaterialManager,
|
||||
IIndexedIdentifier,
|
||||
IMaterialAssetData,
|
||||
BlockCls,
|
||||
IBigImageReturn,
|
||||
IAssetBuilder,
|
||||
IMaterialFramedData,
|
||||
ITrackedAssetData
|
||||
} from './types';
|
||||
import { logger } from '@motajs/common';
|
||||
import { getClsByString, getTextureFrame } from './utils';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { AssetBuilder } from './builder';
|
||||
import { AutotileProcessor } from './autotile';
|
||||
|
||||
interface TilesetCache {
|
||||
/** 是否已经在贴图库中存在 */
|
||||
readonly existed: boolean;
|
||||
/** 贴图对象 */
|
||||
readonly texture: ITexture;
|
||||
}
|
||||
|
||||
export class MaterialManager implements IMaterialManager {
|
||||
readonly tileStore: ITextureStore = new TextureStore();
|
||||
readonly tilesetStore: ITextureStore = new TextureStore();
|
||||
readonly imageStore: ITextureStore = new TextureStore();
|
||||
readonly assetStore: ITextureStore = new TextureStore();
|
||||
readonly bigImageStore: ITextureStore = new TextureStore();
|
||||
|
||||
/** 自动元件图像源映射 */
|
||||
readonly autotileSource: Map<number, SizedCanvasImageSource> = new Map();
|
||||
|
||||
/** 图集信息存储 */
|
||||
readonly assetDataStore: Map<number, ITextureComposedData> = new Map();
|
||||
/** 贴图到图集索引的映射 */
|
||||
readonly assetMap: Map<ITexture, number> = new Map();
|
||||
/** 带有脏标记追踪的图集对象 */
|
||||
readonly trackedAsset: ITrackedAssetData;
|
||||
|
||||
/** 大怪物数据 */
|
||||
readonly bigImageData: Map<number, IMaterialFramedData> = new Map();
|
||||
/** tileset 中 `Math.floor(id / 10000) + 1` 映射到 tileset 对应索引的映射,用于处理图块超出 10000 的 tileset */
|
||||
readonly tilesetOffsetMap: Map<number, number> = new Map();
|
||||
/** 图集打包器 */
|
||||
readonly assetBuilder: IAssetBuilder;
|
||||
|
||||
/** 图块 id 到图块数字的映射 */
|
||||
readonly idNumMap: Map<string, number> = new Map();
|
||||
/** 图块数字到图块 id 的映射 */
|
||||
readonly numIdMap: Map<number, string> = new Map();
|
||||
/** 图块数字到图块类型的映射 */
|
||||
readonly clsMap: Map<number, BlockCls> = new Map();
|
||||
/** 图块的默认帧数 */
|
||||
readonly defaultFrames: Map<number, number> = new Map();
|
||||
|
||||
/** 网格切分器 */
|
||||
readonly gridSplitter: TextureGridSplitter = new TextureGridSplitter();
|
||||
/** 行切分器 */
|
||||
readonly rowSplitter: TextureRowSplitter = new TextureRowSplitter();
|
||||
|
||||
/** 大怪物贴图的标识符 */
|
||||
private bigImageId: number = 0;
|
||||
/** 当前 tileset 索引 */
|
||||
private nowTilesetIndex: number = -1;
|
||||
/** 当前 tileset 偏移 */
|
||||
private nowTilesetOffset: number = 0;
|
||||
/** 是否已经构建过素材 */
|
||||
private built: boolean = false;
|
||||
|
||||
constructor() {
|
||||
this.assetBuilder = new AssetBuilder(this);
|
||||
this.assetBuilder.pipe(this.assetStore);
|
||||
this.trackedAsset = this.assetBuilder.tracked();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加由分割器和图块映射组成的图像源贴图
|
||||
* @param source 图像源
|
||||
* @param map 图块 id 与图块数字映射
|
||||
* @param store 要添加至的贴图存储对象
|
||||
* @param splitter 使用的分割器
|
||||
* @param splitterData 传递给分割器的数据
|
||||
* @param processTexture 对每个纹理进行处理
|
||||
*/
|
||||
private addMappedSource<T>(
|
||||
source: SizedCanvasImageSource,
|
||||
map: ArrayLike<IBlockIdentifier>,
|
||||
store: ITextureStore,
|
||||
splitter: ITextureSplitter<T>,
|
||||
splitterData: T,
|
||||
processTexture?: (tex: ITexture) => void
|
||||
): Iterable<IMaterialData> {
|
||||
const tex = new Texture(source);
|
||||
const textures = [...splitter.split(tex, splitterData)];
|
||||
if (textures.length !== map.length) {
|
||||
logger.warn(75, textures.length.toString(), map.length.toString());
|
||||
}
|
||||
const res: IMaterialData[] = textures.map((v, i) => {
|
||||
if (!map[i]) {
|
||||
return {
|
||||
store,
|
||||
texture: v,
|
||||
identifier: -1,
|
||||
alias: '@internal-unknown'
|
||||
};
|
||||
}
|
||||
const { id, num, cls } = map[i];
|
||||
store.addTexture(num, v);
|
||||
store.alias(num, id);
|
||||
this.clsMap.set(num, getClsByString(cls));
|
||||
processTexture?.(v);
|
||||
const data: IMaterialData = {
|
||||
store,
|
||||
texture: v,
|
||||
identifier: num,
|
||||
alias: id
|
||||
};
|
||||
return data;
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
addGrid(
|
||||
source: SizedCanvasImageSource,
|
||||
map: ArrayLike<IBlockIdentifier>
|
||||
): Iterable<IMaterialData> {
|
||||
return this.addMappedSource(
|
||||
source,
|
||||
map,
|
||||
this.tileStore,
|
||||
this.gridSplitter,
|
||||
[32, 32]
|
||||
);
|
||||
}
|
||||
|
||||
addRowAnimate(
|
||||
source: SizedCanvasImageSource,
|
||||
map: ArrayLike<IBlockIdentifier>,
|
||||
height: number
|
||||
): Iterable<IMaterialData> {
|
||||
return this.addMappedSource(
|
||||
source,
|
||||
map,
|
||||
this.tileStore,
|
||||
this.rowSplitter,
|
||||
height
|
||||
);
|
||||
}
|
||||
|
||||
addAutotile(
|
||||
source: SizedCanvasImageSource,
|
||||
identifier: IBlockIdentifier
|
||||
): void {
|
||||
this.autotileSource.set(identifier.num, source);
|
||||
this.tileStore.alias(identifier.num, identifier.id);
|
||||
this.clsMap.set(identifier.num, BlockCls.Autotile);
|
||||
}
|
||||
|
||||
addTileset(
|
||||
source: SizedCanvasImageSource,
|
||||
identifier: IIndexedIdentifier
|
||||
): IMaterialData | null {
|
||||
const tex = new Texture(source);
|
||||
this.tilesetStore.addTexture(identifier.index, tex);
|
||||
this.tilesetStore.alias(identifier.index, identifier.alias);
|
||||
const width = Math.floor(source.width / 32);
|
||||
const height = Math.floor(source.height / 32);
|
||||
const count = width * height;
|
||||
const offset = Math.ceil(count / 10000);
|
||||
if (identifier.index === 0) {
|
||||
this.tilesetOffsetMap.set(0, 0);
|
||||
this.nowTilesetIndex = 0;
|
||||
this.nowTilesetOffset = offset;
|
||||
} else {
|
||||
if (identifier.index - 1 !== this.nowTilesetIndex) {
|
||||
logger.warn(78);
|
||||
return null;
|
||||
}
|
||||
// 一个 tileset 可能不止 10000 个图块,需要计算偏移
|
||||
const width = Math.floor(source.width / 32);
|
||||
const height = Math.floor(source.height / 32);
|
||||
const count = width * height;
|
||||
const offset = Math.ceil(count / 10000);
|
||||
const end = this.nowTilesetOffset + offset;
|
||||
for (let i = this.nowTilesetOffset; i < end; i++) {
|
||||
this.tilesetOffsetMap.set(i, identifier.index);
|
||||
}
|
||||
this.nowTilesetOffset = end;
|
||||
this.nowTilesetIndex = identifier.index;
|
||||
}
|
||||
const data: IMaterialData = {
|
||||
store: this.tilesetStore,
|
||||
texture: tex,
|
||||
identifier: identifier.index,
|
||||
alias: identifier.alias
|
||||
};
|
||||
return data;
|
||||
}
|
||||
|
||||
addImage(
|
||||
source: SizedCanvasImageSource,
|
||||
identifier: IIndexedIdentifier
|
||||
): IMaterialData {
|
||||
const texture = new Texture(source);
|
||||
this.imageStore.addTexture(identifier.index, texture);
|
||||
this.imageStore.alias(identifier.index, identifier.alias);
|
||||
const data: IMaterialData = {
|
||||
store: this.imageStore,
|
||||
texture,
|
||||
identifier: identifier.index,
|
||||
alias: identifier.alias
|
||||
};
|
||||
return data;
|
||||
}
|
||||
|
||||
setDefaultFrame(identifier: number, defaultFrame: number): void {
|
||||
this.defaultFrames.set(identifier, defaultFrame);
|
||||
const bigImageData = this.bigImageData.get(identifier);
|
||||
if (bigImageData) {
|
||||
bigImageData.defaultFrame = defaultFrame;
|
||||
}
|
||||
}
|
||||
|
||||
getDefaultFrame(identifier: number): number {
|
||||
return this.defaultFrames.get(identifier) ?? -1;
|
||||
}
|
||||
|
||||
getTile(identifier: number): Readonly<IMaterialFramedData> | null {
|
||||
if (identifier < 10000) {
|
||||
const cls = this.clsMap.get(identifier) ?? BlockCls.Unknown;
|
||||
if (
|
||||
cls === BlockCls.Autotile &&
|
||||
this.autotileSource.has(identifier)
|
||||
) {
|
||||
this.cacheAutotile(identifier);
|
||||
}
|
||||
const texture = this.tileStore.getTexture(identifier);
|
||||
if (!texture) return null;
|
||||
return {
|
||||
texture,
|
||||
cls,
|
||||
offset: 32,
|
||||
frames: getTextureFrame(cls, texture),
|
||||
defaultFrame: this.defaultFrames.get(identifier) ?? -1
|
||||
};
|
||||
} else {
|
||||
const texture = this.cacheTileset(identifier);
|
||||
if (!texture) return null;
|
||||
return {
|
||||
texture,
|
||||
cls: BlockCls.Tileset,
|
||||
offset: 32,
|
||||
frames: 1,
|
||||
defaultFrame: -1
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
getTileset(identifier: number): ITexture | null {
|
||||
return this.tilesetStore.getTexture(identifier);
|
||||
}
|
||||
|
||||
getImage(identifier: number): ITexture | null {
|
||||
return this.imageStore.getTexture(identifier);
|
||||
}
|
||||
|
||||
getTileByAlias(alias: string): Readonly<IMaterialFramedData> | null {
|
||||
if (/X\d{5,}/.test(alias)) {
|
||||
return this.getTile(parseInt(alias.slice(1)));
|
||||
} else {
|
||||
const identifier = this.tileStore.identifierOf(alias);
|
||||
if (isNil(identifier)) return null;
|
||||
return this.getTile(identifier);
|
||||
}
|
||||
}
|
||||
|
||||
getTilesetByAlias(alias: string): ITexture | null {
|
||||
return this.tilesetStore.fromAlias(alias);
|
||||
}
|
||||
|
||||
getImageByAlias(alias: string): ITexture | null {
|
||||
return this.imageStore.fromAlias(alias);
|
||||
}
|
||||
|
||||
private getTilesetOwnTexture(identifier: number): TilesetCache | null {
|
||||
const texture = this.tileStore.getTexture(identifier);
|
||||
if (texture) return { existed: true, texture };
|
||||
// 如果 tileset 不存在,那么执行缓存操作
|
||||
const offset = Math.floor(identifier / 10000);
|
||||
const index = this.tilesetOffsetMap.get(offset - 1);
|
||||
if (isNil(index)) return null;
|
||||
// 获取对应的 tileset 贴图
|
||||
const tileset = this.tilesetStore.getTexture(index);
|
||||
if (!tileset) return null;
|
||||
// 计算图块位置
|
||||
const rest = identifier - offset * 10000;
|
||||
const { width, height } = tileset;
|
||||
const tileWidth = Math.floor(width / 32);
|
||||
const tileHeight = Math.floor(height / 32);
|
||||
// 如果图块位置超出了贴图范围
|
||||
if (rest > tileWidth * tileHeight) return null;
|
||||
// 裁剪 tileset,生成贴图
|
||||
const x = rest % tileWidth;
|
||||
const y = Math.floor(rest / tileWidth);
|
||||
const newTexture = new Texture(tileset.source);
|
||||
newTexture.clip(x * 32, y * 32, 32, 32);
|
||||
return { existed: false, texture: newTexture };
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查图集状态,如果已存在图集则标记为脏,否则新增图集
|
||||
* @param data 图集数据
|
||||
*/
|
||||
private checkAssetDirty(data: ITextureComposedData) {
|
||||
if (!this.built) return;
|
||||
const asset = this.assetDataStore.get(data.index);
|
||||
if (!asset) {
|
||||
// 如果有新图集,需要添加
|
||||
const alias = `asset-${data.index}`;
|
||||
this.assetStore.alias(data.index, alias);
|
||||
this.assetDataStore.set(data.index, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将指定的贴图列表转换至指定的图集数据中
|
||||
* @param composedData 组合数据
|
||||
* @param textures 贴图列表
|
||||
*/
|
||||
private cacheToAsset(
|
||||
composedData: ITextureComposedData[],
|
||||
textures: ITexture[]
|
||||
) {
|
||||
textures.forEach(tex => {
|
||||
const assetData = composedData.find(v => v.assetMap.has(tex));
|
||||
if (!assetData) {
|
||||
logger.error(38);
|
||||
return;
|
||||
}
|
||||
tex.toAsset(assetData);
|
||||
});
|
||||
composedData.forEach(v => this.checkAssetDirty(v));
|
||||
}
|
||||
|
||||
cacheTileset(identifier: number): ITexture | null {
|
||||
const newTexture = this.getTilesetOwnTexture(identifier);
|
||||
if (!newTexture) return null;
|
||||
const { existed, texture } = newTexture;
|
||||
if (existed) return texture;
|
||||
// 缓存贴图
|
||||
this.tileStore.addTexture(identifier, texture);
|
||||
this.idNumMap.set(`X${identifier}`, identifier);
|
||||
this.numIdMap.set(identifier, `X${identifier}`);
|
||||
const data = this.assetBuilder.addTexture(texture);
|
||||
texture.toAsset(data);
|
||||
this.checkAssetDirty(data);
|
||||
return texture;
|
||||
}
|
||||
|
||||
cacheTilesetList(
|
||||
identifierList: Iterable<number>
|
||||
): Iterable<ITexture | null> {
|
||||
const arr = [...identifierList];
|
||||
const toAdd: ITexture[] = [];
|
||||
|
||||
arr.forEach(v => {
|
||||
const newTexture = this.getTilesetOwnTexture(v);
|
||||
if (!newTexture) return;
|
||||
const { existed, texture } = newTexture;
|
||||
if (existed) return;
|
||||
toAdd.push(texture);
|
||||
this.tileStore.addTexture(v, texture);
|
||||
this.idNumMap.set(`X${v}`, v);
|
||||
this.numIdMap.set(v, `X${v}`);
|
||||
});
|
||||
|
||||
const data = this.assetBuilder.addTextureList(toAdd);
|
||||
const res = [...data];
|
||||
this.cacheToAsset(res, toAdd);
|
||||
|
||||
return toAdd;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取自动元件展开后的图片,如果图片不存在,或是已经展开并存储至了 `tileStore`,那么返回 `null`
|
||||
* @param identifier 自动元件标识符
|
||||
*/
|
||||
private getFlattenedAutotile(
|
||||
identifier: number
|
||||
): SizedCanvasImageSource | null {
|
||||
const cls = this.clsMap.get(identifier);
|
||||
if (cls !== BlockCls.Autotile) return null;
|
||||
if (this.tileStore.getTexture(identifier)) return null;
|
||||
const source = this.autotileSource.get(identifier);
|
||||
if (!source) return null;
|
||||
const frames = source.width === 96 ? 1 : 4;
|
||||
const flattened = AutotileProcessor.flatten({ source, frames });
|
||||
if (!flattened) return null;
|
||||
return flattened;
|
||||
}
|
||||
|
||||
cacheAutotile(identifier: number): ITexture | null {
|
||||
const flattened = this.getFlattenedAutotile(identifier);
|
||||
if (!flattened) return null;
|
||||
const tex = new Texture(flattened);
|
||||
this.tileStore.addTexture(identifier, tex);
|
||||
const data = this.assetBuilder.addTexture(tex);
|
||||
tex.toAsset(data);
|
||||
this.autotileSource.delete(identifier);
|
||||
this.checkAssetDirty(data);
|
||||
return tex;
|
||||
}
|
||||
|
||||
cacheAutotileList(
|
||||
identifierList: Iterable<number>
|
||||
): Iterable<ITexture | null> {
|
||||
const arr = [...identifierList];
|
||||
const toAdd: ITexture[] = [];
|
||||
|
||||
arr.forEach(v => {
|
||||
const flattened = this.getFlattenedAutotile(v);
|
||||
if (!flattened) return;
|
||||
const tex = new Texture(flattened);
|
||||
this.tileStore.addTexture(v, tex);
|
||||
toAdd.push(tex);
|
||||
this.autotileSource.delete(v);
|
||||
});
|
||||
|
||||
const data = this.assetBuilder.addTextureList(toAdd);
|
||||
const res = [...data];
|
||||
this.cacheToAsset(res, toAdd);
|
||||
|
||||
return toAdd;
|
||||
}
|
||||
|
||||
buildAssets(): Iterable<IMaterialAssetData> {
|
||||
if (this.built) {
|
||||
logger.warn(79);
|
||||
return [];
|
||||
}
|
||||
this.built = true;
|
||||
return this.buildListToAsset(this.tileStore.values());
|
||||
}
|
||||
|
||||
buildToAsset(texture: ITexture): IMaterialAssetData {
|
||||
const data = this.assetBuilder.addTexture(texture);
|
||||
const assetData: IMaterialAssetData = {
|
||||
data: data,
|
||||
identifier: data.index,
|
||||
alias: `asset-${data.index}`,
|
||||
store: this.assetStore
|
||||
};
|
||||
this.checkAssetDirty(data);
|
||||
texture.toAsset(data);
|
||||
return assetData;
|
||||
}
|
||||
|
||||
buildListToAsset(
|
||||
texture: Iterable<ITexture>
|
||||
): Iterable<IMaterialAssetData> {
|
||||
const data = this.assetBuilder.addTextureList(texture);
|
||||
const arr = [...data];
|
||||
const res: IMaterialAssetData[] = [];
|
||||
arr.forEach(v => {
|
||||
const alias = `asset-${v.index}`;
|
||||
if (!this.assetDataStore.has(v.index)) {
|
||||
this.assetDataStore.set(v.index, v);
|
||||
}
|
||||
const data: IMaterialAssetData = {
|
||||
data: v,
|
||||
identifier: v.index,
|
||||
alias,
|
||||
store: this.assetStore
|
||||
};
|
||||
for (const tex of v.assetMap.keys()) {
|
||||
tex.toAsset(v);
|
||||
}
|
||||
res.push(data);
|
||||
});
|
||||
arr.forEach(v => {
|
||||
this.checkAssetDirty(v);
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
getAsset(identifier: number): ITextureComposedData | null {
|
||||
return this.assetDataStore.get(identifier) ?? null;
|
||||
}
|
||||
|
||||
getAssetByAlias(alias: string): ITextureComposedData | null {
|
||||
const id = this.assetStore.identifierOf(alias);
|
||||
if (isNil(id)) return null;
|
||||
return this.assetDataStore.get(id) ?? null;
|
||||
}
|
||||
|
||||
private getTextureOf(identifier: number, cls: BlockCls): ITexture | null {
|
||||
if (cls === BlockCls.Unknown) return null;
|
||||
if (cls !== BlockCls.Tileset) {
|
||||
return this.tileStore.getTexture(identifier);
|
||||
}
|
||||
if (identifier < 10000) return null;
|
||||
return this.cacheTileset(identifier);
|
||||
}
|
||||
|
||||
getRenderable(identifier: number): ITextureRenderable | null {
|
||||
const cls = this.clsMap.get(identifier);
|
||||
if (isNil(cls)) return null;
|
||||
const texture = this.getTextureOf(identifier, cls);
|
||||
if (!texture) return null;
|
||||
return texture.render();
|
||||
}
|
||||
|
||||
getRenderableByAlias(alias: string): ITextureRenderable | null {
|
||||
const identifier = this.idNumMap.get(alias);
|
||||
if (isNil(identifier)) return null;
|
||||
return this.getRenderable(identifier);
|
||||
}
|
||||
|
||||
getBlockCls(identifier: number): BlockCls {
|
||||
return this.clsMap.get(identifier) ?? BlockCls.Unknown;
|
||||
}
|
||||
|
||||
getBlockClsByAlias(alias: string): BlockCls {
|
||||
const id = this.idNumMap.get(alias);
|
||||
if (isNil(id)) return BlockCls.Unknown;
|
||||
return this.clsMap.get(id) ?? BlockCls.Unknown;
|
||||
}
|
||||
|
||||
getIdentifierByAlias(alias: string): number | undefined {
|
||||
return this.idNumMap.get(alias);
|
||||
}
|
||||
|
||||
getAliasByIdentifier(identifier: number): string | undefined {
|
||||
return this.numIdMap.get(identifier);
|
||||
}
|
||||
|
||||
setBigImage(
|
||||
identifier: number,
|
||||
image: ITexture,
|
||||
frames: number
|
||||
): IBigImageReturn {
|
||||
const bigImageId = this.bigImageId++;
|
||||
this.bigImageStore.addTexture(bigImageId, image);
|
||||
const cls = this.clsMap.get(identifier) ?? BlockCls.Unknown;
|
||||
const store: IMaterialFramedData = {
|
||||
texture: image,
|
||||
cls,
|
||||
offset: image.width / 4,
|
||||
frames,
|
||||
defaultFrame: this.defaultFrames.get(identifier) ?? -1
|
||||
};
|
||||
this.bigImageData.set(identifier, store);
|
||||
const data: IBigImageReturn = {
|
||||
identifier: bigImageId,
|
||||
store: this.bigImageStore
|
||||
};
|
||||
return data;
|
||||
}
|
||||
|
||||
isBigImage(identifier: number): boolean {
|
||||
return this.bigImageData.has(identifier);
|
||||
}
|
||||
|
||||
getBigImage(identifier: number): Readonly<IMaterialFramedData> | null {
|
||||
return this.bigImageData.get(identifier) ?? null;
|
||||
}
|
||||
|
||||
getBigImageByAlias(alias: string): Readonly<IMaterialFramedData> | null {
|
||||
const identifier = this.idNumMap.get(alias);
|
||||
if (isNil(identifier)) return null;
|
||||
return this.bigImageData.get(identifier) ?? null;
|
||||
}
|
||||
|
||||
getIfBigImage(identifier: number): Readonly<IMaterialFramedData> | null {
|
||||
const bigImage = this.bigImageData.get(identifier);
|
||||
if (bigImage) return bigImage;
|
||||
else return this.getTile(identifier);
|
||||
}
|
||||
|
||||
assetContainsTexture(texture: ITexture): boolean {
|
||||
return this.trackedAsset.skipRef.has(texture.source);
|
||||
}
|
||||
|
||||
getTextureAsset(texture: ITexture): number | undefined {
|
||||
return this.trackedAsset.skipRef.get(texture.source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,528 @@
|
||||
import { IDirtyTracker, IDirtyMarker } from '@motajs/common';
|
||||
import {
|
||||
ITexture,
|
||||
ITextureComposedData,
|
||||
ITextureRenderable,
|
||||
ITextureStore,
|
||||
SizedCanvasImageSource
|
||||
} from '@motajs/render-assets';
|
||||
|
||||
export const enum BlockCls {
|
||||
Unknown,
|
||||
Terrains,
|
||||
Animates,
|
||||
Enemys,
|
||||
Npcs,
|
||||
Items,
|
||||
Enemy48,
|
||||
Npc48,
|
||||
Tileset,
|
||||
Autotile
|
||||
}
|
||||
|
||||
export const enum AutotileType {
|
||||
Small2x3,
|
||||
Big3x4
|
||||
}
|
||||
|
||||
export const enum AutotileConnection {
|
||||
LeftUp = 0b1000_0000,
|
||||
Up = 0b0100_0000,
|
||||
RightUp = 0b0010_0000,
|
||||
Right = 0b0001_0000,
|
||||
RightDown = 0b0000_1000,
|
||||
Down = 0b0000_0100,
|
||||
LeftDown = 0b0000_0010,
|
||||
Left = 0b0000_0001
|
||||
}
|
||||
|
||||
export interface IMaterialData {
|
||||
/** 此素材的贴图对象存入了哪个贴图存储对象 */
|
||||
readonly store: ITextureStore;
|
||||
/** 贴图对象 */
|
||||
readonly texture: ITexture;
|
||||
/** 此素材的贴图对象的数字 id,一般对应到图块数字 */
|
||||
readonly identifier: number;
|
||||
/** 此素材的贴图对象的字符串别名,一般对应到图块 id */
|
||||
readonly alias?: string;
|
||||
}
|
||||
|
||||
export interface IBlockIdentifier {
|
||||
/** 图块 id */
|
||||
readonly id: string;
|
||||
/** 图块数字 */
|
||||
readonly num: number;
|
||||
/** 图块类型 */
|
||||
readonly cls: Cls;
|
||||
}
|
||||
|
||||
export interface IIndexedIdentifier {
|
||||
/** 标识符索引 */
|
||||
readonly index: number;
|
||||
/** 标识符别名 */
|
||||
readonly alias: string;
|
||||
}
|
||||
|
||||
export interface IMaterialAssetData {
|
||||
/** 图集数据 */
|
||||
readonly data: ITextureComposedData;
|
||||
/** 贴图的标识符 */
|
||||
readonly identifier: number;
|
||||
/** 贴图的别名 */
|
||||
readonly alias: string;
|
||||
/** 贴图所属的存储对象 */
|
||||
readonly store: ITextureStore;
|
||||
}
|
||||
|
||||
export interface IAutotileConnection {
|
||||
/** 连接方式,最高位表示左上,低位依次顺时针旋转 */
|
||||
readonly connection: number;
|
||||
/** 中心自动元件对应的图块数字 */
|
||||
readonly center: number;
|
||||
}
|
||||
|
||||
export interface IBigImageReturn {
|
||||
/** 大怪物贴图在 store 中的标识符 */
|
||||
readonly identifier: number;
|
||||
/** 存储大怪物贴图的存储对象 */
|
||||
readonly store: ITextureStore;
|
||||
}
|
||||
|
||||
export interface IMaterialFramedData {
|
||||
/** 贴图对象 */
|
||||
texture: ITexture;
|
||||
/** 图块类型 */
|
||||
cls: BlockCls;
|
||||
/** 贴图总帧数 */
|
||||
frames: number;
|
||||
/** 每帧的横向偏移量 */
|
||||
offset: number;
|
||||
/** 默认帧数 */
|
||||
defaultFrame: number;
|
||||
}
|
||||
|
||||
export interface IMaterialAsset
|
||||
extends IDirtyTracker<boolean>,
|
||||
IDirtyMarker<void> {
|
||||
/** 图集的贴图数据 */
|
||||
readonly data: ITextureComposedData;
|
||||
}
|
||||
|
||||
export interface IAutotileProcessor {
|
||||
/** 该自动元件处理器使用的素材管理器 */
|
||||
readonly manager: IMaterialManager;
|
||||
|
||||
/**
|
||||
* 设置一个自动元件的特殊连接方式,设置后当前自动元件将会单方面与目标元件连接,
|
||||
* 一个自动元件可以与多个自动元件有特殊连接
|
||||
* @param autotile 自动元件
|
||||
* @param target 当前自动元件将会连接至的自动元件
|
||||
*/
|
||||
setConnection(autotile: number, target: number): void;
|
||||
|
||||
/**
|
||||
* 获取自动元件的连接情况
|
||||
* @param array 地图图块数组
|
||||
* @param index 自动元件图块所在的索引
|
||||
* @param width 地图每一行的宽度
|
||||
*/
|
||||
connect(
|
||||
array: Uint32Array,
|
||||
index: number,
|
||||
width: number
|
||||
): IAutotileConnection;
|
||||
|
||||
/**
|
||||
* 检查一个图块与指定方向的连接方式
|
||||
* @param connection 当前的连接
|
||||
* @param center 中心点的图块数字
|
||||
* @param target 连接点的图块数字
|
||||
* @param direction 连接点的方向
|
||||
* @returns 经过连接后的连接数字
|
||||
*/
|
||||
updateConnectionFor(
|
||||
connection: number,
|
||||
center: number,
|
||||
target: number,
|
||||
direction: AutotileConnection
|
||||
): number;
|
||||
|
||||
/**
|
||||
* 根据图块数字,获取指定自动元件经过连接的可渲染对象
|
||||
* @param autotile 自动元件的图块数字
|
||||
* @param connection 连接方式,上方连接是第一位,顺时针旋转位次依次升高
|
||||
* @returns 连接方式的可渲染对象,可以通过偏移量依次获取其他帧
|
||||
*/
|
||||
render(autotile: number, connection: number): ITextureRenderable | null;
|
||||
|
||||
/**
|
||||
* 根据图块贴图对象,获取指定自动元件经过连接的可渲染对象
|
||||
* @param tile 自动元件的图块贴图数据
|
||||
* @param connection 连接方式,上方连接是第一位,顺时针旋转位次依次升高
|
||||
* @returns 连接方式的可渲染对象,可以通过偏移量依次获取其他帧
|
||||
*/
|
||||
renderWith(
|
||||
tile: Readonly<IMaterialFramedData>,
|
||||
connection: number
|
||||
): ITextureRenderable | null;
|
||||
|
||||
/**
|
||||
* 根据图块贴图对象,获取指定自动元件经过连接的可渲染对象,但是会假设传入的图块就是自动元件,不做不必要的判断
|
||||
* @param tile 自动元件的图块贴图数据
|
||||
* @param connection 连接方式,上方连接是第一位,顺时针旋转位次依次升高
|
||||
* @returns 连接方式的可渲染对象,可以通过偏移量依次获取其他帧
|
||||
*/
|
||||
renderWithoutCheck(
|
||||
tile: Readonly<IMaterialFramedData>,
|
||||
connection: number
|
||||
): ITextureRenderable | null;
|
||||
|
||||
/**
|
||||
* 根据图块数字,获取指定自动元件经过链接的动态可渲染对象
|
||||
* @param autotile 自动元件的图块数字
|
||||
* @param connection 自动元件的连接方式
|
||||
* @returns 生成器,每一个输出代表每一帧的渲染对象,不同自动元件的帧数可能不同
|
||||
*/
|
||||
renderAnimated(
|
||||
autotile: number,
|
||||
connection: number
|
||||
): Generator<ITextureRenderable, void>;
|
||||
|
||||
/**
|
||||
* 根据图块贴图对象,获取指定自动元件经过链接的动态可渲染对象
|
||||
* @param autotile 自动元件的图块数字
|
||||
* @param connection 自动元件的连接方式
|
||||
* @returns 生成器,每一个输出代表每一帧的渲染对象,不同自动元件的帧数可能不同
|
||||
*/
|
||||
renderAnimatedWith(
|
||||
tile: Readonly<IMaterialFramedData>,
|
||||
connection: number
|
||||
): Generator<ITextureRenderable, void>;
|
||||
}
|
||||
|
||||
export interface IMaterialGetter {
|
||||
/**
|
||||
* 根据图块数字获取图块,可以获取额外素材,会自动将未缓存的额外素材缓存
|
||||
* @param identifier 图块的图块数字
|
||||
*/
|
||||
getTile(identifier: number): Readonly<IMaterialFramedData> | null;
|
||||
|
||||
/**
|
||||
* 根据图块标识符获取图块类型
|
||||
* @param identifier 图块标识符,即图块数字
|
||||
*/
|
||||
getBlockCls(identifier: number): BlockCls;
|
||||
|
||||
/**
|
||||
* 判断一个图块是否包含 `bigImage` 贴图,即是否是大怪物
|
||||
* @param identifier 图块标识符,即图块数字
|
||||
*/
|
||||
isBigImage(identifier: number): boolean;
|
||||
|
||||
/**
|
||||
* 根据图块标识符获取一个图块的 `bigImage` 贴图
|
||||
* @param identifier 图块标识符,即图块数字
|
||||
*/
|
||||
getBigImage(identifier: number): Readonly<IMaterialFramedData> | null;
|
||||
|
||||
/**
|
||||
* 根据图块标识符,首先判断是否是 `bigImage` 贴图,如果是,则返回 `bigImage` 贴图,
|
||||
* 否则返回普通贴图。如果图块不存在,则返回 `null`
|
||||
* @param identifier 图块标识符,即图块数字
|
||||
*/
|
||||
getIfBigImage(identifier: number): Readonly<IMaterialFramedData> | null;
|
||||
|
||||
/**
|
||||
* 根据标识符获取图集信息
|
||||
* @param identifier 图集的标识符
|
||||
*/
|
||||
getAsset(identifier: number): ITextureComposedData | null;
|
||||
|
||||
/**
|
||||
* 根据额外素材索引获取额外素材
|
||||
* @param identifier 额外素材的索引
|
||||
*/
|
||||
getTileset(identifier: number): ITexture | null;
|
||||
|
||||
/**
|
||||
* 根据图片的索引获取图片
|
||||
* @param identifier 图片的索引
|
||||
*/
|
||||
getImage(identifier: number): ITexture | null;
|
||||
}
|
||||
|
||||
export interface IMaterialAliasGetter {
|
||||
/**
|
||||
* 根据图块 id 获取图块,可以获取额外素材,会自动将未缓存的额外素材缓存
|
||||
* @param alias 图块 id
|
||||
*/
|
||||
getTileByAlias(alias: string): Readonly<IMaterialFramedData> | null;
|
||||
|
||||
/**
|
||||
* 根据额外素材名称获取额外素材
|
||||
* @param alias 额外素材名称
|
||||
*/
|
||||
getTilesetByAlias(alias: string): ITexture | null;
|
||||
|
||||
/**
|
||||
* 根据图片名称获取图片
|
||||
* @param alias 图片名称
|
||||
*/
|
||||
getImageByAlias(alias: string): ITexture | null;
|
||||
|
||||
/**
|
||||
* 根据别名获取图集信息
|
||||
* @param alias 图集的别名
|
||||
*/
|
||||
getAssetByAlias(alias: string): ITextureComposedData | null;
|
||||
|
||||
/**
|
||||
* 根据图块别名获取图块类型
|
||||
* @param alias 图块别名,即图块的 id
|
||||
*/
|
||||
getBlockClsByAlias(alias: string): BlockCls;
|
||||
|
||||
/**
|
||||
* 根据图块别名获取一个图块的 `bigImage` 贴图
|
||||
* @param alias 图块别名,即图块的 id
|
||||
*/
|
||||
getBigImageByAlias(alias: string): Readonly<IMaterialFramedData> | null;
|
||||
}
|
||||
|
||||
export interface IMaterialManager
|
||||
extends IMaterialGetter,
|
||||
IMaterialAliasGetter {
|
||||
/** 贴图存储,把 terrains 等内容单独分开存储 */
|
||||
readonly tileStore: ITextureStore;
|
||||
/** tilesets 贴图存储,每个 tileset 是一个贴图对象 */
|
||||
readonly tilesetStore: ITextureStore;
|
||||
/** 存储注册的图像的存储对象 */
|
||||
readonly imageStore: ITextureStore;
|
||||
/** 图集存储,将常用贴图存入其中 */
|
||||
readonly assetStore: ITextureStore;
|
||||
/** bigImage 存储,存储大怪物数据 */
|
||||
readonly bigImageStore: ITextureStore;
|
||||
|
||||
/** 图集信息存储 */
|
||||
readonly assetDataStore: Iterable<[number, ITextureComposedData]>;
|
||||
/** 带有脏标记追踪的图集信息 */
|
||||
readonly trackedAsset: ITrackedAssetData;
|
||||
|
||||
/** 图块类型映射 */
|
||||
readonly clsMap: Map<number, BlockCls>;
|
||||
|
||||
/**
|
||||
* 添加网格类型的贴图,包括 terrains 和 items 类型
|
||||
* @param source 图像源
|
||||
* @param map 贴图字符串 id 与图块数字映射,按照先从左到右,再从上到下的顺序映射
|
||||
*/
|
||||
addGrid(
|
||||
source: SizedCanvasImageSource,
|
||||
map: ArrayLike<IBlockIdentifier>
|
||||
): Iterable<IMaterialData>;
|
||||
|
||||
/**
|
||||
* 添加行动画的贴图,包括 animates enemys npcs enemy48 npc48 类型
|
||||
* @param source 图像源
|
||||
* @param map 贴图字符串 id 与图块数字映射,按从上到下的顺序映射
|
||||
* @param frames 每一行的帧数
|
||||
* @param height 每一行的高度
|
||||
*/
|
||||
addRowAnimate(
|
||||
source: SizedCanvasImageSource,
|
||||
map: ArrayLike<IBlockIdentifier>,
|
||||
frames: number,
|
||||
height: number
|
||||
): Iterable<IMaterialData>;
|
||||
|
||||
/**
|
||||
* 添加自动元件
|
||||
* @param source 图像源
|
||||
* @param identifier 自动元件的字符串 id 及图块数字
|
||||
* @returns 由于自动元件是懒加载的,因此不会返回任何东西
|
||||
*/
|
||||
addAutotile(
|
||||
source: SizedCanvasImageSource,
|
||||
identifier: IBlockIdentifier
|
||||
): void;
|
||||
|
||||
/**
|
||||
* 添加一个 tileset 类型的素材
|
||||
* @param source 图像源
|
||||
* @param alias tileset 的标识符,包含其在 tilesets 列表中的索引和图片名称
|
||||
*/
|
||||
addTileset(
|
||||
source: SizedCanvasImageSource,
|
||||
identifier: IIndexedIdentifier
|
||||
): IMaterialData | null;
|
||||
|
||||
/**
|
||||
* 添加一个图片
|
||||
* @param source 图像源
|
||||
* @param identifier 图片的标识符,包含其在 images 列表中的索引和图片名称
|
||||
*/
|
||||
addImage(
|
||||
source: SizedCanvasImageSource,
|
||||
identifier: IIndexedIdentifier
|
||||
): IMaterialData;
|
||||
|
||||
/**
|
||||
* 设置指定图块默认显示第几帧
|
||||
* @param identifier 图块标识符,即图块数字
|
||||
* @param defaultFrame 图块的默认帧数
|
||||
*/
|
||||
setDefaultFrame(identifier: number, defaultFrame: number): void;
|
||||
|
||||
/**
|
||||
* 获取图块的默认帧数,-1 表示正常动画,非负整数表示默认使用指定帧数,除非单独指定
|
||||
* @param identifier 图块标识符,即图块数字
|
||||
*/
|
||||
getDefaultFrame(identifier: number): number;
|
||||
|
||||
/**
|
||||
* 缓存某个 tileset,当需要缓存多个时,请使用 {@link cacheTilesetList} 方法
|
||||
* @param identifier tileset 的标识符,即图块数字
|
||||
*/
|
||||
cacheTileset(identifier: number): ITexture | null;
|
||||
|
||||
/**
|
||||
* 缓存一系列 tileset
|
||||
* @param identifierList 标识符列表,即图块数字列表
|
||||
*/
|
||||
cacheTilesetList(
|
||||
identifierList: Iterable<number>
|
||||
): Iterable<ITexture | null>;
|
||||
|
||||
/**
|
||||
* 缓存某个自动元件,当需要缓存多个时,请使用 {@link cacheAutotileList} 方法
|
||||
* @param identifier 自动元件标识符,即图块数字
|
||||
*/
|
||||
cacheAutotile(identifier: number): ITexture | null;
|
||||
|
||||
/**
|
||||
* 缓存一系列自动元件
|
||||
* @param identifierList 自动元件标识符列表,即图块数字列表
|
||||
*/
|
||||
cacheAutotileList(
|
||||
identifierList: Iterable<number>
|
||||
): Iterable<ITexture | null>;
|
||||
|
||||
/**
|
||||
* 把常用素材打包成为图集形式供后续使用
|
||||
*/
|
||||
buildAssets(): Iterable<IMaterialAssetData>;
|
||||
|
||||
/**
|
||||
* 将指定贴图打包进图集
|
||||
* @param texture 贴图对象
|
||||
*/
|
||||
buildToAsset(texture: ITexture): IMaterialAssetData;
|
||||
|
||||
/**
|
||||
* 将一系列贴图打包进贴图对象
|
||||
* @param texture 贴图列表
|
||||
*/
|
||||
buildListToAsset(texture: Iterable<ITexture>): Iterable<IMaterialAssetData>;
|
||||
|
||||
/**
|
||||
* 根据图块标识符在图集中获取对应的可渲染对象
|
||||
* @param identifier 图块标识符,即图块数字
|
||||
*/
|
||||
getRenderable(identifier: number): ITextureRenderable | null;
|
||||
|
||||
/**
|
||||
* 根据图块别名在图集中获取对应的可渲染对象
|
||||
* @param alias 图块的别名,即图块的 id
|
||||
*/
|
||||
getRenderableByAlias(alias: string): ITextureRenderable | null;
|
||||
|
||||
/**
|
||||
* 根据图块别名获取图块标识符,即图块数字
|
||||
* @param alias 图块别名,即图块的 id
|
||||
*/
|
||||
getIdentifierByAlias(alias: string): number | undefined;
|
||||
|
||||
/**
|
||||
* 根据图块标识符获取图块别名,即图块的 id
|
||||
* @param identifier 图块标识符,即图块数字
|
||||
*/
|
||||
getAliasByIdentifier(identifier: number): string | undefined;
|
||||
|
||||
/**
|
||||
* 设置一个图块的 `bigImage` 贴图,即大怪物贴图,但不止怪物能用
|
||||
* @param identifier 图块标识符,即图块数字
|
||||
* @param image `bigImage` 对应的贴图对象
|
||||
* @param frames `bigImage` 的帧数,即贴图有多少帧
|
||||
*/
|
||||
setBigImage(
|
||||
identifier: number,
|
||||
image: ITexture,
|
||||
frames: number
|
||||
): IBigImageReturn;
|
||||
|
||||
/**
|
||||
* 当前的所有图集中是否包含指定的贴图对象
|
||||
* @param texture 贴图对象
|
||||
*/
|
||||
assetContainsTexture(texture: ITexture): boolean;
|
||||
|
||||
/**
|
||||
* 获取指定贴图对象所属的图集索引
|
||||
* @param texture 贴图对象
|
||||
*/
|
||||
getTextureAsset(texture: ITexture): number | undefined;
|
||||
}
|
||||
|
||||
export interface IAssetBuilder {
|
||||
/**
|
||||
* 将图集打包器输出至指定贴图存储对象,只能输出到一个存储对象中,设置多个仅最后一个有效
|
||||
* @param store 贴图存储对象
|
||||
*/
|
||||
pipe(store: ITextureStore): void;
|
||||
|
||||
/**
|
||||
* 添加贴图对象至打包器
|
||||
* @param texture 贴图对象
|
||||
* @returns 当前打包的贴图对象对应的组合数据
|
||||
*/
|
||||
addTexture(texture: ITexture): ITextureComposedData;
|
||||
|
||||
/**
|
||||
* 添加一个贴图对象列表至打包器
|
||||
* @param texture 贴图对象列表
|
||||
* @returns 当前打包的贴图列表的组合数据,每一项代表一个图集,只包含使用到的图集,之前已经打包完成的将不会在列表内
|
||||
*/
|
||||
addTextureList(texture: Iterable<ITexture>): Iterable<ITextureComposedData>;
|
||||
|
||||
/**
|
||||
* 获取可追踪贴图对象
|
||||
*/
|
||||
tracked(): ITrackedAssetData;
|
||||
|
||||
/**
|
||||
* 结束此打包器
|
||||
*/
|
||||
close(): void;
|
||||
}
|
||||
|
||||
export interface ITrackedAssetData extends IDirtyTracker<Set<number>> {
|
||||
/** 图像源列表 */
|
||||
readonly sourceList: Map<number, ImageBitmap>;
|
||||
/**
|
||||
* 贴图引用跳接,`ImageBitmap` 的传递性能远好于其他类型,而贴图图集为了能够动态增加内容会使用画布类型,
|
||||
* 因此需要把贴图生成为额外的 `ImageBitmap`,并提供引用跳接映射。值代表在 `sourceList` 中的索引。
|
||||
*/
|
||||
readonly skipRef: Map<SizedCanvasImageSource, number>;
|
||||
/** 贴图数据 */
|
||||
readonly materials: IMaterialGetter;
|
||||
|
||||
/**
|
||||
* 取消使用此图集,释放相关资源
|
||||
*/
|
||||
close(): void;
|
||||
|
||||
/**
|
||||
* 等待所有打包操作结束
|
||||
*/
|
||||
then(): Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { ITexture } from '@motajs/render-assets';
|
||||
import { BlockCls } from './types';
|
||||
|
||||
export function getClsByString(cls: Cls): BlockCls {
|
||||
switch (cls) {
|
||||
case 'terrains':
|
||||
return BlockCls.Terrains;
|
||||
case 'animates':
|
||||
return BlockCls.Animates;
|
||||
case 'autotile':
|
||||
return BlockCls.Autotile;
|
||||
case 'enemys':
|
||||
return BlockCls.Enemys;
|
||||
case 'items':
|
||||
return BlockCls.Items;
|
||||
case 'npcs':
|
||||
return BlockCls.Npcs;
|
||||
case 'npc48':
|
||||
return BlockCls.Npc48;
|
||||
case 'enemy48':
|
||||
return BlockCls.Enemy48;
|
||||
case 'tileset':
|
||||
return BlockCls.Tileset;
|
||||
default:
|
||||
return BlockCls.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
export function getTextureFrame(cls: BlockCls, texture: ITexture) {
|
||||
switch (cls) {
|
||||
case BlockCls.Animates:
|
||||
case BlockCls.Enemy48:
|
||||
case BlockCls.Npc48:
|
||||
return 4;
|
||||
case BlockCls.Autotile:
|
||||
return texture.width === 384 ? 4 : 1;
|
||||
case BlockCls.Enemys:
|
||||
case BlockCls.Npcs:
|
||||
return 2;
|
||||
case BlockCls.Items:
|
||||
case BlockCls.Terrains:
|
||||
case BlockCls.Tileset:
|
||||
return 1;
|
||||
case BlockCls.Unknown:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user