mirror of
https://github.com/unanmed/HumanBreak.git
synced 2026-11-15 03:04:30 +08:00
refactor: src/core/common/resource.ts 移入 monorepo
This commit is contained in:
1 parent
dc502ddd64
commit
4aa894a204
4 files changed
+4
-213
No files matched your search
@@ -1,211 +0,0 @@
|
||||
function has<T>(value: T): value is NonNullable<T> {
|
||||
return value !== null && value !== undefined;
|
||||
}
|
||||
|
||||
export type Callable = (...params: any) => any;
|
||||
|
||||
export interface Listener<T extends (...params: any) => any> {
|
||||
fn: T;
|
||||
once?: boolean;
|
||||
immediate?: boolean;
|
||||
}
|
||||
|
||||
export interface ListenerOptions {
|
||||
once: boolean;
|
||||
immediate: boolean;
|
||||
}
|
||||
|
||||
type EmitFn<F extends (...params: any) => any> = (
|
||||
events: Listener<F>[],
|
||||
...params: Parameters<F>
|
||||
) => any;
|
||||
|
||||
type Key = number | string | symbol;
|
||||
|
||||
export class EventEmitter<T extends Record<keyof T, Callable> = {}> {
|
||||
protected events: {
|
||||
[x: Key]: Listener<Callable>[];
|
||||
} = {};
|
||||
private emitted: Set<string> = new Set();
|
||||
|
||||
protected emitter: {
|
||||
[x: Key]: EmitFn<Callable> | undefined;
|
||||
} = {};
|
||||
|
||||
/**
|
||||
* 监听某个事件
|
||||
* @param event 要监听的事件类型
|
||||
* @param fn 触发事件时执行的函数
|
||||
* @param options 监听选项
|
||||
*/
|
||||
on<K extends keyof T>(
|
||||
event: K,
|
||||
fn: T[K],
|
||||
options?: Partial<ListenerOptions>
|
||||
): void;
|
||||
on(event: string, fn: Callable, options?: Partial<ListenerOptions>): void;
|
||||
on(event: string, fn: Callable, options?: Partial<ListenerOptions>): void {
|
||||
if (options?.immediate && this.emitted.has(event)) {
|
||||
fn();
|
||||
if (!options.once) {
|
||||
this.events[event] ??= [];
|
||||
this.events[event]?.push({
|
||||
fn
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.events[event] ??= [];
|
||||
this.events[event]?.push({
|
||||
fn,
|
||||
once: options?.once
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消监听某个事件
|
||||
* @param event 要取消监听的事件类型
|
||||
* @param fn 要取消监听的函数
|
||||
*/
|
||||
off<K extends keyof T>(event: K, fn: T[K]): void;
|
||||
off(event: string, fn: Callable): void;
|
||||
off(event: string, fn: Callable): void {
|
||||
const index = this.events[event]?.findIndex(v => v.fn === fn);
|
||||
if (index === -1 || index === void 0) return;
|
||||
this.events[event]?.splice(index, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听事件,并只触发一次
|
||||
* @param event 要监听的事件
|
||||
* @param fn 监听函数
|
||||
*/
|
||||
once<K extends keyof T>(event: K, fn: T[K]): void;
|
||||
once(event: string, fn: Callable): void;
|
||||
once(event: string, fn: Callable): void {
|
||||
this.on(event, fn, { once: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发某个事件
|
||||
* @param event 要触发的事件类型
|
||||
* @param params 传入的参数
|
||||
*/
|
||||
emit<K extends keyof T>(
|
||||
event: K,
|
||||
...params: Parameters<T[K]>
|
||||
): ReturnType<T[K]>[];
|
||||
emit(event: string, ...params: any[]): any[];
|
||||
emit<K extends keyof T, R>(event: K, ...params: Parameters<T[K]>): R;
|
||||
emit<R>(event: string, ...params: any[]): R;
|
||||
emit(event: string, ...params: any[]): any[] {
|
||||
this.emitted.add(event);
|
||||
const events = (this.events[event] ??= []);
|
||||
if (this.emitter[event]) {
|
||||
const returns = this.emitter[event]!(events, ...params);
|
||||
this.events[event] = events.filter(v => !v.once);
|
||||
return returns;
|
||||
} else {
|
||||
const returns: ReturnType<Callable>[] = [];
|
||||
for (let i = 0; i < events.length; i++) {
|
||||
const e = events[i];
|
||||
returns.push(e.fn(...(params as any)));
|
||||
}
|
||||
this.events[event] = events.filter(v => !v.once);
|
||||
return returns;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置一个事件的执行器(emitter)
|
||||
* @param event 要设置的事件
|
||||
* @param fn 事件执行器,留空以清除触发器
|
||||
*/
|
||||
// @ts-expect-error 无法推导
|
||||
setEmitter<K extends keyof T>(event: K, fn?: EmitFn<T[K]>): void;
|
||||
setEmitter(event: string, fn?: EmitFn<Callable>): void;
|
||||
setEmitter(event: string, fn?: EmitFn<Callable>): void {
|
||||
this.emitter[event] = fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消监听所有的事件,删除所有监听函数
|
||||
*/
|
||||
removeAllListeners(): void;
|
||||
/**
|
||||
* 取消监听一个事件的所有函数
|
||||
* @param event 要取消监听的事件
|
||||
*/
|
||||
removeAllListeners(event: keyof T): void;
|
||||
/**
|
||||
* 取消监听一个事件的所有函数
|
||||
* @param event 要取消监听的事件
|
||||
*/
|
||||
removeAllListeners(event: string): void;
|
||||
removeAllListeners(event?: string | keyof T) {
|
||||
if (has(event)) this.events[event] = [];
|
||||
else this.events = {};
|
||||
}
|
||||
}
|
||||
|
||||
type IndexedSymbol = number | string | symbol;
|
||||
|
||||
export class IndexedEventEmitter<
|
||||
T extends Record<keyof T, Callable>
|
||||
> extends EventEmitter<T> {
|
||||
private fnMap: {
|
||||
[P in keyof T]?: Map<IndexedSymbol, T[P]>;
|
||||
} = {};
|
||||
|
||||
/**
|
||||
* 监听事件,并将函数与唯一标识符绑定
|
||||
* @param event 事件类型
|
||||
* @param symbol 监听函数的唯一标识符
|
||||
* @param fn 监听函数
|
||||
* @param options 监听配置
|
||||
*/
|
||||
onIndex<K extends keyof T>(
|
||||
event: K,
|
||||
symbol: IndexedSymbol,
|
||||
fn: T[K],
|
||||
options: Partial<ListenerOptions>
|
||||
) {
|
||||
const map = this.ensureMap(event);
|
||||
if (map.has(symbol)) {
|
||||
console.warn(
|
||||
`监听${String(event)}出错:已存在标识符为${String(
|
||||
symbol
|
||||
)}的监听函数,已将其覆盖`
|
||||
);
|
||||
this.offIndex(event, symbol);
|
||||
}
|
||||
map.set(symbol, fn);
|
||||
this.on(event, fn, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听事件,绑定唯一标识符,但监听函数只会执行一次
|
||||
* @param event 要监听的事件
|
||||
* @param symbol 监听函数的唯一标识符
|
||||
* @param fn 监听函数
|
||||
*/
|
||||
onceIndex<K extends keyof T>(event: K, symbol: IndexedSymbol, fn: T[K]) {
|
||||
this.onIndex(event, symbol, fn, { once: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消监听一个事件
|
||||
* @param event 要取消监听的事件
|
||||
* @param symbol 取消监听的函数的唯一标识符
|
||||
*/
|
||||
offIndex<K extends keyof T>(event: K, symbol: IndexedSymbol) {
|
||||
const map = this.ensureMap(event);
|
||||
const fn = map.get(symbol);
|
||||
if (!fn) return;
|
||||
this.off(event, fn);
|
||||
}
|
||||
|
||||
private ensureMap<K extends keyof T>(event: K) {
|
||||
return this.fnMap[event] ?? new Map<IndexedSymbol, T[K]>();
|
||||
}
|
||||
}
|
||||
@@ -1,731 +0,0 @@
|
||||
import axios, { AxiosRequestConfig, ResponseType } from 'axios';
|
||||
import { Disposable, EventEmitter } from '@motajs/legacy-common';
|
||||
import { logger } from '@motajs/common';
|
||||
import JSZip from 'jszip';
|
||||
|
||||
type ProgressFn = (now: number, total: number) => void;
|
||||
|
||||
interface ResourceType {
|
||||
text: string;
|
||||
buffer: ArrayBuffer;
|
||||
image: HTMLImageElement;
|
||||
material: HTMLImageElement;
|
||||
audio: HTMLAudioElement;
|
||||
json: any;
|
||||
zip: JSZip;
|
||||
byte: Uint8Array;
|
||||
}
|
||||
|
||||
interface ResourceMap {
|
||||
text: TextResource;
|
||||
buffer: BufferResource;
|
||||
image: ImageResource;
|
||||
material: MaterialResource;
|
||||
audio: AudioResource;
|
||||
json: JSONResource;
|
||||
zip: ZipResource;
|
||||
byte: ByteResource;
|
||||
}
|
||||
|
||||
interface CompressedLoadListItem {
|
||||
type: keyof ResourceType;
|
||||
name: string;
|
||||
usage: string;
|
||||
}
|
||||
type CompressedLoadList = Record<string, CompressedLoadListItem[]>;
|
||||
|
||||
const types: Record<keyof ResourceType, JSZip.OutputType> = {
|
||||
text: 'string',
|
||||
buffer: 'arraybuffer',
|
||||
image: 'blob',
|
||||
material: 'blob',
|
||||
audio: 'arraybuffer',
|
||||
json: 'string',
|
||||
zip: 'arraybuffer',
|
||||
byte: 'uint8array'
|
||||
};
|
||||
|
||||
const base = import.meta.env.DEV ? '/' : '';
|
||||
|
||||
function toURL(uri: string) {
|
||||
return import.meta.env.DEV ? uri : `${import.meta.env.BASE_URL}${uri}`;
|
||||
}
|
||||
|
||||
export abstract class Resource<T = any> extends Disposable<string> {
|
||||
type = 'none';
|
||||
|
||||
uri: string = '';
|
||||
resource?: T;
|
||||
loaded: boolean = false;
|
||||
|
||||
/**
|
||||
* 创建一个资源
|
||||
* @param uri 资源的URI,格式为 type/file
|
||||
* @param type 资源类型,不填为none,并会抛出警告
|
||||
*/
|
||||
constructor(uri: string, type: string = 'none') {
|
||||
super(uri);
|
||||
this.type = type;
|
||||
this.uri = uri;
|
||||
|
||||
if (this.type === 'none') {
|
||||
logger.warn(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载这个资源,需要被子类override
|
||||
*/
|
||||
abstract load(onProgress?: ProgressFn): Promise<T>;
|
||||
|
||||
/**
|
||||
* 解析资源URI,解析为一个URL,可以直接由请求获取
|
||||
*/
|
||||
abstract resolveURI(): string;
|
||||
|
||||
/**
|
||||
* 获取资源数据,当数据未加载完毕或未启用时返回null
|
||||
*/
|
||||
getData(): T | null {
|
||||
if (!this.activated || !this.loaded) return null;
|
||||
if (this.resource === null || this.resource === void 0) return null;
|
||||
return this.resource;
|
||||
}
|
||||
}
|
||||
|
||||
export class ImageResource extends Resource<HTMLImageElement> {
|
||||
/**
|
||||
* 创建一个图片资源
|
||||
* @param uri 图片资源的URI,格式为 image/file,例如 'image/project/images/hero.png'
|
||||
*/
|
||||
constructor(uri: string) {
|
||||
super(uri, 'image');
|
||||
}
|
||||
|
||||
load(_onProgress?: ProgressFn): Promise<HTMLImageElement> {
|
||||
const img = new Image();
|
||||
img.src = this.resolveURI();
|
||||
this.resource = img;
|
||||
return new Promise<HTMLImageElement>(res => {
|
||||
img.addEventListener('load', () => {
|
||||
this.loaded = true;
|
||||
img.setAttribute('_width', img.width.toString());
|
||||
img.setAttribute('_height', img.height.toString());
|
||||
res(img);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
resolveURI(): string {
|
||||
return toURL(`${base}${findURL(this.uri)}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class MaterialResource extends ImageResource {
|
||||
/**
|
||||
* 创建一个material资源
|
||||
* @param uri 资源的URI,格式为 material/file,例如 'material/enemys.png'
|
||||
*/
|
||||
constructor(uri: string) {
|
||||
super(uri);
|
||||
this.type = 'material';
|
||||
}
|
||||
|
||||
override resolveURI(): string {
|
||||
return toURL(`${base}project/materials/${findURL(this.uri)}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class TextResource extends Resource<string> {
|
||||
/**
|
||||
* 创建一个文字资源
|
||||
* @param uri 文字资源的URI,格式为 text/file,例如 'text/myText.txt'
|
||||
* 这样的话会加载塔根目录下的 myText.txt 文件
|
||||
*/
|
||||
constructor(uri: string) {
|
||||
super(uri, 'text');
|
||||
}
|
||||
|
||||
load(onProgress?: ProgressFn): Promise<string> {
|
||||
return new Promise(res => {
|
||||
createAxiosLoader<string>(
|
||||
this.resolveURI(),
|
||||
'text',
|
||||
onProgress
|
||||
).then(value => {
|
||||
this.resource = value.data;
|
||||
this.loaded = true;
|
||||
res(value.data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
resolveURI(): string {
|
||||
return toURL(`${base}${findURL(this.uri)}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class BufferResource extends Resource<ArrayBuffer> {
|
||||
/**
|
||||
* 创建一个二进制缓冲区资源
|
||||
* @param uri 资源的URI,格式为 buffer/file,例如 'buffer/myBuffer.mp3'
|
||||
*/
|
||||
constructor(uri: string) {
|
||||
super(uri, 'buffer');
|
||||
}
|
||||
|
||||
load(onProgress?: ProgressFn): Promise<ArrayBuffer> {
|
||||
return new Promise(res => {
|
||||
createAxiosLoader<ArrayBuffer>(
|
||||
this.resolveURI(),
|
||||
'arraybuffer',
|
||||
onProgress
|
||||
).then(value => {
|
||||
this.resource = value.data;
|
||||
this.loaded = true;
|
||||
res(value.data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
resolveURI(): string {
|
||||
return toURL(`${base}${findURL(this.uri)}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class ByteResource extends Resource<Uint8Array> {
|
||||
/**
|
||||
* 创建一个二进制缓冲区资源
|
||||
* @param uri 资源的URI,格式为 byte/file,例如 'byte/myBuffer.mp3'
|
||||
*/
|
||||
constructor(uri: string) {
|
||||
super(uri, 'buffer');
|
||||
}
|
||||
|
||||
async load(_onProgress?: ProgressFn): Promise<Uint8Array> {
|
||||
const response = await fetch(this.resolveURI());
|
||||
const data = await response.arrayBuffer();
|
||||
this.resource = new Uint8Array(data);
|
||||
return this.resource;
|
||||
}
|
||||
|
||||
resolveURI(): string {
|
||||
return toURL(`${base}${findURL(this.uri)}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class JSONResource<T = any> extends Resource<T> {
|
||||
/**
|
||||
* 创建一个JSON对象资源
|
||||
* @param uri 资源的URI,格式为 json/file,例如 'buffer/myJSON.json'
|
||||
*/
|
||||
constructor(uri: string) {
|
||||
super(uri, 'json');
|
||||
}
|
||||
|
||||
load(onProgress?: ProgressFn): Promise<any> {
|
||||
return new Promise(res => {
|
||||
createAxiosLoader<any>(this.resolveURI(), 'json', onProgress).then(
|
||||
value => {
|
||||
this.resource = value.data;
|
||||
this.loaded = true;
|
||||
res(value.data);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
resolveURI(): string {
|
||||
return toURL(`${base}${findURL(this.uri)}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class AudioResource extends Resource<HTMLAudioElement> {
|
||||
/**
|
||||
* 创建一个音乐资源
|
||||
* @param uri 音乐资源的URI,格式为 audio/file,例如 'audio/bgm.mp3'
|
||||
* 注意这个资源类型为 bgm 等只在播放时才开始流式加载的音乐资源类型,
|
||||
* 对于需要一次性加载完毕的需要使用 BufferResource 进行加载,
|
||||
* 并可以通过 AudioPlayer 类进行解析播放
|
||||
*/
|
||||
constructor(uri: string) {
|
||||
super(uri, 'audio');
|
||||
}
|
||||
|
||||
load(_onProgress?: ProgressFn): Promise<HTMLAudioElement> {
|
||||
const audio = new Audio();
|
||||
audio.src = this.resolveURI();
|
||||
audio.preload = 'none';
|
||||
this.resource = audio;
|
||||
return new Promise<HTMLAudioElement>(res => {
|
||||
this.loaded = true;
|
||||
res(audio);
|
||||
});
|
||||
}
|
||||
|
||||
resolveURI(): string {
|
||||
return toURL(`${base}project/bgms/${findURL(this.uri)}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class ZipResource extends Resource<JSZip> {
|
||||
/**
|
||||
* 创建一个zip压缩资源
|
||||
* @param uri 资源的URI,格式为 zip/file,例如 'zip/myZip.h5data'
|
||||
* 注意后缀名不要是zip,不然有的浏览器会触发下载,而不是加载
|
||||
*/
|
||||
constructor(uri: string) {
|
||||
super(uri, 'zip');
|
||||
this.type = 'zip';
|
||||
}
|
||||
|
||||
async load(onProgress?: ProgressFn): Promise<JSZip> {
|
||||
const data = await new Promise<ArrayBuffer>(res => {
|
||||
createAxiosLoader<ArrayBuffer>(
|
||||
this.resolveURI(),
|
||||
'arraybuffer',
|
||||
onProgress
|
||||
).then(value => {
|
||||
res(value.data);
|
||||
});
|
||||
});
|
||||
const unzipped = await JSZip.loadAsync(data);
|
||||
this.resource = unzipped;
|
||||
this.loaded = true;
|
||||
return unzipped;
|
||||
}
|
||||
|
||||
resolveURI(): string {
|
||||
return toURL(`${base}${findURL(this.uri)}`);
|
||||
}
|
||||
}
|
||||
|
||||
function createAxiosLoader<T = any>(
|
||||
url: string,
|
||||
responseType: ResponseType,
|
||||
onProgress?: (now: number, total: number) => void
|
||||
) {
|
||||
const config: AxiosRequestConfig<T> = {};
|
||||
config.responseType = responseType;
|
||||
if (onProgress) {
|
||||
config.onDownloadProgress = e => {
|
||||
onProgress(e.loaded, e.total ?? 0);
|
||||
};
|
||||
}
|
||||
return axios.get<T>(url, config);
|
||||
}
|
||||
|
||||
function findURL(uri: string) {
|
||||
return uri.slice(uri.indexOf('/') + 1);
|
||||
}
|
||||
|
||||
export const resourceTypeMap = {
|
||||
text: TextResource,
|
||||
buffer: BufferResource,
|
||||
image: ImageResource,
|
||||
material: MaterialResource,
|
||||
audio: AudioResource,
|
||||
json: JSONResource,
|
||||
zip: ZipResource,
|
||||
byte: ByteResource
|
||||
};
|
||||
|
||||
interface LoadEvent<T extends keyof ResourceType> {
|
||||
progress: (
|
||||
type: keyof ResourceType,
|
||||
uri: string,
|
||||
now: number,
|
||||
total: number
|
||||
) => void;
|
||||
load: (resource: ResourceMap[T]) => void | Promise<any>;
|
||||
loadStart: (resource: ResourceMap[T]) => void;
|
||||
}
|
||||
|
||||
type TaskProgressFn = (
|
||||
loadedByte: number,
|
||||
totalByte: number,
|
||||
loadedTask: number,
|
||||
totalTask: number
|
||||
) => void;
|
||||
|
||||
export class LoadTask<
|
||||
T extends keyof ResourceType = keyof ResourceType
|
||||
> extends EventEmitter<LoadEvent<T>> {
|
||||
static totalByte: number = 0;
|
||||
static loadedByte: number = 0;
|
||||
static totalTask: number = 0;
|
||||
static loadedTask: number = 0;
|
||||
static errorTask: number = 0;
|
||||
|
||||
/** 所有的资源,包括没有添加到加载任务里面的 */
|
||||
static store: Map<string, Resource> = new Map();
|
||||
static taskList: Set<LoadTask> = new Set();
|
||||
static loadedTaskList: Set<LoadTask> = new Set();
|
||||
|
||||
private static progress: TaskProgressFn;
|
||||
private static caledTask: Set<string> = new Set();
|
||||
|
||||
resource: Resource;
|
||||
type: T;
|
||||
uri: string;
|
||||
|
||||
private loadingStarted: boolean = false;
|
||||
loading: boolean = false;
|
||||
loaded: number = 0;
|
||||
|
||||
/**
|
||||
* 新建一个加载任务
|
||||
* @param type 任务类型
|
||||
* @param uri 加载内容的URL
|
||||
*/
|
||||
constructor(type: T, uri: string) {
|
||||
super();
|
||||
this.resource = new resourceTypeMap[type](uri);
|
||||
this.type = type;
|
||||
this.uri = uri;
|
||||
LoadTask.store.set(uri, this.resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行加载过程,当加载完毕后,返回的Promise将会被resolve
|
||||
* @returns 加载的Promise
|
||||
*/
|
||||
async load(): Promise<ResourceType[T]> {
|
||||
if (this.loadingStarted) {
|
||||
logger.warn(2, this.resource.type, this.resource.uri);
|
||||
return new Promise<void>(res => res());
|
||||
}
|
||||
this.loadingStarted = true;
|
||||
let totalByte = 0;
|
||||
const load = this.resource
|
||||
.load((now, total) => {
|
||||
this.loading = true;
|
||||
this.emit('progress', this.type, this.uri, now, total);
|
||||
if (!LoadTask.caledTask.has(this.uri) && total !== 0) {
|
||||
LoadTask.totalByte += total;
|
||||
totalByte = total;
|
||||
LoadTask.caledTask.add(this.uri);
|
||||
}
|
||||
this.loaded = now;
|
||||
})
|
||||
.catch(() => {
|
||||
LoadTask.errorTask++;
|
||||
logger.error(2, this.resource.type, this.resource.uri);
|
||||
});
|
||||
this.emit('loadStart', this.resource);
|
||||
const value = await load;
|
||||
LoadTask.loadedTaskList.add(this);
|
||||
this.loaded = totalByte;
|
||||
LoadTask.loadedTask++;
|
||||
await Promise.all(this.emit('load', this.resource));
|
||||
return await value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新建一个加载任务,同时将任务加入加载列表
|
||||
* @param type 任务类型
|
||||
* @param uri 加载内容的URI
|
||||
*/
|
||||
static add<T extends keyof ResourceType>(
|
||||
type: T,
|
||||
uri: string
|
||||
): LoadTask<T> {
|
||||
const task = new LoadTask(type, uri);
|
||||
this.taskList.add(task);
|
||||
return task;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将一个加载任务加入加载列表
|
||||
* @param task 要加入列表的任务
|
||||
*/
|
||||
static addTask(task: LoadTask) {
|
||||
this.taskList.add(task);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行所有加载
|
||||
*/
|
||||
static async load() {
|
||||
this.totalTask = this.taskList.size;
|
||||
const fn = () => {
|
||||
this.loadedByte = [...this.taskList].reduce((prev, curr) => {
|
||||
return prev + curr.loaded;
|
||||
}, 0);
|
||||
this.progress?.(
|
||||
this.loadedByte,
|
||||
this.totalByte,
|
||||
this.loadedTask,
|
||||
this.totalTask
|
||||
);
|
||||
};
|
||||
fn();
|
||||
const interval = window.setInterval(fn, 100);
|
||||
const data = await Promise.all([...this.taskList].map(v => v.load()));
|
||||
window.clearInterval(interval);
|
||||
this.loadedByte = this.totalByte;
|
||||
fn();
|
||||
this.progress?.(
|
||||
this.totalByte,
|
||||
this.totalByte,
|
||||
this.totalTask,
|
||||
this.totalTask
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当加载进度改变时执行的函数
|
||||
*/
|
||||
static onProgress(progress: TaskProgressFn) {
|
||||
this.progress = progress;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置加载设置
|
||||
*/
|
||||
static reset() {
|
||||
this.loadedByte = 0;
|
||||
this.loadedTask = 0;
|
||||
this.totalByte = 0;
|
||||
this.totalTask = 0;
|
||||
this.errorTask = 0;
|
||||
this.caledTask.clear();
|
||||
this.taskList.clear();
|
||||
}
|
||||
}
|
||||
|
||||
export function loadDefaultResource() {
|
||||
const data = data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d;
|
||||
const icon = icons_4665ee12_3a1f_44a4_bea3_0fccba634dc1;
|
||||
// bgm
|
||||
// data.main.bgms.forEach(v => {
|
||||
// const res = LoadTask.add('audio', `audio/${v}`);
|
||||
// Mota.r(() => {
|
||||
// res.once('loadStart', res => {
|
||||
// Mota.require('var', 'bgm').add(`bgms.${v}`, res.resource!);
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
// fonts
|
||||
data.main.fonts.forEach(v => {
|
||||
const res = LoadTask.add('buffer', `buffer/project/fonts/${v}.ttf`);
|
||||
Mota.r(() => {
|
||||
res.once('load', res => {
|
||||
document.fonts.add(new FontFace(v, res.resource!));
|
||||
});
|
||||
});
|
||||
});
|
||||
// image
|
||||
data.main.images.forEach(v => {
|
||||
const res = LoadTask.add('image', `image/project/images/${v}`);
|
||||
res.once('load', res => {
|
||||
core.material.images.images[v] = res.resource!;
|
||||
});
|
||||
});
|
||||
// sound
|
||||
data.main.sounds.forEach(v => {
|
||||
const res = LoadTask.add('byte', `byte/project/sounds/${v}`);
|
||||
Mota.r(() => {
|
||||
res.once('load', res => {
|
||||
const { soundPlayer } = Mota.require('module', 'Audio');
|
||||
soundPlayer.add(v, res.resource!);
|
||||
});
|
||||
});
|
||||
});
|
||||
// tileset
|
||||
data.main.tilesets.forEach(v => {
|
||||
const res = LoadTask.add('image', `image/project/tilesets/${v}`);
|
||||
res.once('load', res => {
|
||||
core.material.images.tilesets[v] = res.resource!;
|
||||
});
|
||||
});
|
||||
// autotile
|
||||
const autotiles: Partial<Record<AllIdsOf<'autotile'>, HTMLImageElement>> =
|
||||
{};
|
||||
Object.keys(icon.autotile).forEach(v => {
|
||||
const res = LoadTask.add('image', `image/project/autotiles/${v}.png`);
|
||||
res.once('load', res => {
|
||||
autotiles[v as AllIdsOf<'autotile'>] = res.resource;
|
||||
const loading = Mota.require('var', 'loading');
|
||||
loading.addAutotileLoaded();
|
||||
loading.onAutotileLoaded(autotiles);
|
||||
core.material.images.autotile[v as AllIdsOf<'autotile'>] =
|
||||
res.resource!;
|
||||
});
|
||||
});
|
||||
// materials
|
||||
const imgs = core.materials.slice() as SelectKey<
|
||||
MaterialImages,
|
||||
HTMLImageElement
|
||||
>[];
|
||||
imgs.push('keyboard');
|
||||
core.materials
|
||||
.map(v => `${v}.png`)
|
||||
.forEach(v => {
|
||||
const res = LoadTask.add('material', `material/${v}`);
|
||||
res.once('load', res => {
|
||||
// @ts-expect-error 不能推导
|
||||
core.material.images[
|
||||
v.slice(0, -4) as SelectKey<
|
||||
MaterialImages,
|
||||
HTMLImageElement
|
||||
>
|
||||
] = res.resource;
|
||||
});
|
||||
});
|
||||
const weathers: (keyof Weather)[] = ['fog', 'cloud', 'sun'];
|
||||
weathers.forEach(v => {
|
||||
const res = LoadTask.add('material', `material/${v}.png`);
|
||||
res.once('load', res => {
|
||||
// @ts-expect-error 需要赋值
|
||||
core.animateFrame.weather[v] = res.resource;
|
||||
});
|
||||
});
|
||||
// animates
|
||||
{
|
||||
const res = LoadTask.add(
|
||||
'text',
|
||||
`text/all/__all_animates__?v=${
|
||||
main.version
|
||||
}&id=${data.main.animates.join(',')}`
|
||||
);
|
||||
res.once('load', res => {
|
||||
const data = res.resource!.split('@@@~~~###~~~@@@');
|
||||
data.forEach((v, i) => {
|
||||
const id = main.animates[i];
|
||||
if (v === '') {
|
||||
throw new Error(`Cannot find animate: '${id}'`);
|
||||
}
|
||||
core.material.animates[id] = core.loader._loadAnimate(v);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadCompressedResource() {
|
||||
const data = await axios.get(toURL('loadList.json'), {
|
||||
responseType: 'text'
|
||||
});
|
||||
const list: CompressedLoadList = JSON.parse(data.data);
|
||||
|
||||
// const d = data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d;
|
||||
// 对于bgm,直接按照原来的方式加载即可
|
||||
// d.main.bgms.forEach(v => {
|
||||
// const res = LoadTask.add('audio', `audio/${v}`);
|
||||
// Mota.r(() => {
|
||||
// res.once('loadStart', res => {
|
||||
// Mota.require('var', 'bgm').add(`bgms.${v}`, res.resource!);
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
// 对于区域内容,按照zip格式进行加载,然后解压处理
|
||||
const autotiles: Partial<Record<AllIdsOf<'autotile'>, HTMLImageElement>> =
|
||||
{};
|
||||
const materialImages = core.materials.slice() as SelectKey<
|
||||
MaterialImages,
|
||||
HTMLImageElement
|
||||
>[];
|
||||
materialImages.push('keyboard');
|
||||
const weathers: (keyof Weather)[] = ['fog', 'cloud', 'sun'];
|
||||
|
||||
Object.entries(list).forEach(v => {
|
||||
const [uri, list] = v;
|
||||
const res = LoadTask.add('zip', `zip/${uri}`);
|
||||
|
||||
res.once('load', resource => {
|
||||
const res = resource.resource;
|
||||
if (!res) return;
|
||||
return Promise.all(
|
||||
list.map(async v => {
|
||||
const { type, name, usage } = v;
|
||||
const asyncType = types[type];
|
||||
const value = await res
|
||||
.file(`${type}/${name}`)
|
||||
?.async(asyncType);
|
||||
|
||||
if (!value) return;
|
||||
|
||||
// 图片类型的资源
|
||||
if (type === 'image') {
|
||||
const img = value as Blob;
|
||||
const image = new Image();
|
||||
image.src = URL.createObjectURL(img);
|
||||
image.addEventListener('load', () => {
|
||||
image.setAttribute(
|
||||
'_width',
|
||||
image.width.toString()
|
||||
);
|
||||
image.setAttribute(
|
||||
'_height',
|
||||
image.height.toString()
|
||||
);
|
||||
});
|
||||
|
||||
// 图片
|
||||
if (usage === 'image') {
|
||||
core.material.images.images[name as ImageIds] =
|
||||
image;
|
||||
} else if (usage === 'tileset') {
|
||||
// 额外素材
|
||||
core.material.images.tilesets[name] = image;
|
||||
} else if (usage === 'autotile') {
|
||||
// 自动元件
|
||||
autotiles[
|
||||
name.slice(0, -4) as AllIdsOf<'autotile'>
|
||||
] = image;
|
||||
const loading = Mota.require('var', 'loading');
|
||||
loading.addAutotileLoaded();
|
||||
loading.onAutotileLoaded(autotiles);
|
||||
core.material.images.autotile[
|
||||
name.slice(0, -4) as AllIdsOf<'autotile'>
|
||||
] = image;
|
||||
}
|
||||
} else if (type === 'material') {
|
||||
const img = value as Blob;
|
||||
const image = new Image();
|
||||
image.src = URL.createObjectURL(img);
|
||||
image.addEventListener('load', () => {
|
||||
image.setAttribute(
|
||||
'_width',
|
||||
image.width.toString()
|
||||
);
|
||||
image.setAttribute(
|
||||
'_height',
|
||||
image.height.toString()
|
||||
);
|
||||
});
|
||||
|
||||
// material
|
||||
if (materialImages.some(v => name === v + '.png')) {
|
||||
core.material.images[
|
||||
name.slice(0, -4) as SelectKey<
|
||||
MaterialImages,
|
||||
HTMLImageElement
|
||||
>
|
||||
] = image;
|
||||
} else if (weathers.some(v => name === v + '.png')) {
|
||||
// @ts-expect-error 需要赋值
|
||||
core.animateFrame.weather[v] = image;
|
||||
}
|
||||
}
|
||||
|
||||
if (usage === 'font') {
|
||||
const font = value as ArrayBuffer;
|
||||
document.fonts.add(
|
||||
new FontFace(name.slice(0, -4), font)
|
||||
);
|
||||
} else if (usage === 'sound') {
|
||||
const { soundPlayer } = Mota.require('module', 'Audio');
|
||||
soundPlayer.add(v, value);
|
||||
} else if (usage === 'animate') {
|
||||
const ani = value as string;
|
||||
core.material.animates[
|
||||
name.slice(0, -8) as AnimationIds
|
||||
] = core.loader._loadAnimate(ani);
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
+1
-1
@@ -30,7 +30,7 @@ import {
|
||||
loadCompressedResource,
|
||||
loadDefaultResource,
|
||||
LoadTask
|
||||
} from '@/core/common/resource';
|
||||
} from '@motajs/legacy-common';
|
||||
import { GameUi } from '@/core/main/custom/ui';
|
||||
import { formatSize } from '@/plugin/utils';
|
||||
import { logger } from '@motajs/common';
|
||||
|
||||
Reference in new issue
Block a user