From 4d5f14e8a54870020f644acf8882f2a1c7929498 Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Fri, 3 May 2024 20:44:43 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9E=84=E5=BB=BA=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/libs/core.js | 24 +- public/main.js | 12 +- script/build.ts | 27 +- script/resource.ts | 489 +++++++++++++++-------------------- script/template/启动服务.exe | Bin 0 -> 59392 bytes src/core/common/resource.ts | 159 +++++++++++- src/core/main/setting.ts | 2 +- src/types/core.d.ts | 2 + src/ui/danmaku.vue | 6 +- src/ui/danmakuEditor.vue | 24 +- src/ui/load.vue | 52 ++-- src/ui/start.vue | 4 +- src/ui/statusBar.vue | 33 +-- vite.config.ts | 1 - 14 files changed, 444 insertions(+), 391 deletions(-) create mode 100644 script/template/启动服务.exe diff --git a/public/libs/core.js b/public/libs/core.js index 2b45e11..0963388 100644 --- a/public/libs/core.js +++ b/public/libs/core.js @@ -601,18 +601,18 @@ core.prototype._init_others = function () { ); core.bigmap.tempCanvas = document.createElement('canvas').getContext('2d'); core.bigmap.cacheCanvas = document.createElement('canvas').getContext('2d'); - core.loadImage('materials', 'fog', function (name, img) { - core.animateFrame.weather.fog = img; - }); - core.loadImage('materials', 'cloud', function (name, img) { - core.animateFrame.weather.cloud = img; - }); - core.loadImage('materials', 'sun', function (name, img) { - core.animateFrame.weather.sun = img; - }); - core.loadImage('materials', 'keyboard', function (name, img) { - core.material.images.keyboard = img; - }); + // core.loadImage('materials', 'fog', function (name, img) { + // core.animateFrame.weather.fog = img; + // }); + // core.loadImage('materials', 'cloud', function (name, img) { + // core.animateFrame.weather.cloud = img; + // }); + // core.loadImage('materials', 'sun', function (name, img) { + // core.animateFrame.weather.sun = img; + // }); + // core.loadImage('materials', 'keyboard', function (name, img) { + // core.material.images.keyboard = img; + // }); // 记录存档编号 core.saves.saveIndex = core.getLocalStorage('saveIndex', 1); core.control.getSaveIndexes(function (indexes) { diff --git a/public/main.js b/public/main.js index 33f4520..975bd5c 100644 --- a/public/main.js +++ b/public/main.js @@ -215,15 +215,6 @@ function main() { this.__VERSION__ = '2.10.0'; this.__VERSION_CODE__ = 510; - - this.timestamp = 0; - - // 远程资源地址,在线游戏中,塔本体不包含任何资源,只包含源码,从而可以降低游戏本体的体积并平均分担资源包体积 - // 从而可以优化加载并避免网站发布的大小限制 - this.RESOURCE_TYPE = 'dev'; - this.RESOURCE_URL = ''; - this.RESOURCE_SYMBOL = ''; - this.RESOURCE_INDEX = {}; } // >>>> body end @@ -336,7 +327,7 @@ main.prototype.loadAsync = async function (mode, callback) { const mainData = data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d.main; Object.assign(main, mainData); - main.importFonts(main.fonts); + // main.importFonts(main.fonts); // 加载核心js代码 if (main.useCompress) { @@ -496,7 +487,6 @@ main.prototype.importFonts = function (fonts) { font + '"; src: url("project/fonts/' + font + - (main.pluginUseCompress ? '-' + main.timestamp : '') + '.ttf") format("truetype"); }'; }); style.innerHTML = html; diff --git a/script/build.ts b/script/build.ts index 996f844..ed29855 100644 --- a/script/build.ts +++ b/script/build.ts @@ -8,12 +8,12 @@ import rollupBabel from '@rollup/plugin-babel'; import terser from '@rollup/plugin-terser'; import resolve from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; -import { splitResorce } from './resource.js'; +import { splitResource } from './resource.js'; import compressing from 'compressing'; const type = process.argv[2]; const map = false; -const resorce = type !== 'dev'; +const resorce = true; const compress = type === 'dist'; (async function () { @@ -115,14 +115,6 @@ const compress = type === 'dist'; })() ) ]); - await Promise.all([ - ...fonts.map(v => { - return fs.rename( - `./dist/project/fonts/${v}.ttf`, - `./dist/project/fonts/${v}-${timestamp}.ttf` - ); - }) - ]); } catch (e) { console.log('字体压缩失败'); console.log(e); @@ -133,7 +125,7 @@ const compress = type === 'dist'; await fs.remove('./dist/project/plugin.min.js'); const build = await rollup.rollup({ - input: 'src/plugin/game/index.js', + input: 'src/game/index.ts', plugins: [ typescript({ sourceMap: false @@ -163,12 +155,7 @@ const compress = type === 'dist'; // 4. 压缩main.js try { // 先获取不能压缩的部分 - const main = (await fs.readFile('./public/main.js', 'utf-8')) - .replace( - /this.pluginUseCompress\s*=\s*false\;/, - 'this.pluginUseCompress = true;' - ) - .replace('this.timestamp = 0', `this.timestamp = ${timestamp};`); + const main = await fs.readFile('./public/main.js', 'utf-8'); const endIndex = main.indexOf('// >>>> body end'); const nonCompress = main.slice(0, endIndex); @@ -190,7 +177,11 @@ const compress = type === 'dist'; // 6. 资源分离 if (resorce) { - await splitResorce(type); + await splitResource(); + } + + if (!compress) { + await fs.copy('./script/template/启动服务.exe', './dist/启动服务.exe'); } // 7. 压缩 diff --git a/script/resource.ts b/script/resource.ts index 4a572cf..e0a91ad 100644 --- a/script/resource.ts +++ b/script/resource.ts @@ -1,297 +1,236 @@ -import fs from 'fs-extra'; -import { uniqueSymbol } from './utils.js'; -import { resolve } from 'path'; -import motaConfig from '../mota.config.js'; -import compressing from 'compressing'; +import fs, { Stats } from 'fs-extra'; +import JSZip from 'jszip'; +import { formatSize, uniqueSymbol } from './utils.js'; -const SYMBOL = uniqueSymbol(); -const MAX_SIZE = 100 * (1 << 20) - 20 * (1 << 10); -const sourceIndex: Record = {}; -const toMove: Stats[] = []; -const all = [ - 'bgms', - 'sounds', - 'autotiles', - 'images', - 'materials', - 'tilesets', - 'animates', - 'fonts' -]; +// 资源拆分模块,可以加快在线加载速度 -type Stats = fs.Stats & { name?: string }; +type ResourceType = + | 'text' + | 'buffer' + | 'image' + | 'material' + | 'audio' + | 'json' + | 'zip'; +interface CompressedLoadListItem { + type: ResourceType; + name: string; + usage: string; +} +type CompressedLoadList = Record; -export async function splitResorce(type: string) { - await fs.ensureDir('./dist-resource'); - await fs.emptyDir('./dist-resource'); - await readySplit(); - - await zipResource(); - await split(type === 'dist' ? MAX_SIZE : void 0); - - await endSplit(type); +interface MainData { + main: { + images: string[]; + tilesets: string[]; + animates: string[]; + sounds: string[]; + fonts: string[]; + }; } -async function readySplit() { - await fs.ensureDir('./_temp'); - await fs.emptyDir('./_temp'); - await fs.ensureDir('./_temp/origin'); - await copyAll(); -} +/** 单包大小,2M */ +const SPLIT_SIZE = 2 ** 20 * 2; -async function endSplit(type: string) { - await rewriteMain(type); - await fs.emptyDir('./_temp'); - await fs.rmdir('./_temp'); -} +export async function splitResource() { + const splitResult: CompressedLoadList = {}; + let now: CompressedLoadListItem[] = []; + let totalSize: number = 0; + let nowZip: JSZip = new JSZip(); -async function zipResource() { - const zip = motaConfig.zip; - if (!zip) return; - for await (const [name, files] of Object.entries(zip)) { - const stream = new compressing.zip.Stream(); - const dirs: string[] = []; + await fs.ensureDir('./dist/resource/'); + await fs.emptyDir('./dist/resource'); - for await (const file of files) { - if (/^.+\/\*$/.test(file)) { - const dir = file.split('/')[0]; - dirs.push(dir); - await fs.copy(`./_temp/origin/${dir}`, `./_temp/${dir}`); - } else if (file.startsWith('!')) { - const dir = file.slice(1); - await fs.remove(`./_temp/${dir}`); - } else { - const [dir, name] = file.split('/'); - if (dirs.includes(dir)) dirs.push(dir); - await fs.ensureDir(`./_temp/${dir}`); - await fs.copyFile( - `./_temp/origin/${dir}/${name}`, - `./_temp/${dir}/${name}` + const pushItem = async ( + type: ResourceType, + name: string, + usage: string, + file: Stats, + content: any + ) => { + totalSize += file.size; + + if (totalSize > SPLIT_SIZE) { + if (file.size > SPLIT_SIZE) { + const symbol = uniqueSymbol() + `.h5data`; + console.warn( + `file ${type}/${name}(${formatSize( + file.size + )}) is larger than split limit (${formatSize( + SPLIT_SIZE + )}), single zip will be generated.` ); + splitResult['resource/' + symbol] = [{ type, name, usage }]; + const zip = new JSZip(); + addZippedFile(zip, type, name, content); + await writeZip(zip, `./dist/resource/${symbol}`, file.size); + totalSize -= file.size; + return; + } else { + const symbol = uniqueSymbol() + `.h5data`; + splitResult['resource/' + symbol] = now; + await writeZip( + nowZip, + `./dist/resource/${symbol}`, + totalSize - file.size + ); + nowZip = new JSZip(); + totalSize = 0; + now = []; + await pushItem(type, name, usage, file, content); } - } - - dirs.forEach(v => stream.addEntry(`./_temp/${v}`)); - const dest = fs.createWriteStream(`./_temp/${name}`); - await new Promise(res => - stream.pipe(dest).on('finish', () => { - res(); - }) - ); - const stat = await fs.stat(`./_temp/${name}`); - toMove.push({ ...stat, name: `./_temp/${name}` }); - } -} - -async function getRemainReource() { - const zip = motaConfig.zip; - if (!zip) return; - const values = Object.values(zip); - for await (const one of all) { - if (values.some(v => v.includes(`${one}/*`))) continue; - const list = await fs.readdir(`./_temp/origin/${one}`); - for await (const name of list) { - if (!values.some(vv => vv.includes(`${one}/${name}`))) { - const stat = await fs.stat(`./_temp/origin/${one}/${name}`); - toMove.push({ - ...stat, - name: `./_temp/origin/${one}/${name}` - }); - } - } - } - toMove.sort((a, b) => { - if (a.name?.endsWith('.zip') && b.name?.endsWith('.zip')) { - return b.size - a.size; - } - if (a.name?.endsWith('.zip')) return -1; - if (b.name?.endsWith('.zip')) return 1; - return b.size - a.size; - }); -} - -async function split(max?: number) { - await getRemainReource(); - - const doSplit = async (index: string | number) => { - const base = - typeof index === 'string' ? index : `./dist-resource/${index}`; - - await fs.ensureDir(base); - await generatePublishStructure( - base, - typeof index === 'string' ? 0 : index - ); - - let size = (await fs.stat(base)).size; - // 计算出要移动多少资源 - const res = (() => { - if (!max) return toMove.splice(0, toMove.length); - let remain = max - size; - for (let i = 0; i < toMove.length; i++) { - const ele = toMove[i]; - remain -= ele.size; - if (remain <= 0) { - return toMove.splice(0, i); - } - } - return toMove.splice(0, toMove.length); - })(); - - if (base.endsWith('dist')) { - await fs.ensureDir(resolve(base, 'resource')); - } - - // 执行移动 - await Promise.all( - res.map(async v => { - if (!v.name) return; - // 压缩包 - if (v.name.endsWith('.zip')) { - const [, , name] = v.name.split('/'); - const split = name.split('.'); - const target = `${split - .slice(0, -1) - .join('.')}-${SYMBOL}.${split.at(-1)}`; - if (base.endsWith('dist')) { - await fs.ensureDir(resolve(base, 'resource/zip')); - return fs.copyFile( - v.name, - resolve(base, 'resource', 'zip', target) - ); - } else { - await fs.ensureDir(resolve(base, 'zip')); - return fs.copyFile( - v.name, - resolve(base, 'zip', target) - ); - } - } - - // 非压缩包 - if (!v.name.endsWith('.zip')) { - const [, , , type, name] = v.name.split('/'); - const split = name.split('.'); - const target = `${split - .slice(0, -1) - .join('.')}-${SYMBOL}.${split.at(-1)}`; - if (base.endsWith('dist')) { - await fs.ensureDir(resolve(base, 'resource', type)); - } else { - await fs.ensureDir(resolve(base, type)); - } - if (base.endsWith('dist')) { - return fs.copyFile( - v.name, - resolve(base, 'resource', type, target) - ); - } else { - return fs.copyFile(v.name, resolve(base, type, target)); - } - } - }) - ); - - // 标记资源索引 - res.forEach(v => { - if (!v.name) return; - // 压缩包 - if (v.name.endsWith('.zip')) { - const [, , name] = v.name.split('/'); - sourceIndex[`zip.${name}`] = index.toString(); - } - // 非压缩包 - if (!v.name.endsWith('.zip')) { - const [, , , type, name] = v.name.split('/'); - sourceIndex[`${type}.${name}`] = index.toString(); - } - }); - - if (toMove.length > 0) { - await doSplit(typeof index === 'string' ? 0 : index + 1); + } else { + now.push({ type, name, usage }); + addZippedFile(nowZip, type, name, content); } }; - await doSplit('dist'); -} -async function copyAll() { - await Promise.all( - all.map(v => { - return fs.move(`./dist/project/${v}`, `./_temp/origin/${v}`); - }) - ); -} + const writeZip = (zip: JSZip, name: string, size: number) => { + return new Promise(res => { + zip.generateNodeStream({ type: 'nodebuffer', streamFiles: true }) + .pipe(fs.createWriteStream(name)) + .once('finish', function () { + console.log( + `Generated ${name}. Unzipped size: ${formatSize(size)}` + ); + res(); + }); + }); + }; -async function rewriteMain(type: string) { - const main = await fs.readFile('./dist/main.js', 'utf-8'); - const res = main - .replace( - /this\.RESOURCE_TYPE\s*\=\s*.*;/, - `this.RESOURCE_TYPE = '${type}';` - ) - .replace( - /this\.RESOURCE_URL\s*\=\s*'.*'/, - `this.RESOURCE_URL = '/games/${motaConfig.resourceName}'` - ) - .replace( - /this\.RESOURCE_SYMBOL\s*\=\s*'.*'/, - `this.RESOURCE_SYMBOL = '${SYMBOL}'` - ) - .replace( - /this\.RESOURCE_INDEX\s*\=\s*\{.*\}/, - `this.RESOURCE_INDEX = ${JSON.stringify(sourceIndex, void 0, 8)}` - ); - await fs.writeFile('./dist/main.js', res, 'utf-8'); -} + const addZippedFile = ( + zip: JSZip, + type: ResourceType, + name: string, + content: any + ) => { + zip.file(`${type}/${name}`, content); + }; -/** - * 生成可发布目录 - */ -async function generatePublishStructure(dir: string, index: number) { - await fs.ensureDir(resolve(dir, 'libs')); - await fs.ensureDir(resolve(dir, 'libs/thirdparty')); - await fs.ensureDir(resolve(dir, 'project')); - await Promise.all( - all.map(v => { - fs.ensureDir(resolve(dir, 'project', v)); - fs.emptyDir(resolve(dir, 'project', v)); - }) - ); + const file = await fs.readFile('./dist/project/data.js', 'utf-8'); + const data = JSON.parse(file.split('\n').slice(1).join('')) as MainData; - if (!dir.endsWith('dist')) { - await fs.writeFile( - resolve(dir, 'project/icons.js'), - `var icons_4665ee12_3a1f_44a4_bea3_0fccba634dc1 = -{"autotile": {}} -`, - 'utf-8' - ); - await fs.writeFile( - resolve(dir, 'project/floors/none.js'), - '"none"', - 'utf-8' - ); - await fs.writeFile(resolve(dir, 'libs/none.js'), '"none"', 'utf-8'); + // images + for (const image of data.main.images) { + const path = `./dist/project/images/${image}`; + const stat = await fs.stat(path); + await pushItem('image', image, 'image', stat, await fs.readFile(path)); + } - await fs.copyFile('./script/template/main.js', resolve(dir, 'main.js')); - const data = await fs.readFile('./script/template/data.js', 'utf-8'); - await fs.writeFile( - resolve(dir, 'project/data.js'), - data.replace('@name', `${motaConfig.resourceName}${index}`) - ); - - await fs.copyFile( - './script/template/lz-string.min.js', - resolve(dir, 'libs/thirdparty/lz-string.min.js') + // tileset + for (const tileset of data.main.tilesets) { + const path = `./dist/project/tilesets/${tileset}`; + const stat = await fs.stat(path); + await pushItem( + 'image', + tileset, + 'tileset', + stat, + await fs.readFile(path) ); } - await Promise.all( - ['animates', 'images', 'materials', 'sounds', 'tilesets'].map(v => { - fs.copyFile( - './script/template/.h5data', - resolve(dir, `project/${v}/${v}.h5data`) - ); - }) + // animates + for (const ani of data.main.animates) { + const path = `./dist/project/animates/${ani}.animate`; + const stat = await fs.stat(path); + await pushItem( + 'text', + ani + '.animate', + 'animate', + stat, + await fs.readFile(path, 'utf-8') + ); + } + + // sounds + for (const sound of data.main.sounds) { + const path = `./dist/project/sounds/${sound}`; + const stat = await fs.stat(path); + await pushItem('audio', sound, 'sound', stat, await fs.readFile(path)); + } + + // fonts + for (const font of data.main.fonts) { + const path = `./dist/project/fonts/${font}.ttf`; + const stat = await fs.stat(path); + await pushItem( + 'buffer', + font + '.ttf', + 'font', + stat, + await fs.readFile(path) + ); + } + + // autotiles + const autotiles = await fs.readdir('./dist/project/autotiles'); + for (const a of autotiles) { + const path = `./dist/project/autotiles/${a}`; + const stat = await fs.stat(path); + await pushItem('image', a, 'autotile', stat, await fs.readFile(path)); + } + + // materials + const materials = await fs.readdir('./dist/project/materials'); + for (const m of materials) { + const path = `./dist/project/materials/${m}`; + const stat = await fs.stat(path); + await pushItem( + 'material', + m, + 'material', + stat, + await fs.readFile(path) + ); + } + + const symbol = uniqueSymbol() + `.h5data`; + splitResult['resource/' + symbol] = now; + await writeZip(nowZip, `./dist/resource/${symbol}`, totalSize); + + // 添加资源映射 + await fs.writeFile( + './dist/loadList.json', + JSON.stringify(splitResult), + 'utf-8' + ); + + // 删除原资源 + await fs.emptyDir('./dist/project/images'); + await fs.emptyDir('./dist/project/tilesets'); + await fs.emptyDir('./dist/project/animates'); + await fs.emptyDir('./dist/project/fonts'); + await fs.emptyDir('./dist/project/materials'); + await fs.emptyDir('./dist/project/sounds'); + await fs.emptyDir('./dist/project/autotiles'); + // 然后加入填充内容 + await fs.copy( + './script/template/.h5data', + './dist/project/images/images.h5data' + ); + await fs.copy( + './script/template/.h5data', + './dist/project/tilesets/tilesets.h5data' + ); + await fs.copy( + './script/template/.h5data', + './dist/project/animates/animates.h5data' + ); + await fs.copy( + './script/template/.h5data', + './dist/project/fonts/fonts.h5data' + ); + await fs.copy( + './script/template/.h5data', + './dist/project/materials/materials.h5data' + ); + await fs.copy( + './script/template/.h5data', + './dist/project/sounds/sounds.h5data' + ); + await fs.copy( + './script/template/.h5data', + './dist/project/autotiles/autotiles.h5data' ); } diff --git a/script/template/启动服务.exe b/script/template/启动服务.exe new file mode 100644 index 0000000000000000000000000000000000000000..349d1de43f4a72c22a8ceee05b074259af84013e GIT binary patch literal 59392 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 literal 0 HcmV?d00001 diff --git a/src/core/common/resource.ts b/src/core/common/resource.ts index c568029..ed4dcf6 100644 --- a/src/core/common/resource.ts +++ b/src/core/common/resource.ts @@ -26,6 +26,29 @@ interface ResourceMap { zip: ZipResource; } +interface CompressedLoadListItem { + type: keyof ResourceType; + name: string; + usage: string; +} +type CompressedLoadList = Record; + +const types: Record = { + text: 'string', + buffer: 'arraybuffer', + image: 'blob', + material: 'blob', + audio: 'arraybuffer', + json: 'string', + zip: 'arraybuffer' +}; + +const base = import.meta.env.DEV ? '/' : ''; + +function toURL(uri: string) { + return import.meta.env.DEV ? uri : `${import.meta.env.BASE_URL}${uri}`; +} + export abstract class Resource extends Disposable { type = 'none'; @@ -92,7 +115,7 @@ export class ImageResource extends Resource { } resolveURI(): string { - return `/${findURL(this.uri)}`; + return toURL(`${base}${findURL(this.uri)}`); } } @@ -107,7 +130,7 @@ export class MaterialResource extends ImageResource { } override resolveURI(): string { - return `/project/materials/${findURL(this.uri)}`; + return toURL(`${base}project/materials/${findURL(this.uri)}`); } } @@ -136,7 +159,7 @@ export class TextResource extends Resource { } resolveURI(): string { - return `/${findURL(this.uri)}`; + return toURL(`${base}${findURL(this.uri)}`); } } @@ -164,7 +187,7 @@ export class BufferResource extends Resource { } resolveURI(): string { - return `/${findURL(this.uri)}`; + return toURL(`${base}${findURL(this.uri)}`); } } @@ -190,7 +213,7 @@ export class JSONResource extends Resource { } resolveURI(): string { - return `/${findURL(this.uri)}`; + return toURL(`${base}${findURL(this.uri)}`); } } @@ -217,7 +240,7 @@ export class AudioResource extends Resource { } resolveURI(): string { - return `/project/bgms/${findURL(this.uri)}`; + return toURL(`${base}project/bgms/${findURL(this.uri)}`); } } @@ -228,7 +251,7 @@ export class ZipResource extends Resource { * 注意后缀名不要是zip,不然有的浏览器会触发下载,而不是加载 */ constructor(uri: string) { - super(uri); + super(uri, 'zip'); this.type = 'zip'; } @@ -249,7 +272,7 @@ export class ZipResource extends Resource { } resolveURI(): string { - return `/${findURL(this.uri)}`; + return toURL(`${base}${findURL(this.uri)}`); } } @@ -289,7 +312,7 @@ interface LoadEvent extends EmitableEvent { now: number, total: number ) => void; - load: (resource: ResourceMap[T]) => void; + load: (resource: ResourceMap[T]) => void | Promise; loadStart: (resource: ResourceMap[T]) => void; } @@ -371,14 +394,14 @@ export class LoadTask< ); }); this.emit('loadStart', this.resource); - load.then(() => { + return load.then(async value => { // @ts-ignore LoadTask.loadedTaskList.add(this); this.loaded = totalByte; LoadTask.loadedTask++; - this.emit('load', this.resource); + await Promise.all(this.emit('load', this.resource)); + return value; }); - return load; } /** @@ -493,7 +516,7 @@ export function loadDefaultResource() { }); }); }); - // tilseset + // tileset data.main.tilesets.forEach(v => { const res = LoadTask.add('image', `image/project/tilesets/${v}`); res.once('load', res => { @@ -563,4 +586,112 @@ export function loadDefaultResource() { } } -export function loadCompressedResource() {} +export async function loadCompressedResource() { + const data = await axios.get(toURL('loadList.json'), { + responseType: 'text' + }); + const list: CompressedLoadList = JSON.parse(data.data); + + const d = data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d; + // 对于bgm,直接按照原来的方式加载即可 + d.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!); + }); + }); + }); + // 对于区域内容,按照zip格式进行加载,然后解压处理 + const autotiles: Partial, HTMLImageElement>> = + {}; + const materialImages = core.materials.slice() as SelectKey< + MaterialImages, + HTMLImageElement + >[]; + materialImages.push('keyboard'); + const weathers: (keyof Weather)[] = ['fog', 'cloud', 'sun']; + + Object.entries(list).forEach(v => { + const [uri, list] = v; + const res = LoadTask.add('zip', `zip/${uri}`); + + res.once('load', resource => { + const res = resource.resource; + if (!res) return; + list.forEach(async v => { + const { type, name, usage } = v; + const asyncType = types[type]; + const value = await res + .file(`${type}/${name}`) + ?.async(asyncType); + + if (!value) return; + + // 图片类型的资源 + if (type === 'image') { + const img = value as Blob; + const image = new Image(); + image.src = URL.createObjectURL(img); + image.addEventListener('load', () => { + image.setAttribute('_width', image.width.toString()); + image.setAttribute('_height', image.height.toString()); + }); + + // 图片 + if (usage === 'image') { + core.material.images.images[name as ImageIds] = image; + } else if (usage === 'tileset') { + // 额外素材 + core.material.images.tilesets[name] = image; + } else if (usage === 'autotile') { + // 自动元件 + autotiles[name.slice(0, -4) as AllIdsOf<'autotile'>] = + image; + const loading = Mota.require('var', 'loading'); + loading.addAutotileLoaded(); + loading.onAutotileLoaded(autotiles); + core.material.images.autotile[ + name.slice(0, -4) as AllIdsOf<'autotile'> + ] = image; + } + } else if (type === 'material') { + const img = value as Blob; + const image = new Image(); + image.src = URL.createObjectURL(img); + image.addEventListener('load', () => { + image.setAttribute('_width', image.width.toString()); + image.setAttribute('_height', image.height.toString()); + }); + + // material + if (materialImages.some(v => name === v + '.png')) { + // @ts-ignore + core.material.images[ + name.slice(0, -4) as SelectKey< + MaterialImages, + HTMLImageElement + > + ] = image; + } else if (weathers.some(v => name === v + '.png')) { + // @ts-ignore + core.animateFrame.weather[v] = image; + } else { + } + } + + if (usage === 'font') { + const font = value as ArrayBuffer; + document.fonts.add(new FontFace(name.slice(0, -4), font)); + } else if (usage === 'sound') { + const sound = value as ArrayBuffer; + Mota.require('var', 'sound').add(`sounds.${name}`, sound); + } else if (usage === 'animate') { + const ani = value as string; + core.material.animates[name.slice(0, -8) as AnimationIds] = + core.loader._loadAnimate(ani); + } + }); + }); + }); +} diff --git a/src/core/main/setting.ts b/src/core/main/setting.ts index 1a6fc66..ddcfb8a 100644 --- a/src/core/main/setting.ts +++ b/src/core/main/setting.ts @@ -394,7 +394,7 @@ function handleAudioSetting( ) { if (key === 'bgmEnabled') { bgm.disable = !n; - core.checkBgm(); + if (core.isPlaying()) core.checkBgm(); } else if (key === 'bgmVolume') { bgm.volume = (n as number) / 100; } else if (key === 'soundEnabled') { diff --git a/src/types/core.d.ts b/src/types/core.d.ts index e920eab..a9a0d99 100644 --- a/src/types/core.d.ts +++ b/src/types/core.d.ts @@ -101,6 +101,8 @@ type MaterialImages = { keyboard: HTMLImageElement; hero: HTMLImageElement; + + icons: HTMLImageElement; }; interface Material { diff --git a/src/ui/danmaku.vue b/src/ui/danmaku.vue index 22ca102..7c6b48b 100644 --- a/src/ui/danmaku.vue +++ b/src/ui/danmaku.vue @@ -10,11 +10,13 @@ @touchstart="touchStart(one.id)" > - + > + + {{ Math.abs(likedMap[one.id]) }} diff --git a/src/ui/danmakuEditor.vue b/src/ui/danmakuEditor.vue index df5ba6d..b1a023f 100644 --- a/src/ui/danmakuEditor.vue +++ b/src/ui/danmakuEditor.vue @@ -7,21 +7,27 @@ @click="openTool('css')" >CSS - - + + + - + + + + > + +
- + > + +
diff --git a/src/ui/load.vue b/src/ui/load.vue index 059a60e..93c65a4 100644 --- a/src/ui/load.vue +++ b/src/ui/load.vue @@ -26,7 +26,11 @@ diff --git a/src/ui/start.vue b/src/ui/start.vue index 43b152e..346eea4 100644 --- a/src/ui/start.vue +++ b/src/ui/start.vue @@ -1,7 +1,7 @@