mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-01-19 12:49:25 +08:00
feat: 新的加载系统
This commit is contained in:
parent
30a1fd9013
commit
80a67d5197
@ -297,9 +297,19 @@ core.prototype.init = async function (coreData, callback) {
|
||||
}
|
||||
}
|
||||
|
||||
if (main.replayChecking || main.mode === 'editor') {
|
||||
core.loader._load(function () {
|
||||
core._afterLoadResources(callback);
|
||||
});
|
||||
} else {
|
||||
if (main.renderLoaded)
|
||||
Mota.require('var', 'fixedUi').open('load', { callback });
|
||||
else {
|
||||
Mota.require('var', 'hook').once('renderLoaded', () => {
|
||||
Mota.require('var', 'fixedUi').open('load', { callback });
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
core.prototype.initSync = function (coreData, callback) {
|
||||
|
@ -316,6 +316,7 @@ async function writeMultiFiles(req: Request, res: Response) {
|
||||
}
|
||||
|
||||
async function writeDevResource(data: string) {
|
||||
return;
|
||||
try {
|
||||
const buf = Buffer.from(data, 'base64');
|
||||
data = buf.toString('utf-8');
|
||||
@ -324,12 +325,14 @@ async function writeDevResource(data: string) {
|
||||
const icons = await fs.readFile('./public/project/icons.js', 'utf-8');
|
||||
const iconData = JSON.parse(icons.split('\n').slice(1).join(''));
|
||||
res.push(
|
||||
...info.main.bgms.map((v: any) => `bgms.${v}`),
|
||||
...info.main.fonts.map((v: any) => `fonts.${v}.ttf`),
|
||||
...info.main.images.map((v: any) => `images.${v}`),
|
||||
...info.main.sounds.map((v: any) => `sounds.${v}`),
|
||||
...info.main.tilesets.map((v: any) => `tilesets.${v}`),
|
||||
...Object.keys(iconData.autotile).map(v => `autotiles.${v}.png`),
|
||||
...info.main.bgms.map((v: any) => `audio/${v}`),
|
||||
...info.main.fonts.map((v: any) => `buffer/project/fonts/${v}.ttf`),
|
||||
...info.main.images.map((v: any) => `image/project/images/${v}`),
|
||||
...info.main.sounds.map((v: any) => `buffer/${v}`),
|
||||
...info.main.tilesets.map((v: any) => `image/project/tilesets${v}`),
|
||||
...Object.keys(iconData.autotile).map(
|
||||
v => `image/project/autotiles/${v}.png`
|
||||
),
|
||||
...[
|
||||
'animates',
|
||||
'cloud',
|
||||
@ -343,7 +346,7 @@ async function writeDevResource(data: string) {
|
||||
'npcs',
|
||||
'sun',
|
||||
'terrains'
|
||||
].map(v => `materials.${v}.png`)
|
||||
].map(v => `material/${v}.png`)
|
||||
);
|
||||
const text = JSON.stringify(res, void 0, 4);
|
||||
await fs.writeFile('./src/data/resource-dev.json', text, 'utf-8');
|
||||
|
566
src/core/common/resource.ts
Normal file
566
src/core/common/resource.ts
Normal file
@ -0,0 +1,566 @@
|
||||
import axios, { AxiosRequestConfig, ResponseType } from 'axios';
|
||||
import { Disposable } from './disposable';
|
||||
import { logger } from './logger';
|
||||
import JSZip from 'jszip';
|
||||
import { EmitableEvent, EventEmitter } from './eventEmitter';
|
||||
|
||||
type ProgressFn = (now: number, total: number) => void;
|
||||
|
||||
interface ResourceType {
|
||||
text: string;
|
||||
buffer: ArrayBuffer;
|
||||
image: HTMLImageElement;
|
||||
material: HTMLImageElement;
|
||||
audio: HTMLAudioElement;
|
||||
json: any;
|
||||
zip: JSZip;
|
||||
}
|
||||
|
||||
interface ResourceMap {
|
||||
text: TextResource;
|
||||
buffer: BufferResource;
|
||||
image: ImageResource;
|
||||
material: MaterialResource;
|
||||
audio: AudioResource;
|
||||
json: JSONResource;
|
||||
zip: ZipResource;
|
||||
}
|
||||
|
||||
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, `Resource with type of 'none' is loaded.`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载这个资源,需要被子类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 `/${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 `/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 `/${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 `/${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 `/${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();
|
||||
this.resource = audio;
|
||||
return new Promise<HTMLAudioElement>(res => {
|
||||
this.loaded = true;
|
||||
res(audio);
|
||||
});
|
||||
}
|
||||
|
||||
resolveURI(): string {
|
||||
return `/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);
|
||||
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 `/${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
|
||||
};
|
||||
|
||||
interface LoadEvent<T extends keyof ResourceType> extends EmitableEvent {
|
||||
progress: (
|
||||
type: keyof ResourceType,
|
||||
uri: string,
|
||||
now: number,
|
||||
total: number
|
||||
) => void;
|
||||
load: (resource: ResourceMap[T]) => void;
|
||||
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
|
||||
*/
|
||||
load(): Promise<ResourceType[T]> {
|
||||
if (this.loadingStarted) {
|
||||
logger.warn(
|
||||
2,
|
||||
`Repeat load of resource '${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(reason => {
|
||||
LoadTask.errorTask++;
|
||||
logger.error(
|
||||
2,
|
||||
`Unexpected loading error in loading resource '${this.resource.type}/${this.resource.uri}'. Error info: ${reason}`
|
||||
);
|
||||
});
|
||||
this.emit('loadStart', this.resource);
|
||||
load.then(() => {
|
||||
// @ts-ignore
|
||||
LoadTask.loadedTaskList.add(this);
|
||||
this.loaded = totalByte;
|
||||
LoadTask.loadedTask++;
|
||||
this.emit('load', this.resource);
|
||||
});
|
||||
return load;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新建一个加载任务,同时将任务加入加载列表
|
||||
* @param type 任务类型
|
||||
* @param uri 加载内容的URI
|
||||
*/
|
||||
static add<T extends keyof ResourceType>(
|
||||
type: T,
|
||||
uri: string
|
||||
): LoadTask<T> {
|
||||
const task = new LoadTask(type, uri);
|
||||
// @ts-ignore
|
||||
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('buffer', `buffer/project/sounds/${v}`);
|
||||
Mota.r(() => {
|
||||
res.once('load', res => {
|
||||
Mota.require('var', 'sound').add(`sounds.${v}`, res.resource!);
|
||||
});
|
||||
});
|
||||
});
|
||||
// tilseset
|
||||
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-ignore
|
||||
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-ignore
|
||||
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 function loadCompressedResource() {}
|
@ -57,7 +57,6 @@ import EnemyTarget from '@/panel/enemyTarget.vue';
|
||||
import KeyboardPanel from '@/panel/keyboard.vue';
|
||||
import { MCGenerator } from './main/layout';
|
||||
import { ResourceController } from './loader/controller';
|
||||
import { readyAllResource } from './loader/load';
|
||||
import { logger } from './common/logger';
|
||||
|
||||
// ----- 类注册
|
||||
@ -133,5 +132,3 @@ Mota.register('module', 'MCGenerator', MCGenerator);
|
||||
|
||||
main.renderLoaded = true;
|
||||
Mota.require('var', 'hook').emit('renderLoaded');
|
||||
|
||||
readyAllResource();
|
||||
|
@ -1,46 +0,0 @@
|
||||
import resource from '@/data/resource.json';
|
||||
import { EmitableEvent, EventEmitter } from '../common/eventEmitter';
|
||||
import {
|
||||
Resource,
|
||||
getTypeByResource,
|
||||
zipResource,
|
||||
resource as res
|
||||
} from './resource';
|
||||
|
||||
const info = resource;
|
||||
|
||||
/**
|
||||
* 构建游戏包后的加载
|
||||
*/
|
||||
export function readyAllResource() {
|
||||
/* @__PURE__ */ if (main.RESOURCE_TYPE === 'dev') return readyDevResource();
|
||||
info.resource.forEach(v => {
|
||||
const type = getTypeByResource(v);
|
||||
if (type === 'zip') {
|
||||
zipResource.set(v, new Resource(v, 'zip'));
|
||||
} else {
|
||||
res.set(v, new Resource(v, type));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 开发时的加载
|
||||
*/
|
||||
/* @__PURE__ */ async function readyDevResource() {
|
||||
const loading = Mota.require('var', 'loading');
|
||||
const loadData = (await import('../../data/resource-dev.json')).default;
|
||||
|
||||
loadData.forEach(v => {
|
||||
const type = getTypeByResource(v);
|
||||
if (type !== 'zip') {
|
||||
res.set(v, new Resource(v, type));
|
||||
}
|
||||
});
|
||||
res.forEach(v => v.active());
|
||||
loading.once('coreInit', () => {
|
||||
const animates = new Resource('__all_animates__', 'text');
|
||||
res.set('__all_animates__', animates);
|
||||
animates.active();
|
||||
});
|
||||
}
|
@ -1,383 +0,0 @@
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
import { Disposable } from '../common/disposable';
|
||||
import { ensureArray } from '@/plugin/utils';
|
||||
import { has } from '@/plugin/utils';
|
||||
import JSZip from 'jszip';
|
||||
import { EmitableEvent, EventEmitter } from '../common/eventEmitter';
|
||||
import { bgm } from '../audio/bgm';
|
||||
|
||||
// todo: 应当用register去注册资源类型,然后进行分块处理
|
||||
|
||||
interface ResourceData {
|
||||
image: HTMLImageElement;
|
||||
arraybuffer: ArrayBuffer;
|
||||
text: string;
|
||||
json: any;
|
||||
zip: ZippedResource;
|
||||
bgm: HTMLAudioElement;
|
||||
}
|
||||
|
||||
export type ResourceType = keyof ResourceData;
|
||||
export type NonZipResource = Exclude<ResourceType, 'zip'>;
|
||||
|
||||
const autotiles: Partial<Record<AllIdsOf<'autotile'>, HTMLImageElement>> = {};
|
||||
|
||||
export class Resource<
|
||||
T extends ResourceType = ResourceType
|
||||
> extends Disposable<string> {
|
||||
format: T;
|
||||
request?: Promise<
|
||||
AxiosResponse<ResourceData[T]> | '@imageLoaded' | '@bgmLoaded'
|
||||
>;
|
||||
loaded: boolean = false;
|
||||
uri: string;
|
||||
|
||||
type!: string;
|
||||
name!: string;
|
||||
ext!: string;
|
||||
|
||||
/** 资源数据 */
|
||||
resource?: ResourceData[T];
|
||||
|
||||
constructor(resource: string, format: T) {
|
||||
super(resource);
|
||||
this.data = this.resolveUrl(resource);
|
||||
|
||||
this.format = format;
|
||||
this.uri = resource;
|
||||
|
||||
this.once('active', () => this.load());
|
||||
this.once('load', v => this.onLoad(v));
|
||||
this.once('loadstart', v => this.onLoadStart(v));
|
||||
}
|
||||
|
||||
protected onLoadStart(v?: ResourceData[T]) {
|
||||
if (this.format === 'bgm') {
|
||||
// bgm 单独处理,因为它可以边播放边加载
|
||||
bgm.add(this.uri, v!);
|
||||
}
|
||||
}
|
||||
|
||||
protected onLoad(v: ResourceData[T]) {
|
||||
const loading = Mota.require('var', 'loading');
|
||||
// 资源类型处理
|
||||
if (this.type === 'fonts') {
|
||||
document.fonts.add(new FontFace(this.name, v as ArrayBuffer));
|
||||
} else if (this.type === 'sounds') {
|
||||
Mota.require('var', 'sound').add(this.uri, v as ArrayBuffer);
|
||||
} else if (this.type === 'images') {
|
||||
const name = `${this.name}${this.ext}` as ImageIds;
|
||||
loading.on(
|
||||
'coreLoaded',
|
||||
() => {
|
||||
core.material.images.images[name] = v as HTMLImageElement;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
} else if (this.type === 'materials') {
|
||||
const name = this.name as SelectKey<
|
||||
MaterialImages,
|
||||
HTMLImageElement
|
||||
>;
|
||||
|
||||
loading.on(
|
||||
'coreLoaded',
|
||||
() => {
|
||||
core.material.images[name] = v;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
loading.addMaterialLoaded();
|
||||
} else if (this.type === 'autotiles') {
|
||||
const name = this.name as AllIdsOf<'autotile'>;
|
||||
autotiles[name] = v;
|
||||
loading.addAutotileLoaded();
|
||||
loading.onAutotileLoaded(autotiles);
|
||||
} else if (this.type === 'tilesets') {
|
||||
const name = `${this.name}${this.ext}`;
|
||||
loading.on(
|
||||
'coreLoaded',
|
||||
() => {
|
||||
core.material.images.tilesets[name] = v;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
}
|
||||
|
||||
// 资源加载类型处理
|
||||
if (this.format === 'zip') {
|
||||
(this.resource as ZippedResource).once('ready', data => {
|
||||
data.forEach((path, file) => {
|
||||
const [base, name] = path.split(/(\/|\\)/);
|
||||
const id = `${base}.${name}`;
|
||||
const type = getTypeByResource(id) as NonZipResource;
|
||||
const format = getZipFormatByType(type);
|
||||
resource.set(
|
||||
id,
|
||||
new Resource(id, type).setData(file.async(format))
|
||||
);
|
||||
});
|
||||
});
|
||||
} else if (this.format === 'image') {
|
||||
const img = v as HTMLImageElement;
|
||||
img.setAttribute('_width', img.width.toString());
|
||||
img.setAttribute('_height', img.height.toString());
|
||||
}
|
||||
|
||||
if (this.name === '__all_animates__') {
|
||||
if (this.format !== 'text') {
|
||||
throw new Error(
|
||||
`Unexpected mismatch of '__all_animates__' response type.` +
|
||||
` Expected: text. Meet: ${this.format}`
|
||||
);
|
||||
}
|
||||
const data = (v as string).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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析资源url
|
||||
* @param resource 资源字符串
|
||||
* @returns 解析出的资源url
|
||||
*/
|
||||
protected resolveUrl(resource: string) {
|
||||
if (resource === '__all_animates__') {
|
||||
this.type = 'animates';
|
||||
this.name = '__all_animates__';
|
||||
this.ext = '.animate';
|
||||
|
||||
return `/all/__all_animates__?v=${
|
||||
main.version
|
||||
}&id=${main.animates.join(',')}`;
|
||||
}
|
||||
const resolve = resource.split('.');
|
||||
const type = (this.type = resolve[0]);
|
||||
const name = (this.name = resolve.slice(1, -1).join('.'));
|
||||
const ext = (this.ext = '.' + resolve.at(-1));
|
||||
|
||||
const distBase = import.meta.env.BASE_URL;
|
||||
|
||||
const base = main.RESOURCE_URL;
|
||||
const indexes = main.RESOURCE_INDEX;
|
||||
const symbol = main.RESOURCE_SYMBOL;
|
||||
const t = main.RESOURCE_TYPE;
|
||||
|
||||
if (t === 'dist') {
|
||||
if (has(indexes[`${type}.*`])) {
|
||||
const i = indexes[`${type}.*`];
|
||||
if (i !== 'dist') {
|
||||
return `${base}${i}/${type}/${name}-${symbol}${ext}`;
|
||||
} else {
|
||||
return `${distBase}resource/${type}/${name}-${symbol}${ext}`;
|
||||
}
|
||||
} else {
|
||||
const i = indexes[`${type}.${name}${ext}`];
|
||||
const index = has(i) ? i : '0';
|
||||
if (i !== 'dist') {
|
||||
return `${base}${index}/${type}/${name}-${symbol}${ext}`;
|
||||
} else {
|
||||
return `${distBase}resource/${type}/${name}-${symbol}${ext}`;
|
||||
}
|
||||
}
|
||||
} else if (t === 'gh' || t === 'local') {
|
||||
return `${distBase}resource/${type}/${name}-${symbol}${ext}`;
|
||||
} else {
|
||||
return `${distBase}project/${type}/${name}${ext}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载资源
|
||||
*/
|
||||
protected load() {
|
||||
if (this.loaded) {
|
||||
throw new Error(`Cannot load one resource twice.`);
|
||||
}
|
||||
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;
|
||||
this.emit('loadstart', img);
|
||||
img.addEventListener('load', () => {
|
||||
this.resource = img;
|
||||
this.loaded = true;
|
||||
this.emit('load', img);
|
||||
res('@imageLoaded');
|
||||
});
|
||||
});
|
||||
} else if (this.format === 'bgm') {
|
||||
this.request = new Promise(res => {
|
||||
const audio = new Audio();
|
||||
audio.src = data;
|
||||
this.emit('loadstart', audio);
|
||||
audio.addEventListener('load', () => {
|
||||
this.resource = audio;
|
||||
this.loaded = true;
|
||||
this.emit('load', audio);
|
||||
res('@bgmLoaded');
|
||||
});
|
||||
});
|
||||
} else if (
|
||||
this.format === 'json' ||
|
||||
this.format === 'text' ||
|
||||
this.format === 'arraybuffer'
|
||||
) {
|
||||
this.emit('loadstart');
|
||||
this.request = axios
|
||||
.get(data, {
|
||||
responseType: this.format,
|
||||
onDownloadProgress: e => {
|
||||
this.emit('progress', e);
|
||||
}
|
||||
})
|
||||
.then(v => {
|
||||
this.resource = v.data;
|
||||
this.loaded = true;
|
||||
this.emit('load', v.data);
|
||||
return v;
|
||||
});
|
||||
} else if (this.format === 'zip') {
|
||||
this.emit('loadstart');
|
||||
this.request = axios
|
||||
.get(data, {
|
||||
responseType: 'arraybuffer',
|
||||
onDownloadProgress: e => {
|
||||
this.emit('progress', e);
|
||||
}
|
||||
})
|
||||
.then(v => {
|
||||
this.resource = new ZippedResource(v.data);
|
||||
this.loaded = true;
|
||||
this.emit('load', this.resource);
|
||||
return v;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取资源,如果还没加载会等待加载完毕再获取
|
||||
*/
|
||||
async getData(): Promise<ResourceData[T] | null> {
|
||||
if (!this.activated) return null;
|
||||
if (this.loaded) return this.resource ?? null;
|
||||
else {
|
||||
if (!this.request) this.load();
|
||||
await this.request;
|
||||
return this.resource ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置资源数据,不再需要加载
|
||||
* @param data 数据
|
||||
*/
|
||||
protected setData(data: ResourceData[T] | Promise<ResourceData[T]>) {
|
||||
if (data instanceof Promise) {
|
||||
data.then(v => {
|
||||
this.loaded = true;
|
||||
this.resource = v;
|
||||
this.emit('load', v);
|
||||
});
|
||||
} else {
|
||||
this.loaded = true;
|
||||
this.resource = data;
|
||||
this.emit('load', data);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
interface ZippedEvent extends EmitableEvent {
|
||||
ready: (data: JSZip) => void;
|
||||
}
|
||||
|
||||
export class ZippedResource extends EventEmitter<ZippedEvent> {
|
||||
zip: Promise<JSZip>;
|
||||
data?: JSZip;
|
||||
|
||||
constructor(buffer: ArrayBuffer) {
|
||||
super();
|
||||
this.zip = JSZip.loadAsync(buffer).then(v => {
|
||||
this.emit('ready', v);
|
||||
this.data = v;
|
||||
return v;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class ResourceStore<T extends ResourceType> extends Map<
|
||||
string,
|
||||
Resource<T>
|
||||
> {
|
||||
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<T>][] | Record<string, Resource<T>>): 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;
|
||||
}
|
||||
|
||||
getDataSync<T extends ResourceType = ResourceType>(
|
||||
key: string
|
||||
): ResourceData[T] | null {
|
||||
return this.get(key)?.resource ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
export function getTypeByResource(resource: string): ResourceType {
|
||||
const type = resource.split('.')[0];
|
||||
|
||||
if (type === 'zip') return 'zip';
|
||||
else if (type === 'bgms') return 'bgm';
|
||||
else if (['images', 'autotiles', 'materials', 'tilesets'].includes(type)) {
|
||||
return 'image';
|
||||
} else if (['sounds', 'fonts'].includes(type)) return 'arraybuffer';
|
||||
else if (type === 'animates') return 'json';
|
||||
|
||||
return 'arraybuffer';
|
||||
}
|
||||
|
||||
export function getZipFormatByType(type: ResourceType): 'arraybuffer' | 'text' {
|
||||
if (type === 'text' || type === 'json') return 'text';
|
||||
else return 'arraybuffer';
|
||||
}
|
||||
|
||||
export const resource = new ResourceStore();
|
||||
export const zipResource = new ResourceStore();
|
@ -32,7 +32,8 @@ fixedUi.register(
|
||||
new GameUi('chapter', UI.Chapter),
|
||||
new GameUi('completeAchi', UI.CompleteAchi),
|
||||
new GameUi('start', UI.Start),
|
||||
new GameUi('toolbar', UI.Toolbar)
|
||||
new GameUi('toolbar', UI.Toolbar),
|
||||
new GameUi('load', UI.Load)
|
||||
);
|
||||
fixedUi.showAll();
|
||||
|
||||
@ -72,15 +73,5 @@ hook.once('mounted', () => {
|
||||
fixed.style.display = 'none';
|
||||
});
|
||||
|
||||
if (loaded && !mounted) {
|
||||
fixedUi.open('start');
|
||||
}
|
||||
mounted = true;
|
||||
});
|
||||
hook.once('load', () => {
|
||||
if (mounted) {
|
||||
// todo: 暂时先这么搞,之后重写加载界面,需要改成先显示加载界面,加载完毕后再打开这个界面
|
||||
fixedUi.open('start');
|
||||
}
|
||||
loaded = true;
|
||||
});
|
||||
|
@ -7,12 +7,6 @@ import type {
|
||||
IndexedEventEmitter
|
||||
} from '@/core/common/eventEmitter';
|
||||
import type { loading } from './game';
|
||||
import type {
|
||||
Resource,
|
||||
ResourceStore,
|
||||
ResourceType,
|
||||
ZippedResource
|
||||
} from '@/core/loader/resource';
|
||||
import type { Hotkey } from '@/core/main/custom/hotkey';
|
||||
import type { Keyboard } from '@/core/main/custom/keyboard';
|
||||
import type { CustomToolbar } from '@/core/main/custom/toolbar';
|
||||
@ -38,9 +32,6 @@ interface ClassInterface {
|
||||
GameStorage: typeof GameStorage;
|
||||
MotaSetting: typeof MotaSetting;
|
||||
SettingDisplayer: typeof SettingDisplayer;
|
||||
Resource: typeof Resource;
|
||||
ZippedResource: typeof ZippedResource;
|
||||
ResourceStore: typeof ResourceStore;
|
||||
Focus: typeof Focus;
|
||||
GameUi: typeof GameUi;
|
||||
UiController: typeof UiController;
|
||||
@ -82,8 +73,6 @@ interface VariableInterface {
|
||||
// isMobile: boolean;
|
||||
bgm: BgmController;
|
||||
sound: SoundController;
|
||||
resource: ResourceStore<Exclude<ResourceType, 'zip'>>;
|
||||
zipResource: ResourceStore<'zip'>;
|
||||
settingStorage: GameStorage;
|
||||
status: Ref<boolean>;
|
||||
// 定义于游戏进程,渲染进程依然可用
|
||||
@ -529,7 +518,8 @@ function r<T = undefined>(
|
||||
fn: (this: T, packages: PackageInterface) => void,
|
||||
thisArg?: T
|
||||
) {
|
||||
if (!main.replayChecking) fn.call(thisArg as T, MPackage.requireAll());
|
||||
if (!main.replayChecking && main.mode === 'play')
|
||||
fn.call(thisArg as T, MPackage.requireAll());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -548,7 +538,7 @@ function rf<F extends (...params: any) => any, T>(
|
||||
thisArg?: T
|
||||
): (this: T, ...params: Parameters<F>) => ReturnType<F> | undefined {
|
||||
// @ts-ignore
|
||||
if (main.replayChecking) return () => {};
|
||||
if (main.replayChecking || main.mode === 'editor') return () => {};
|
||||
else {
|
||||
return (...params) => {
|
||||
return fn.call(thisArg, ...params);
|
||||
|
@ -386,3 +386,13 @@ export function getVitualKeyOnce(
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function formatSize(size: number) {
|
||||
return size < 1 << 10
|
||||
? `${size.toFixed(2)}B`
|
||||
: size < 1 << 20
|
||||
? `${(size / (1 << 10)).toFixed(2)}KB`
|
||||
: size < 1 << 30
|
||||
? `${(size / (1 << 20)).toFixed(2)}MB`
|
||||
: `${(size / (1 << 30)).toFixed(2)}GB`;
|
||||
}
|
||||
|
4
src/types/core.d.ts
vendored
4
src/types/core.d.ts
vendored
@ -98,6 +98,8 @@ type MaterialImages = {
|
||||
*/
|
||||
tilesets: Record<string, HTMLImageElement>;
|
||||
|
||||
keyboard: HTMLImageElement;
|
||||
|
||||
hero: HTMLImageElement;
|
||||
};
|
||||
|
||||
@ -1081,6 +1083,8 @@ interface Core extends Pick<Main, CoreDataFromMain> {
|
||||
_this: any,
|
||||
...params: Parameters<F>
|
||||
): ReturnType<F>;
|
||||
|
||||
_afterLoadResources(callback?: () => void): void;
|
||||
}
|
||||
|
||||
type CoreMixin = Core &
|
||||
|
@ -1,13 +1,105 @@
|
||||
<template>
|
||||
<div id="load"></div>
|
||||
<div id="load">
|
||||
<a-progress
|
||||
class="task-progress"
|
||||
type="circle"
|
||||
:percent="(loading / totalTask) * 100"
|
||||
:success="{ percent: (loaded / totalTask) * 100 }"
|
||||
>
|
||||
<template #format>
|
||||
<span>{{ loaded }} / {{ totalTask }}</span>
|
||||
</template>
|
||||
</a-progress>
|
||||
<div class="byte-div">
|
||||
<span class="byte-progress-tip"
|
||||
>{{ formatSize(loadedByte) }} /
|
||||
{{ formatSize(totalByte) }}</span
|
||||
>
|
||||
<a-progress
|
||||
class="byte-progress"
|
||||
type="line"
|
||||
:percent="loadedPercent"
|
||||
></a-progress>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup></script>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { loadDefaultResource, LoadTask } from '@/core/common/resource';
|
||||
import { GameUi } from '@/core/main/custom/ui';
|
||||
import { formatSize } from '@/plugin/utils';
|
||||
import { logger } from '@/core/common/logger';
|
||||
import { fixedUi } from '@/core/main/init/ui';
|
||||
import { sleep } from 'mutate-animate';
|
||||
|
||||
const props = defineProps<{
|
||||
ui: GameUi;
|
||||
num: number;
|
||||
callback?: () => void;
|
||||
}>();
|
||||
|
||||
const loading = ref(0);
|
||||
const loaded = ref(0);
|
||||
const loadedByte = ref(0);
|
||||
const loadedPercent = ref(0);
|
||||
const totalByte = ref(0);
|
||||
const totalTask = ref(0);
|
||||
|
||||
let loadDiv: HTMLDivElement;
|
||||
|
||||
loadDefaultResource();
|
||||
|
||||
LoadTask.onProgress(() => {
|
||||
const loadingNum = [...LoadTask.taskList].filter(v => v.loading).length;
|
||||
|
||||
loadedByte.value = LoadTask.loadedByte;
|
||||
loadedPercent.value = parseFloat(
|
||||
((LoadTask.loadedByte / LoadTask.totalByte) * 100).toFixed(2)
|
||||
);
|
||||
loading.value = loadingNum;
|
||||
loaded.value = LoadTask.loadedTask;
|
||||
totalByte.value = LoadTask.totalByte;
|
||||
totalTask.value = LoadTask.totalTask;
|
||||
});
|
||||
|
||||
LoadTask.load().then(async () => {
|
||||
core.loader._loadMaterials_afterLoad();
|
||||
core._afterLoadResources(props.callback);
|
||||
logger.log(`Resource load end.`);
|
||||
loadDiv.style.opacity = '0';
|
||||
await sleep(1000);
|
||||
fixedUi.close(props.num);
|
||||
fixedUi.open('start');
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
loadDiv = document.getElementById('load') as HTMLDivElement;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
#load {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
font-family: 'Arial';
|
||||
transition: opacity 1s linear;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
.byte-div {
|
||||
width: 50%;
|
||||
margin-top: 10vh;
|
||||
}
|
||||
|
||||
.byte-progress {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
@ -310,9 +310,6 @@ onMounted(async () => {
|
||||
start = document.getElementById('start') as HTMLDivElement;
|
||||
background = document.getElementById('background') as HTMLImageElement;
|
||||
|
||||
const loading = Mota.require('var', 'loading');
|
||||
|
||||
loading.once('coreInit', async () => {
|
||||
window.addEventListener('resize', resize);
|
||||
resize();
|
||||
|
||||
@ -328,7 +325,6 @@ onMounted(async () => {
|
||||
await sleep(1000);
|
||||
showCursor();
|
||||
await sleep(1200);
|
||||
});
|
||||
|
||||
CustomToolbar.closeAll();
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user