feat: 构建版本加载

This commit is contained in:
unanmed 2024-05-03 20:44:43 +08:00
parent 684fba1475
commit 4d5f14e8a5
14 changed files with 444 additions and 391 deletions

View File

@ -601,18 +601,18 @@ core.prototype._init_others = function () {
); );
core.bigmap.tempCanvas = document.createElement('canvas').getContext('2d'); core.bigmap.tempCanvas = document.createElement('canvas').getContext('2d');
core.bigmap.cacheCanvas = document.createElement('canvas').getContext('2d'); core.bigmap.cacheCanvas = document.createElement('canvas').getContext('2d');
core.loadImage('materials', 'fog', function (name, img) { // core.loadImage('materials', 'fog', function (name, img) {
core.animateFrame.weather.fog = img; // core.animateFrame.weather.fog = img;
}); // });
core.loadImage('materials', 'cloud', function (name, img) { // core.loadImage('materials', 'cloud', function (name, img) {
core.animateFrame.weather.cloud = img; // core.animateFrame.weather.cloud = img;
}); // });
core.loadImage('materials', 'sun', function (name, img) { // core.loadImage('materials', 'sun', function (name, img) {
core.animateFrame.weather.sun = img; // core.animateFrame.weather.sun = img;
}); // });
core.loadImage('materials', 'keyboard', function (name, img) { // core.loadImage('materials', 'keyboard', function (name, img) {
core.material.images.keyboard = img; // core.material.images.keyboard = img;
}); // });
// 记录存档编号 // 记录存档编号
core.saves.saveIndex = core.getLocalStorage('saveIndex', 1); core.saves.saveIndex = core.getLocalStorage('saveIndex', 1);
core.control.getSaveIndexes(function (indexes) { core.control.getSaveIndexes(function (indexes) {

View File

@ -215,15 +215,6 @@ function main() {
this.__VERSION__ = '2.10.0'; this.__VERSION__ = '2.10.0';
this.__VERSION_CODE__ = 510; this.__VERSION_CODE__ = 510;
this.timestamp = 0;
// 远程资源地址,在线游戏中,塔本体不包含任何资源,只包含源码,从而可以降低游戏本体的体积并平均分担资源包体积
// 从而可以优化加载并避免网站发布的大小限制
this.RESOURCE_TYPE = 'dev';
this.RESOURCE_URL = '';
this.RESOURCE_SYMBOL = '';
this.RESOURCE_INDEX = {};
} }
// >>>> body end // >>>> body end
@ -336,7 +327,7 @@ main.prototype.loadAsync = async function (mode, callback) {
const mainData = data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d.main; const mainData = data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d.main;
Object.assign(main, mainData); Object.assign(main, mainData);
main.importFonts(main.fonts); // main.importFonts(main.fonts);
// 加载核心js代码 // 加载核心js代码
if (main.useCompress) { if (main.useCompress) {
@ -496,7 +487,6 @@ main.prototype.importFonts = function (fonts) {
font + font +
'"; src: url("project/fonts/' + '"; src: url("project/fonts/' +
font + font +
(main.pluginUseCompress ? '-' + main.timestamp : '') +
'.ttf") format("truetype"); }'; '.ttf") format("truetype"); }';
}); });
style.innerHTML = html; style.innerHTML = html;

View File

@ -8,12 +8,12 @@ import rollupBabel from '@rollup/plugin-babel';
import terser from '@rollup/plugin-terser'; import terser from '@rollup/plugin-terser';
import resolve from '@rollup/plugin-node-resolve'; import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs'; import commonjs from '@rollup/plugin-commonjs';
import { splitResorce } from './resource.js'; import { splitResource } from './resource.js';
import compressing from 'compressing'; import compressing from 'compressing';
const type = process.argv[2]; const type = process.argv[2];
const map = false; const map = false;
const resorce = type !== 'dev'; const resorce = true;
const compress = type === 'dist'; const compress = type === 'dist';
(async function () { (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) { } catch (e) {
console.log('字体压缩失败'); console.log('字体压缩失败');
console.log(e); console.log(e);
@ -133,7 +125,7 @@ const compress = type === 'dist';
await fs.remove('./dist/project/plugin.min.js'); await fs.remove('./dist/project/plugin.min.js');
const build = await rollup.rollup({ const build = await rollup.rollup({
input: 'src/plugin/game/index.js', input: 'src/game/index.ts',
plugins: [ plugins: [
typescript({ typescript({
sourceMap: false sourceMap: false
@ -163,12 +155,7 @@ const compress = type === 'dist';
// 4. 压缩main.js // 4. 压缩main.js
try { try {
// 先获取不能压缩的部分 // 先获取不能压缩的部分
const main = (await fs.readFile('./public/main.js', 'utf-8')) 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 endIndex = main.indexOf('// >>>> body end'); const endIndex = main.indexOf('// >>>> body end');
const nonCompress = main.slice(0, endIndex); const nonCompress = main.slice(0, endIndex);
@ -190,7 +177,11 @@ const compress = type === 'dist';
// 6. 资源分离 // 6. 资源分离
if (resorce) { if (resorce) {
await splitResorce(type); await splitResource();
}
if (!compress) {
await fs.copy('./script/template/启动服务.exe', './dist/启动服务.exe');
} }
// 7. 压缩 // 7. 压缩

View File

@ -1,297 +1,236 @@
import fs from 'fs-extra'; import fs, { Stats } from 'fs-extra';
import { uniqueSymbol } from './utils.js'; import JSZip from 'jszip';
import { resolve } from 'path'; import { formatSize, uniqueSymbol } from './utils.js';
import motaConfig from '../mota.config.js';
import compressing from 'compressing';
const SYMBOL = uniqueSymbol(); // 资源拆分模块,可以加快在线加载速度
const MAX_SIZE = 100 * (1 << 20) - 20 * (1 << 10);
const sourceIndex: Record<string, string> = {};
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<string, CompressedLoadListItem[]>;
export async function splitResorce(type: string) { interface MainData {
await fs.ensureDir('./dist-resource'); main: {
await fs.emptyDir('./dist-resource'); images: string[];
await readySplit(); tilesets: string[];
animates: string[];
await zipResource(); sounds: string[];
await split(type === 'dist' ? MAX_SIZE : void 0); fonts: string[];
};
await endSplit(type);
} }
async function readySplit() { /** 单包大小2M */
await fs.ensureDir('./_temp'); const SPLIT_SIZE = 2 ** 20 * 2;
await fs.emptyDir('./_temp');
await fs.ensureDir('./_temp/origin');
await copyAll();
}
async function endSplit(type: string) { export async function splitResource() {
await rewriteMain(type); const splitResult: CompressedLoadList = {};
await fs.emptyDir('./_temp'); let now: CompressedLoadListItem[] = [];
await fs.rmdir('./_temp'); let totalSize: number = 0;
} let nowZip: JSZip = new JSZip();
async function zipResource() { await fs.ensureDir('./dist/resource/');
const zip = motaConfig.zip; await fs.emptyDir('./dist/resource');
if (!zip) return;
for await (const [name, files] of Object.entries(zip)) {
const stream = new compressing.zip.Stream();
const dirs: string[] = [];
for await (const file of files) { const pushItem = async (
if (/^.+\/\*$/.test(file)) { type: ResourceType,
const dir = file.split('/')[0]; name: string,
dirs.push(dir); usage: string,
await fs.copy(`./_temp/origin/${dir}`, `./_temp/${dir}`); file: Stats,
} else if (file.startsWith('!')) { content: any
const dir = file.slice(1); ) => {
await fs.remove(`./_temp/${dir}`); totalSize += file.size;
} else {
const [dir, name] = file.split('/'); if (totalSize > SPLIT_SIZE) {
if (dirs.includes(dir)) dirs.push(dir); if (file.size > SPLIT_SIZE) {
await fs.ensureDir(`./_temp/${dir}`); const symbol = uniqueSymbol() + `.h5data`;
await fs.copyFile( console.warn(
`./_temp/origin/${dir}/${name}`, `file ${type}/${name}(${formatSize(
`./_temp/${dir}/${name}` 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);
} }
} } else {
now.push({ type, name, usage });
dirs.forEach(v => stream.addEntry(`./_temp/${v}`)); addZippedFile(nowZip, type, name, content);
const dest = fs.createWriteStream(`./_temp/${name}`);
await new Promise<void>(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);
} }
}; };
await doSplit('dist');
}
async function copyAll() { const writeZip = (zip: JSZip, name: string, size: number) => {
await Promise.all( return new Promise<void>(res => {
all.map(v => { zip.generateNodeStream({ type: 'nodebuffer', streamFiles: true })
return fs.move(`./dist/project/${v}`, `./_temp/origin/${v}`); .pipe(fs.createWriteStream(name))
}) .once('finish', function () {
); console.log(
} `Generated ${name}. Unzipped size: ${formatSize(size)}`
);
res();
});
});
};
async function rewriteMain(type: string) { const addZippedFile = (
const main = await fs.readFile('./dist/main.js', 'utf-8'); zip: JSZip,
const res = main type: ResourceType,
.replace( name: string,
/this\.RESOURCE_TYPE\s*\=\s*.*;/, content: any
`this.RESOURCE_TYPE = '${type}';` ) => {
) zip.file(`${type}/${name}`, content);
.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 file = await fs.readFile('./dist/project/data.js', 'utf-8');
* const data = JSON.parse(file.split('\n').slice(1).join('')) as MainData;
*/
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));
})
);
if (!dir.endsWith('dist')) { // images
await fs.writeFile( for (const image of data.main.images) {
resolve(dir, 'project/icons.js'), const path = `./dist/project/images/${image}`;
`var icons_4665ee12_3a1f_44a4_bea3_0fccba634dc1 = const stat = await fs.stat(path);
{"autotile": {}} await pushItem('image', image, 'image', stat, await fs.readFile(path));
`, }
'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');
await fs.copyFile('./script/template/main.js', resolve(dir, 'main.js')); // tileset
const data = await fs.readFile('./script/template/data.js', 'utf-8'); for (const tileset of data.main.tilesets) {
await fs.writeFile( const path = `./dist/project/tilesets/${tileset}`;
resolve(dir, 'project/data.js'), const stat = await fs.stat(path);
data.replace('@name', `${motaConfig.resourceName}${index}`) await pushItem(
); 'image',
tileset,
await fs.copyFile( 'tileset',
'./script/template/lz-string.min.js', stat,
resolve(dir, 'libs/thirdparty/lz-string.min.js') await fs.readFile(path)
); );
} }
await Promise.all( // animates
['animates', 'images', 'materials', 'sounds', 'tilesets'].map(v => { for (const ani of data.main.animates) {
fs.copyFile( const path = `./dist/project/animates/${ani}.animate`;
'./script/template/.h5data', const stat = await fs.stat(path);
resolve(dir, `project/${v}/${v}.h5data`) 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'
); );
} }

Binary file not shown.

View File

@ -26,6 +26,29 @@ interface ResourceMap {
zip: ZipResource; zip: ZipResource;
} }
interface CompressedLoadListItem {
type: keyof ResourceType;
name: string;
usage: string;
}
type CompressedLoadList = Record<string, CompressedLoadListItem[]>;
const types: Record<keyof ResourceType, JSZip.OutputType> = {
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<T = any> extends Disposable<string> { export abstract class Resource<T = any> extends Disposable<string> {
type = 'none'; type = 'none';
@ -92,7 +115,7 @@ export class ImageResource extends Resource<HTMLImageElement> {
} }
resolveURI(): string { resolveURI(): string {
return `/${findURL(this.uri)}`; return toURL(`${base}${findURL(this.uri)}`);
} }
} }
@ -107,7 +130,7 @@ export class MaterialResource extends ImageResource {
} }
override resolveURI(): string { 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<string> {
} }
resolveURI(): string { resolveURI(): string {
return `/${findURL(this.uri)}`; return toURL(`${base}${findURL(this.uri)}`);
} }
} }
@ -164,7 +187,7 @@ export class BufferResource extends Resource<ArrayBuffer> {
} }
resolveURI(): string { resolveURI(): string {
return `/${findURL(this.uri)}`; return toURL(`${base}${findURL(this.uri)}`);
} }
} }
@ -190,7 +213,7 @@ export class JSONResource<T = any> extends Resource<T> {
} }
resolveURI(): string { resolveURI(): string {
return `/${findURL(this.uri)}`; return toURL(`${base}${findURL(this.uri)}`);
} }
} }
@ -217,7 +240,7 @@ export class AudioResource extends Resource<HTMLAudioElement> {
} }
resolveURI(): string { 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<JSZip> {
* zip * zip
*/ */
constructor(uri: string) { constructor(uri: string) {
super(uri); super(uri, 'zip');
this.type = 'zip'; this.type = 'zip';
} }
@ -249,7 +272,7 @@ export class ZipResource extends Resource<JSZip> {
} }
resolveURI(): string { resolveURI(): string {
return `/${findURL(this.uri)}`; return toURL(`${base}${findURL(this.uri)}`);
} }
} }
@ -289,7 +312,7 @@ interface LoadEvent<T extends keyof ResourceType> extends EmitableEvent {
now: number, now: number,
total: number total: number
) => void; ) => void;
load: (resource: ResourceMap[T]) => void; load: (resource: ResourceMap[T]) => void | Promise<void>;
loadStart: (resource: ResourceMap[T]) => void; loadStart: (resource: ResourceMap[T]) => void;
} }
@ -371,14 +394,14 @@ export class LoadTask<
); );
}); });
this.emit('loadStart', this.resource); this.emit('loadStart', this.resource);
load.then(() => { return load.then(async value => {
// @ts-ignore // @ts-ignore
LoadTask.loadedTaskList.add(this); LoadTask.loadedTaskList.add(this);
this.loaded = totalByte; this.loaded = totalByte;
LoadTask.loadedTask++; 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 => { data.main.tilesets.forEach(v => {
const res = LoadTask.add('image', `image/project/tilesets/${v}`); const res = LoadTask.add('image', `image/project/tilesets/${v}`);
res.once('load', res => { 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<Record<AllIdsOf<'autotile'>, 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);
}
});
});
});
}

View File

@ -394,7 +394,7 @@ function handleAudioSetting<T extends number | boolean>(
) { ) {
if (key === 'bgmEnabled') { if (key === 'bgmEnabled') {
bgm.disable = !n; bgm.disable = !n;
core.checkBgm(); if (core.isPlaying()) core.checkBgm();
} else if (key === 'bgmVolume') { } else if (key === 'bgmVolume') {
bgm.volume = (n as number) / 100; bgm.volume = (n as number) / 100;
} else if (key === 'soundEnabled') { } else if (key === 'soundEnabled') {

2
src/types/core.d.ts vendored
View File

@ -101,6 +101,8 @@ type MaterialImages = {
keyboard: HTMLImageElement; keyboard: HTMLImageElement;
hero: HTMLImageElement; hero: HTMLImageElement;
icons: HTMLImageElement;
}; };
interface Material { interface Material {

View File

@ -10,11 +10,13 @@
@touchstart="touchStart(one.id)" @touchstart="touchStart(one.id)"
> >
<span class="danmaku-info"> <span class="danmaku-info">
<like-filled <span
class="danmaku-like-icon" class="danmaku-like-icon"
:liked="likedMap[one.id] < 0" :liked="likedMap[one.id] < 0"
@click="postLike(one)" @click="postLike(one)"
/> >
<like-filled />
</span>
<span class="danmaku-like-num">{{ <span class="danmaku-like-num">{{
Math.abs(likedMap[one.id]) Math.abs(likedMap[one.id])
}}</span> }}</span>

View File

@ -7,21 +7,27 @@
@click="openTool('css')" @click="openTool('css')"
>CSS</span >CSS</span
> >
<font-colors-outlined <span
class="danmaku-tool" class="danmaku-tool"
:open="fillOpened" :open="fillOpened"
@click="openTool('fillColor')" @click="openTool('fillColor')"
/> >
<highlight-outlined <font-colors-outlined />
</span>
<span
class="danmaku-tool" class="danmaku-tool"
:open="strokeOpened" :open="strokeOpened"
@click="openTool('strokeColor')" @click="openTool('strokeColor')"
/> >
<meh-outlined <highlight-outlined />
</span>
<span
class="danmaku-tool" class="danmaku-tool"
:open="iconOpened" :open="iconOpened"
@click="openTool('icon')" @click="openTool('icon')"
/> >
<meh-outlined />
</span>
<div id="danmaku-input-div"> <div id="danmaku-input-div">
<a-input <a-input
id="danmaku-input-input" id="danmaku-input-input"
@ -33,11 +39,13 @@
@pressEnter="inputEnter()" @pressEnter="inputEnter()"
/> />
</div> </div>
<send-outlined <span
class="danmaku-tool danmaku-post" class="danmaku-tool danmaku-post"
:posting="posting" :posting="posting"
@click="send()" @click="send()"
/> >
<send-outlined />
</span>
</div> </div>
<Transition name="danmaku"> <Transition name="danmaku">
<div v-if="cssOpened" id="danmaku-css"> <div v-if="cssOpened" id="danmaku-css">

View File

@ -26,7 +26,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { loadDefaultResource, LoadTask } from '@/core/common/resource'; import {
loadCompressedResource,
loadDefaultResource,
LoadTask
} from '@/core/common/resource';
import { GameUi } from '@/core/main/custom/ui'; import { GameUi } from '@/core/main/custom/ui';
import { formatSize } from '@/plugin/utils'; import { formatSize } from '@/plugin/utils';
import { logger } from '@/core/common/logger'; import { logger } from '@/core/common/logger';
@ -48,32 +52,32 @@ const totalTask = ref(0);
let loadDiv: HTMLDivElement; let loadDiv: HTMLDivElement;
loadDefaultResource(); onMounted(async () => {
if (import.meta.env.DEV) loadDefaultResource();
else await loadCompressedResource();
LoadTask.onProgress(() => { LoadTask.onProgress(() => {
const loadingNum = [...LoadTask.taskList].filter(v => v.loading).length; const loadingNum = [...LoadTask.taskList].filter(v => v.loading).length;
loadedByte.value = LoadTask.loadedByte; loadedByte.value = LoadTask.loadedByte;
loadedPercent.value = parseFloat( loadedPercent.value = parseFloat(
((LoadTask.loadedByte / LoadTask.totalByte) * 100).toFixed(2) ((LoadTask.loadedByte / LoadTask.totalByte) * 100).toFixed(2)
); );
loading.value = loadingNum; loading.value = loadingNum;
loaded.value = LoadTask.loadedTask; loaded.value = LoadTask.loadedTask;
totalByte.value = LoadTask.totalByte; totalByte.value = LoadTask.totalByte;
totalTask.value = LoadTask.totalTask; totalTask.value = LoadTask.totalTask;
}); });
LoadTask.load().then(async () => { LoadTask.load().then(async () => {
core.loader._loadMaterials_afterLoad(); core.loader._loadMaterials_afterLoad();
core._afterLoadResources(props.callback); core._afterLoadResources(props.callback);
logger.log(`Resource load end.`); logger.log(`Resource load end.`);
loadDiv.style.opacity = '0'; loadDiv.style.opacity = '0';
await sleep(1000); await sleep(1000);
fixedUi.close(props.num); fixedUi.close(props.num);
fixedUi.open('start'); fixedUi.open('start');
}); });
onMounted(() => {
loadDiv = document.getElementById('load') as HTMLDivElement; loadDiv = document.getElementById('load') as HTMLDivElement;
}); });
</script> </script>

View File

@ -1,7 +1,7 @@
<template> <template>
<div id="start"> <div id="start">
<div id="start-div"> <div id="start-div">
<img id="background" src="/project/images/bg.jpg" /> <img id="background" :src="bg.src" />
<div id="start-main"> <div id="start-main">
<div id="title">人类开天辟地</div> <div id="title">人类开天辟地</div>
<div id="settings"> <div id="settings">
@ -80,6 +80,8 @@ const props = defineProps<{
ui: GameUi; ui: GameUi;
}>(); }>();
const bg = core.material.images.images['bg.jpg'];
let startdiv: HTMLDivElement; let startdiv: HTMLDivElement;
let start: HTMLDivElement; let start: HTMLDivElement;
let main: HTMLDivElement; let main: HTMLDivElement;

View File

@ -20,10 +20,7 @@
> >
<span id="status-lv">{{ lvName }}</span> <span id="status-lv">{{ lvName }}</span>
<div id="status-skill" class="status-item"> <div id="status-skill" class="status-item">
<img <img :src="imgs['skill.png'].src" class="status-icon" />
src="/project/images/skill.png"
class="status-icon"
/>
<span>{{ skill }}</span> <span>{{ skill }}</span>
<span <span
v-if="has(spring)" v-if="has(spring)"
@ -33,7 +30,7 @@
> >
</div> </div>
<div id="status-hp" class="status-item"> <div id="status-hp" class="status-item">
<img src="/project/images/hp.png" class="status-icon" /> <img :src="imgs['hp.png'].src" class="status-icon" />
<span class="status-item-bold">{{ <span class="status-item-bold">{{
format(hero.hp!) format(hero.hp!)
}}</span> }}</span>
@ -50,10 +47,7 @@
> >
</div> </div>
<div id="status-atk" class="status-item"> <div id="status-atk" class="status-item">
<img <img :src="imgs['atk.png'].src" class="status-icon" />
src="/project/images/atk.png"
class="status-icon"
/>
<span class="status-item-bold">{{ <span class="status-item-bold">{{
format(hero.atk!) format(hero.atk!)
}}</span> }}</span>
@ -64,28 +58,19 @@
> >
</div> </div>
<div id="status-def" class="status-item status-item-bold"> <div id="status-def" class="status-item status-item-bold">
<img <img :src="imgs['def.png'].src" class="status-icon" />
src="/project/images/def.png"
class="status-icon"
/>
<span>{{ format(hero.def!) }}</span> <span>{{ format(hero.def!) }}</span>
</div> </div>
<div id="status-mdef" class="status-item status-item-bold"> <div id="status-mdef" class="status-item status-item-bold">
<img src="/project/images/IQ.png" class="status-icon" /> <img :src="imgs['IQ.png'].src" class="status-icon" />
<span>{{ format(hero.mdef!) }}</span> <span>{{ format(hero.mdef!) }}</span>
</div> </div>
<div id="status-money" class="status-item status-item-bold"> <div id="status-money" class="status-item status-item-bold">
<img <img :src="imgs['money.png'].src" class="status-icon" />
src="/project/images/money.png"
class="status-icon"
/>
<span>{{ format(hero.money!) }}</span> <span>{{ format(hero.money!) }}</span>
</div> </div>
<div id="status-exp" class="status-item status-item-bold"> <div id="status-exp" class="status-item status-item-bold">
<img <img :src="imgs['exp.png'].src" class="status-icon" />
src="/project/images/exp.png"
class="status-icon"
/>
<span>{{ format(up) }}</span> <span>{{ format(up) }}</span>
</div> </div>
<div id="status-key" class="status-item status-item-bold"> <div id="status-key" class="status-item status-item-bold">
@ -143,8 +128,8 @@ import { has } from '@/plugin/utils';
let main: HTMLDivElement; let main: HTMLDivElement;
const items = core.flags.statusBarItems; const imgs = core.material.images.images;
const icons = core.statusBar.icons;
const skillTree = Mota.Plugin.require('skillTree_g'); const skillTree = Mota.Plugin.require('skillTree_g');
const width = ref( const width = ref(

View File

@ -40,7 +40,6 @@ export default defineConfig({
'lz-string', 'lz-string',
'chart.js', 'chart.js',
'mutate-animate', 'mutate-animate',
'three',
'@vueuse/core' '@vueuse/core'
] ]
} }