mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-01-18 11:59:26 +08:00
加载资源初步
This commit is contained in:
parent
7cc7fe375b
commit
5cbd9950b9
21
README.md
21
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 脚本
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
interface MotaConfig {
|
||||
name: string;
|
||||
/** 资源分组打包信息 */
|
||||
resourceZip?: string[][];
|
||||
}
|
||||
|
||||
function defineConfig(config: MotaConfig): MotaConfig {
|
||||
|
@ -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;
|
||||
|
@ -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 = {
|
||||
|
@ -3,21 +3,33 @@ import { EmitableEvent, EventEmitter } from './eventEmitter';
|
||||
interface DisposableEvent<T> extends EmitableEvent {
|
||||
active: (value: T) => void;
|
||||
dispose: (value: T) => void;
|
||||
destroy: () => void;
|
||||
}
|
||||
|
||||
export class Disposable<T> extends EventEmitter<DisposableEvent<T>> {
|
||||
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<T> extends EventEmitter<DisposableEvent<T>> {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
124
src/core/loader/resource.ts
Normal file
124
src/core/loader/resource.ts
Normal 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();
|
Loading…
Reference in New Issue
Block a user