Compare commits

...

5 Commits

3 changed files with 133 additions and 249 deletions

View File

@ -83,24 +83,22 @@ export const SAVE_PAGES = 1000;
//#region 标题界面 //#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_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_HEIGHT = 200;
/** 标题界面按钮左上角横坐标 */ /** 标题界面按钮中心横坐标 */
export const BUTTONS_X = 50; export const BUTTONS_X = HALF_WIDTH;
/** 标题界面按钮左上角纵坐标 */ /** 标题界面按钮左上角纵坐标 */
export const BUTTONS_Y = MAIN_HEIGHT - BUTTONS_HEIGHT; export const BUTTONS_Y = MAIN_HEIGHT - BUTTONS_HEIGHT;

View File

@ -1,4 +1,4 @@
import { DefaultProps, onTick } from '@motajs/render-vue'; import { DefaultProps } from '@motajs/render-vue';
import { import {
GameUI, GameUI,
SetupComponentOptions, SetupComponentOptions,
@ -11,24 +11,16 @@ import {
BUTTONS_X, BUTTONS_X,
BUTTONS_Y, BUTTONS_Y,
HALF_HEIGHT, HALF_HEIGHT,
HALF_TITLE_HEIGHT,
HALF_TITLE_WIDTH,
HALF_WIDTH, HALF_WIDTH,
MAIN_HEIGHT, MAIN_HEIGHT,
MAIN_WIDTH, MAIN_WIDTH,
TITLE_HEIGHT, TITLE_FILL,
TITLE_WIDTH, TITLE_STROKE,
TITLE_STROKE_WIDTH,
TITLE_X, TITLE_X,
TITLE_Y TITLE_Y
} from '../shared'; } from '../shared';
import { import { ElementLocator } from '@motajs/render-core';
ElementLocator,
IActionEvent,
MotaOffscreenCanvas2D,
Shader,
Sprite
} from '@motajs/render-core';
import { Image3DEffect } from '../fx';
import { import {
ITransitionedController, ITransitionedController,
transitioned, transitioned,
@ -46,8 +38,7 @@ import { adjustCover } from '../utils';
const enum TitleButton { const enum TitleButton {
StartGame, StartGame,
LoadGame, LoadGame,
Replay, Replay
Achievement
} }
interface ButtonItem { interface ButtonItem {
@ -62,7 +53,6 @@ interface ButtonOption {
name: string; name: string;
hard: string; hard: string;
colorTrans: ITransitionedController<string>; colorTrans: ITransitionedController<string>;
scale: ITransitionedController<number>;
} }
export interface GameTitleProps extends DefaultProps, UIComponentProps {} export interface GameTitleProps extends DefaultProps, UIComponentProps {}
@ -84,10 +74,14 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
//#region 标题设置 //#region 标题设置
/** 当前是否全屏 */
const fullscreen = ref(!!document.fullscreenElement); const fullscreen = ref(!!document.fullscreenElement);
/** 是否开启了声音 */
const soundOpened = ref(true); const soundOpened = ref(true);
/** 是否在选择难度界面 */
const selectHard = ref(false); const selectHard = ref(false);
/** 开始界面按钮定义,你可以在这新增自己的按钮 */
const buttonItems: ButtonItem[] = [ const buttonItems: ButtonItem[] = [
{ {
code: TitleButton.StartGame, code: TitleButton.StartGame,
@ -103,14 +97,10 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
code: TitleButton.Replay, code: TitleButton.Replay,
color: 'rgb(255, 251, 0)', color: 'rgb(255, 251, 0)',
name: '录像回放' name: '录像回放'
},
{
code: TitleButton.Achievement,
color: 'rgb(0, 208, 255)',
name: '查看成就'
} }
]; ];
/** 开始界面按钮 */
const buttons = buttonItems.map<ButtonOption>(v => { const buttons = buttonItems.map<ButtonOption>(v => {
return { return {
code: v.code, code: v.code,
@ -122,6 +112,7 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
}; };
}); });
/** 选择难度界面按钮 */
const hard = main.levelChoose.map<ButtonOption>(v => { const hard = main.levelChoose.map<ButtonOption>(v => {
return { return {
code: v.hard, code: v.hard,
@ -132,46 +123,52 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
scale: transitioned(1, 400, hyper('sin', 'out'))! scale: transitioned(1, 400, hyper('sin', 'out'))!
}; };
}); });
// 返回按钮
hard.push({ hard.push({
code: main.levelChoose.length, code: -1,
color: '#aaa', color: '#aaa',
name: '返回', name: '返回',
hard: '', hard: '',
colorTrans: transitionedColor('#fff', 400, hyper('sin', 'out'))!, colorTrans: transitionedColor('#fff', 400, hyper('sin', 'out'))!
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 soundColor = transitionedColor('#ddd', 400, hyper('sin', 'out'))!;
/** 开始界面按钮的不透明度,选择难度界面的不透明度使用 `1-buttonsAlpha` 计算 */
const buttonsAlpha = transitioned(1, 300, linear())!; const buttonsAlpha = transitioned(1, 300, linear())!;
const mainAlpha = transitioned(1, 600, linear())!; /** 开始界面的不透明度 */
const mainAlpha = transitioned(0, 600, linear())!;
const buttonFilter = ` const buttonFilter = `
drop-shadow(3px 3px 5px rgba(0, 0, 0, 0.4)) drop-shadow(3px 3px 5px rgba(0, 0, 0, 0.4))
drop-shadow(0px 0px 2px rgba(255, 255, 255, 0.5)) drop-shadow(0px 0px 2px rgba(255, 255, 255, 0.5))
`; `;
let cursorScale = 1;
const titleFont = Font.defaults({ size: 72 }); const titleFont = Font.defaults({ size: 72 });
const buttonFont = Font.defaults({ size: 24, weight: 600 }); 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 按钮功能 //#region 按钮功能
/**
*
*/
const toggleHard = async () => { const toggleHard = async () => {
if (selectHard.value) { if (selectHard.value) {
// 当前在难度界面,将要切换至开始界面
enterMain(0); enterMain(0);
rectHeight.set(buttonHeight);
} else { } else {
// 当前在开始界面,将要切换至难度界面
enterHard(0); enterHard(0);
rectHeight.set(hardHeight);
} }
buttonsAlpha.set(0); buttonsAlpha.set(0);
await sleep(300); await sleep(300);
@ -179,6 +176,9 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
buttonsAlpha.set(1); buttonsAlpha.set(1);
}; };
/**
*
*/
const loadGame = async () => { const loadGame = async () => {
const loc: ElementLocator = [0, 0, MAIN_WIDTH, MAIN_HEIGHT]; const loc: ElementLocator = [0, 0, MAIN_WIDTH, MAIN_HEIGHT];
const success = await saveLoad(props.controller, loc); const success = await saveLoad(props.controller, loc);
@ -188,10 +188,17 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
} }
}; };
/**
*
*/
const replay = () => { const replay = () => {
core.chooseReplayFile(); core.chooseReplayFile();
}; };
/**
*
* @param hard
*/
const startGame = async (hard: string) => { const startGame = async (hard: string) => {
mainAlpha.set(0); mainAlpha.set(0);
await sleep(600); await sleep(600);
@ -202,13 +209,18 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
}); });
}; };
const clickButton = (code: number) => { /**
*
* @param code
*/
const clickButton = (code: number, index: number) => {
if (selectHard.value) { if (selectHard.value) {
if (code === hard.length - 1) { if (index === hard.length - 1) {
// 选中了最后一个按钮
toggleHard(); toggleHard();
return; return;
} }
const item = hard[code]; const item = hard[index];
startGame(item.name); startGame(item.name);
} else { } else {
switch (code) { switch (code) {
@ -221,8 +233,6 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
case TitleButton.Replay: case TitleButton.Replay:
replay(); replay();
break; break;
case TitleButton.Achievement:
break;
} }
} }
}; };
@ -266,7 +276,11 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
{ type: 'down' } { type: 'down' }
) )
.realize('confirm', () => { .realize('confirm', () => {
clickButton(selected.value); if (selectHard.value) {
clickButton(hard[selected.value].code, selected.value);
} else {
clickButton(buttons[selected.value].code, selected.value);
}
}); });
//#region 鼠标操作 //#region 鼠标操作
@ -278,23 +292,15 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
soundColor.set('#d22'); soundColor.set('#d22');
} }
const moveCursor = (index: number) => {
cursorX.set(40 - index * 10);
cursorY.set(20 + 30 * index);
};
const enterMain = (index: number) => { const enterMain = (index: number) => {
buttons.forEach((v, i) => { buttons.forEach((v, i) => {
if (index !== i) { if (index !== i) {
v.colorTrans.set('#fff'); v.colorTrans.set('#fff');
v.scale.set(1);
} else { } else {
v.colorTrans.set(v.color); v.colorTrans.set(v.color);
v.scale.set(1.1);
} }
}); });
selected.value = index; selected.value = index;
moveCursor(index);
}; };
const enterHard = (index: number) => { const enterHard = (index: number) => {
@ -306,7 +312,6 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
} }
}); });
selected.value = index; selected.value = index;
moveCursor(index);
}; };
const toggleSound = () => { const toggleSound = () => {
@ -324,113 +329,14 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
fullscreen.value = !!document.fullscreenElement; fullscreen.value = !!document.fullscreenElement;
}; };
//#region 渲染
const imageEffect = new Image3DEffect();
const imageShader = ref<Shader>();
const maskSprite = ref<Sprite>();
const cursorSprite = ref<Sprite>();
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<Sprite>();
onMounted(() => { onMounted(() => {
createImageEffect();
maskPos.set(MAIN_WIDTH);
enterMain(0); enterMain(0);
mainAlpha.set(1);
}); });
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 () => ( return () => (
<container <container
loc={[0, 0, MAIN_WIDTH, MAIN_HEIGHT]} loc={[0, 0, MAIN_WIDTH, MAIN_HEIGHT]}
onMoveCapture={moveBackground}
alpha={mainAlpha.ref.value} alpha={mainAlpha.ref.value}
> >
<image <image
@ -439,110 +345,111 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
anc={[0.5, 0.5]} anc={[0.5, 0.5]}
zIndex={0} zIndex={0}
/> />
<shader <text
ref={imageShader} text={core.firstData.title}
zIndex={5} loc={[TITLE_X, TITLE_Y]}
loc={[0, 0, MAIN_WIDTH, MAIN_HEIGHT]} anc={[0.5, 0.5]}
/> fillStyle={TITLE_FILL}
<sprite strokeStyle={TITLE_STROKE}
ref={maskSprite} font={titleFont}
render={renderMask} strokeWidth={TITLE_STROKE_WIDTH}
composite="multiply"
loc={[0, 0, MAIN_WIDTH, MAIN_HEIGHT]}
zIndex={20}
noevent
/>
<sprite
ref={titleSprite}
render={renderTitle}
loc={[TITLE_X, TITLE_Y, TITLE_WIDTH, TITLE_HEIGHT, 0.5, 0.5]}
zIndex={10}
/> />
<container <container
zIndex={15} zIndex={15}
loc={[BUTTONS_X, BUTTONS_Y, BUTTONS_WIDTH, BUTTONS_HEIGHT]} loc={[BUTTONS_X, BUTTONS_Y, BUTTONS_WIDTH, BUTTONS_HEIGHT]}
anc={[0.5, 0]}
> >
<g-rectr
loc={[2, BUTTONS_HEIGHT / 2]}
width={BUTTONS_WIDTH - 4}
height={rectHeight.ref.value - 4}
anc={[0, 0.5]}
circle={[16]}
fill
stroke
fillStyle="rgba(50, 54, 159, 0.7)"
strokeStyle="rgba(255, 204, 0, 1)"
lineWidth={3}
zIndex={0}
/>
<container <container
hidden={selectHard.value} hidden={selectHard.value}
loc={[0, 0, BUTTONS_WIDTH, BUTTONS_HEIGHT]} loc={[0, BUTTONS_HEIGHT / 2, BUTTONS_WIDTH, buttonHeight]}
anc={[0, 0.5]}
alpha={buttonsAlpha.ref.value} alpha={buttonsAlpha.ref.value}
zIndex={5}
> >
{buttons.map((v, i) => { {buttons.map((v, i) => {
const x = 50 - i * 10; const x = BUTTONS_WIDTH / 2;
const y = 20 + i * 30; const y = 30 + i * 40;
return ( return (
<text <text
text={v.name} text={v.name}
font={buttonFont} font={buttonFont}
loc={[x, y, void 0, void 0, 0, 0.5]} loc={[x, y, void 0, void 0, 0.5, 0.5]}
cursor="pointer" cursor="pointer"
filter={buttonFilter} filter={buttonFilter}
fillStyle={v.colorTrans.ref.value} fillStyle={v.colorTrans.ref.value}
// 这个缩放性能影响极大,原因不明
// scale={[v.scale.ref.value, v.scale.ref.value]}
onEnter={() => enterMain(i)} onEnter={() => enterMain(i)}
onClick={() => clickButton(i)} onClick={() => clickButton(v.code, i)}
/> />
); );
})} })}
</container> </container>
<container <container
hidden={!selectHard.value} hidden={!selectHard.value}
loc={[0, 0, BUTTONS_WIDTH, BUTTONS_HEIGHT]} loc={[0, BUTTONS_HEIGHT / 2, BUTTONS_WIDTH, hardHeight]}
anc={[0, 0.5]}
alpha={buttonsAlpha.ref.value} alpha={buttonsAlpha.ref.value}
> >
{hard.map((v, i) => { {hard.map((v, i) => {
const x = 50 - i * 10; const x = BUTTONS_WIDTH / 2;
const y = 20 + i * 30; const y = 30 + i * 40;
return ( return (
<text <text
text={v.name} text={v.name}
font={buttonFont} font={buttonFont}
loc={[x, y, void 0, void 0, 0, 0.5]} loc={[x, y, void 0, void 0, 0.5, 0.5]}
cursor="pointer" cursor="pointer"
filter={buttonFilter} filter={buttonFilter}
fillStyle={v.colorTrans.ref.value} fillStyle={v.colorTrans.ref.value}
onEnter={() => enterHard(i)} onEnter={() => enterHard(i)}
onClick={() => clickButton(i)} onClick={() => clickButton(v.code, i)}
/> />
); );
})} })}
</container> </container>
<sprite
ref={cursorSprite}
width={10}
height={10}
render={renderCursor}
loc={[cursorX.ref.value, cursorY.ref.value]}
anc={[1, 0.5]}
nocache
/>
</container> </container>
<container <container
zIndex={15} zIndex={15}
loc={[MAIN_WIDTH - 40, MAIN_HEIGHT - 20, 80, 40, 1, 1]} loc={[MAIN_WIDTH - 20, MAIN_HEIGHT - 20, 120, 40, 1, 1]}
> >
<g-rectr
loc={[0, 0, 120, 40]}
circle={[20]}
fillStyle="rgba(0, 0, 0, 0.72)"
/>
<SoundVolume <SoundVolume
loc={[0, 0, 40, 40]} loc={[20, 0, 40, 40]}
cursor="pointer" cursor="pointer"
strokeStyle={soundColor.ref.value} strokeStyle={soundColor.ref.value}
onClick={toggleSound} onClick={toggleSound}
/> />
{!fullscreen.value ? ( {!fullscreen.value ? (
<Fullscreen <Fullscreen
loc={[40, 0, 40, 40]} loc={[60, 0, 40, 40]}
onClick={toggleFullscreen} onClick={toggleFullscreen}
cursor="pointer" cursor="pointer"
/> />
) : ( ) : (
<ExitFullscreen <ExitFullscreen
loc={[40, 0, 40, 40]} loc={[60, 0, 40, 40]}
onClick={toggleFullscreen} onClick={toggleFullscreen}
cursor="pointer" cursor="pointer"
/> />
)} )}
<g-line <g-line
line={[5, 35, 35, 5]} line={[25, 35, 55, 5]}
strokeStyle="gray" strokeStyle="gray"
lineWidth={3} lineWidth={3}
lineCap="round" lineCap="round"

View File

@ -124,59 +124,34 @@ var data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d =
"color": [ "color": [
0, 0,
255, 255,
22, 0,
1 1
], ],
"action": [ "action": []
},
{ {
"type": "setCurtain", "title": "普通",
"name": "medium",
"hard": 2,
"color": [ "color": [
0, 255,
0, 255,
0, 0,
1 1
], ],
"time": 0, "action": []
"keep": true
},
{
"type": "setValue",
"name": "status:atk",
"operator": "+=",
"value": "3"
},
{
"type": "setValue",
"name": "status:def",
"operator": "+=",
"value": "2"
},
"简单难度下,初始攻击+3初始防御+2全局减伤10%,绿宝石效果*2"
]
}, },
{ {
"title": "困难", "title": "困难",
"name": "hard", "name": "hard",
"hard": 2, "hard": 3,
"color": [ "color": [
255, 255,
0, 0,
0, 0,
1 1
], ],
"action": [ "action": []
{
"type": "setCurtain",
"color": [
0,
0,
0,
1
],
"time": 0,
"keep": true
}
]
} }
], ],
"equipName": [ "equipName": [
@ -382,6 +357,10 @@ var data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d =
"type": "text", "type": "text",
"text": "欢迎使用古祠制作的 2.B 样板,本样板主要针对渲染系统进行了重构,现在我们有了更加方便强大的渲染系统,也对部分相关事件进行了重置!" "text": "欢迎使用古祠制作的 2.B 样板,本样板主要针对渲染系统进行了重构,现在我们有了更加方便强大的渲染系统,也对部分相关事件进行了重置!"
}, },
{
"type": "text",
"text": "同时 2.B 样板也新增了很多接口,在造塔时可以提供非常大的帮助!"
},
{ {
"type": "text", "type": "text",
"text": "这里是开场剧情,可以在编辑器全塔属性中修改,试着修改一下吧!" "text": "这里是开场剧情,可以在编辑器全塔属性中修改,试着修改一下吧!"