refactor(04-07): adapt material interfaces (drop big-image, inject state)

- delete MaterialManager big-image implementation (IBigImageReturn import, bigImageStore/bigImageData/bigImageId fields, 5 methods, setDefaultFrame coupling)
- change 7 consumers getIfBigImage -> getTile (door x2, hero x3, renderer, vertex); drop getOffsetPool big-image offset loop
- delete cache.ts legacy big-image path (RenderableDataBase.bigImage, literals, destructuring, enemys special-case, big-image branch, unused enemys decl)
- inject state via constructor into MaterialManager/AutotileProcessor/AssetBuilder/TrackedAssetData (from ClientCore; renderer uses manager.state)
- add MaterialManager.textures (dedicated TextureStore)
- declare @user/data-state in client-base/package.json (lockfile not synced)
This commit is contained in:
unanmed 2026-09-22 11:52:55 +08:00
parent 786c00c499
commit 78873d0a27
10 changed files with 31 additions and 130 deletions

View File

@ -4,6 +4,7 @@
"@motajs/audio": "workspace:*",
"@motajs/render": "workspace:*",
"@motajs/client-base": "workspace:*",
"@user/data-base": "workspace:*"
"@user/data-base": "workspace:*",
"@user/data-state": "workspace:*"
}
}

View File

