mirror of
https://github.com/motajs/template.git
synced 2026-09-23 08:40:16 +08:00
refactor: 贴图存储方式
This commit is contained in:
parent
3bcba48f48
commit
d36ea69066
@ -1,9 +1,3 @@
|
||||
import { createMaterial } from './material';
|
||||
|
||||
export function create() {
|
||||
createMaterial();
|
||||
}
|
||||
|
||||
export * from './load';
|
||||
export * from './material';
|
||||
export * from './save';
|
||||
|
||||
@ -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 表示 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,9 +1,3 @@
|
||||
import { createAutotile } from './autotile';
|
||||
|
||||
export function createMaterial() {
|
||||
createAutotile();
|
||||
}
|
||||
|
||||
export * from './autotile';
|
||||
export * from './builder';
|
||||
export * from './manager';
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 贴图对象
|
||||
|
||||
@ -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 激励源 */
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
});
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -33,7 +33,6 @@ export function create() {
|
||||
}
|
||||
|
||||
async function createModule() {
|
||||
UserClientBase.create();
|
||||
ClientModules.create();
|
||||
LegacyUI.create();
|
||||
|
||||
|
||||
@ -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."
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user