Compare commits

...

3 Commits

Author SHA1 Message Date
AncTe
f27a47ab47
Merge 37cd85cd1e into 820dc5bf4c 2025-11-19 10:29:12 +00:00
37cd85cd1e feat: 勇士贴图打包 2025-11-19 18:28:59 +08:00
efe6931799 feat: 图集可以动态更新,但抛出警告 2025-11-19 18:09:08 +08:00
13 changed files with 174 additions and 282 deletions

View File

@ -1,35 +0,0 @@
import { ITextureComposedData } from '@motajs/render-assets';
import { IMaterialAsset } from './types';
import { IDirtyMark } from '@motajs/common';
export class MaterialAsset implements IMaterialAsset {
/** 标记列表 */
private readonly marks: WeakMap<IDirtyMark, number> = new WeakMap();
/** 脏标记,所有值小于此标记的都视为需要更新 */
private dirtyFlag: number = 0;
constructor(readonly data: ITextureComposedData) {}
dirty(): void {
this.dirtyFlag++;
}
mark(): IDirtyMark {
const symbol = {};
this.marks.set(symbol, this.dirtyFlag);
return symbol;
}
unmark(mark: IDirtyMark): void {
this.marks.delete(mark);
}
dirtySince(mark: IDirtyMark): boolean {
const value = this.marks.get(mark) ?? -1;
return value < this.dirtyFlag;
}
hasMark(symbol: IDirtyMark): boolean {
return this.marks.has(symbol);
}
}

View File

@ -113,10 +113,16 @@ class TrackedAssetData
}
markDirty(index: number) {
if (index >= this.length) {
this.updateLength(index + 1);
}
this.dirty(index);
}
async updateSource(index: number, source: SizedCanvasImageSource) {
if (index >= this.length) {
this.updateLength(this.length + 1);
}
const origin = this.originSourceMap.get(index);
const prev = this.sourceList.get(index);
if (origin) {

View File

@ -1,3 +1,4 @@
import { ITexture } from '@motajs/render-assets';
import { materials } from './ins';
import { IBlockIdentifier, IIndexedIdentifier } from './types';
@ -118,8 +119,16 @@ export function fallbackLoad() {
addAutotile(autotileSet, floor.fg2map);
});
const heroTextures: ITexture[] = [];
data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d.main.heroImages.forEach(v => {
const tex = materials.getImageByAlias(v);
if (tex) heroTextures.push(tex);
});
materials.buildAssets();
materials.cacheAutotileList(autotileSet);
materials.cacheTilesetList(tilesetSet);
materials.buildListToAsset(heroTextures);
}

View File

