From 5cbd9950b90642c3e65e597efa9ce8bc41b54b08 Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Wed, 7 Jun 2023 18:17:45 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E8=B5=84=E6=BA=90=E5=88=9D?= =?UTF-8?q?=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 21 +++--- mota.config.ts | 2 + script/lines.ts | 3 +- script/resource.ts | 3 +- src/core/common/disposable.ts | 26 +++++-- src/core/loader/resource.ts | 124 ++++++++++++++++++++++++++++++++++ 6 files changed, 163 insertions(+), 16 deletions(-) create mode 100644 src/core/loader/resource.ts diff --git a/README.md b/README.md index 08b9b36..7e734b6 100644 --- a/README.md +++ b/README.md @@ -8,16 +8,17 @@ `src`: 游戏除样板核心代码外所有内容所在目录,所有内容支持`typescript`。其中包含以下内容: -1. `plugin`: 所有相关插件的源码,其中包含多个文件夹,内有不同的内容,其中`game`文件夹与游戏进程有关,不能涉及`dom`等`node`无法运行的操作,否则录像验证会报错 -2. `ui`: 所有 ui 的 vue 源码 -3. `panel`: ui 中用到的部分面板 -4. `components`: 所有 ui 的通用组件 -5. `data`: 数据文件,包含百科全书的内容、成就的内容等 -6. `fonts`: ui 中用到的字体文件 -7. `types`: mota-js 的类型声明文件 -8. `source`: mota-js 的图块等资源的类型声明文件,会通过热重载更新 -9. `initPlugin.ts`: 所有插件的入口文件 -10. `main.ts`: 主入口,会将`App.vue`与`App2.vue`渲染到 html 上 +1. `core`: 游戏除样板外的的核心代码,包括加载和一些基础功能等 +2. `plugin`: 所有相关插件的源码,其中包含多个文件夹,内有不同的内容,其中`game`文件夹与游戏进程有关,不能涉及`dom`等`node`无法运行的操作,否则录像验证会报错 +3. `ui`: 所有 ui 的 vue 源码 +4. `panel`: ui 中用到的部分面板 +5. `components`: 所有 ui 的通用组件 +6. `data`: 数据文件,包含百科全书的内容、成就的内容等 +7. `fonts`: ui 中用到的字体文件 +8. `types`: mota-js 的类型声明文件 +9. `source`: mota-js 的图块等资源的类型声明文件,会通过热重载更新 +10. `initPlugin.ts`: 所有插件的入口文件 +11. `main.ts`: 主入口,会将`App.vue`与`App2.vue`渲染到 html 上 `script`: 在构建、发布等操作时会用到的 node 脚本 diff --git a/mota.config.ts b/mota.config.ts index 759b572..defa94e 100644 --- a/mota.config.ts +++ b/mota.config.ts @@ -1,5 +1,7 @@ interface MotaConfig { name: string; + /** 资源分组打包信息 */ + resourceZip?: string[][]; } function defineConfig(config: MotaConfig): MotaConfig { diff --git a/script/lines.ts b/script/lines.ts index 3dcf1d0..27af861 100644 --- a/script/lines.ts +++ b/script/lines.ts @@ -3,7 +3,8 @@ import { extname, resolve } from 'path'; import { formatSize } from './utils.js'; (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 totalFiles = 0; let totalSize = 0; diff --git a/script/resource.ts b/script/resource.ts index fab06cb..7952181 100644 --- a/script/resource.ts +++ b/script/resource.ts @@ -33,7 +33,7 @@ type Stats = fs.Stats & { name?: string }; export async function splitResorce(compress: boolean = false) { const folder = await fs.stat('./dist'); totalSize = folder.size; - // if (totalSize < MAX_SIZE) return; + if (totalSize < MAX_SIZE) return; await fs.ensureDir('./dist-resource'); await doSplit(compress); @@ -45,6 +45,7 @@ async function sortDir(dir: string, ext?: string[]) { for await (const one of path) { if (ext && !ext.includes(extname(one))) continue; + if (one === 'bg.jpg') continue; const stat = await fs.stat(resolve(dir, one)); if (!stat.isFile()) continue; const status: Stats = { diff --git a/src/core/common/disposable.ts b/src/core/common/disposable.ts index 2187a8f..0ba1fc9 100644 --- a/src/core/common/disposable.ts +++ b/src/core/common/disposable.ts @@ -3,21 +3,33 @@ import { EmitableEvent, EventEmitter } from './eventEmitter'; interface DisposableEvent extends EmitableEvent { active: (value: T) => void; dispose: (value: T) => void; + destroy: () => void; } export class Disposable extends EventEmitter> { - protected _data: T; + protected _data?: T; set data(value: T | null) { + if (this.destroyed) { + throw new Error( + `Cannot set value of destroyed disposable variable.` + ); + } if (value !== null) this._data = value; } get data(): T | null { + if (this.destroyed) { + throw new Error( + `Cannot get value of destroyed disposable variable.` + ); + } if (!this.activated) { return null; } - return this._data; + return this._data!; } protected activated: boolean = false; + protected destroyed: boolean = false; constructor(data: T) { super(); @@ -26,11 +38,17 @@ export class Disposable extends EventEmitter> { active() { this.activated = true; - this.emit('active', this._data); + this.emit('active', this._data!); } dispose() { this.activated = false; - this.emit('dispose', this._data); + this.emit('dispose', this._data!); + } + + destroy() { + this.destroyed = true; + this.emit('destroy'); + delete this._data; } } diff --git a/src/core/loader/resource.ts b/src/core/loader/resource.ts new file mode 100644 index 0000000..5768fa6 --- /dev/null +++ b/src/core/loader/resource.ts @@ -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 { + format: T; + request?: Promise | '@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 { + if (this.loaded) return this.resource ?? null; + else { + await this.request; + return this.resource ?? null; + } + } +} + +class ReosurceStore extends Map { + 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): 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( + key: string + ): Promise { + return this.get(key)?.getData() ?? null; + } +} + +declare global { + interface Window { + /** 游戏资源 */ + gameResource: ReosurceStore; + } + /** 游戏资源 */ + const gameResource: ReosurceStore; +} + +window.gameResource = new ReosurceStore();