加载资源初步

This commit is contained in:
unanmed 2023-06-07 18:17:45 +08:00
parent 7cc7fe375b
commit 5cbd9950b9
6 changed files with 163 additions and 16 deletions

View File

@ -8,16 +8,17 @@
`src`: 游戏除样板核心代码外所有内容所在目录,所有内容支持`typescript`。其中包含以下内容: `src`: 游戏除样板核心代码外所有内容所在目录,所有内容支持`typescript`。其中包含以下内容:
1. `plugin`: 所有相关插件的源码,其中包含多个文件夹,内有不同的内容,其中`game`文件夹与游戏进程有关,不能涉及`dom`等`node`无法运行的操作,否则录像验证会报错 1. `core`: 游戏除样板外的的核心代码,包括加载和一些基础功能等
2. `ui`: 所有 ui 的 vue 源码 2. `plugin`: 所有相关插件的源码,其中包含多个文件夹,内有不同的内容,其中`game`文件夹与游戏进程有关,不能涉及`dom`等`node`无法运行的操作,否则录像验证会报错
3. `panel`: ui 中用到的部分面板 3. `ui`: 所有 ui 的 vue 源码
4. `components`: 所有 ui 的通用组件 4. `panel`: ui 中用到的部分面板
5. `data`: 数据文件,包含百科全书的内容、成就的内容等 5. `components`: 所有 ui 的通用组件
6. `fonts`: ui 中用到的字体文件 6. `data`: 数据文件,包含百科全书的内容、成就的内容等
7. `types`: mota-js 的类型声明文件 7. `fonts`: ui 中用到的字体文件
8. `source`: mota-js 的图块等资源的类型声明文件,会通过热重载更新 8. `types`: mota-js 的类型声明文件
9. `initPlugin.ts`: 所有插件的入口文件 9. `source`: mota-js 的图块等资源的类型声明文件,会通过热重载更新
10. `main.ts`: 主入口,会将`App.vue`与`App2.vue`渲染到 html 上 10. `initPlugin.ts`: 所有插件的入口文件
11. `main.ts`: 主入口,会将`App.vue`与`App2.vue`渲染到 html 上
`script`: 在构建、发布等操作时会用到的 node 脚本 `script`: 在构建、发布等操作时会用到的 node 脚本

View File