@ -155,21 +155,10 @@ export class MaterialManager implements IMaterialManager {
addAutotile(
source: SizedCanvasImageSource,
identifier: IBlockIdentifier
): IMaterialData | null {
const frames = source.width === 96 ? 1 : 4;
const flattened = AutotileProcessor.flatten({ source, frames });
if (!flattened) return null;
const texture = new Texture(flattened);
this.tileStore.addTexture(identifier.num, texture);
): void {
this.autotileSource.set(identifier.num, source);
this.tileStore.alias(identifier.num, identifier.id);
this.clsMap.set(identifier.num, BlockCls.Autotile);
const data: IMaterialData = {
store: this.tileStore,
texture,
identifier: identifier.num,
alias: identifier.id
};
return data;
}
addTileset(
@ -231,9 +220,15 @@ export class MaterialManager implements IMaterialManager {
getTile(identifier: number): IMaterialFramedData | null {
if (identifier < 10000) {
const cls = this.clsMap.get(identifier) ?? BlockCls.Unknown;
if (
cls === BlockCls.Autotile &&
this.autotileSource.has(identifier)
) {
this.cacheAutotile(identifier);
}
const texture = this.tileStore.getTexture(identifier);
if (!texture) return null;
const cls = this.clsMap.get(identifier) ?? BlockCls.Unknown;
return {
texture,
cls,
@ -402,6 +397,7 @@ export class MaterialManager implements IMaterialManager {
this.tileStore.addTexture(identifier, tex);
const data = this.assetBuilder.addTexture(tex);
tex.toAsset(data);
this.autotileSource.delete(identifier);
this.checkAssetDirty(data);
return tex;
}
@ -418,6 +414,7 @@ export class MaterialManager implements IMaterialManager {
const tex = new Texture(flattened);
this.tileStore.addTexture(v, tex);
toAdd.push(tex);
this.autotileSource.delete(v);
});
const data = this.assetBuilder.addTextureList(toAdd);
@ -433,13 +430,32 @@ export class MaterialManager implements IMaterialManager {
return [];
}
this.built = true;
const data = this.assetBuilder.addTextureList(this.tileStore.values());
return this.buildListToAsset(this.tileStore.values());
}
buildToAsset(texture: ITexture): IMaterialAssetData {
const data = this.assetBuilder.addTexture(texture);
const assetData: IMaterialAssetData = {
data: data,
identifier: data.index,
alias: `asset-${data.index}`,
store: this.assetStore
};
this.checkAssetDirty(data);
return assetData;
}
buildListToAsset(
texture: Iterable<ITexture>
): Iterable<IMaterialAssetData> {
const data = this.assetBuilder.addTextureList(texture);
const arr = [...data];
const res: IMaterialAssetData[] = [];
arr.forEach(v => {
const alias = `asset-${v.index}`;
this.assetStore.alias(v.index, alias);
if (!this.assetDataStore.has(v.index)) {
this.assetDataStore.set(v.index, v);
}
const data: IMaterialAssetData = {
data: v,
identifier: v.index,
@ -451,6 +467,9 @@ export class MaterialManager implements IMaterialManager {
}
res.push(data);
});
arr.forEach(v => {
this.checkAssetDirty(v);
});
return res;
}
@ -542,7 +561,7 @@ export class MaterialManager implements IMaterialManager {
}
getIfBigImage(identifier: number): IMaterialFramedData | null {
const bigImage = this.bigImageData.get(identifier) ?? null;
const bigImage = this.bigImageData.get(identifier);
if (bigImage) return bigImage;
else return this.getTile(identifier);
}

View File

@ -397,6 +397,18 @@ export interface IMaterialManager
*/
buildAssets(): Iterable<IMaterialAssetData>;
/**
*
* @param texture
*/
buildToAsset(texture: ITexture): IMaterialAssetData;
/**
*
* @param texture
*/
buildListToAsset(texture: Iterable<ITexture>): Iterable<IMaterialAssetData>;
/**
*
* @param identifier

View File

@ -785,6 +785,7 @@ export class MapRenderer
if (dirty.size === 0) return;
this.assetData.unmark(data.tileTextureMark);
data.tileTextureMark = this.assetData.mark();
logger.warn(87);
gl.bindTexture(gl.TEXTURE_2D_ARRAY, tile);
const sizeChanged = this.checkTextureArraySize(
gl,

View File

@ -261,7 +261,6 @@ const MainScene = defineComponent(() => {
return () => (
<container id="main-scene" width={MAIN_WIDTH} height={MAIN_HEIGHT}>
<sprite
hidden
render={testRender}
loc={[180, 0, 480, 480]}
zIndex={1000}

View File

@ -1,6 +1,11 @@
import { IHookable, IHookBase, IHookController } from '@motajs/common';
import { IMapLayer } from '../map';
export interface ICoreState {
/** 地图状态 */
readonly layer: ILayerState;
}
export interface ILayerStateHooks extends IHookBase {
/**
*
@ -136,7 +141,82 @@ export interface ILayerState extends IHookable<ILayerStateHooks> {
getBackground(): number;
}
export interface ICoreState {
/** 地图状态 */
readonly layer: ILayerState;
export const enum HeroDirection {
Left,
Up,
Right,
Down
}
export interface IHeroStateHooks extends IHookBase {
/**
*
* @param controller
* @param x
* @param y
*/
onSetPosition(
controller: IHookController<this>,
x: number,
y: number
): void;
/**
*
* @param controller
* @param direction
* @param time
*/
onMoveHero(
controller: IHookController<this>,
direction: HeroDirection,
time: number
): Promise<void>;
/**
*
* @param controller
* @param x
* @param y
* @param time
*/
onJumpHero(
controller: IHookController<this>,
x: number,
y: number,
time: number
): Promise<void>;
}
export interface IHeroState extends IHookable<IHeroStateHooks> {
/** 勇士横坐标 */
readonly x: number;
/** 勇士纵坐标 */
readonly y: number;
/** 勇士朝向 */
readonly direction: HeroDirection;
/**
*
* @param x
* @param y
*/
setPosition(x: number, y: number): void;
/**
*
* @param dir
* @param time 100ms
* @returns `Promise`
*/
move(dir: HeroDirection, time?: number): Promise<void>;
/**
*
* @param x
* @param y
* @param time 500ms
* @returns `Promise`
*/
jumpHero(x: number, y: number, time?: number): Promise<void>;
}

View File

@ -1,6 +1,3 @@
import { logger } from '@motajs/common';
import { EventEmitter } from 'eventemitter3';
import { cloneDeep } from 'lodash-es';
import { HeroSkill, NightSpecial } from '../mechanism';
/**
@ -21,7 +18,7 @@ export function getHeroStatusOn(
name: keyof HeroStatus | 'all' | (keyof HeroStatus)[],
floorId?: FloorIds
) {
// @ts-ignore
// @ts-expect-error 推导错误
return getHeroStatusOf(core.status.hero, name, floorId);
}
@ -129,216 +126,3 @@ export interface IHeroStatusDefault {
def: number;
hp: number;
}
interface HeroStateEvent {
set: [key: string | number | symbol, value: any];
}
type HeroStatusCalculate = (
hero: HeroState<any>,
key: string | number | symbol,
value: any
) => any;
export class HeroState<
T extends object = IHeroStatusDefault
> extends EventEmitter<HeroStateEvent> {
readonly status: T;
readonly computedStatus: T;
readonly buffable: Set<keyof T> = new Set();
readonly buffMap: Map<keyof T, number> = new Map();
private static cal: HeroStatusCalculate = (_0, _1, value) => value;
constructor(init: T) {
super();
this.status = init;
this.computedStatus = cloneDeep(init);
}
/**
*
* @param key
* @param value
* @returns
*/
setStatus<K extends keyof T>(key: K, value: T[K]): boolean {
this.status[key] = value;
this.emit('set', key, value);
return this.refreshStatus(key);
}
/**
*
* @param key
* @param value
* @returns
*/
addStatus<K extends SelectKey<T, number>>(key: K, value: number): boolean {
if (typeof this.status[key] !== 'number') {
logger.warn(14, String(key));
return false;
}
return this.setStatus<K>(key, (this.status[key] + value) as T[K]);
}
/**
*
* @param key
* @returns
*/
getStatus<K extends keyof T>(key: K): T[K] {
return this.status[key];
}
/**
* 2.x所说的勇士真实属性
* @param key
*/
getComputedStatus<K extends keyof T>(key: K): T[K] {
return this.computedStatus[key];
}
/**
* buff加成
*/
markBuffable(key: SelectKey<T, number>): void {
if (typeof this.status[key] !== 'number') {
logger.warn(12, String(key));
return;
}
this.buffable.add(key);
this.buffMap.set(key, 1);
}
/**
* buff值
* @param key buff的属性
* @param value buff值
* @returns
*/
setBuff(key: SelectKey<T, number>, value: number): boolean {
if (!this.buffable.has(key) || typeof this.status[key] !== 'number') {
logger.warn(13, String(key));
return false;
}
this.buffMap.set(key, value);
return this.refreshStatus(key);
}
/**
* buff值
* @param key buff属性
* @param value buff增量
* @returns
*/
addBuff(key: SelectKey<T, number>, value: number): boolean {
if (!this.buffable.has(key) || typeof this.status[key] !== 'number') {
logger.warn(13, String(key));
return false;
}
return this.setBuff(key, this.buffMap.get(key)! + value);
}
/**
*
* @param key
* @returns
*/
refreshStatus(key?: keyof T): boolean {
if (key === void 0) {
for (const [key, value] of Object.entries(this.status)) {
// @ts-ignore
this.computedStatus[key] = HeroState.cal(this, key, value);
}
return true;
}
this.computedStatus[key] = HeroState.cal(this, key, this.status[key]);
return true;
}
/**
* buff加成的属性
* @returns
*/
refreshBuffable(): boolean {
for (const key of this.buffable) {
this.computedStatus[key] = HeroState.cal(
this,
key,
this.status[key]
);
}
return true;
}
/**
*
* @param fn key表示属性名value表示属性值
*/
static overrideCalculate(fn: HeroStatusCalculate) {
this.cal = fn;
}
}
interface IHeroItem {
items: Map<AllIdsOf<'items'>, number>;
/**
*
* @param item id
* @param value
* @returns
*/
setItem(item: AllIdsOf<'items'>, value: number): boolean;
/**
*
* @param item id
* @param value
* @returns
*/
addItem(item: AllIdsOf<'items'>, value: number): boolean;
/**
* 使
* @param item id
* @returns 使
*/
useItem(item: AllIdsOf<'items'>, x?: number, y?: number): boolean;
/**
*
* @param item id
* @param num
*/
getItem(item: AllIdsOf<'items'>, num: number): void;
/**
*
* @param item id
* @param x x坐标
* @param y y坐标
* @param floorId
* @param num
*/
getItem(
item: AllIdsOf<'items'>,
x: number,
y: number,
floorId?: FloorIds,
num?: number
): void;
/**
*
* @param item id
*/
itemCount(item: AllIdsOf<'items'>): number;
/**
*
* @param item id
*/
hasItem(item: AllIdsOf<'items'>): boolean;
}

View File

@ -134,6 +134,7 @@
"84": "Cannot set alias '$1' for layer, since '$1' is already an alias for another layer.",
"85": "Hook to load does not belong to current hookable object.",
"86": "Cannot restore vertex data since delivered state does not belong to current vertex generator instance.",
"87": "Map texture asset cache not hit. This has no effect on rendering, but may extremely affect performance, please see the following link to find solution.",
"1001": "Item-detail extension needs 'floor-binder' and 'floor-damage' extension as dependency.",
"1101": "Cannot add new effect to point effect instance, for there's no more reserve space for it. Please increase the max count of the instance."
}

View File

@ -18,13 +18,6 @@ var data_comment_c456ea59_6018_45ef_8bcc_211a24c627dc = {
"_docs": "楼层列表",
"_data": "在这里按顺序放所有的楼层;其顺序直接影响到楼层传送器、浏览地图和上/下楼器的顺序"
},
"plugin": {
"_leaf": true,
"_type": "textarea",
"_range": "thiseval instanceof Array",
"_docs": "插件列表",
"_data": "在这里按顺序放所有的插件,顺序会影响到插件的加载,越靠前越早加载"
},
"floorPartitions": {
"_leaf": true,
"_type": "event",
@ -128,6 +121,19 @@ var data_comment_c456ea59_6018_45ef_8bcc_211a24c627dc = {
"_docs": "使用字体",
"_data": "在此存放所有可能使用的字体 \n 字体名不能使用中文,不能带空格或特殊字符"
},
"heroImages": {
"_leaf": true,
"_type": "material",
"_range": "editor.mode.checkImages(thiseval, './project/images/')",
"_directory": "./project/images/",
"_transform": (function (one) {
if (one.endsWith('.png') || one.endsWith('.jpg') || one.endsWith('.jpeg') || one.endsWith('.gif') || one.endsWith('.webp'))
return one;
return null;
}).toString(),
"_docs": "勇士贴图",
"_data": "在这里填写游戏中所有可能使用到的勇士贴图,贴图需要先在全塔属性-使用图片中注册。如果一个贴图不会被用作勇士贴图,请不要填写进去!",
},
"nameMap": {
"_leaf": true,
"_type": "event",

View File

@ -382,7 +382,12 @@ var data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d =
],
"font": "normal"
},
"splitImages": []
"splitImages": [],
"heroImages": [
"hero.png",
"hero1.png",
"hero2.png"
]
},
"firstData": {
"title": "人类:开天辟地",

View File

@ -69,6 +69,11 @@ interface MainData {
*/
readonly splitImages: SplitImageData;
/**
*
*/
readonly heroImages: readonly ImageIds[];
readonly plugin: string[];
}