diff --git a/packages-user/client-modules/src/render/shared.ts b/packages-user/client-modules/src/render/shared.ts index 863452b..acff2da 100644 --- a/packages-user/client-modules/src/render/shared.ts +++ b/packages-user/client-modules/src/render/shared.ts @@ -83,24 +83,22 @@ export const SAVE_PAGES = 1000; //#region 标题界面 -/** 标题文字宽度 */ -export const TITLE_WIDTH = 640; -/** 标题文字高度 */ -export const TITLE_HEIGHT = 100; -/** 标题文字宽度的一半 */ -export const HALF_TITLE_WIDTH = TITLE_WIDTH / 2; -/** 标题文字高度的一半 */ -export const HALF_TITLE_HEIGHT = TITLE_HEIGHT / 2; /** 标题文字中心横坐标 */ export const TITLE_X = HALF_WIDTH; /** 标题文字中心纵坐标 */ -export const TITLE_Y = 120; +export const TITLE_Y = 100; +/** 标题文字的填充颜色 */ +export const TITLE_FILL = 'white'; +/** 标题文字的描边颜色 */ +export const TITLE_STROKE = 'black'; +/** 标题文字的描边宽度 */ +export const TITLE_STROKE_WIDTH = 2; /** 标题界面按钮宽度,如果文字被裁剪可以考虑扩大此值 */ -export const BUTTONS_WIDTH = 200; +export const BUTTONS_WIDTH = 160; /** 标题界面按钮高度,如果文字被裁剪可以考虑扩大此值 */ -export const BUTTONS_HEIGHT = 160; -/** 标题界面按钮左上角横坐标 */ -export const BUTTONS_X = 50; +export const BUTTONS_HEIGHT = 200; +/** 标题界面按钮中心横坐标 */ +export const BUTTONS_X = HALF_WIDTH; /** 标题界面按钮左上角纵坐标 */ export const BUTTONS_Y = MAIN_HEIGHT - BUTTONS_HEIGHT; diff --git a/packages-user/client-modules/src/render/ui/title.tsx b/packages-user/client-modules/src/render/ui/title.tsx index e6b9da7..566bc2d 100644 --- a/packages-user/client-modules/src/render/ui/title.tsx +++ b/packages-user/client-modules/src/render/ui/title.tsx @@ -1,34 +1,26 @@ -import { DefaultProps, onTick } from '@motajs/render-vue'; +import { DefaultProps } from '@motajs/render-vue'; import { GameUI, SetupComponentOptions, UIComponentProps } from '@motajs/system-ui'; -import { defineComponent, nextTick, onMounted, ref } from 'vue'; +import { computed, defineComponent, nextTick, onMounted, ref } from 'vue'; import { BUTTONS_HEIGHT, BUTTONS_WIDTH, BUTTONS_X, BUTTONS_Y, HALF_HEIGHT, - HALF_TITLE_HEIGHT, - HALF_TITLE_WIDTH, HALF_WIDTH, MAIN_HEIGHT, MAIN_WIDTH, - TITLE_HEIGHT, - TITLE_WIDTH, + TITLE_FILL, + TITLE_STROKE, + TITLE_STROKE_WIDTH, TITLE_X, TITLE_Y } from '../shared'; -import { - ElementLocator, - IActionEvent, - MotaOffscreenCanvas2D, - Shader, - Sprite -} from '@motajs/render-core'; -import { Image3DEffect } from '../fx'; +import { ElementLocator, Sprite } from '@motajs/render-core'; import { ITransitionedController, transitioned, @@ -46,8 +38,7 @@ import { adjustCover } from '../utils'; const enum TitleButton { StartGame, LoadGame, - Replay, - Achievement + Replay } interface ButtonItem { @@ -103,11 +94,6 @@ export const GameTitle = defineComponent(props => { code: TitleButton.Replay, color: 'rgb(255, 251, 0)', name: '录像回放' - }, - { - code: TitleButton.Achievement, - color: 'rgb(0, 208, 255)', - name: '查看成就' } ]; @@ -141,15 +127,6 @@ export const GameTitle = defineComponent(props => { scale: transitioned(1, 400, hyper('sin', 'out'))! }); - //#region 渐变动画 - const maskPos = transitioned( - -MAIN_HEIGHT - 100, - 7000, - hyper('sin', 'out') - )!; - const cursorX = transitioned(40, 400, hyper('sin', 'out'))!; - const cursorY = transitioned(20, 400, hyper('sin', 'out'))!; - const soundColor = transitionedColor('#ddd', 400, hyper('sin', 'out'))!; const buttonsAlpha = transitioned(1, 300, linear())!; @@ -160,18 +137,25 @@ export const GameTitle = defineComponent(props => { drop-shadow(0px 0px 2px rgba(255, 255, 255, 0.5)) `; - let cursorScale = 1; - 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, hyper('sin', 'in-out'))!; + //#region 按钮功能 const toggleHard = async () => { if (selectHard.value) { + // 当前在难度界面,将要切换至开始界面 enterMain(0); + rectHeight.set(buttonHeight); } else { + // 当前在开始界面,将要切换至难度界面 enterHard(0); + rectHeight.set(hardHeight); } buttonsAlpha.set(0); await sleep(300); @@ -221,8 +205,6 @@ export const GameTitle = defineComponent(props => { case TitleButton.Replay: replay(); break; - case TitleButton.Achievement: - break; } } }; @@ -278,11 +260,6 @@ export const GameTitle = defineComponent(props => { soundColor.set('#d22'); } - const moveCursor = (index: number) => { - cursorX.set(40 - index * 10); - cursorY.set(20 + 30 * index); - }; - const enterMain = (index: number) => { buttons.forEach((v, i) => { if (index !== i) { @@ -294,7 +271,6 @@ export const GameTitle = defineComponent(props => { } }); selected.value = index; - moveCursor(index); }; const enterHard = (index: number) => { @@ -306,7 +282,6 @@ export const GameTitle = defineComponent(props => { } }); selected.value = index; - moveCursor(index); }; const toggleSound = () => { @@ -324,113 +299,13 @@ export const GameTitle = defineComponent(props => { fullscreen.value = !!document.fullscreenElement; }; - //#region 渲染 - - const imageEffect = new Image3DEffect(); - const imageShader = ref(); - - const maskSprite = ref(); - const cursorSprite = ref(); - - let maskGradient: CanvasGradient | null = null; - let titleGradient: CanvasGradient | null = null; - - const createImageEffect = () => { - if (!imageShader.value) return; - imageEffect.create(imageShader.value); - const model = imageEffect.getModel(); - const view = imageEffect.getView(); - view.lookAt([0, 0, 1], [0, 0, 0], [0, 1, 0]); - model.scale(1.1, 1.1, 1.1); - imageEffect.use(); - }; - - const createMaskGradient = (ctx: CanvasRenderingContext2D) => { - maskGradient = ctx.createLinearGradient(100, 100, 200, 0); - maskGradient.addColorStop(0, 'rgba(255,255,255,0)'); - maskGradient.addColorStop(1, 'rgba(0,0,0,1)'); - }; - - const createTitleGradient = (ctx: CanvasRenderingContext2D) => { - titleGradient = ctx.createLinearGradient(0, 0, 640, 0); - titleGradient.addColorStop(0, 'rgb(0, 65, 62)'); - titleGradient.addColorStop(0.25, 'rgb(0, 33, 71)'); - titleGradient.addColorStop(0.5, 'rgb(136, 0, 214)'); - titleGradient.addColorStop(0.75, 'rgb(0, 2, 97)'); - titleGradient.addColorStop(1, 'rgb(0, 2, 97)'); - }; - - const titleSprite = ref(); onMounted(() => { - createImageEffect(); - maskPos.set(MAIN_WIDTH); enterMain(0); }); - onTick(time => { - if (maskPos.value < MAIN_WIDTH) { - maskSprite.value?.update(); - } - cursorScale = Math.sin(time / 600); - cursorSprite.value?.update(); - }); - - const moveBackground = (ev: IActionEvent) => { - const model = imageEffect.getModel(); - const px = (ev.offsetX / MAIN_WIDTH - 0.5) * 2; - const py = (ev.offsetY / MAIN_HEIGHT - 0.5) * 2; - model.reset(); - model.scale(1.1, 1.1, 1.1); - model.rotateY(px / 24); - model.rotateX(py / 24); - }; - - const renderMask = (canvas: MotaOffscreenCanvas2D) => { - const ctx = canvas.ctx; - if (maskGradient === null) { - createMaskGradient(ctx); - } - const pos = maskPos.value; - ctx.translate(pos, 0); - ctx.fillStyle = maskGradient!; - ctx.fillRect(0, 0, MAIN_WIDTH + MAIN_HEIGHT + 200, MAIN_HEIGHT); - }; - - const renderTitle = (canvas: MotaOffscreenCanvas2D) => { - const ctx = canvas.ctx; - if (titleGradient === null) { - createTitleGradient(ctx); - } - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - ctx.font = titleFont.string(); - ctx.fillStyle = titleGradient!; - ctx.filter = ` - drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.5)) - drop-shadow(-3px -3px 4px rgba(255,255,255,0.3)) - drop-shadow(12px 12px 4px rgba(0, 0, 0, 0.4)) - blur(1px) - `; - ctx.fillText(core.firstData.title, HALF_TITLE_WIDTH, HALF_TITLE_HEIGHT); - }; - - const renderCursor = (canvas: MotaOffscreenCanvas2D) => { - const ctx = canvas.ctx; - ctx.translate(0, 5); - ctx.scale(1, cursorScale); - ctx.beginPath(); - ctx.moveTo(1, -4); - ctx.lineTo(9, 0); - ctx.lineTo(1, 4); - ctx.strokeStyle = '#fff'; - ctx.lineWidth = 1; - ctx.stroke(); - }; - return () => ( (props => { anc={[0.5, 0.5]} zIndex={0} /> - - - + - + {!fullscreen.value ? ( ) : ( )}