@ -1,5 +1,7 @@
interface MotaConfig { interface MotaConfig {
name: string; name: string;
/** 资源分组打包信息 */
resourceZip?: string[][];
} }
function defineConfig(config: MotaConfig): MotaConfig { function defineConfig(config: MotaConfig): MotaConfig {

View File

@ -3,7 +3,8 @@ import { extname, resolve } from 'path';
import { formatSize } from './utils.js'; import { formatSize } from './utils.js';
(async function () { (async function () {
const dir = process.argv.slice(2) || ['./src']; let dir = process.argv.slice(2);
if (dir.length === 0) dir = ['./src'];
let totalLines = 0; let totalLines = 0;
let totalFiles = 0; let totalFiles = 0;
let totalSize = 0; let totalSize = 0;

View File

@ -33,7 +33,7 @@ type Stats = fs.Stats & { name?: string };
export async function splitResorce(compress: boolean = false) { export async function splitResorce(compress: boolean = false) {
const folder = await fs.stat('./dist'); const folder = await fs.stat('./dist');
totalSize = folder.size; totalSize = folder.size;
// if (totalSize < MAX_SIZE) return; if (totalSize < MAX_SIZE) return;
await fs.ensureDir('./dist-resource'); await fs.ensureDir('./dist-resource');
await doSplit(compress); await doSplit(compress);
@ -45,6 +45,7 @@ async function sortDir(dir: string, ext?: string[]) {
for await (const one of path) { for await (const one of path) {
if (ext && !ext.includes(extname(one))) continue; if (ext && !ext.includes(extname(one))) continue;
if (one === 'bg.jpg') continue;
const stat = await fs.stat(resolve(dir, one)); const stat = await fs.stat(resolve(dir, one));
if (!stat.isFile()) continue; if (!stat.isFile()) continue;
const status: Stats = { const status: Stats = {

View File

@ -3,21 +3,33 @@ import { EmitableEvent, EventEmitter } from './eventEmitter';
interface DisposableEvent<T> extends EmitableEvent { interface DisposableEvent<T> extends EmitableEvent {
active: (value: T) => void; active: (value: T) => void;
dispose: (value: T) => void; dispose: (value: T) => void;
destroy: () => void;
} }
export class Disposable<T> extends EventEmitter<DisposableEvent<T>> { export class Disposable<T> extends EventEmitter<DisposableEvent<T>> {
protected _data: T; protected _data?: T;
set data(value: T | null) { set data(value: T | null) {
if (this.destroyed) {
throw new Error(
`Cannot set value of destroyed disposable variable.`
);
}
if (value !== null) this._data = value; if (value !== null) this._data = value;
} }
get data(): T | null { get data(): T | null {
if (this.destroyed) {
throw new Error(
`Cannot get value of destroyed disposable variable.`
);
}
if (!this.activated) { if (!this.activated) {
return null; return null;
} }
return this._data; return this._data!;
} }
protected activated: boolean = false; protected activated: boolean = false;
protected destroyed: boolean = false;
constructor(data: T) { constructor(data: T) {
super(); super();
@ -26,11 +38,17 @@ export class Disposable<T> extends EventEmitter<DisposableEvent<T>> {
active() { active() {
this.activated = true; this.activated = true;
this.emit('active', this._data); this.emit('active', this._data!);
} }
dispose() { dispose() {
this.activated = false; this.activated = false;
this.emit('dispose', this._data); this.emit('dispose', this._data!);
}
destroy() {
this.destroyed = true;
this.emit('destroy');
delete this._data;
} }
} }

124
src/core/loader/resource.ts Normal file
View File

@ -0,0 +1,124 @@
import axios, { AxiosResponse } from 'axios';
import { Disposable } from '../common/disposable';
import { ensureArray } from '../../plugin/game/utils';
interface ResourceData {
image: HTMLImageElement;
arraybuffer: ArrayBuffer;
text: string;
json: any;
}
type ResourceType = keyof ResourceData;
export class Resource<
T extends ResourceType = ResourceType
> extends Disposable<string> {
format: T;
request?: Promise<AxiosResponse<ResourceData[T]> | '@imageLoaded'>;
loaded: boolean = false;
/** 资源数据 */
resource?: ResourceData[T];
constructor(resource: string, type: T) {
super(resource);
this.data = this.resolveUrl(resource);
this.format = type;
this.on('active', this.load);
}
protected resolveUrl(resource: string) {
const resolve = resource.split('.');
const type = resolve[0];
const name = resolve.slice(1).join('.');
return resource;
}
/**
*
*/
protected async load() {
const data = this.data;
if (!data) {
throw new Error(`Unexpected null of url in loading resource.`);
}
if (this.format === 'image') {
this.request = new Promise(res => {
const img = new Image();
img.src = data;
img.addEventListener('load', () => {
this.resource = img;
this.loaded = true;
res('@imageLoaded');
});
});
} else {
this.request = axios
.get(data, { responseType: this.format })
.then(v => {
this.resource = v;
this.loaded = true;
return v;
});
}
}
/**
*
*/
async getData(): Promise<ResourceData[T] | null> {
if (this.loaded) return this.resource ?? null;
else {
await this.request;
return this.resource ?? null;
}
}
}
class ReosurceStore extends Map<string, Resource> {
active(key: string[] | string) {
const keys = ensureArray(key);
keys.forEach(v => this.get(v)?.active());
}
dispose(key: string[] | string) {
const keys = ensureArray(key);
keys.forEach(v => this.get(v)?.dispose());
}
destroy(key: string[] | string) {
const keys = ensureArray(key);
keys.forEach(v => this.get(v)?.destroy());
}
push(data: [string, Resource][] | Record<string, Resource>): void {
if (data instanceof Array) {
for (const [key, res] of data) {
if (this.has(key)) {
console.warn(`Resource already exists: '${key}'.`);
}
this.set(key, res);
}
} else {
return this.push(Object.entries(data));
}
}
async getData<T extends ResourceType = ResourceType>(
key: string
): Promise<ResourceData[T] | null> {
return this.get(key)?.getData() ?? null;
}
}
declare global {
interface Window {
/** 游戏资源 */
gameResource: ReosurceStore;
}
/** 游戏资源 */
const gameResource: ReosurceStore;
}
window.gameResource = new ReosurceStore();