mirror of
https://github.com/motajs/template.git
synced 2026-11-02 06:34:31 +08:00
refactor: 客户端加载
This commit is contained in:
1 parent
fdeee99d40
commit
4fc6db3e79
55 files changed
+1699
-1234
No files matched your search
@@ -1,9 +1,16 @@
|
||||
import { createMaterial } from './material';
|
||||
import { loading } from '@user/data-base';
|
||||
import { createMaterial, fallbackLoad } from './material';
|
||||
import { materials } from './ins';
|
||||
|
||||
export function create() {
|
||||
createMaterial();
|
||||
loading.once('loaded', () => {
|
||||
fallbackLoad(materials);
|
||||
loading.emit('assetBuilt');
|
||||
});
|
||||
}
|
||||
|
||||
export * from './load';
|
||||
export * from './material';
|
||||
|
||||
export * from './ins';
|
||||
@@ -1,4 +1,16 @@
|
||||
import { BGMPlayer, MotaAudioContext, SoundPlayer } from '@motajs/audio';
|
||||
import {
|
||||
AudioType,
|
||||
BGMPlayer,
|
||||
MotaAudioContext,
|
||||
OpusDecoder,
|
||||
SoundPlayer,
|
||||
VorbisDecoder
|
||||
} from '@motajs/audio';
|
||||
import { MotaAssetsLoader } from './load/loader';
|
||||
import { AutotileProcessor, MaterialManager } from './material';
|
||||
import { loadProgress } from '@user/data-base';
|
||||
|
||||
//#region 音频实例
|
||||
|
||||
/** 游戏全局音频上下文 */
|
||||
export const audioContext = new MotaAudioContext();
|
||||
@@ -6,3 +18,29 @@ export const audioContext = new MotaAudioContext();
|
||||
export const soundPlayer = new SoundPlayer(audioContext);
|
||||
/** 音乐播放器 */
|
||||
export const bgmPlayer = new BGMPlayer(audioContext);
|
||||
|
||||
audioContext.registerDecoder(AudioType.Opus, () => new OpusDecoder());
|
||||
audioContext.registerDecoder(AudioType.Ogg, () => new VorbisDecoder());
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 素材实例
|
||||
|
||||
/** 素材管理器 */
|
||||
export const materials = new MaterialManager();
|
||||
/** 自动元件处理器 */
|
||||
export const autotile = new AutotileProcessor(materials);
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 加载实例
|
||||
|
||||
/** 全局加载实例 */
|
||||
export const loader = new MotaAssetsLoader(
|
||||
loadProgress,
|
||||
audioContext,
|
||||
soundPlayer,
|
||||
materials
|
||||
);
|
||||
|
||||
//#endregion
|
||||
@@ -0,0 +1,37 @@
|
||||
export const iconNames: string[] = [
|
||||
'floor',
|
||||
'lv',
|
||||
'hpmax',
|
||||
'hp',
|
||||
'atk',
|
||||
'def',
|
||||
'mdef',
|
||||
'money',
|
||||
'exp',
|
||||
'up',
|
||||
'book',
|
||||
'fly',
|
||||
'toolbox',
|
||||
'keyboard',
|
||||
'shop',
|
||||
'save',
|
||||
'load',
|
||||
'settings',
|
||||
'play',
|
||||
'pause',
|
||||
'stop',
|
||||
'speedDown',
|
||||
'speedUp',
|
||||
'rewind',
|
||||
'equipbox',
|
||||
'mana',
|
||||
'skill',
|
||||
'btn1',
|
||||
'btn2',
|
||||
'btn3',
|
||||
'btn4',
|
||||
'btn5',
|
||||
'btn6',
|
||||
'btn7',
|
||||
'btn8'
|
||||
];
|
||||
@@ -0,0 +1 @@
|
||||
export * from './processor';
|
||||
@@ -0,0 +1,532 @@
|
||||
import {
|
||||
ILoadProgressTotal,
|
||||
LoadDataType,
|
||||
ILoadTask,
|
||||
LoadTask,
|
||||
ILoadTaskProcessor
|
||||
} from '@motajs/loader';
|
||||
import {
|
||||
CompressedUsage,
|
||||
CustomLoadFunc,
|
||||
ICompressedMotaAssetsData,
|
||||
ICompressedMotaAssetsLoadList,
|
||||
IMotaAssetsLoader
|
||||
} from './types';
|
||||
import JSZip from 'jszip';
|
||||
import {
|
||||
LoadAudioProcessor,
|
||||
LoadFontProcessor,
|
||||
LoadImageProcessor,
|
||||
LoadJSONProcessor,
|
||||
LoadTextProcessor,
|
||||
LoadZipProcessor
|
||||
} from './processor';
|
||||
import { IMotaAudioContext, ISoundPlayer } from '@motajs/audio';
|
||||
import { loading } from '@user/data-base';
|
||||
import { IMaterialManager } from '../material';
|
||||
import { ITextureSplitter, Texture, TextureRowSplitter } from '@motajs/render';
|
||||
import { iconNames } from './data';
|
||||
|
||||
interface LoadTaskStore<T extends LoadDataType = LoadDataType, R = any> {
|
||||
/** 加载任务对象 */
|
||||
readonly task: ILoadTask<T, R>;
|
||||
/** 当 `onLoaded` 兑现后兑现的 `Promise` */
|
||||
readonly loadPromise: Promise<R>;
|
||||
/** 兑现 `loadPromise` */
|
||||
readonly loadResolve: (data: R) => void;
|
||||
/** 当加载任务完成时执行的函数 */
|
||||
readonly onLoaded: CustomLoadFunc<R>;
|
||||
}
|
||||
|
||||
export class MotaAssetsLoader implements IMotaAssetsLoader {
|
||||
/** 当前是否正在进行加载 */
|
||||
loading: boolean = false;
|
||||
/** 当前加载工作是否已经完成 */
|
||||
loaded: boolean = false;
|
||||
|
||||
readonly imageProcessor: ILoadTaskProcessor<LoadDataType.Blob, ImageBitmap>;
|
||||
readonly audioProcessor: ILoadTaskProcessor<
|
||||
LoadDataType.Uint8Array,
|
||||
AudioBuffer | null
|
||||
>;
|
||||
readonly fontProcessor: ILoadTaskProcessor<
|
||||
LoadDataType.ArrayBuffer,
|
||||
FontFace
|
||||
>;
|
||||
readonly textProcessor: ILoadTaskProcessor<LoadDataType.Text, string>;
|
||||
readonly jsonProcessor: ILoadTaskProcessor<LoadDataType.JSON, any>;
|
||||
readonly zipProcessor: ILoadTaskProcessor<LoadDataType.ArrayBuffer, JSZip>;
|
||||
|
||||
/** 当前已添加的加载任务 */
|
||||
private readonly tasks: Set<LoadTaskStore> = new Set();
|
||||
|
||||
/** 素材索引 */
|
||||
private materialsCounter: number = 0;
|
||||
/** 贴图行分割器,用于处理遗留 `icons.png` */
|
||||
private readonly rowSplitter: ITextureSplitter<number>;
|
||||
|
||||
constructor(
|
||||
readonly progress: ILoadProgressTotal,
|
||||
private readonly ac: IMotaAudioContext,
|
||||
private readonly sounds: ISoundPlayer<SoundIds>,
|
||||
private readonly materials: IMaterialManager
|
||||
) {
|
||||
this.imageProcessor = new LoadImageProcessor();
|
||||
this.audioProcessor = new LoadAudioProcessor(ac);
|
||||
this.fontProcessor = new LoadFontProcessor();
|
||||
this.textProcessor = new LoadTextProcessor();
|
||||
this.jsonProcessor = new LoadJSONProcessor();
|
||||
this.zipProcessor = new LoadZipProcessor();
|
||||
this.rowSplitter = new TextureRowSplitter();
|
||||
}
|
||||
|
||||
//#region 其他处理
|
||||
|
||||
private splitMaterialIcons(image: ImageBitmap) {
|
||||
const tex = new Texture(image);
|
||||
const splitted = [...this.rowSplitter.split(tex, 32)];
|
||||
for (let i = 0; i < splitted.length; i++) {
|
||||
const name = iconNames[i] ? `icon-${iconNames[i]}` : `icons-${i}`;
|
||||
// todo: 早晚删除 icons.png
|
||||
const index = this.materialsCounter++;
|
||||
this.materials.imageStore.addTexture(index, splitted[i]);
|
||||
this.materials.imageStore.alias(index, name);
|
||||
}
|
||||
}
|
||||
|
||||
//#region 加载后处理
|
||||
|
||||
/**
|
||||
* 当字体加载完成后的操作
|
||||
* @param font 字体名称
|
||||
* @param fontFace 字体 `FontFace` 对象
|
||||
*/
|
||||
private fontLoaded(font: string, fontFace: FontFace) {
|
||||
const suffix = font.lastIndexOf('.');
|
||||
const family = font.slice(0, suffix);
|
||||
fontFace.family = family;
|
||||
document.fonts.add(fontFace);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片加载完成后的操作
|
||||
* @param name 图片名称
|
||||
* @param image 图片的 `ImageBitmap`
|
||||
*/
|
||||
private customImagesLoaded(name: ImageIds, image: ImageBitmap) {
|
||||
core.material.images.images[name] = image;
|
||||
this.materials.addImage(image, {
|
||||
index: this.materialsCounter++,
|
||||
alias: name
|
||||
});
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* 音效加载完成后的操作
|
||||
* @param name 音效名称
|
||||
* @param buffer 音效解析完毕的 `AudioBuffer`
|
||||
*/
|
||||
private soundLoaded(name: SoundIds, buffer: AudioBuffer | null) {
|
||||
if (buffer) {
|
||||
this.sounds.add(name, buffer);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* 当 tileset 加载完成后的操作
|
||||
* @param name tileset 名称
|
||||
* @param image 图片 `ImageBitmap`
|
||||
*/
|
||||
private tilesetLoaded(name: string, image: ImageBitmap) {
|
||||
core.material.images.tilesets[name] = image;
|
||||
// this.materials.addTileset(image, {
|
||||
// index: this.materialsCounter++,
|
||||
// alias: name
|
||||
// });
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* 当自动元件加载完成后的操作
|
||||
* @param autotiles 自动元件存储对象
|
||||
* @param name 自动元件名称
|
||||
* @param image 自动元件的 `ImageBitmap`
|
||||
*/
|
||||
private autotileLoaded(
|
||||
autotiles: Partial<Record<AllIdsOf<'autotile'>, ImageBitmap>>,
|
||||
name: AllIdsOf<'autotile'>,
|
||||
image: ImageBitmap
|
||||
) {
|
||||
autotiles[name] = image;
|
||||
loading.addAutotileLoaded();
|
||||
loading.onAutotileLoaded(autotiles);
|
||||
core.material.images.autotile[name] = image;
|
||||
// const num = icon.autotile[name];
|
||||
// this.materials.addAutotile(image, {
|
||||
// id: name,
|
||||
// num,
|
||||
// cls: 'autotile'
|
||||
// });
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* 当素材加载完成后的操作
|
||||
* @param name 素材名称
|
||||
* @param image 素材 `ImageBitmap`
|
||||
*/
|
||||
private materialLoaded(name: string, image: ImageBitmap) {
|
||||
core.material.images[
|
||||
name.slice(0, -4) as SelectKey<MaterialImages, ImageBitmap>
|
||||
] = image;
|
||||
if (name === 'icons.png') {
|
||||
this.splitMaterialIcons(image);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* 当动画加载完成后的操作
|
||||
* @param animation 动画内容
|
||||
*/
|
||||
private animationLoaded(animation: string) {
|
||||
const data = data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d;
|
||||
const rows = animation.split('@@@~~~###~~~@@@');
|
||||
rows.forEach((value, i) => {
|
||||
const id = data.main.animates[i];
|
||||
if (value.length === 0) {
|
||||
throw new Error(`Cannot find animate: '${id}'`);
|
||||
}
|
||||
core.material.animates[id] = core.loader._loadAnimate(value);
|
||||
});
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 加载流程
|
||||
|
||||
/**
|
||||
* 开发时的加载流程
|
||||
*/
|
||||
private developingLoad() {
|
||||
const data = data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d;
|
||||
const icon = icons_4665ee12_3a1f_44a4_bea3_0fccba634dc1;
|
||||
// font
|
||||
data.main.fonts.forEach(font => {
|
||||
const url = `project/fonts/${font}`;
|
||||
const task = new LoadTask<LoadDataType.ArrayBuffer, FontFace>({
|
||||
url,
|
||||
identifier: `@system-font/${font}`,
|
||||
dataType: LoadDataType.ArrayBuffer,
|
||||
processor: this.fontProcessor,
|
||||
progress: this.progress
|
||||
});
|
||||
this.addCustomLoadTask(task, data => this.fontLoaded(font, data));
|
||||
});
|
||||
|
||||
// image
|
||||
data.main.images.forEach(image => {
|
||||
const url = `project/images/${image}`;
|
||||
const task = new LoadTask<LoadDataType.Blob, ImageBitmap>({
|
||||
url,
|
||||
identifier: `@system-image/${image}`,
|
||||
dataType: LoadDataType.Blob,
|
||||
processor: this.imageProcessor,
|
||||
progress: this.progress
|
||||
});
|
||||
this.addCustomLoadTask(task, data =>
|
||||
this.customImagesLoaded(image, data)
|
||||
);
|
||||
});
|
||||
|
||||
// sound
|
||||
data.main.sounds.forEach(sound => {
|
||||
const url = `project/sounds/${sound}`;
|
||||
const task = new LoadTask<
|
||||
LoadDataType.Uint8Array,
|
||||
AudioBuffer | null
|
||||
>({
|
||||
url,
|
||||
identifier: `@system-sound/${sound}`,
|
||||
dataType: LoadDataType.Uint8Array,
|
||||
processor: this.audioProcessor,
|
||||
progress: this.progress
|
||||
});
|
||||
this.addCustomLoadTask(task, data => this.soundLoaded(sound, data));
|
||||
});
|
||||
|
||||
// tileset
|
||||
data.main.tilesets.forEach(tileset => {
|
||||
const url = `project/tilesets/${tileset}`;
|
||||
const task = new LoadTask<LoadDataType.Blob, ImageBitmap>({
|
||||
url,
|
||||
identifier: `@system-tileset/${tileset}`,
|
||||
dataType: LoadDataType.Blob,
|
||||
processor: this.imageProcessor,
|
||||
progress: this.progress
|
||||
});
|
||||
this.addCustomLoadTask(task, data =>
|
||||
this.tilesetLoaded(tileset, data)
|
||||
);
|
||||
});
|
||||
|
||||
// autotile
|
||||
const autotiles: Partial<Record<AllIdsOf<'autotile'>, ImageBitmap>> =
|
||||
{};
|
||||
Object.keys(icon.autotile).forEach(key => {
|
||||
const url = `project/autotiles/${key}.png`;
|
||||
const task = new LoadTask<LoadDataType.Blob, ImageBitmap>({
|
||||
url,
|
||||
identifier: `@system-autotile/${key}`,
|
||||
dataType: LoadDataType.Blob,
|
||||
processor: this.imageProcessor,
|
||||
progress: this.progress
|
||||
});
|
||||
this.addCustomLoadTask(task, data =>
|
||||
this.autotileLoaded(
|
||||
autotiles,
|
||||
key as AllIdsOf<'autotile'>,
|
||||
data
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
// material
|
||||
const materialImages = core.materials.slice() as SelectKey<
|
||||
MaterialImages,
|
||||
ImageBitmap
|
||||
>[];
|
||||
materialImages.push('keyboard');
|
||||
materialImages
|
||||
.map(v => `${v}.png`)
|
||||
.forEach(materialName => {
|
||||
const url = `project/materials/${materialName}`;
|
||||
const task = new LoadTask<LoadDataType.Blob, ImageBitmap>({
|
||||
url,
|
||||
identifier: `@system-material/${materialName}`,
|
||||
dataType: LoadDataType.Blob,
|
||||
processor: this.imageProcessor,
|
||||
progress: this.progress
|
||||
});
|
||||
this.addCustomLoadTask(task, data =>
|
||||
this.materialLoaded(materialName, data)
|
||||
);
|
||||
});
|
||||
|
||||
// animate
|
||||
const animatesUrl = `all/__all_animates__?v=${main.version}&id=${data.main.animates.join(',')}`;
|
||||
const animateTask = new LoadTask<LoadDataType.Text, string>({
|
||||
url: animatesUrl,
|
||||
identifier: '@system-animates',
|
||||
dataType: LoadDataType.Text,
|
||||
processor: this.textProcessor,
|
||||
progress: this.progress
|
||||
});
|
||||
this.addCustomLoadTask(animateTask, data => this.animationLoaded(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 `JSZip` 读取方式
|
||||
* @param type 加载类型
|
||||
*/
|
||||
private getZipOutputType(type: LoadDataType): JSZip.OutputType {
|
||||
switch (type) {
|
||||
case LoadDataType.Text:
|
||||
case LoadDataType.JSON:
|
||||
return 'string';
|
||||
case LoadDataType.ArrayBuffer:
|
||||
return 'arraybuffer';
|
||||
case LoadDataType.Blob:
|
||||
return 'blob';
|
||||
case LoadDataType.Uint8Array:
|
||||
return 'uint8array';
|
||||
default:
|
||||
return 'uint8array';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据应用方式获取其所在文件夹
|
||||
* @param usage 压缩内容的应用方式
|
||||
*/
|
||||
private getZipFolderByUsage(usage: CompressedUsage): string {
|
||||
switch (usage) {
|
||||
case CompressedUsage.Image:
|
||||
return 'image';
|
||||
case CompressedUsage.Tileset:
|
||||
return 'tileset';
|
||||
case CompressedUsage.Autotile:
|
||||
return 'autotile';
|
||||
case CompressedUsage.Material:
|
||||
return 'material';
|
||||
case CompressedUsage.Font:
|
||||
return 'font';
|
||||
case CompressedUsage.Sound:
|
||||
return 'sound';
|
||||
case CompressedUsage.Animate:
|
||||
return 'animate';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理压缩文件
|
||||
* @param name 文件名称
|
||||
* @param value 文件内容
|
||||
* @param usage 文件的应用方式
|
||||
*/
|
||||
private async processZipFile(
|
||||
name: string,
|
||||
value: unknown,
|
||||
usage: CompressedUsage
|
||||
) {
|
||||
switch (usage) {
|
||||
case CompressedUsage.Image: {
|
||||
const image = await createImageBitmap(value as Blob);
|
||||
await this.customImagesLoaded(name as ImageIds, image);
|
||||
break;
|
||||
}
|
||||
case CompressedUsage.Tileset: {
|
||||
const image = await createImageBitmap(value as Blob);
|
||||
await this.tilesetLoaded(name, image);
|
||||
break;
|
||||
}
|
||||
case CompressedUsage.Material: {
|
||||
const image = await createImageBitmap(value as Blob);
|
||||
await this.materialLoaded(name, image);
|
||||
break;
|
||||
}
|
||||
case CompressedUsage.Font: {
|
||||
const fontFace = new FontFace(
|
||||
name.slice(0, -4),
|
||||
value as ArrayBuffer
|
||||
);
|
||||
await fontFace.load();
|
||||
await this.fontLoaded(name, fontFace);
|
||||
break;
|
||||
}
|
||||
case CompressedUsage.Sound: {
|
||||
const buffer = await this.ac.decodeToAudioBuffer(
|
||||
value as Uint8Array<ArrayBuffer>
|
||||
);
|
||||
await this.soundLoaded(name as SoundIds, buffer);
|
||||
break;
|
||||
}
|
||||
case CompressedUsage.Animate: {
|
||||
await this.animationLoaded(value as string);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理单个压缩包
|
||||
* @param list 当前压缩包中包含的内容
|
||||
* @param zip 压缩包
|
||||
*/
|
||||
private async handleZip(list: ICompressedMotaAssetsData[], zip: JSZip) {
|
||||
const autotiles: Partial<Record<AllIdsOf<'autotile'>, ImageBitmap>> =
|
||||
{};
|
||||
const materialImages = core.materials.slice() as SelectKey<
|
||||
MaterialImages,
|
||||
ImageBitmap
|
||||
>[];
|
||||
materialImages.push('keyboard');
|
||||
|
||||
const promises = list.map(async item => {
|
||||
const { readAs, name, usage } = item;
|
||||
const folder = this.getZipFolderByUsage(usage);
|
||||
const file = zip.file(`${folder}/${name}`);
|
||||
if (!file) return;
|
||||
const value = await file.async(this.getZipOutputType(readAs));
|
||||
|
||||
if (usage === CompressedUsage.Autotile) {
|
||||
const image = await createImageBitmap(value as Blob);
|
||||
await this.autotileLoaded(
|
||||
autotiles,
|
||||
name.slice(0, -4) as AllIdsOf<'autotile'>,
|
||||
image
|
||||
);
|
||||
}
|
||||
|
||||
await this.processZipFile(name, value, usage);
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* 游戏中加载(压缩后)
|
||||
*/
|
||||
private async playingLoad() {
|
||||
const loadListTask = new LoadTask<
|
||||
LoadDataType.JSON,
|
||||
ICompressedMotaAssetsLoadList
|
||||
>({
|
||||
url: `loadList.json`,
|
||||
dataType: LoadDataType.JSON,
|
||||
identifier: '@system-loadList',
|
||||
processor: this.jsonProcessor,
|
||||
progress: { onProgress() {} }
|
||||
});
|
||||
|
||||
loadListTask.start();
|
||||
const loadList = await loadListTask.loaded();
|
||||
|
||||
const zipTask = new LoadTask<LoadDataType.ArrayBuffer, JSZip>({
|
||||
url: loadList.file,
|
||||
identifier: `@system-zip/${loadList.file}`,
|
||||
dataType: LoadDataType.ArrayBuffer,
|
||||
processor: this.zipProcessor,
|
||||
progress: this.progress
|
||||
});
|
||||
|
||||
this.addCustomLoadTask(zipTask, zip => {
|
||||
return this.handleZip(loadList.content, zip);
|
||||
});
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 对外接口
|
||||
|
||||
initSystemLoadTask(): void {
|
||||
if (import.meta.env.DEV) {
|
||||
this.developingLoad();
|
||||
} else {
|
||||
this.playingLoad();
|
||||
}
|
||||
}
|
||||
|
||||
addCustomLoadTask<R>(
|
||||
task: ILoadTask<LoadDataType, R>,
|
||||
onLoaded: CustomLoadFunc<R>
|
||||
): Promise<R> {
|
||||
this.progress.addTask(task);
|
||||
const { promise, resolve } = Promise.withResolvers<R>();
|
||||
const store: LoadTaskStore<LoadDataType, R> = {
|
||||
task,
|
||||
onLoaded,
|
||||
loadPromise: promise,
|
||||
loadResolve: resolve
|
||||
};
|
||||
this.tasks.add(store);
|
||||
return promise;
|
||||
}
|
||||
|
||||
load(): Promise<any[]> {
|
||||
const tasks = [...this.tasks].map(async task => {
|
||||
task.task.start();
|
||||
const data = await task.task.loaded();
|
||||
await task.onLoaded(data);
|
||||
task.loadResolve(data);
|
||||
return data;
|
||||
});
|
||||
return Promise.all(tasks);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { IMotaAudioContext } from '@motajs/audio';
|
||||
import { ILoadTask, ILoadTaskProcessor, LoadDataType } from '@motajs/loader';
|
||||
import JSZip from 'jszip';
|
||||
|
||||
export class LoadImageProcessor implements ILoadTaskProcessor<
|
||||
LoadDataType.Blob,
|
||||
ImageBitmap
|
||||
> {
|
||||
process(response: Blob): Promise<ImageBitmap> {
|
||||
return createImageBitmap(response);
|
||||
}
|
||||
}
|
||||
|
||||
export class LoadAudioProcessor implements ILoadTaskProcessor<
|
||||
LoadDataType.Uint8Array,
|
||||
AudioBuffer | null
|
||||
> {
|
||||
constructor(private readonly ac: IMotaAudioContext) {}
|
||||
|
||||
process(response: Uint8Array<ArrayBuffer>): Promise<AudioBuffer | null> {
|
||||
return this.ac.decodeToAudioBuffer(response);
|
||||
}
|
||||
}
|
||||
|
||||
export class LoadFontProcessor implements ILoadTaskProcessor<
|
||||
LoadDataType.ArrayBuffer,
|
||||
FontFace
|
||||
> {
|
||||
process(
|
||||
response: ArrayBuffer,
|
||||
task: ILoadTask<LoadDataType.ArrayBuffer, FontFace>
|
||||
): Promise<FontFace> {
|
||||
const font = new FontFace(task.identifier, response);
|
||||
if (font.status === 'loaded') return Promise.resolve(font);
|
||||
else return font.load();
|
||||
}
|
||||
}
|
||||
|
||||
export class LoadZipProcessor implements ILoadTaskProcessor<
|
||||
LoadDataType.ArrayBuffer,
|
||||
JSZip
|
||||
> {
|
||||
async process(response: ArrayBuffer): Promise<JSZip> {
|
||||
const zip = new JSZip();
|
||||
await zip.loadAsync(response);
|
||||
return zip;
|
||||
}
|
||||
}
|
||||
|
||||
export class LoadTextProcessor implements ILoadTaskProcessor<
|
||||
LoadDataType.Text,
|
||||
string
|
||||
> {
|
||||
process(response: string): Promise<string> {
|
||||
return Promise.resolve(response);
|
||||
}
|
||||
}
|
||||
|
||||
export class LoadJSONProcessor<T> implements ILoadTaskProcessor<
|
||||
LoadDataType.JSON,
|
||||
T
|
||||
> {
|
||||
process(response: any): Promise<T> {
|
||||
return Promise.resolve(response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import {
|
||||
ILoadProgressTotal,
|
||||
ILoadTask,
|
||||
ILoadTaskProcessor,
|
||||
LoadDataType
|
||||
} from '@motajs/loader';
|
||||
import JSZip from 'jszip';
|
||||
|
||||
export type CustomLoadFunc<R> = (data: R) => Promise<void>;
|
||||
|
||||
export const enum CompressedUsage {
|
||||
// ---- 系统加载内容,不可更改
|
||||
Font,
|
||||
Image,
|
||||
Sound,
|
||||
Tileset,
|
||||
Autotile,
|
||||
Material,
|
||||
Animate
|
||||
}
|
||||
|
||||
export interface ICompressedMotaAssetsData {
|
||||
/** 此内容的名称 */
|
||||
readonly name: string;
|
||||
/** 此内容应该由什么方式读取 */
|
||||
readonly readAs: LoadDataType;
|
||||
/** 此内容的应用方式 */
|
||||
readonly usage: CompressedUsage;
|
||||
}
|
||||
|
||||
export interface ICompressedMotaAssetsLoadList {
|
||||
/** 压缩文件名称 */
|
||||
readonly file: string;
|
||||
/** 压缩包所包含的内容 */
|
||||
readonly content: ICompressedMotaAssetsData[];
|
||||
}
|
||||
|
||||
export interface IMotaAssetsLoader {
|
||||
/** 加载进度对象 */
|
||||
readonly progress: ILoadProgressTotal;
|
||||
/** 当前是否正在加载 */
|
||||
readonly loading: boolean;
|
||||
/** 当前是否已经加载完毕 */
|
||||
readonly loaded: boolean;
|
||||
|
||||
/** 图片处理器 */
|
||||
readonly imageProcessor: ILoadTaskProcessor<LoadDataType.Blob, ImageBitmap>;
|
||||
/** 音频处理器 */
|
||||
readonly audioProcessor: ILoadTaskProcessor<
|
||||
LoadDataType.Uint8Array,
|
||||
AudioBuffer | null
|
||||
>;
|
||||
/** 字体处理器 */
|
||||
readonly fontProcessor: ILoadTaskProcessor<
|
||||
LoadDataType.ArrayBuffer,
|
||||
FontFace
|
||||
>;
|
||||
/** 文字处理器 */
|
||||
readonly textProcessor: ILoadTaskProcessor<LoadDataType.Text, string>;
|
||||
/** JSON 处理器 */
|
||||
readonly jsonProcessor: ILoadTaskProcessor<LoadDataType.JSON, any>;
|
||||
/** `zip` 压缩包处理器 */
|
||||
readonly zipProcessor: ILoadTaskProcessor<LoadDataType.ArrayBuffer, JSZip>;
|
||||
|
||||
/**
|
||||
* 初始化系统加载任务
|
||||
*/
|
||||
initSystemLoadTask(): void;
|
||||
|
||||
/**
|
||||
* 添加自定义加载任务
|
||||
* @param task 自定义加载任务
|
||||
* @param onLoad 当任务加载完成时执行
|
||||
* @returns 一个 `Promise`,当添加的任务加载完毕,且 `onLoad` 返回的 `Promise` 兑现后兑现
|
||||
*/
|
||||
addCustomLoadTask<R>(
|
||||
task: ILoadTask<LoadDataType, R>,
|
||||
onLoad: CustomLoadFunc<R>
|
||||
): Promise<R>;
|
||||
|
||||
/**
|
||||
* 开始所有加载任务的加载工作
|
||||
* @returns 一个 `Promise`,当所有加载任务加载完成后兑现
|
||||
*/
|
||||
load(): Promise<any[]>;
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
import { ITexture } from '@motajs/render';
|
||||
import { materials } from './ins';
|
||||
import { IBlockIdentifier, IIndexedIdentifier } from './types';
|
||||
import {
|
||||
IBlockIdentifier,
|
||||
IIndexedIdentifier,
|
||||
IMaterialManager
|
||||
} from './types';
|
||||
import { isNil } from 'lodash-es';
|
||||
|
||||
function extractClsBlocks<C extends Exclude<Cls, 'tileset'>>(
|
||||
@@ -47,7 +50,7 @@ function addAutotile(set: Set<number>, map?: readonly (readonly number[])[]) {
|
||||
/**
|
||||
* 兼容旧版加载
|
||||
*/
|
||||
export function fallbackLoad() {
|
||||
export function fallbackLoad(materials: IMaterialManager) {
|
||||
// 基本素材
|
||||
const icons = core.icons.icons;
|
||||
const images = core.material.images;
|
||||
@@ -102,12 +105,6 @@ export function fallbackLoad() {
|
||||
materials.addTileset(img, identifier);
|
||||
});
|
||||
|
||||
// Images
|
||||
core.images.forEach((v, i) => {
|
||||
const img = core.material.images.images[v];
|
||||
materials.addImage(img, { index: i, alias: v });
|
||||
});
|
||||
|
||||
// 地图上出现过的 tileset
|
||||
const tilesetSet = new Set<number>();
|
||||
const autotileSet = new Set<number>();
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
import { loading } from '@user/data-base';
|
||||
import { fallbackLoad } from './fallback';
|
||||
import { createAutotile } from './autotile';
|
||||
|
||||
export function createMaterial() {
|
||||
createAutotile();
|
||||
loading.once('loaded', () => {
|
||||
fallbackLoad();
|
||||
loading.emit('assetBuilt');
|
||||
});
|
||||
}
|
||||
|
||||
export * from './autotile';
|
||||
export * from './builder';
|
||||
export * from './fallback';
|
||||
export * from './ins';
|
||||
export * from './manager';
|
||||
export * from './types';
|
||||
export * from './utils';
|
||||
@@ -1,5 +0,0 @@
|
||||
import { AutotileProcessor } from './autotile';
|
||||
import { MaterialManager } from './manager';
|
||||
|
||||
export const materials = new MaterialManager();
|
||||
export const autotile = new AutotileProcessor(materials);
|
||||
@@ -102,8 +102,7 @@ export interface IMaterialFramedData {
|
||||
}
|
||||
|
||||
export interface IMaterialAsset
|
||||
extends IDirtyTracker<boolean>,
|
||||
IDirtyMarker<void> {
|
||||
extends IDirtyTracker<boolean>, IDirtyMarker<void> {
|
||||
/** 图集的贴图数据 */
|
||||
readonly data: ITextureComposedData;
|
||||
}
|
||||
@@ -290,8 +289,7 @@ export interface IMaterialAliasGetter {
|
||||
}
|
||||
|
||||
export interface IMaterialManager
|
||||
extends IMaterialGetter,
|
||||
IMaterialAliasGetter {
|
||||
extends IMaterialGetter, IMaterialAliasGetter {
|
||||
/** 贴图存储,把 terrains 等内容单独分开存储 */
|
||||
readonly tileStore: ITextureStore;
|
||||
/** tilesets 贴图存储,每个 tileset 是一个贴图对象 */
|
||||
@@ -331,7 +329,6 @@ export interface IMaterialManager
|
||||
addRowAnimate(
|
||||
source: SizedCanvasImageSource,
|
||||
map: ArrayLike<IBlockIdentifier>,
|
||||
frames: number,
|
||||
height: number
|
||||
): Iterable<IMaterialData>;
|
||||
|
||||
|
||||
Reference in new issue
Block a user