refactor: 贴图存储方式

This commit is contained in:
unanmed 2026-09-22 21:11:21 +08:00
parent 3bcba48f48
commit d36ea69066
12 changed files with 627 additions and 585 deletions

View File

@ -1,9 +1,3 @@
import { createMaterial } from './material';
export function create() {
createMaterial();
}
export * from './load';
export * from './material';
export * from './save';

View File

@ -6,65 +6,317 @@ import {
import {
AutotileConnection,
AutotileType,
BlockCls,
IAutotileConnection,
IAutotileProcessor,
IMaterialFramedData,
ITextureManager
IMaterialFramedData
} from './types';
import { ICoreState } from '@user/data-state';
import { isNil } from 'lodash-es';
import { logger } from '@motajs/common';
interface ConnectedAutotile {
// 3x4 自动元件索引图
//
// | <- 当上下左右都没有连接时,会使用左上角的内容,其实等同于使用 [12, 17, 42, 47]
// |-------|-------|-------| <- 当上左、上右、下左、下右有连接时,会使用右上角的内容
// | 00 01 | 02 03 | 04 05 |
// | 06 07 | 08 09 | 10 11 |
// |-------|-------|-------| <- 分割线,上面用于控制无连接(左)以及十字连接(右),中间用于判断父子关系(特殊连接)
// | 12 13 | 14 15 | 16 17 |
// | 18 19 | 20 21 | 22 23 |
// |-------|-------|-------| <- 当仅下方有连接时,会用第二行的内容
// | 24 25 | 26 27 | 28 29 |
// | 30 31 | 32 33 | 34 35 |
// |-------|-------|-------| <- 当上下都有连接时,会用第三行的内容
// | 36 37 | 38 39 | 40 41 |
// | 42 43 | 44 45 | 46 47 |
// |-------|-------|-------| <- 当仅上方有连接时,会用第四行的内容
// | | | |
// | | | | <- 左右和上下的连接会相互干扰,具体干扰方式间右上角内容的描述
// | | | <- 当仅左方有连接时,会使用第三列的内容
// | | <- 当左右都有连接时,会使用第二列的内容
// | <- 当仅右方有连接时,会用第一列的内容
// 2x3 自动元件索引图
//
// |-------|-------|
// | 00 01 | 02 03 |
// | 04 05 | 06 07 |
// |-------|-------| <- 分割线,上面用于控制无连接(左)以及十字连接(右),这种自动元件无法从图片获取父子关系
// | 08 09 | 10 11 |
// | 12 13 | 14 15 |
// |-------|-------|
// | 16 17 | 18 19 |
// | 20 21 | 22 23 |
// |-------|-------|
// 此自动元件本质上是把 3x4 自动元件中间的 4x4 区域合并为了这里的 [13, 14, 17, 18]
interface IConnectedAutotile {
/** 左上角 */
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();
/** 自动元件特殊连接方式映射 */
private readonly spec: Map<number, Set<number>> = new Map();
constructor(
readonly manager: ITextureManager,
readonly state: ICoreState
) {}
/** 3x4 自动元件的各方向连接索引 */
readonly conn3x4: Map<number, [number, number, number, number]> = new Map();
/** 2x3 自动元件的各方向连接索引 */
readonly conn2x3: Map<number, [number, number, number, number]> = new Map();
/** 不重复连接映射,用于平铺自动元件,一共 48 种 */
readonly distinct: Map<number, number> = new Map();
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;
constructor(readonly state: ICoreState) {
this.conn3x4 = this.mapAutotile(AutotileType.Big3x4);
this.conn2x3 = this.mapAutotile(AutotileType.Small2x3);
this.deduplicateConnection();
}
setConnection(autotile: number, parent: number): void {
this.parentMap.set(autotile, parent);
const child = this.ensureChildSet(parent);
child.add(autotile);
/**
*
* @param type
*/
private mapAutotile(type: AutotileType) {
// 这些常量非常 magic可以参考文件开头的索引注释来理解
const h = type === AutotileType.Big3x4 ? 2 : 1; // 横向偏移因子
const v = type === AutotileType.Big3x4 ? 12 : 4; // 纵向偏移因子
const luo = type === AutotileType.Big3x4 ? 12 : 8; // leftup origin
const ruo = type === AutotileType.Big3x4 ? 17 : 11; // rightup origin
const ldo = type === AutotileType.Big3x4 ? 42 : 20; // leftdown origin
const rdo = type === AutotileType.Big3x4 ? 47 : 23; // rightdown origin
const luc = type === AutotileType.Big3x4 ? 4 : 2; // leftup corner
const ruc = type === AutotileType.Big3x4 ? 5 : 3; // rightup corner
const rdc = type === AutotileType.Big3x4 ? 11 : 7; // rightdown corner
const ldc = type === AutotileType.Big3x4 ? 10 : 6; // leftdown corner
const result = new Map<number, [number, number, number, number]>();
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;
}
result.set(i, [lu, ru, rd, ld]);
}
return result;
}
/**
*
*/
private deduplicateConnection() {
const usedRect: [number, number, number, number][] = [];
let flag = 0;
// 2x3 和 3x4 的自动元件连接方式一样,因此没必要映射两次
this.conn2x3.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) {
this.distinct.set(num, flag);
usedRect.push(conn.slice() as [number, number, number, number]);
flag++;
} else {
this.distinct.set(num, index);
}
});
}
/**
*
* @param connection
* @param type
* @param cw tile
* @param ch tile
*/
private getSliceRect(
connection: number,
type: AutotileType,
hw: number,
hh: number
): IConnectedAutotile | null {
const map = type === AutotileType.Big3x4 ? this.conn3x4 : this.conn2x3;
const data = map.get(connection);
if (!data) return null;
// 每行的切块数量,可以参考开头的注释理解其含义
const n = type === AutotileType.Big3x4 ? 6 : 4;
const [ltd, rtd, rbd, lbd] = data;
const ltx = (ltd % n) * hw;
const lty = Math.floor(ltd / n) * hh;
const rtx = (rtd % n) * hw;
const rty = Math.floor(rtd / n) * hh;
const rbx = (rbd % n) * hw;
const rby = Math.floor(rbd / n) * hh;
const lbx = (lbd % n) * hw;
const lby = Math.floor(lbd / n) * hh;
const rect: IConnectedAutotile = {
lt: { x: ltx, y: lty, w: hw, h: hh },
rt: { x: rtx, y: rty, w: hw, h: hh },
rb: { x: rbx, y: rby, w: hw, h: hh },
lb: { x: lbx, y: lby, w: hw, h: hh }
};
return rect;
}
/**
*
* @param frameWidth
* @param height
* @param type
*/
private getAutotileCellSize(
frameWidth: number,
height: number,
type: AutotileType
): [width: number, height: number] {
if (type === AutotileType.Big3x4) {
if (frameWidth % 3 !== 0 || height % 4 !== 0) {
logger.warn(190, frameWidth.toString(), height.toString());
return [0, 0];
}
return [frameWidth / 3, height / 4];
} else {
if (frameWidth % 2 !== 0 || height % 3 !== 0) {
logger.warn(190, frameWidth.toString(), height.toString());
return [0, 0];
}
return [frameWidth / 2, height / 3];
}
}
flatten(
source: SizedCanvasImageSource,
type: AutotileType,
frames: number
): SizedCanvasImageSource | null {
if (source.width % frames !== 0) {
logger.warn(189, source.width.toString(), frames.toString());
return null;
}
const { width, height } = source;
// frame width
const fw = width / frames;
// cell width, cell height
const [cw, ch] = this.getAutotileCellSize(fw, height, type);
if (cw % 2 !== 0 || ch % 2 !== 0) {
logger.warn(190, fw.toString(), height.toString());
return null;
}
// 画到画布上
const canvas = document.createElement('canvas');
canvas.width = cw * frames;
canvas.height = ch * 48;
const ctx = canvas.getContext('2d');
if (!ctx) return null;
// half width, half height
const hw = cw / 2;
const hh = ch / 2;
// 遍历每个组合
this.distinct.forEach((index, conn) => {
const rect = this.getSliceRect(conn, type, hw, hh)!;
const { lt, rt, rb, lb } = rect;
const y = index * ch;
for (let i = 0; i < frames; i++) {
const x = i * cw;
const ox = i * fw;
// prettier-ignore
ctx.drawImage(source, lt.x + ox, lt.y, lt.w, lt.h, x, y, hw, hh);
// prettier-ignore
ctx.drawImage(source, rt.x + ox, rt.y, rt.w, rt.h, x + hw, y, hw, hh);
// prettier-ignore
ctx.drawImage(source, rb.x + ox, rb.y, rb.w, rb.h, x + hw, y + hh, hw, hh);
// prettier-ignore
ctx.drawImage(source, lb.x + ox, lb.y, lb.w, lb.h, x, y + hh, hw, hh);
}
});
return canvas;
}
setConnection(autotile: number, target: number): void {
const set = this.spec.getOrInsertComputed(autotile, () => new Set());
set.add(target);
}
/**
*
* @param length
* @param index
* @param width
*/
private connectEdge(length: number, index: number, width: number): number {
// 最高位表示左上,低位依次顺时针旋转
@ -136,10 +388,12 @@ export class AutotileProcessor implements IAutotileProcessor {
center: 0
};
}
let res: number = this.connectEdge(array.length, index, width);
const childList = this.childMap.get(block);
let res = this.connectEdge(array.length, index, width);
const spec = this.spec.get(block);
// 最高位表示左上,低位依次顺时针旋转
// 在边缘时,在地图外的部分一定会连接上,所以哪怕索引可能导致串行,也对结果没有任何影响
// 例如在右边缘时,右侧一定连接,此时不论其与下一行的首个图块是否一个连接,都不会影响需要连接的结果
const a7 = array[index - width - 1] ?? 0;
const a6 = array[index - width] ?? 0;
const a5 = array[index - width + 1] ?? 0;
@ -151,7 +405,7 @@ export class AutotileProcessor implements IAutotileProcessor {
// Benchmark https://www.measurethat.net/Benchmarks/Show/35271/0/convert-boolean-to-number
if (!childList || childList.size === 0) {
if (!spec || spec.size === 0) {
// 不包含子元件,那么直接跟相同的连接
res |=
+(a0 === block) |
@ -164,14 +418,14 @@ export class AutotileProcessor implements IAutotileProcessor {
(+(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);
+spec.has(a0) |
(+spec.has(a1) << 1) |
(+spec.has(a2) << 2) |
(+spec.has(a3) << 3) |
(+spec.has(a4) << 4) |
(+spec.has(a5) << 5) |
(+spec.has(a6) << 6) |
(+spec.has(a7) << 7);
}
return {
@ -186,7 +440,7 @@ export class AutotileProcessor implements IAutotileProcessor {
target: number,
direction: AutotileConnection
): number {
const childList = this.childMap.get(center);
const childList = this.spec.get(center);
if (!childList || !childList.has(target)) {
return connection & ~direction;
} else {
@ -194,40 +448,13 @@ export class AutotileProcessor implements IAutotileProcessor {
}
}
/**
*
* @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(
render(
tile: IMaterialFramedData,
connection: number
): ITextureRenderable | null {
const { texture } = tile;
const size = texture.height === 32 * 48 ? 32 : 48;
const index = distinctConnectionMap.get(connection);
const index = this.distinct.get(connection);
if (isNil(index)) return null;
const { rect } = texture.render();
return {
@ -237,22 +464,12 @@ export class AutotileProcessor implements IAutotileProcessor {
}
*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);
const index = this.distinct.get(connection);
if (isNil(index)) return;
for (let i = 0; i < frames; i++) {
yield {
@ -261,198 +478,4 @@ export class AutotileProcessor implements IAutotileProcessor {
};
}
}
/**
* 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 3x42 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);
}
});
}

View File

@ -1,9 +1,3 @@
import { createAutotile } from './autotile';
export function createMaterial() {
createAutotile();
}
export * from './autotile';
export * from './builder';
export * from './manager';

View File

@ -2,7 +2,6 @@ import {
ITexture,
ITextureComposedData,
ITextureRenderable,
ITextureSplitter,
ITextureStore,
SizedCanvasImageSource,
Texture,
@ -11,36 +10,38 @@ import {
TextureStore
} from '@motajs/render';
import {
IBlockIdentifier,
IMaterialData,
ITextureManager,
IIndexedIdentifier,
IMaterialAssetData,
BlockCls,
IAssetBuilder,
IMaterialFramedData,
ITrackedAssetData
ITrackedAssetData,
IAutotileProcessor,
AutotileType
} from './types';
import { ICoreState } from '@user/data-state';
import { logger } from '@motajs/common';
import { getClsByString, getTextureFrame } from './utils';
import { isNil } from 'lodash-es';
import { AssetBuilder } from './builder';
import { AutotileProcessor } from './autotile';
import { ITileStore, TileType } from '@user/data-common';
interface TilesetCache {
interface ITilesetCache {
/** 是否已经在贴图库中存在 */
readonly existed: boolean;
/** 贴图对象 */
readonly texture: ITexture;
}
export class MaterialManager implements ITextureManager {
export class TextureManager implements ITextureManager {
readonly autotile: IAutotileProcessor;
readonly textures: ITextureStore = new TextureStore();
readonly tileStore: ITextureStore = new TextureStore();
readonly tilesetStore: ITextureStore = new TextureStore();
readonly imageStore: ITextureStore = new TextureStore();
readonly assetStore: ITextureStore = new TextureStore();
readonly textures: ITextureStore = new TextureStore();
/** 自动元件图像源映射 */
readonly autotileSource: Map<number, SizedCanvasImageSource> = new Map();
@ -52,19 +53,21 @@ export class MaterialManager implements ITextureManager {
/** 带有脏标记追踪的图集对象 */
readonly trackedAsset: ITrackedAssetData;
/** tileset 中 `Math.floor(id / 10000) + 1` 映射到 tileset 对应索引的映射,用于处理图块超出 10000 的 tileset */
/** tileset 中的偏移索引映射,可以处理超出 Tileset 图块数量单元的 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 frames: Map<number, number> = new Map();
/** 每个图块的帧偏移量 */
readonly tileOffsets: Map<number, number> = new Map();
/** 每个 Tileset 的 tile 尺寸 */
readonly tilesetCells: Map<number, [number, number]> = new Map();
/** 自动元件类型映射 */
readonly autotileType: Map<number, AutotileType> = new Map();
/** 网格切分器 */
readonly gridSplitter: TextureGridSplitter = new TextureGridSplitter();
@ -78,120 +81,151 @@ export class MaterialManager implements ITextureManager {
/** 是否已经构建过素材 */
private built: boolean = false;
constructor(readonly state: ICoreState) {
constructor(
readonly state: ICoreState,
readonly tiles: ITileStore,
readonly tilesetReserve: number,
readonly tilesetUnit: number
) {
this.autotile = new AutotileProcessor(state);
this.assetBuilder = new AssetBuilder(this, state);
this.assetBuilder.pipe(this.assetStore);
this.trackedAsset = this.assetBuilder.tracked();
}
/**
*
* @param source
* @param map id
* @param store
* @param splitter 使
* @param splitterData
* @param processTexture
*
* @param splitted
* @param map
* @param frames 使
* @param offsets
*/
private addMappedSource<T>(
source: SizedCanvasImageSource,
map: ArrayLike<IBlockIdentifier>,
store: ITextureStore,
splitter: ITextureSplitter<T>,
splitterData: T,
processTexture?: (tex: ITexture) => void
private addSplittedSource(
splitted: Iterable<ITexture>,
map: ArrayLike<number>,
frames: number,
offsets: number
): 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 [...splitted].map((v, i) => {
if (isNil(map[i])) {
return {
store,
store: this.tileStore,
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 num = map[i];
const id = this.tiles.id(num);
if (isNil(id)) {
logger.warn(184, num.toString());
return {
store: this.tileStore,
texture: v,
identifier: -1,
alias: '@internal-unknown'
};
}
this.tileStore.addTexture(num, v);
this.tileStore.alias(num, id);
this.frames.set(num, frames);
this.tileOffsets.set(num, offsets);
const data: IMaterialData = {
store,
store: this.tileStore,
texture: v,
identifier: num,
alias: id
};
return data;
});
return res;
}
addGrid(
source: SizedCanvasImageSource,
map: ArrayLike<IBlockIdentifier>
map: ArrayLike<number>,
width: number,
height: number
): Iterable<IMaterialData> {
return this.addMappedSource(
source,
map,
this.tileStore,
this.gridSplitter,
[32, 32]
);
const tex = new Texture(source);
const textures = [...this.gridSplitter.split(tex, [width, height])];
if (textures.length !== map.length) {
logger.warn(75, textures.length.toString(), map.length.toString());
}
return this.addSplittedSource(textures, map, 1, width);
}
addRowAnimate(
source: SizedCanvasImageSource,
map: ArrayLike<IBlockIdentifier>,
map: ArrayLike<number>,
width: number,
height: number
): Iterable<IMaterialData> {
return this.addMappedSource(
source,
map,
this.tileStore,
this.rowSplitter,
height
);
if (source.width % width !== 0) {
logger.warn(185, source.width.toString(), width.toString());
return [];
}
const tex = new Texture(source);
const textures = [...this.rowSplitter.split(tex, height)];
if (textures.length !== map.length) {
logger.warn(75, textures.length.toString(), map.length.toString());
}
const frames = source.width / width;
return this.addSplittedSource(textures, map, frames, width);
}
addAutotile(
source: SizedCanvasImageSource,
identifier: IBlockIdentifier
num: number,
type: AutotileType
): void {
this.autotileSource.set(identifier.num, source);
this.tileStore.alias(identifier.num, identifier.id);
this.clsMap.set(identifier.num, BlockCls.Autotile);
const id = this.tiles.id(num);
if (isNil(id)) {
logger.warn(184, num.toString());
return;
}
this.autotileSource.set(num, source);
this.tileStore.alias(num, id);
this.autotileType.set(num, type);
}
addTileset(
source: SizedCanvasImageSource,
identifier: IIndexedIdentifier
identifier: IIndexedIdentifier,
cellWidth: number,
cellHeight: number
): 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);
if (
source.width % cellWidth !== 0 ||
source.height % cellHeight !== 0
) {
logger.warn(
186,
source.width.toString(),
source.height.toString(),
cellWidth.toString(),
cellHeight.toString()
);
}
// 计算 tile 数量,溢出部分不算
const width = Math.floor(source.width / cellWidth);
const height = Math.floor(source.height / cellHeight);
const count = width * height;
const offset = Math.ceil(count / 10000);
// 一个 tileset 可能不止 tilesetUnit 个图块,需要计算偏移
const offset = Math.ceil(count / this.tilesetUnit);
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);
// 这个 tileset 所包含的所有单位,都应该映射至当前 tileset所以循环设置这期间的所有映射
const end = this.nowTilesetOffset + offset;
for (let i = this.nowTilesetOffset; i < end; i++) {
this.tilesetOffsetMap.set(i, identifier.index);
@ -205,6 +239,7 @@ export class MaterialManager implements ITextureManager {
identifier: identifier.index,
alias: identifier.alias
};
this.tilesetCells.set(identifier.index, [cellWidth, cellHeight]);
return data;
}
@ -232,31 +267,43 @@ export class MaterialManager implements ITextureManager {
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);
getFrameCount(num: number): number {
return this.frames.get(num) ?? 0;
}
getTile(num: number): Readonly<IMaterialFramedData> | null {
if (num < this.tilesetReserve) {
const type = this.tiles.getType(num);
if (type === TileType.Autotile && this.autotileSource.has(num)) {
this.cacheAutotile(num);
}
const texture = this.tileStore.getTexture(identifier);
const texture = this.tileStore.getTexture(num);
if (!texture) return null;
const frames = this.frames.get(num);
const offset = this.tileOffsets.get(num);
if (isNil(frames) || isNil(offset)) {
logger.warn(187);
return null;
}
return {
texture,
cls,
offset: 32,
frames: getTextureFrame(cls, texture),
defaultFrame: this.defaultFrames.get(identifier) ?? -1
tileType: type,
offset,
frames,
defaultFrame: this.defaultFrames.get(num) ?? -1
};
} else {
const texture = this.cacheTileset(identifier);
const texture = this.cacheTileset(num);
if (!texture) return null;
return {
texture,
cls: BlockCls.Tileset,
offset: 32,
tileType: TileType.Tileset,
// Tileset 不需要偏移量
offset: 0,
frames: 1,
defaultFrame: -1
};
@ -289,28 +336,34 @@ export class MaterialManager implements ITextureManager {
return this.imageStore.fromAlias(alias);
}
private getTilesetOwnTexture(identifier: number): TilesetCache | null {
const texture = this.tileStore.getTexture(identifier);
private getTilesetOwnTexture(num: number): ITilesetCache | null {
const texture = this.tileStore.getTexture(num);
if (texture) return { existed: true, texture };
// 如果 tileset 不存在,那么执行缓存操作
const offset = Math.floor(identifier / 10000);
const index = this.tilesetOffsetMap.get(offset - 1);
const adjustedNum = num - this.tilesetReserve;
const offset = Math.floor(adjustedNum / this.tilesetUnit);
const index = this.tilesetOffsetMap.get(offset);
if (isNil(index)) return null;
// 获取对应的 tileset 贴图
const tileset = this.tilesetStore.getTexture(index);
if (!tileset) return null;
// 计算图块位置
const rest = identifier - offset * 10000;
const cell = this.tilesetCells.get(index);
if (!tileset || !cell) return null;
// 计算图块位置,首先计算没有偏移时图块的索引,即假如 Tileset 的左上角是 0计算 num 的索引应该是什么
const unoffset = adjustedNum - offset * this.tilesetUnit;
const { width, height } = tileset;
const tileWidth = Math.floor(width / 32);
const tileHeight = Math.floor(height / 32);
const [cellWidth, cellHeight] = cell;
const tileWidth = Math.floor(width / cellWidth);
const tileHeight = Math.floor(height / cellHeight);
// 如果图块位置超出了贴图范围
if (rest > tileWidth * tileHeight) return null;
if (unoffset > tileWidth * tileHeight) {
logger.warn(188, num.toString());
return null;
}
// 裁剪 tileset生成贴图
const x = rest % tileWidth;
const y = Math.floor(rest / tileWidth);
const x = unoffset % tileWidth;
const y = Math.floor(unoffset / tileWidth);
const newTexture = new Texture(tileset.source);
newTexture.clip(x * 32, y * 32, 32, 32);
newTexture.clip(x * cellWidth, y * cellHeight, cellWidth, cellHeight);
return { existed: false, texture: newTexture };
}
@ -356,8 +409,6 @@ export class MaterialManager implements ITextureManager {
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);
@ -377,8 +428,6 @@ export class MaterialManager implements ITextureManager {
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);
@ -390,38 +439,41 @@ export class MaterialManager implements ITextureManager {
/**
* `tileStore` `null`
* @param identifier
* @param num
*/
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);
private getFlattenedAutotile(num: number): SizedCanvasImageSource | null {
const type = this.tiles.getType(num);
if (type !== TileType.Autotile) return null;
if (this.tileStore.getTexture(num)) return null;
const source = this.autotileSource.get(num);
if (!source) return null;
const frames = source.width === 96 ? 1 : 4;
const flattened = AutotileProcessor.flatten({ source, frames });
const autotileType = this.autotileType.get(num);
const frames = this.frames.get(num);
if (isNil(autotileType) || isNil(frames)) {
logger.warn(191);
return null;
}
const flattened = this.autotile.flatten(source, autotileType, frames);
if (!flattened) return null;
return flattened;
}
cacheAutotile(identifier: number): ITexture | null {
const flattened = this.getFlattenedAutotile(identifier);
cacheAutotile(num: number): ITexture | null {
const existed = this.tileStore.getTexture(num);
if (existed) return existed;
const flattened = this.getFlattenedAutotile(num);
if (!flattened) return null;
const tex = new Texture(flattened);
this.tileStore.addTexture(identifier, tex);
this.tileStore.addTexture(num, tex);
const data = this.assetBuilder.addTexture(tex);
tex.toAsset(data);
this.autotileSource.delete(identifier);
this.autotileSource.delete(num);
this.checkAssetDirty(data);
return tex;
}
cacheAutotileList(
identifierList: Iterable<number>
): Iterable<ITexture | null> {
const arr = [...identifierList];
cacheAutotileList(list: Iterable<number>): Iterable<ITexture | null> {
const arr = [...list];
const toAdd: ITexture[] = [];
arr.forEach(v => {
@ -500,45 +552,31 @@ export class MaterialManager implements ITextureManager {
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);
private getTextureOf(num: number): ITexture | null {
const existed = this.tileStore.getTexture(num);
if (existed) return existed;
if (num >= this.tilesetReserve) {
return this.cacheTileset(num);
} else {
const type = this.tiles.getType(num);
if (type === TileType.Autotile) {
return this.cacheAutotile(num);
} else {
return null;
}
}
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);
getRenderable(num: number): ITextureRenderable | null {
const texture = this.getTextureOf(num);
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);
const num = this.tiles.num(alias);
if (isNil(num)) return null;
return this.getRenderable(num);
}
assetContainsTexture(texture: ITexture): boolean {

View File

@ -6,23 +6,13 @@ import {
ITextureStore,
SizedCanvasImageSource
} from '@motajs/render';
import { ITileStore, TileType } from '@user/data-common';
import { ICoreStateExtended } from '@user/data-state';
export const enum BlockCls {
Unknown,
Terrains,
Animates,
Enemys,
Npcs,
Items,
Enemy48,
Npc48,
Tileset,
Autotile
}
export const enum AutotileType {
/** 2x3 大小的自动元件 */
Small2x3,
/** 3x4 大小的自动元件 */
Big3x4
}
@ -85,8 +75,8 @@ export interface IAutotileConnection {
export interface IMaterialFramedData {
/** 贴图对象 */
texture: ITexture;
/** 图块类型 */
cls: BlockCls;
/** 贴图对应图块的图块类型 */
tileType: TileType;
/** 贴图总帧数 */
frames: number;
/** 每帧的横向偏移量 */
@ -102,12 +92,21 @@ export interface IMaterialAsset
}
export interface IAutotileProcessor extends ICoreStateExtended {
/** 该自动元件处理器使用的素材管理器 */
readonly manager: ITextureManager;
/**
* 48
* @param source
* @param type
* @param frames
*/
flatten(
source: SizedCanvasImageSource,
type: AutotileType,
frames: number
): SizedCanvasImageSource | null;
/**
*
*
*
*
* @param autotile
* @param target
*/
@ -142,52 +141,22 @@ export interface IAutotileProcessor extends ICoreStateExtended {
/**
*
* @param autotile
* @param tile
* @param connection
* @returns
*/
render(autotile: number, connection: number): ITextureRenderable | null;
/**
*
* @param tile
* @param connection
* @returns
*/
renderWith(
tile: Readonly<IMaterialFramedData>,
render(
tile: IMaterialFramedData,
connection: number
): ITextureRenderable | null;
/**
*
* @param tile
* @param connection
* @returns
*/
renderWithoutCheck(
tile: Readonly<IMaterialFramedData>,
connection: number
): ITextureRenderable | null;
/**
*
* @param autotile
*
* @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>;
@ -196,33 +165,27 @@ export interface IAutotileProcessor extends ICoreStateExtended {
export interface ITextureGetter {
/**
*
* @param identifier
* @param num
*/
getTile(identifier: number): Readonly<IMaterialFramedData> | null;
/**
*
* @param identifier
*/
getBlockCls(identifier: number): BlockCls;
getTile(num: number): Readonly<IMaterialFramedData> | null;
/**
*
* @param identifier
* @param num
*/
getAsset(identifier: number): ITextureComposedData | null;
getAsset(num: number): ITextureComposedData | null;
/**
*
* @param identifier
* @param num
*/
getTileset(identifier: number): ITexture | null;
getTileset(num: number): ITexture | null;
/**
*
* @param identifier
* @param num
*/
getImage(identifier: number): ITexture | null;
getImage(num: number): ITexture | null;
}
export interface ITextureAliasGetter {
@ -249,16 +212,15 @@ export interface ITextureAliasGetter {
* @param alias
*/
getAssetByAlias(alias: string): ITextureComposedData | null;
/**
*
* @param alias id
*/
getBlockClsByAlias(alias: string): BlockCls;
}
export interface ITextureManager
extends ITextureGetter, ITextureAliasGetter, ICoreStateExtended {
/** 使用的图块数据存储对象 */
readonly tiles: ITileStore;
/** 自动元件处理器 */
readonly autotile: IAutotileProcessor;
/** 通过加载获取的所有纹理贴图存储,包括图块、普通图片等 */
readonly textures: ITextureStore;
/** 贴图存储,把 terrains 等内容单独分开存储 */
@ -275,51 +237,73 @@ export interface ITextureManager
/** 带有脏标记追踪的图集信息 */
readonly trackedAsset: ITrackedAssetData;
/** 图块类型映射 */
readonly clsMap: Map<number, BlockCls>;
/** Tileset 额外素材预留多少数字,即从多少数字开始,素材变为额外素材 */
readonly tilesetReserve: number;
/**
* Tileset 10000 使 10000
* 20000, 30000
*/
readonly tilesetUnit: number;
/**
* terrains items
*
* @param source
* @param map id
* @param width
* @param height
* @param map
*
* ```txt
* 0 1 2 ... n-1
* n n+1 n+2 ... 2n-1
* . ... ... ... ...
* ```
*/
addGrid(
source: SizedCanvasImageSource,
map: ArrayLike<IBlockIdentifier>
map: ArrayLike<number>,
width: number,
height: number
): Iterable<IMaterialData>;
/**
* animates enemys npcs enemy48 npc48
*
* @param source
* @param map id
* @param frames
* @param height
* @param map
* @param width
* @param height
*/
addRowAnimate(
source: SizedCanvasImageSource,
map: ArrayLike<IBlockIdentifier>,
map: ArrayLike<number>,
width: number,
height: number
): Iterable<IMaterialData>;
/**
*
* @param source
* @param identifier id
* @param identifier
* @param type 3x4 2x3
* @returns 西
*/
addAutotile(
source: SizedCanvasImageSource,
identifier: IBlockIdentifier
identifier: number,
type: AutotileType
): void;
/**
* tileset
* @param source
* @param alias tileset tilesets
* @param identifier tileset
* @param cellWidth Tileset tile
* @param cellHeight Tileset tile
*/
addTileset(
source: SizedCanvasImageSource,
identifier: IIndexedIdentifier
identifier: IIndexedIdentifier,
cellWidth: number,
cellHeight: number
): IMaterialData | null;
/**
@ -334,44 +318,48 @@ export interface ITextureManager
/**
*
* @param identifier
* @param num
* @param defaultFrame
*/
setDefaultFrame(identifier: number, defaultFrame: number): void;
setDefaultFrame(num: number, defaultFrame: number): void;
/**
* -1 使
* @param identifier
* @param num
*/
getDefaultFrame(identifier: number): number;
getDefaultFrame(num: number): number;
/**
* tileset使 {@link cacheTilesetList}
* @param identifier tileset
* 0
* @param num
*/
cacheTileset(identifier: number): ITexture | null;
getFrameCount(num: number): number;
/**
* tileset
* @param identifierList
* tileset
* 使 {@link cacheTilesetList}
* @param num tileset
*/
cacheTilesetList(
identifierList: Iterable<number>
): Iterable<ITexture | null>;
cacheTileset(num: number): ITexture | null;
/**
* 使 {@link cacheAutotileList}
* @param identifier
* tileset
* @param list
*/
cacheAutotile(identifier: number): ITexture | null;
cacheTilesetList(list: Iterable<number>): Iterable<ITexture | null>;
/**
* 48
* 使 {@link cacheAutotileList}
* @param num
*/
cacheAutotile(num: number): ITexture | null;
/**
*
* @param identifierList
* @param list
*/
cacheAutotileList(
identifierList: Iterable<number>
): Iterable<ITexture | null>;
cacheAutotileList(list: Iterable<number>): Iterable<ITexture | null>;
/**
* 使
@ -392,9 +380,9 @@ export interface ITextureManager
/**
*
* @param identifier
* @param num
*/
getRenderable(identifier: number): ITextureRenderable | null;
getRenderable(num: number): ITextureRenderable | null;
/**
*
@ -402,18 +390,6 @@ export interface ITextureManager
*/
getRenderableByAlias(alias: string): ITextureRenderable | null;
/**
*
* @param alias id
*/
getIdentifierByAlias(alias: string): number | undefined;
/**
* id
* @param identifier
*/
getAliasByIdentifier(identifier: number): string | undefined;
/**
*
* @param texture

View File

@ -1,7 +1,7 @@
import { IBGMPlayer, IMotaAudioContext, ISoundPlayer } from '@motajs/audio';
import { ISaveSystem } from './save';
import { ICoreState } from '@user/data-state';
import { IAutotileProcessor, ITextureManager } from './material';
import { ITextureManager } from './material';
import { IRenderTreeRoot } from '@motajs/render';
import { IExcitation, IExcitationDivider } from '@motajs/animate';
@ -16,8 +16,6 @@ export interface IClientBase extends ICoreState {
readonly bgmPlayer: IBGMPlayer<BgmIds>;
/** 素材管理器 */
readonly materials: ITextureManager;
/** 自动元件处理器 */
readonly autotile: IAutotileProcessor;
/** 渲染画面的根元素 */
readonly renderer: IRenderTreeRoot;
/** 用于渲染系统的 Raf 激励源 */

View File

@ -12,7 +12,7 @@ import { IRenderTreeRoot, MotaRenderer } from '@motajs/render';
import {
ITextureManager,
IAutotileProcessor,
MaterialManager,
TextureManager,
AutotileProcessor,
ISaveSystem,
SaveSystem
@ -48,7 +48,6 @@ export class ClientCore extends CoreState implements IClientCore {
// Layer 5 渲染顶层
readonly materials: ITextureManager;
readonly autotile: IAutotileProcessor;
readonly rafExcitation: IExcitation<number>;
readonly excitationDivider: IExcitationDivider<number>;
@ -81,8 +80,12 @@ export class ClientCore extends CoreState implements IClientCore {
//#region 素材系统
this.materials = new MaterialManager(this);
this.autotile = new AutotileProcessor(this.materials, this);
this.materials = new TextureManager(
this,
this.tileStore,
config.tilesetReserve,
config.tilesetUnit
);
//#endregion

View File

@ -3,4 +3,9 @@ import { ClientCore } from './client';
// TODO: 逐渐弱化 ClientCore 的单例概念,每个接口都通过参数传入 IClientCore 对象
/** 客户端实例 */
export const client = new ClientCore();
export const client = new ClientCore({
clientURL: 'placeholder',
// 这两个值不要随意调整,若需要调整,务必先在编辑器中调整“额外素材预留”与“额外素材单位”,否则必定会导致游戏出错
tilesetReserve: 100000,
tilesetUnit: 5000
});

View File

@ -1261,7 +1261,7 @@ export class MapRenderer
} else {
// 多帧图块
if (tex.cls === BlockCls.Autotile) {
const gen = this.autotile.renderAnimatedWith(tex, 0b1111_1111);
const gen = this.autotile.renderAnimated(tex, 0b1111_1111);
return this.useDynamicBackground(gl, data, [...gen]);
} else {
const gen = this.tileAnimater.once(tex.texture, tex.frames);

View File

@ -4,6 +4,10 @@ import { IMapExtensionManager, IMapRenderer } from './render/map';
export interface IClientCoreConfig {
/** 渲染端数据配置文件路径,相对于 `src/content` */
readonly clientURL: string;
/** Tileset 预留数量 */
readonly tilesetReserve: number;
/** Tileset 单元数量 */
readonly tilesetUnit: number;
}
export interface IClientCore extends IClientSystem {

View File

@ -33,7 +33,6 @@ export function create() {
}
async function createModule() {
UserClientBase.create();
ClientModules.create();
LegacyUI.create();

View File

@ -262,6 +262,14 @@
"181": "Number $1 has been registered to $2 store, old one will be overridden.",
"182": "Cannot bind '$1' instance to '$2' since they belong to different CoreState instance.",
"183": "An in-map location is expected to begin map graph building, but got outside.",
"184": "Cannot add tile texture $1 since no registered id for it in tile store.",
"185": "Cannot add row animated texture since it cannot be splitted averagely by specified width. Texture width: $1, split width: $2.",
"186": "Tileset has a size that cannot be splitted evenly by specified cell size, overflowed content will be ignored. Tileset size: [$1, $2], cell size: [$1, $2].",
"187": "Cannot get tile that indeed has a texture but no frame and offset infomation, which seems like a internal bug of mota-js engine, please report a bug.",
"188": "Tileset tile $1 exceeds tileset itself. You may check the num indeed corresponding to a tileset tile, or may a internal bug of mota-js engine.",
"189": "Cannot flatten autotile since it cannot be splitted evenly. Autotile width: $1, frames: $2.",
"190": "Cannot flatten autotile since its width or height cannot be divided evenly. Autotile frame size: [$1,$2].",
"191": "Cannot cache autotile since no its frame or type data stored.",
"1001": "Event(setBlock): Unknown tile '$1' when setting block."
}
}