template/packages-user/client-modules/src/render/ui/load.tsx

167 lines
5.5 KiB
TypeScript

import { DefaultProps } from '@motajs/render-vue';
import {
GameUI,
SetupComponentOptions,
UIComponentProps
} from '@motajs/system';
import { defineComponent } from 'vue';
import {
FULL_LOC,
LOAD_BYTE_HEIGHT,
LOAD_BYTE_LENGTH,
LOAD_BYTE_LINE_WIDTH,
LOAD_FONT_COLOR,
LOAD_LOADED_COLOR,
LOAD_TASK_CENTER_HEIGHT,
LOAD_TASK_LINE_WIDTH,
LOAD_TASK_RADIUS,
LOAD_UNLOADED_COLOR,
MAIN_WIDTH
} from '../../shared';
import { ElementLocator, Font, MotaOffscreenCanvas2D } from '@motajs/render';
import { transitioned } from '../use';
import { cosh, CurveMode, linear } from '@motajs/animate';
import { clamp } from 'lodash-es';
import { sleep } from '@motajs/common';
import { loading } from '@user/data-base';
import { GameTitleUI } from './title';
import { client } from '../../core';
export interface ILoadProps extends UIComponentProps, DefaultProps {}
const loadSceneProps = {
props: ['controller', 'instance']
} satisfies SetupComponentOptions<ILoadProps>;
export const LoadScene = defineComponent<ILoadProps>(props => {
const loader = client.loader;
const taskFont = new Font('Verdana', 24);
const byteFont = new Font('Verdana', 12);
/** 当前加载进度 */
const taskProgress = transitioned(0, 500, cosh(2, CurveMode.EaseOut))!;
const byteProgress = transitioned(0, 500, cosh(2, CurveMode.EaseOut))!;
const alpha = transitioned(1, 400, linear())!;
// 两个进度条的位置
const taskLoc: ElementLocator = [
MAIN_WIDTH / 2,
LOAD_TASK_CENTER_HEIGHT,
LOAD_TASK_RADIUS * 2 + LOAD_TASK_LINE_WIDTH * 2,
LOAD_TASK_RADIUS * 2 + LOAD_TASK_LINE_WIDTH * 2,
0.5,
0.5
];
const byteLoc: ElementLocator = [
MAIN_WIDTH / 2,
LOAD_BYTE_HEIGHT,
LOAD_BYTE_LENGTH + LOAD_BYTE_LINE_WIDTH,
LOAD_BYTE_LINE_WIDTH * 2 + byteFont.size,
0.5,
0.5
];
const loadEnd = async () => {
loading.emit('loaded');
alpha.set(0);
await sleep(400);
props.controller.closeAll();
props.controller.open(GameTitleUI, {});
};
const startLoad = async () => {
loader.initSystemLoadTask();
loader.load().then(() => {
loadEnd();
});
for await (const _ of loader.progress) {
taskProgress.set(loader.progress.getLoadedTasks());
byteProgress.set(loader.progress.getLoadedByte());
}
};
// 开始加载
startLoad();
/** 渲染加载任务进度 */
const renderTaskList = (canvas: MotaOffscreenCanvas2D) => {
const ctx = canvas.ctx;
ctx.lineCap = 'round';
ctx.lineWidth = LOAD_TASK_LINE_WIDTH;
ctx.font = taskFont.string();
const loaded = loader.progress.getLoadedTasks();
const total = loader.progress.getAddedTasks();
// 这里使用渐变参数,因为要有动画效果
const progress = clamp(taskProgress.value / total, 0, 1);
const cx = taskLoc[2]! / 2;
const cy = taskLoc[3]! / 2;
ctx.beginPath();
ctx.arc(cx, cy, LOAD_TASK_RADIUS, 0, Math.PI * 2);
ctx.strokeStyle = LOAD_UNLOADED_COLOR;
ctx.stroke();
ctx.beginPath();
const end = progress * Math.PI * 2 - Math.PI / 2;
ctx.arc(cx, cy, LOAD_TASK_RADIUS, -Math.PI / 2, end);
ctx.strokeStyle = LOAD_LOADED_COLOR;
ctx.stroke();
ctx.fillStyle = LOAD_FONT_COLOR;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(`${loaded} / ${total}`, cx, cy + 3);
};
/** 渲染加载字节进度 */
const renderByteList = (canvas: MotaOffscreenCanvas2D) => {
const ctx = canvas.ctx;
ctx.lineCap = 'round';
ctx.lineWidth = LOAD_BYTE_LINE_WIDTH;
ctx.font = byteFont.string();
const total = loader.progress.getTotalByte();
const loaded = loader.progress.getLoadedByte();
// 这里使用渐变参数,因为要有动画效果
const progress = clamp(byteProgress.value / total, 0, 1);
const sx = LOAD_BYTE_LINE_WIDTH;
const sy = byteFont.size + LOAD_BYTE_LINE_WIDTH;
ctx.beginPath();
ctx.moveTo(sx, sy);
ctx.lineTo(sx + LOAD_BYTE_LENGTH, sy);
ctx.strokeStyle = LOAD_UNLOADED_COLOR;
ctx.stroke();
ctx.beginPath();
ctx.moveTo(sx, sy);
ctx.lineTo(sx + progress * LOAD_BYTE_LENGTH, sy);
ctx.strokeStyle = LOAD_LOADED_COLOR;
ctx.stroke();
ctx.textAlign = 'right';
ctx.textBaseline = 'bottom';
ctx.fillStyle = LOAD_FONT_COLOR;
const loadedMB = (loaded / 2 ** 20).toFixed(2);
const totalMB = (total / 2 ** 20).toFixed(2);
const percent = loader.progress.getByteRatio() * 100;
ctx.fillText(
`${loadedMB}MB / ${totalMB}MB | ${percent.toFixed(2)}%`,
byteLoc[2]! - LOAD_BYTE_LINE_WIDTH,
byteLoc[3]! - LOAD_BYTE_LINE_WIDTH * 2
);
};
return () => (
<container loc={FULL_LOC} alpha={alpha.ref.value}>
<custom
loc={taskLoc}
render={renderTaskList}
bindings={[taskProgress.ref]}
nocache
/>
<custom
loc={byteLoc}
render={renderByteList}
bindings={[byteProgress.ref]}
nocache
/>
</container>
);
}, loadSceneProps);
export const LoadSceneUI = new GameUI('load-scene', LoadScene);