diff --git a/packages-user/client-modules/src/client.ts b/packages-user/client-modules/src/client.ts index 771f6cd..2325a1f 100644 --- a/packages-user/client-modules/src/client.ts +++ b/packages-user/client-modules/src/client.ts @@ -41,7 +41,7 @@ import { WebLoadStarter } from '@motajs/loader'; import { IRendererUsing, RendererUsing } from '@motajs/render-vue'; import { IUIController, UIController } from '@motajs/system'; import { createApp } from './render'; -import { LoadSceneUI } from './render/ui/load'; +import { LoadSceneUI } from './ui/load'; import { createUIPropsBase } from './ui/func'; export class ClientCore extends CoreState implements IClientCore { diff --git a/packages-user/client-modules/src/index.ts b/packages-user/client-modules/src/index.ts index 9367a5e..0e0ce00 100644 --- a/packages-user/client-modules/src/index.ts +++ b/packages-user/client-modules/src/index.ts @@ -9,6 +9,7 @@ export function create() { export * from './action'; export * from './fallback'; export * from './render'; +export * from './ui'; export * from './client'; export * from './core'; diff --git a/packages-user/client-modules/src/render/action.ts b/packages-user/client-modules/src/render/action.ts index 13b87b8..0dd5e9d 100644 --- a/packages-user/client-modules/src/render/action.ts +++ b/packages-user/client-modules/src/render/action.ts @@ -8,7 +8,7 @@ import { openViewMap, openReplay, openStatistics -} from './ui'; +} from '../ui'; import { ElementLocator } from '@motajs/render'; export function createAction() { diff --git a/packages-user/client-modules/src/render/index.tsx b/packages-user/client-modules/src/render/index.tsx index 6bc7c86..733b8ac 100644 --- a/packages-user/client-modules/src/render/index.tsx +++ b/packages-user/client-modules/src/render/index.tsx @@ -3,12 +3,12 @@ import { defineComponent } from 'vue'; import { DEFAULT_FONT, MAIN_HEIGHT, MAIN_WIDTH } from '../shared'; import { createElements } from '@user/client-base'; import { mainRenderer } from './renderer'; -import { createUI } from './ui'; +import { createUI } from '../ui'; import { createAction } from './action'; import { sceneController } from './scene'; import { createWeather } from './weather'; import { createApp } from './renderer'; -import { LoadSceneUI } from './ui/load'; +import { LoadSceneUI } from '../ui/load'; export function createGameRenderer() { const App = defineComponent(_props => { @@ -36,7 +36,6 @@ export function createRender() { } export * from './fx'; -export * from './ui'; export * from './utils'; export * from './weather'; export * from './renderer'; diff --git a/packages-user/client-modules/src/render/ui/index.ts b/packages-user/client-modules/src/render/ui/index.ts deleted file mode 100644 index 67db8c2..0000000 --- a/packages-user/client-modules/src/render/ui/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { createMainController } from './controller'; - -export function createUI() { - createMainController(); -} - -export * from './controller'; -export * from './main'; -export * from './save'; -export * from './settings'; -export * from './statistics'; -export * from './statusBar'; -export * from './toolbar'; -export * from './viewmap'; diff --git a/packages-user/client-modules/src/render/ui/load.tsx b/packages-user/client-modules/src/render/ui/load.tsx deleted file mode 100644 index 73815ca..0000000 --- a/packages-user/client-modules/src/render/ui/load.tsx +++ /dev/null @@ -1,161 +0,0 @@ -import { DefaultProps } from '@motajs/render-vue'; -import { - GameUI, - SetupComponentOptions, - IUIDefaultPropsBase -} 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 { GameTitleUI } from './title'; -import { client } from '../../core'; - -export interface ILoadProps extends IUIDefaultPropsBase, DefaultProps {} - -const loadSceneProps = { - props: ['controller', 'instance'] -} satisfies SetupComponentOptions; - -export const LoadScene = defineComponent(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 () => { - alpha.set(0); - await sleep(400); - props.controller.closeAll(); - props.controller.open(GameTitleUI, {}); - }; - - const startLoad = async () => { - for await (const _ of loader.start()) { - taskProgress.set(loader.manager.getLoadedTasks()); - byteProgress.set(loader.manager.getLoadedByte()); - } - loadEnd(); - }; - - // 开始加载 - 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.manager.getLoadedTasks(); - const total = loader.manager.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.manager.getTotalByte(); - const loaded = loader.manager.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.manager.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 () => ( - - - - - ); -}, loadSceneProps); - -export const LoadSceneUI = new GameUI('load-scene', LoadScene); diff --git a/packages-user/client-modules/src/render/ui/title.tsx b/packages-user/client-modules/src/render/ui/title.tsx deleted file mode 100644 index e54949d..0000000 --- a/packages-user/client-modules/src/render/ui/title.tsx +++ /dev/null @@ -1,484 +0,0 @@ -import { DefaultProps } from '@motajs/render-vue'; -import { - GameUI, - SetupComponentOptions, - IUIDefaultPropsBase -} from '@motajs/system'; -import { defineComponent, nextTick, onMounted, ref } from 'vue'; -import { - BUTTONS_HEIGHT, - BUTTONS_WIDTH, - BUTTONS_X, - BUTTONS_Y, - HALF_HEIGHT, - HALF_WIDTH, - MAIN_HEIGHT, - MAIN_WIDTH, - TITLE_BACKGROUND_IMAGE, - TITLE_FILL, - TITLE_STROKE, - TITLE_STROKE_WIDTH, - TITLE_X, - TITLE_Y -} from '../../shared'; -import { ElementLocator, Font } from '@motajs/render'; -import { - ITransitionedController, - transitioned, - transitionedColor, - useKey -} from '../use'; -import { ExitFullscreen, Fullscreen, SoundVolume } from '@user/client-base'; -import { mainSetting, triggerFullscreen } from '@motajs/legacy-ui'; -import { saveLoad } from './save'; -import { MainSceneUI } from './main'; -import { adjustCover } from '@user/client-base'; -import { cosh, CurveMode, linear } from '@motajs/animate'; -import { sleep } from '@motajs/common'; -import { client } from '../../core'; - -const enum TitleButton { - StartGame, - LoadGame, - Replay -} - -interface ButtonItem { - code: TitleButton; - name: string; - color: string; -} - -interface ButtonOption { - code: number; - color: string; - name: string; - hard: string; - colorTrans: ITransitionedController; -} - -export interface GameTitleProps extends DefaultProps, IUIDefaultPropsBase {} - -const gameTitleProps = { - props: ['controller', 'instance'] -} satisfies SetupComponentOptions; - -export const GameTitle = defineComponent(props => { - const materials = client.materials; - const bg = materials.getImageByAlias(TITLE_BACKGROUND_IMAGE); - - //#region 计算背景图 - const [width, height] = adjustCover( - bg?.width ?? MAIN_WIDTH, - bg?.height ?? MAIN_HEIGHT, - MAIN_WIDTH, - MAIN_HEIGHT - ); - - //#region 标题设置 - - /** 当前是否全屏 */ - const fullscreen = ref(!!document.fullscreenElement); - /** 是否开启了声音 */ - const soundOpened = ref(true); - /** 是否在选择难度界面 */ - const selectHard = ref(false); - - /** 开始界面按钮定义,你可以在这新增自己的按钮 */ - const buttonItems: ButtonItem[] = [ - { - code: TitleButton.StartGame, - color: 'rgb(40, 194, 255)', - name: '开始游戏' - }, - { - code: TitleButton.LoadGame, - color: 'rgb(0, 255, 55)', - name: '读取存档' - }, - { - code: TitleButton.Replay, - color: 'rgb(255, 251, 0)', - name: '录像回放' - } - ]; - - /** 开始界面按钮 */ - const buttons = buttonItems.map(v => { - return { - code: v.code, - color: v.color, - name: v.name, - hard: '', - colorTrans: transitionedColor( - '#fff', - 400, - cosh(2, CurveMode.EaseOut) - )!, - scale: transitioned(1, 400, cosh(2, CurveMode.EaseOut))! - }; - }); - - /** 选择难度界面按钮 */ - const hard = main.levelChoose.map(v => { - return { - code: v.hard, - color: core.arrayToRGBA(v.color!), - name: v.title, - hard: v.name, - colorTrans: transitionedColor( - '#fff', - 400, - cosh(2, CurveMode.EaseOut) - )!, - scale: transitioned(1, 400, cosh(2, CurveMode.EaseOut))! - }; - }); - // 返回按钮 - hard.push({ - code: -1, - color: '#aaa', - name: '返回', - hard: '', - colorTrans: transitionedColor('#fff', 400, cosh(2, CurveMode.EaseOut))! - }); - - /** 声音设置按钮的颜色 */ - const soundColor = transitionedColor( - '#ddd', - 400, - cosh(2, CurveMode.EaseOut) - )!; - - /** 开始界面按钮的不透明度,选择难度界面的不透明度使用 `1-buttonsAlpha` 计算 */ - const buttonsAlpha = transitioned(1, 300, linear())!; - /** 开始界面的不透明度 */ - const mainAlpha = transitioned(0, 600, linear())!; - - const buttonFilter = ` - drop-shadow(3px 3px 5px rgba(0, 0, 0, 0.4)) - drop-shadow(0px 0px 2px rgba(255, 255, 255, 0.5)) - `; - - const titleFont = Font.defaults({ size: 72 }); - const buttonFont = Font.defaults({ size: 24, weight: 600 }); - - /** 开始界面按钮的高度 */ - const buttonHeight = (buttons.length - 1) * 40 + 60; - /** 选择难度界面按钮的高度 */ - const hardHeight = (hard.length - 1) * 40 + 60; - /** 按钮的背景框高度 */ - const rectHeight = transitioned( - buttonHeight, - 600, - cosh(2, CurveMode.EaseOut) - )!; - - //#region 按钮功能 - - /** - * 在开始界面和选择难度界面切换 - */ - const toggleHard = async () => { - if (selectHard.value) { - // 当前在难度界面,将要切换至开始界面 - enterMain(0); - rectHeight.set(buttonHeight); - } else { - // 当前在开始界面,将要切换至难度界面 - enterHard(0); - rectHeight.set(hardHeight); - } - buttonsAlpha.set(0); - await sleep(300); - selectHard.value = !selectHard.value; - buttonsAlpha.set(1); - }; - - /** - * 点击读取存档按钮 - */ - const loadGame = async () => { - const loc: ElementLocator = [0, 0, MAIN_WIDTH, MAIN_HEIGHT]; - const success = await saveLoad(props.controller, loc); - if (success) { - props.controller.close(props.instance); - props.controller.open(MainSceneUI, {}); - } - }; - - /** - * 点击录像回放按钮 - */ - const replay = () => { - core.chooseReplayFile(); - }; - - /** - * 选择难度并开始游戏 - * @param hard 选择的难度 - */ - const startGame = async (hard: string) => { - mainAlpha.set(0); - await sleep(600); - props.controller.close(props.instance); - props.controller.open(MainSceneUI, {}); - nextTick(() => { - core.startGame(hard); - }); - }; - - /** - * 点击按钮时触发 - * @param code 选中的按钮的代码 - */ - const clickButton = (code: number, index: number) => { - if (selectHard.value) { - if (index === hard.length - 1) { - // 选中了最后一个按钮 - toggleHard(); - return; - } - const item = hard[index]; - startGame(item.name); - } else { - switch (code) { - case TitleButton.StartGame: - toggleHard(); - break; - case TitleButton.LoadGame: - loadGame(); - break; - case TitleButton.Replay: - replay(); - break; - } - } - }; - - //#region 键盘操作 - - const selected = ref(0); - - const [key] = useKey(); - key.realize( - '@start_up', - () => { - selected.value--; - if (selected.value < 0) { - selected.value = 0; - } - if (selectHard.value) { - enterHard(selected.value); - } else { - enterMain(selected.value); - } - }, - { type: 'down' } - ) - .realize( - '@start_down', - () => { - selected.value++; - if (selectHard.value) { - if (selected.value > hard.length - 1) { - selected.value = hard.length - 1; - } - enterHard(selected.value); - } else { - if (selected.value > buttons.length - 1) { - selected.value = buttons.length - 1; - } - enterMain(selected.value); - } - }, - { type: 'down' } - ) - .realize('confirm', () => { - if (selectHard.value) { - clickButton(hard[selected.value].code, selected.value); - } else { - clickButton(buttons[selected.value].code, selected.value); - } - }); - - //#region 鼠标操作 - - soundOpened.value = mainSetting.getValue('audio.bgmEnabled', true); - if (soundOpened.value) { - soundColor.set('#ddd'); - } else { - soundColor.set('#d22'); - } - - const enterMain = (index: number) => { - buttons.forEach((v, i) => { - if (index !== i) { - v.colorTrans.set('#fff'); - } else { - v.colorTrans.set(v.color); - } - }); - selected.value = index; - }; - - const enterHard = (index: number) => { - hard.forEach((v, i) => { - if (index !== i) { - v.colorTrans.set('#fff'); - } else { - v.colorTrans.set(v.color); - } - }); - selected.value = index; - }; - - const toggleSound = () => { - soundOpened.value = !soundOpened.value; - mainSetting.setValue('audio.bgmEnabled', soundOpened.value); - if (soundOpened.value) { - soundColor.set('#ddd'); - } else { - soundColor.set('#d22'); - } - }; - - const toggleFullscreen = async () => { - await triggerFullscreen(!fullscreen.value); - fullscreen.value = !!document.fullscreenElement; - }; - - onMounted(() => { - enterMain(0); - mainAlpha.set(1); - }); - - return () => ( - - - - - - - - - - - - {!fullscreen.value ? ( - - ) : ( - - )} - - - ); -}, gameTitleProps); - -export const GameTitleUI = new GameUI('game-title', GameTitle);