mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-01-18 20:09:27 +08:00
加载资源初步
This commit is contained in:
parent
7cc7fe375b
commit
5cbd9950b9
21
README.md
21
README.md
@ -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 脚本
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
interface MotaConfig {
|
interface MotaConfig {
|
||||||
name: string;
|
name: string;
|
||||||
|
/** 资源分组打包信息 */
|
||||||
|
resourceZip?: string[][];
|
||||||
}
|
}
|
||||||
|
|
||||||
function defineConfig(config: MotaConfig): MotaConfig {
|
function defineConfig(config: MotaConfig): MotaConfig {
|
||||||
|
@ -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;
|
||||||
|
@ -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 = {
|
||||||
|
@ -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
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