@ -12,6 +12,7 @@ import {
IMaterialFramedData,
ITextureManager
} from './types';
import { ICoreState } from '@user/data-state';
import { isNil } from 'lodash-es';
interface ConnectedAutotile {
@ -45,7 +46,10 @@ export class AutotileProcessor implements IAutotileProcessor {
/** 自动元件父子关系映射,父元件 -> 子元件列表 */
readonly childMap: Map<number, Set<number>> = new Map();
constructor(readonly manager: ITextureManager) {}
constructor(
readonly manager: ITextureManager,
readonly state: ICoreState
) {}
private ensureChildSet(num: number) {
const set = this.childMap.get(num);

View File

@ -7,6 +7,7 @@ import {
SizedCanvasImageSource
} from '@motajs/render';
import { IAssetBuilder, ITextureGetter, ITrackedAssetData } from './types';
import { ICoreState } from '@user/data-state';
import { logger, PrivateListDirtyTracker } from '@motajs/common';
export class AssetBuilder implements IAssetBuilder {
@ -24,8 +25,11 @@ export class AssetBuilder implements IAssetBuilder {
/** 贴图更新的 promise */
private pending: Promise<void> = Promise.resolve();
constructor(readonly materials: ITextureGetter) {
this.trackedData = new TrackedAssetData(materials, this);
constructor(
readonly materials: ITextureGetter,
readonly state: ICoreState
) {
this.trackedData = new TrackedAssetData(materials, this, state);
}
pipe(store: ITextureStore): void {
@ -109,7 +113,8 @@ class TrackedAssetData
constructor(
readonly materials: ITextureGetter,
readonly builder: AssetBuilder
readonly builder: AssetBuilder,
readonly state: ICoreState
) {
super(0);
}

View File

@ -17,11 +17,11 @@ import {
IIndexedIdentifier,
IMaterialAssetData,
BlockCls,
IBigImageReturn,
IAssetBuilder,
IMaterialFramedData,
ITrackedAssetData
} from './types';
import { ICoreState } from '@user/data-state';
import { logger } from '@motajs/common';
import { getClsByString, getTextureFrame } from './utils';
import { isNil } from 'lodash-es';
@ -40,7 +40,7 @@ export class MaterialManager implements ITextureManager {
readonly tilesetStore: ITextureStore = new TextureStore();
readonly imageStore: ITextureStore = new TextureStore();
readonly assetStore: ITextureStore = new TextureStore();
readonly bigImageStore: ITextureStore = new TextureStore();
readonly textures: ITextureStore = new TextureStore();
/** 自动元件图像源映射 */
readonly autotileSource: Map<number, SizedCanvasImageSource> = new Map();
@ -52,8 +52,6 @@ export class MaterialManager implements ITextureManager {
/** 带有脏标记追踪的图集对象 */
readonly trackedAsset: ITrackedAssetData;
/** 大怪物数据 */
readonly bigImageData: Map<number, IMaterialFramedData> = new Map();
/** tileset 中 `Math.floor(id / 10000) + 1` 映射到 tileset 对应索引的映射,用于处理图块超出 10000 的 tileset */
readonly tilesetOffsetMap: Map<number, number> = new Map();
/** 图集打包器 */
@ -73,8 +71,6 @@ export class MaterialManager implements ITextureManager {
/** 行切分器 */
readonly rowSplitter: TextureRowSplitter = new TextureRowSplitter();
/** 大怪物贴图的标识符 */
private bigImageId: number = 0;
/** 当前 tileset 索引 */
private nowTilesetIndex: number = -1;
/** 当前 tileset 偏移 */
@ -82,8 +78,8 @@ export class MaterialManager implements ITextureManager {
/** 是否已经构建过素材 */
private built: boolean = false;
constructor() {
this.assetBuilder = new AssetBuilder(this);
constructor(readonly state: ICoreState) {
this.assetBuilder = new AssetBuilder(this, state);
this.assetBuilder.pipe(this.assetStore);
this.trackedAsset = this.assetBuilder.tracked();
}
@ -230,10 +226,6 @@ export class MaterialManager implements ITextureManager {
setDefaultFrame(identifier: number, defaultFrame: number): void {
this.defaultFrames.set(identifier, defaultFrame);
const bigImageData = this.bigImageData.get(identifier);
if (bigImageData) {
bigImageData.defaultFrame = defaultFrame;
}
}
getDefaultFrame(identifier: number): number {
@ -549,49 +541,6 @@ export class MaterialManager implements ITextureManager {
return this.numIdMap.get(identifier);
}
setBigImage(
identifier: number,
image: ITexture,
frames: number
): IBigImageReturn {
const bigImageId = this.bigImageId++;
this.bigImageStore.addTexture(bigImageId, image);
const cls = this.clsMap.get(identifier) ?? BlockCls.Unknown;
const store: IMaterialFramedData = {
texture: image,
cls,
offset: image.width / 4,
frames,
defaultFrame: this.defaultFrames.get(identifier) ?? -1
};
this.bigImageData.set(identifier, store);
const data: IBigImageReturn = {
identifier: bigImageId,
store: this.bigImageStore
};
return data;
}
isBigImage(identifier: number): boolean {
return this.bigImageData.has(identifier);
}
getBigImage(identifier: number): Readonly<IMaterialFramedData> | null {
return this.bigImageData.get(identifier) ?? null;
}
getBigImageByAlias(alias: string): Readonly<IMaterialFramedData> | null {
const identifier = this.idNumMap.get(alias);
if (isNil(identifier)) return null;
return this.bigImageData.get(identifier) ?? null;
}
getIfBigImage(identifier: number): Readonly<IMaterialFramedData> | null {
const bigImage = this.bigImageData.get(identifier);
if (bigImage) return bigImage;
else return this.getTile(identifier);
}
assetContainsTexture(texture: ITexture): boolean {
return this.trackedAsset.skipRef.has(texture.source);
}

View File

@ -81,8 +81,8 @@ export class ClientCore extends CoreState implements IClientCore {
//#region 素材系统
this.materials = new MaterialManager();
this.autotile = new AutotileProcessor(this.materials);
this.materials = new MaterialManager(this);
this.autotile = new AutotileProcessor(this.materials, this);
//#endregion

View File

@ -32,8 +32,6 @@ interface RenderableDataBase {
frame: number;
/** 对应图块属性的动画帧数,-1表示没有设定,0表示第一帧 */
animate: number;
/** 是否是大怪物 */
bigImage: boolean;
render: [x: number, y: number, width: number, height: number][];
}
@ -45,7 +43,6 @@ export interface RenderableData extends RenderableDataBase {
export interface AutotileRenderable extends RenderableDataBase {
image: Record<string, SizedCanvasImageSource>;
autotile: true;
bigImage: false;
}
class TextureCache {
@ -124,7 +121,6 @@ class TextureCache {
num: number
): RenderableData | AutotileRenderable | null {
const map = maps_90f36752_8815_4be8_b32b_d7fad1d0542e;
const enemys = enemys_fcae963b_31c9_42b4_b48c_bb48d09f3f80;
const icons = icons_4665ee12_3a1f_44a4_bea3_0fccba634dc1;
/** 特判空图块与空气墙 */
@ -140,8 +136,7 @@ class TextureCache {
frame: 1,
render: [[x * 32, y * 32, 32, 32]],
animate: 0,
autotile: false,
bigImage: false
autotile: false
};
this.renderable.set(num, data);
return data;
@ -150,48 +145,7 @@ class TextureCache {
const data = map[num as Exclude<AllNumbers, 0>];
// 地狱般的分支if
if (data) {
let { faceIds, bigImage } = data;
const { cls, id, animate } = data;
if (cls === 'enemys' || cls === 'enemy48') {
// 怪物需要特殊处理,因为它的大怪物信息不在 maps 里面
({ bigImage, faceIds } = enemys[id as EnemyIds]);
}
if (bigImage) {
const image = core.material.images.images[bigImage];
if (!image) {
logger.warn(10, id);
return null;
}
let line = 0;
if (faceIds) {
const arr = ['down', 'left', 'right', 'up'];
for (let i = 0; i < arr.length; i++) {
if (faceIds[arr[i] as Dir] === id) {
line = i;
break;
}
}
}
const totalLines = image.width / image.height >= 2 ? 1 : 4;
const w = Math.round(image.width / 4);
const h = Math.round(image.height / totalLines);
const y = h * line;
const data: RenderableData = {
image,
frame: 4,
render: [
[0, y, w, h],
[w, y, w, h],
[w * 2, y, w, h],
[w * 3, y, w, h]
],
animate: (animate ?? 0) - 1,
autotile: false,
bigImage: true
};
this.renderable.set(num, data);
return data;
}
// enemy48和npc48都应该视为大怪物
if (cls === 'enemy48' || cls === 'npc48') {
const img = core.material.images[cls];
@ -211,8 +165,7 @@ class TextureCache {
[w * 3, y, w, h]
],
animate: (animate ?? 0) - 1,
autotile: false,
bigImage: true
autotile: false
};
this.renderable.set(num, data);
return data;
@ -240,7 +193,6 @@ class TextureCache {
frame: auto.frame,
render,
autotile: true,
bigImage: false,
animate: (animate ?? 0) - 1
};
this.renderable.set(num, data);
@ -273,7 +225,6 @@ class TextureCache {
frame: frame,
render,
autotile: false,
bigImage: false,
animate: (animate ?? 0) - 1
};
this.renderable.set(num, data);

View File

@ -33,7 +33,7 @@ export class MapDoorRenderer implements IMapDoorRenderer {
const array = this.layer.getMapRef().array;
const index = y * this.layer.width + x;
const num = array[index];
const data = this.renderer.manager.getIfBigImage(num);
const data = this.renderer.manager.getTile(num);
if (!data) return;
const frames = data.frames;
for (let i = 0; i < frames; i++) {
@ -43,7 +43,7 @@ export class MapDoorRenderer implements IMapDoorRenderer {
}
async closeDoor(num: number, x: number, y: number): Promise<void> {
const data = this.renderer.manager.getIfBigImage(num);
const data = this.renderer.manager.getTile(num);
if (!data) return;
const moving = this.renderer.addMovingBlock(this.layer, num, x, y);
const frames = data.frames;

View File

@ -236,7 +236,7 @@ export class MapHeroRenderer implements IMapHeroRenderer {
const tx = block.x + dx;
const ty = block.y + dy;
const nextTile = state.roleFace.getFaceOf(block.tile, direction);
const nextTex = this.renderer.manager.getIfBigImage(
const nextTex = this.renderer.manager.getTile(
nextTile?.identifier ?? block.tile
);
entity.animateInterval = time;
@ -361,7 +361,7 @@ export class MapHeroRenderer implements IMapHeroRenderer {
const nowFace =
degraded === FaceDirection.Unknown ? FaceDirection.Down : degraded;
const faced = state.roleFace.getFaceOf(image, nowFace);
const tex = this.renderer.manager.getIfBigImage(faced?.face ?? image);
const tex = this.renderer.manager.getTile(faced?.face ?? image);
if (!tex) {
logger.warn(91, image.toString());
return;
@ -425,9 +425,7 @@ export class MapHeroRenderer implements IMapHeroRenderer {
last.nextDirection
);
if (!nextFace) continue;
const tile = this.renderer.manager.getIfBigImage(
nextFace.identifier
);
const tile = this.renderer.manager.getTile(nextFace.identifier);
if (!tile) continue;
moving.block.setTexture(tile);
moving.direction = last.nextDirection;

View File

@ -231,7 +231,7 @@ export class MapRenderer
);
this.contextData = data;
this.vertex = new MapVertexGenerator(this, data);
this.autotile = new AutotileProcessor(manager);
this.autotile = new AutotileProcessor(manager, manager.state);
this.tick = this.tick.bind(this);
this.viewport = new MapViewport(this);
this.tileAnimater = new TextureColumnAnimater();
@ -648,13 +648,6 @@ export class MapRenderer
private getOffsetPool(): number[] {
const pool = new Set([32]);
// 其他的都是 bigImage 了,直接遍历获取
for (const identifier of this.manager.bigImageStore.keys()) {
const data = this.manager.getBigImage(identifier);
if (!data) continue;
const offset = data.texture.width / data.frames;
pool.add(offset);
}
// 还有勇士图片
for (const tex of this.manager.imageStore.values()) {
if (!this.manager.assetContainsTexture(tex)) continue;
@ -1247,7 +1240,7 @@ export class MapRenderer
tile: number
): Promise<void> {
// 图块背景
const tex = this.manager.getIfBigImage(tile);
const tex = this.manager.getTile(tile);
if (!tex) {
// 图块不存在
logger.error(35);

View File

@ -543,7 +543,7 @@ export class MapVertexGenerator
) {
// 此处仅更新当前图块,不更新周围一圈的自动元件
// 周围一圈的自动元件需要在更新某个图块或者某个区域时处理,不在这里处理
const tile = this.renderer.manager.getIfBigImage(num);
const tile = this.renderer.manager.getTile(num);
if (!tile) {
// 不存在可渲染对象,认为是空图块