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 0000000..349d1de Binary files /dev/null and b/script/template/启动服务.exe differ 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 @@