mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-06-28 05:07:59 +08:00
feat: 标题界面换用新渲染系统
This commit is contained in:
parent
58d2b5eb36
commit
f832d75f50
@ -501,3 +501,184 @@ export const StepForward = defineComponent<IconsProps>(props => {
|
||||
);
|
||||
};
|
||||
}, iconsProps);
|
||||
|
||||
export const SoundVolume = defineComponent<IconsProps>(props => {
|
||||
const path = ref<Path2D>();
|
||||
|
||||
const width = computed(() => props.loc[2] ?? 200);
|
||||
const height = computed(() => props.loc[3] ?? 200);
|
||||
const generatePath = adjustPath(1, path, (ox, oy, width, height) => {
|
||||
const path = new Path2D();
|
||||
const left = ox + width / 8;
|
||||
const top = oy + height / 5;
|
||||
const bottom = oy + height - height / 5;
|
||||
path.moveTo(left, height / 2 - height / 10);
|
||||
path.lineTo(left, height / 2 + height / 10);
|
||||
path.lineTo(left + width / 6, height / 2 + height / 10);
|
||||
path.lineTo(width / 2, bottom);
|
||||
path.lineTo(width / 2, top);
|
||||
path.lineTo(left + width / 6, height / 2 - height / 10);
|
||||
path.closePath();
|
||||
const cx = width / 2;
|
||||
const cy = height / 2;
|
||||
const start = -Math.PI / 4;
|
||||
const end = Math.PI / 4;
|
||||
path.moveTo(
|
||||
width / 2 + (Math.SQRT1_2 * width) / 6,
|
||||
height / 2 - (Math.SQRT1_2 * width) / 6
|
||||
);
|
||||
path.arc(cx, cy, width / 6, start, end);
|
||||
path.moveTo(
|
||||
width / 2 + (Math.SQRT1_2 * width) / 3,
|
||||
height / 2 - (Math.SQRT1_2 * width) / 3
|
||||
);
|
||||
path.arc(cx, cy, width / 3, start, end);
|
||||
return path;
|
||||
});
|
||||
|
||||
watch(props, () => {
|
||||
generatePath(width.value, height.value);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
generatePath(width.value, height.value);
|
||||
});
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<g-path
|
||||
loc={props.loc}
|
||||
path={path.value}
|
||||
stroke
|
||||
lineJoin="round"
|
||||
lineCap="round"
|
||||
></g-path>
|
||||
);
|
||||
};
|
||||
}, iconsProps);
|
||||
|
||||
export const Fullscreen = defineComponent<IconsProps>(props => {
|
||||
const path = ref<Path2D>();
|
||||
|
||||
const width = computed(() => props.loc[2] ?? 200);
|
||||
const height = computed(() => props.loc[3] ?? 200);
|
||||
const generatePath = adjustPath(1, path, (ox, oy, width, height) => {
|
||||
const path = new Path2D();
|
||||
const left = ox + width / 4;
|
||||
const right = ox + width - width / 4;
|
||||
const top = oy + height / 4;
|
||||
const bottom = oy + height - height / 4;
|
||||
|
||||
// 左上
|
||||
path.moveTo(left + width / 6, top + height / 6);
|
||||
path.lineTo(left, top);
|
||||
path.moveTo(left, top + height / 8);
|
||||
path.lineTo(left, top);
|
||||
path.lineTo(left + width / 8, top);
|
||||
|
||||
// 右上
|
||||
path.moveTo(right - width / 6, top + height / 6);
|
||||
path.lineTo(right, top);
|
||||
path.moveTo(right, top + height / 8);
|
||||
path.lineTo(right, top);
|
||||
path.lineTo(right - width / 8, top);
|
||||
|
||||
// 左下
|
||||
path.moveTo(left + width / 6, bottom - height / 6);
|
||||
path.lineTo(left, bottom);
|
||||
path.moveTo(left, bottom - height / 8);
|
||||
path.lineTo(left, bottom);
|
||||
path.lineTo(left + width / 8, bottom);
|
||||
|
||||
// 右下
|
||||
path.moveTo(right - width / 6, bottom - height / 6);
|
||||
path.lineTo(right, bottom);
|
||||
path.moveTo(right, bottom - height / 8);
|
||||
path.lineTo(right, bottom);
|
||||
path.lineTo(right - width / 8, bottom);
|
||||
return path;
|
||||
});
|
||||
|
||||
watch(props, () => {
|
||||
generatePath(width.value, height.value);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
generatePath(width.value, height.value);
|
||||
});
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<g-path
|
||||
loc={props.loc}
|
||||
path={path.value}
|
||||
stroke
|
||||
lineJoin="round"
|
||||
lineCap="round"
|
||||
></g-path>
|
||||
);
|
||||
};
|
||||
}, iconsProps);
|
||||
|
||||
export const ExitFullscreen = defineComponent<IconsProps>(props => {
|
||||
const path = ref<Path2D>();
|
||||
|
||||
const width = computed(() => props.loc[2] ?? 200);
|
||||
const height = computed(() => props.loc[3] ?? 200);
|
||||
const generatePath = adjustPath(1, path, (ox, oy, width, height) => {
|
||||
const path = new Path2D();
|
||||
const left = ox + width / 4;
|
||||
const right = ox + width - width / 4;
|
||||
const top = oy + height / 4;
|
||||
const bottom = oy + height - height / 4;
|
||||
|
||||
// 左上
|
||||
path.moveTo(left + width / 6, top + height / 6);
|
||||
path.lineTo(left, top);
|
||||
path.moveTo(left + width / 24, top + height / 6);
|
||||
path.lineTo(left + width / 6, top + height / 6);
|
||||
path.lineTo(left + width / 6, top + height / 24);
|
||||
|
||||
// 右上
|
||||
path.moveTo(right - width / 6, top + height / 6);
|
||||
path.lineTo(right, top);
|
||||
path.moveTo(right - width / 24, top + height / 6);
|
||||
path.lineTo(right - width / 6, top + height / 6);
|
||||
path.lineTo(right - width / 6, top + height / 24);
|
||||
|
||||
// 左下
|
||||
path.moveTo(left + width / 6, bottom - height / 6);
|
||||
path.lineTo(left, bottom);
|
||||
path.moveTo(left + width / 24, bottom - height / 6);
|
||||
path.lineTo(left + width / 6, bottom - height / 6);
|
||||
path.lineTo(left + width / 6, bottom - height / 24);
|
||||
|
||||
// 右下
|
||||
path.moveTo(right - width / 6, bottom - height / 6);
|
||||
path.lineTo(right, bottom);
|
||||
path.moveTo(right - width / 24, bottom - height / 6);
|
||||
path.lineTo(right - width / 6, bottom - height / 6);
|
||||
path.lineTo(right - width / 6, bottom - height / 24);
|
||||
return path;
|
||||
});
|
||||
|
||||
watch(props, () => {
|
||||
generatePath(width.value, height.value);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
generatePath(width.value, height.value);
|
||||
});
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<g-path
|
||||
loc={props.loc}
|
||||
path={path.value}
|
||||
stroke
|
||||
lineJoin="round"
|
||||
lineCap="round"
|
||||
></g-path>
|
||||
);
|
||||
};
|
||||
}, iconsProps);
|
||||
|
@ -121,7 +121,7 @@ export const Tip = defineComponent<TipProps>((props, { expose }) => {
|
||||
loc={rectLoc.value}
|
||||
circle={[props.corner ?? 4]}
|
||||
fill
|
||||
fillStyle="rgba(40,40,40,0.8)"
|
||||
fillStyle="rgba(0,0,0,0.8)"
|
||||
/>
|
||||
<icon
|
||||
hidden={!showIcon.value}
|
||||
|
@ -2,18 +2,27 @@ import { Shader, ShaderProgram } from '@motajs/render-core';
|
||||
|
||||
export abstract class EffectBase<T> {
|
||||
/** 当前使用的程序 */
|
||||
protected readonly program: ShaderProgram;
|
||||
protected program: ShaderProgram | null = null;
|
||||
/** 当前使用的着色器渲染元素 */
|
||||
protected shader: Shader | null = null;
|
||||
|
||||
constructor(
|
||||
public readonly shader: Shader,
|
||||
public readonly options: T
|
||||
) {
|
||||
/**
|
||||
* 在一个着色器元素上创建效果
|
||||
* @param shader 着色器程序
|
||||
* @param options 本效果的配置信息
|
||||
*/
|
||||
create(shader: Shader, options: T) {
|
||||
const vs = this.getVertex(options);
|
||||
const fs = this.getFragment(options);
|
||||
const program = shader.createProgram(ShaderProgram, vs, fs);
|
||||
const program = shader.createProgram(ShaderProgram);
|
||||
program.vs(vs);
|
||||
program.fs(fs);
|
||||
program.requestCompile();
|
||||
|
||||
this.program = program;
|
||||
this.shader = shader;
|
||||
|
||||
this.initProgram(program, options);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -39,13 +48,14 @@ export abstract class EffectBase<T> {
|
||||
* 更新着色器渲染
|
||||
*/
|
||||
requestUpdate() {
|
||||
this.shader.update();
|
||||
this.shader?.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用此着色器
|
||||
*/
|
||||
use() {
|
||||
if (!this.program || !this.shader) return;
|
||||
this.shader.useProgram(this.program);
|
||||
}
|
||||
}
|
||||
|
@ -29,29 +29,33 @@ export class Image3DEffect
|
||||
|
||||
protected getFragment(): string {
|
||||
return /* glsl */ `
|
||||
out vec4 color;
|
||||
|
||||
void main() {
|
||||
gl_FragColor = texture2D(u_sampler, v_texCoord);
|
||||
color = texture(u_sampler, v_texCoord);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
initProgram(program: ShaderProgram): void {
|
||||
if (!this.shader) return;
|
||||
program.defineUniformMatrix(
|
||||
'u_imageTransform',
|
||||
this.shader.U_MATRIX_4x4
|
||||
);
|
||||
const shader = this.shader;
|
||||
const aspect = shader.width / shader.height;
|
||||
this.proj.perspective((Math.PI * 2) / 3, aspect, 0.01, 1000);
|
||||
this.proj.perspective(Math.PI / 2, 1, 0.01, 1000);
|
||||
this.view.lookAt([0, 0, 1], [0, 0, 0], [0, 1, 0]);
|
||||
this.model.bind(this);
|
||||
this.view.bind(this);
|
||||
this.proj.bind(this);
|
||||
this.updateTransform();
|
||||
}
|
||||
|
||||
updateTransform(): void {
|
||||
if (!this.shader || !this.program) return;
|
||||
const matrix = this.program.getMatrix('u_imageTransform');
|
||||
if (!matrix) return;
|
||||
const trans = this.model.multiply(this.view).multiply(this.proj);
|
||||
const trans = this.proj.multiply(this.view).multiply(this.model);
|
||||
matrix.set(false, trans.mat);
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
@ -1,24 +1,21 @@
|
||||
import { createApp } from '@motajs/render';
|
||||
import { defineComponent } from 'vue';
|
||||
import { UIController } from '@motajs/system-ui';
|
||||
import { mainSceneUI } from './ui/main';
|
||||
import { MAIN_HEIGHT, MAIN_WIDTH } from './shared';
|
||||
import { hook } from '@user/data-base';
|
||||
import { loading } from '@user/data-base';
|
||||
import { createLoopMap } from './loopMap';
|
||||
import { createElements } from './elements';
|
||||
import { mainRenderer } from './renderer';
|
||||
import { createUI } from './ui';
|
||||
import { createAction } from './action';
|
||||
import { createLegacy } from './legacy';
|
||||
import { sceneController } from './scene';
|
||||
import { GameTitleUI } from './ui/title';
|
||||
|
||||
export function createGameRenderer() {
|
||||
const App = defineComponent(_props => {
|
||||
const ui = new UIController('root-ui');
|
||||
ui.open(mainSceneUI, {});
|
||||
|
||||
return () => (
|
||||
<container width={MAIN_WIDTH} height={MAIN_HEIGHT}>
|
||||
{ui.render()}
|
||||
{sceneController.render()}
|
||||
</container>
|
||||
);
|
||||
});
|
||||
@ -26,14 +23,6 @@ export function createGameRenderer() {
|
||||
mainRenderer.hide();
|
||||
createApp(App).mount(mainRenderer);
|
||||
|
||||
hook.on('reset', () => {
|
||||
mainRenderer.show();
|
||||
});
|
||||
|
||||
hook.on('restart', () => {
|
||||
mainRenderer.hide();
|
||||
});
|
||||
|
||||
console.log(mainRenderer);
|
||||
}
|
||||
|
||||
@ -43,6 +32,11 @@ export function createRender() {
|
||||
createUI();
|
||||
createAction();
|
||||
createLoopMap();
|
||||
|
||||
loading.on('loaded', () => {
|
||||
sceneController.open(GameTitleUI, {});
|
||||
mainRenderer.show();
|
||||
});
|
||||
}
|
||||
|
||||
export * from './components';
|
||||
|
3
packages-user/client-modules/src/render/scene.ts
Normal file
3
packages-user/client-modules/src/render/scene.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { UIController } from '@motajs/system-ui';
|
||||
|
||||
export const sceneController = new UIController('main-scene');
|
@ -163,7 +163,7 @@ const MainScene = defineComponent(() => {
|
||||
}
|
||||
};
|
||||
|
||||
const loaded = ref(false);
|
||||
const loaded = ref(true);
|
||||
onLoaded(() => {
|
||||
loaded.value = true;
|
||||
});
|
||||
@ -235,4 +235,4 @@ const MainScene = defineComponent(() => {
|
||||
);
|
||||
});
|
||||
|
||||
export const mainSceneUI = new GameUI('main-scene', MainScene);
|
||||
export const MainSceneUI = new GameUI('main-scene', MainScene);
|
||||
|
553
packages-user/client-modules/src/render/ui/title.tsx
Normal file
553
packages-user/client-modules/src/render/ui/title.tsx
Normal file
@ -0,0 +1,553 @@
|
||||
import { DefaultProps, onTick } from '@motajs/render-vue';
|
||||
import {
|
||||
GameUI,
|
||||
SetupComponentOptions,
|
||||
UIComponentProps
|
||||
} from '@motajs/system-ui';
|
||||
import { defineComponent, nextTick, onMounted, ref } from 'vue';
|
||||
import { MAIN_HEIGHT, MAIN_WIDTH } from '../shared';
|
||||
import {
|
||||
IActionEvent,
|
||||
MotaOffscreenCanvas2D,
|
||||
Shader,
|
||||
Sprite
|
||||
} from '@motajs/render-core';
|
||||
import { Image3DEffect } from '../fx';
|
||||
import {
|
||||
ITransitionedController,
|
||||
transitioned,
|
||||
transitionedColor,
|
||||
useKey
|
||||
} from '../use';
|
||||
import { hyper, linear, sleep } from 'mutate-animate';
|
||||
import { Font } from '@motajs/render-style';
|
||||
import { ExitFullscreen, Fullscreen, SoundVolume } from '../components';
|
||||
import { mainSetting, triggerFullscreen } from '@motajs/legacy-ui';
|
||||
import { saveLoad } from './save';
|
||||
import { MainSceneUI } from './main';
|
||||
|
||||
const enum TitleButton {
|
||||
StartGame,
|
||||
LoadGame,
|
||||
Replay,
|
||||
Achievement
|
||||
}
|
||||
|
||||
interface ButtonItem {
|
||||
code: TitleButton;
|
||||
name: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
interface ButtonOption {
|
||||
code: number;
|
||||
color: string;
|
||||
name: string;
|
||||
hard: string;
|
||||
colorTrans: ITransitionedController<string>;
|
||||
scale: ITransitionedController<number>;
|
||||
}
|
||||
|
||||
export interface GameTitleProps extends DefaultProps, UIComponentProps {}
|
||||
|
||||
const gameTitleProps = {
|
||||
props: ['controller', 'instance']
|
||||
} satisfies SetupComponentOptions<GameTitleProps>;
|
||||
|
||||
export const GameTitle = defineComponent<GameTitleProps>(props => {
|
||||
const bg = core.material.images.images['bg.webp'];
|
||||
|
||||
//#region 计算背景图
|
||||
const aspect = bg.width / bg.height;
|
||||
const canvasAspect = MAIN_WIDTH / MAIN_HEIGHT;
|
||||
const [width, height] = (() => {
|
||||
if (canvasAspect > aspect) {
|
||||
const width = MAIN_WIDTH;
|
||||
const height = width / aspect;
|
||||
return [width, height];
|
||||
} else {
|
||||
const height = MAIN_HEIGHT;
|
||||
const width = height * aspect;
|
||||
return [width, 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: '录像回放'
|
||||
},
|
||||
{
|
||||
code: TitleButton.Achievement,
|
||||
color: 'rgb(0, 208, 255)',
|
||||
name: '查看成就'
|
||||
}
|
||||
];
|
||||
|
||||
const buttons = buttonItems.map<ButtonOption>(v => {
|
||||
return {
|
||||
code: v.code,
|
||||
color: v.color,
|
||||
name: v.name,
|
||||
hard: '',
|
||||
colorTrans: transitionedColor('#fff', 400, hyper('sin', 'out'))!,
|
||||
scale: transitioned(1, 400, hyper('sin', 'out'))!
|
||||
};
|
||||
});
|
||||
|
||||
const hard = main.levelChoose.map<ButtonOption>(v => {
|
||||
return {
|
||||
code: v.hard,
|
||||
color: core.arrayToRGBA(v.color),
|
||||
name: v.title,
|
||||
hard: v.name,
|
||||
colorTrans: transitionedColor('#fff', 400, hyper('sin', 'out'))!,
|
||||
scale: transitioned(1, 400, hyper('sin', 'out'))!
|
||||
};
|
||||
});
|
||||
hard.push({
|
||||
code: main.levelChoose.length,
|
||||
color: '#aaa',
|
||||
name: '返回',
|
||||
hard: '',
|
||||
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 buttonsAlpha = transitioned(1, 300, linear())!;
|
||||
const mainAlpha = transitioned(1, 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))
|
||||
`;
|
||||
|
||||
let cursorScale = 1;
|
||||
|
||||
const titleFont = new Font('normal', 72).string();
|
||||
const buttonFont = new Font('normal', 24, 'px', 600);
|
||||
|
||||
//#region 按钮功能
|
||||
|
||||
const toggleHard = async () => {
|
||||
if (selectHard.value) {
|
||||
enterMain(0);
|
||||
} else {
|
||||
enterHard(0);
|
||||
}
|
||||
buttonsAlpha.set(0);
|
||||
await sleep(300);
|
||||
selectHard.value = !selectHard.value;
|
||||
buttonsAlpha.set(1);
|
||||
};
|
||||
|
||||
const loadGame = () => {
|
||||
saveLoad(props.controller, [0, 0, MAIN_WIDTH, MAIN_HEIGHT]);
|
||||
};
|
||||
|
||||
const replay = () => {
|
||||
core.chooseReplayFile();
|
||||
};
|
||||
|
||||
const startGame = async (hard: string) => {
|
||||
mainAlpha.set(0);
|
||||
await sleep(600);
|
||||
props.controller.close(props.instance);
|
||||
props.controller.open(MainSceneUI, {});
|
||||
nextTick(() => {
|
||||
core.startGame(hard);
|
||||
});
|
||||
};
|
||||
|
||||
const clickButton = (code: number) => {
|
||||
if (selectHard.value) {
|
||||
if (code === hard.length - 1) {
|
||||
toggleHard();
|
||||
return;
|
||||
}
|
||||
const item = hard[code];
|
||||
startGame(item.name);
|
||||
} else {
|
||||
switch (code) {
|
||||
case TitleButton.StartGame:
|
||||
toggleHard();
|
||||
break;
|
||||
case TitleButton.LoadGame:
|
||||
loadGame();
|
||||
break;
|
||||
case TitleButton.Replay:
|
||||
replay();
|
||||
break;
|
||||
case TitleButton.Achievement:
|
||||
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', () => {
|
||||
clickButton(selected.value);
|
||||
});
|
||||
|
||||
//#region 鼠标操作
|
||||
|
||||
soundOpened.value = mainSetting.getValue('audio.bgmEnabled', true);
|
||||
if (soundOpened.value) {
|
||||
soundColor.set('#ddd');
|
||||
} else {
|
||||
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) {
|
||||
v.colorTrans.set('#fff');
|
||||
v.scale.set(1);
|
||||
} else {
|
||||
v.colorTrans.set(v.color);
|
||||
v.scale.set(1.1);
|
||||
}
|
||||
});
|
||||
selected.value = index;
|
||||
moveCursor(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;
|
||||
moveCursor(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;
|
||||
};
|
||||
|
||||
//#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, '#fff');
|
||||
maskGradient.addColorStop(1, '#000');
|
||||
};
|
||||
|
||||
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(() => {
|
||||
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.save();
|
||||
ctx.translate(pos, 0);
|
||||
ctx.fillStyle = 'transparent';
|
||||
ctx.fillStyle = maskGradient!;
|
||||
ctx.fillRect(0, 0, MAIN_WIDTH + MAIN_HEIGHT + 200, MAIN_HEIGHT);
|
||||
ctx.restore();
|
||||
};
|
||||
|
||||
const renderTitle = (canvas: MotaOffscreenCanvas2D) => {
|
||||
const ctx = canvas.ctx;
|
||||
if (titleGradient === null) {
|
||||
createTitleGradient(ctx);
|
||||
}
|
||||
ctx.save();
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.font = titleFont;
|
||||
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, 320, 50);
|
||||
ctx.restore();
|
||||
};
|
||||
|
||||
const renderCursor = (canvas: MotaOffscreenCanvas2D) => {
|
||||
const ctx = canvas.ctx;
|
||||
ctx.save();
|
||||
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();
|
||||
ctx.restore();
|
||||
};
|
||||
|
||||
return () => (
|
||||
<container
|
||||
loc={[0, 0, MAIN_WIDTH, MAIN_HEIGHT]}
|
||||
onMoveCapture={moveBackground}
|
||||
alpha={mainAlpha.ref.value}
|
||||
>
|
||||
<image
|
||||
image={bg}
|
||||
loc={[MAIN_WIDTH / 2, MAIN_HEIGHT / 2, width, height]}
|
||||
anc={[0.5, 0.5]}
|
||||
zIndex={0}
|
||||
/>
|
||||
<shader
|
||||
ref={imageShader}
|
||||
zIndex={5}
|
||||
loc={[0, 0, MAIN_WIDTH, MAIN_HEIGHT]}
|
||||
filter="brightness(120%)contrast(110%)"
|
||||
/>
|
||||
<sprite
|
||||
ref={maskSprite}
|
||||
render={renderMask}
|
||||
composite="multiply"
|
||||
loc={[0, 0, MAIN_WIDTH, MAIN_HEIGHT]}
|
||||
zIndex={20}
|
||||
noevent
|
||||
/>
|
||||
<sprite
|
||||
ref={titleSprite}
|
||||
render={renderTitle}
|
||||
loc={[MAIN_WIDTH / 2, 120, 640, 100, 0.5, 0.5]}
|
||||
zIndex={10}
|
||||
/>
|
||||
<container
|
||||
zIndex={15}
|
||||
loc={[50, MAIN_HEIGHT, 200, 160]}
|
||||
anc={[0, 1]}
|
||||
>
|
||||
<container
|
||||
hidden={selectHard.value}
|
||||
loc={[0, 0, 200, 160]}
|
||||
alpha={buttonsAlpha.ref.value}
|
||||
>
|
||||
{buttons.map((v, i) => {
|
||||
const x = 50 - i * 10;
|
||||
const y = 20 + i * 30;
|
||||
return (
|
||||
<text
|
||||
text={v.name}
|
||||
font={buttonFont}
|
||||
loc={[x, y, void 0, void 0, 0, 0.5]}
|
||||
cursor="pointer"
|
||||
filter={buttonFilter}
|
||||
fillStyle={v.colorTrans.ref.value}
|
||||
// 这个缩放性能影响极大,原因不明
|
||||
// scale={[v.scale.ref.value, v.scale.ref.value]}
|
||||
onEnter={() => enterMain(i)}
|
||||
onClick={() => clickButton(i)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</container>
|
||||
<container
|
||||
hidden={!selectHard.value}
|
||||
loc={[0, 0, 200, 160]}
|
||||
alpha={buttonsAlpha.ref.value}
|
||||
>
|
||||
{hard.map((v, i) => {
|
||||
const x = 50 - i * 10;
|
||||
const y = 20 + i * 30;
|
||||
return (
|
||||
<text
|
||||
text={v.name}
|
||||
font={buttonFont}
|
||||
loc={[x, y, void 0, void 0, 0, 0.5]}
|
||||
cursor="pointer"
|
||||
filter={buttonFilter}
|
||||
fillStyle={v.colorTrans.ref.value}
|
||||
onEnter={() => enterHard(i)}
|
||||
onClick={() => clickButton(i)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</container>
|
||||
<sprite
|
||||
ref={cursorSprite}
|
||||
width={10}
|
||||
height={10}
|
||||
render={renderCursor}
|
||||
loc={[cursorX.ref.value, cursorY.ref.value]}
|
||||
anc={[1, 0.5]}
|
||||
nocache
|
||||
/>
|
||||
</container>
|
||||
<container
|
||||
zIndex={15}
|
||||
loc={[MAIN_WIDTH - 40, MAIN_HEIGHT - 20, 80, 40, 1, 1]}
|
||||
>
|
||||
<SoundVolume
|
||||
loc={[0, 0, 40, 40]}
|
||||
cursor="pointer"
|
||||
strokeStyle={soundColor.ref.value}
|
||||
onClick={toggleSound}
|
||||
/>
|
||||
{!fullscreen.value ? (
|
||||
<Fullscreen
|
||||
loc={[40, 0, 40, 40]}
|
||||
onClick={toggleFullscreen}
|
||||
cursor="pointer"
|
||||
/>
|
||||
) : (
|
||||
<ExitFullscreen
|
||||
loc={[40, 0, 40, 40]}
|
||||
onClick={toggleFullscreen}
|
||||
cursor="pointer"
|
||||
/>
|
||||
)}
|
||||
{!soundOpened.value && (
|
||||
<g-line
|
||||
line={[5, 35, 35, 5]}
|
||||
strokeStyle="gray"
|
||||
lineWidth={3}
|
||||
lineCap="round"
|
||||
noevent
|
||||
zIndex={5}
|
||||
/>
|
||||
)}
|
||||
</container>
|
||||
</container>
|
||||
);
|
||||
}, gameTitleProps);
|
||||
|
||||
export const GameTitleUI = new GameUI('game-title', GameTitle);
|
@ -31,7 +31,6 @@ import {
|
||||
loadDefaultResource,
|
||||
LoadTask
|
||||
} from '@motajs/legacy-common';
|
||||
import { GameUi } from '../controller';
|
||||
import { formatSize } from '../utils';
|
||||
import { logger } from '@motajs/common';
|
||||
import { sleep } from 'mutate-animate';
|
||||
@ -70,10 +69,10 @@ onMounted(async () => {
|
||||
core._afterLoadResources(props.callback);
|
||||
logger.log(`Resource load end.`);
|
||||
loadDiv.style.opacity = '0';
|
||||
await sleep(500);
|
||||
Mota.require('@user/data-base').loading.emit('loaded');
|
||||
await sleep(1000);
|
||||
await sleep(500);
|
||||
props.controller.close(props.num);
|
||||
props.controller.open('start');
|
||||
});
|
||||
loadDiv = document.getElementById('load') as HTMLDivElement;
|
||||
});
|
||||
|
@ -419,7 +419,8 @@ onUnmounted(() => {
|
||||
);
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
text-shadow: 1px 1px 4px rgba(0, 0, 0, 0.5),
|
||||
text-shadow:
|
||||
1px 1px 4px rgba(0, 0, 0, 0.5),
|
||||
-1px -1px 3px rgba(255, 255, 255, 0.3),
|
||||
5px 5px 5px rgba(0, 0, 0, 0.4);
|
||||
filter: brightness(1.8);
|
||||
@ -442,14 +443,17 @@ onUnmounted(() => {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
animation: cursor 2.5s linear 0s infinite normal running;
|
||||
transition: left 0.4s ease-out, top 0.4s ease-out,
|
||||
transition:
|
||||
left 0.4s ease-out,
|
||||
top 0.4s ease-out,
|
||||
opacity 1.5s ease-out;
|
||||
}
|
||||
|
||||
.start-button {
|
||||
position: relative;
|
||||
font: bold 1.5em 'normal';
|
||||
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.4),
|
||||
text-shadow:
|
||||
1px 1px 2px rgba(0, 0, 0, 0.4),
|
||||
0px 0px 1px rgba(255, 255, 255, 0.3);
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
|
@ -167,7 +167,7 @@ export abstract class GL2<E extends EGL2Event = EGL2Event> extends RenderItem<
|
||||
protected framebufferMap: Map<string, WebGLFramebuffer> = new Map();
|
||||
|
||||
constructor(type: RenderItemPosition = 'static') {
|
||||
super(type, !GL2.support);
|
||||
super(type, false);
|
||||
|
||||
this.canvas = document.createElement('canvas');
|
||||
this.gl = this.canvas.getContext('webgl2')!;
|
||||
@ -216,17 +216,16 @@ export abstract class GL2<E extends EGL2Event = EGL2Event> extends RenderItem<
|
||||
this.gl.useProgram(this.program.program);
|
||||
}
|
||||
|
||||
if (this.cacheDirty) {
|
||||
// 清空画布
|
||||
const gl = this.gl;
|
||||
gl.viewport(0, 0, this.canvas.width, this.canvas.height);
|
||||
gl.clearColor(0, 0, 0, 0);
|
||||
gl.clearDepth(1);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
||||
this.drawScene(canvas, gl, this.program, transform);
|
||||
this.cacheDirty = false;
|
||||
}
|
||||
// 清空画布
|
||||
const gl = this.gl;
|
||||
gl.viewport(0, 0, this.canvas.width, this.canvas.height);
|
||||
gl.clearColor(0, 0, 0, 0);
|
||||
gl.clearDepth(1);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
||||
this.program.ready();
|
||||
this.drawScene(canvas, gl, this.program, transform);
|
||||
|
||||
canvas.clear();
|
||||
canvas.ctx.drawImage(this.canvas, 0, 0, this.width, this.height);
|
||||
}
|
||||
|
||||
@ -1189,8 +1188,6 @@ export class GL2Program extends EventEmitter<ShaderProgramEvent> {
|
||||
private shader: CompiledShader | null = null;
|
||||
/** 当前的webgl程序 */
|
||||
program: WebGLProgram | null = null;
|
||||
/** 准备函数 */
|
||||
private readyFn?: () => boolean;
|
||||
/** 当前正在使用的顶点索引数组 */
|
||||
usingIndices: IShaderIndices | null = null;
|
||||
|
||||
@ -1219,18 +1216,10 @@ export class GL2Program extends EventEmitter<ShaderProgramEvent> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用这个着色器程序时,在渲染之前执行的准备函数
|
||||
* @param fn 准备函数,返回 false 时将不执行绘制
|
||||
*/
|
||||
setReady(fn: () => boolean) {
|
||||
this.readyFn = fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行准备函数
|
||||
* 渲染前准备
|
||||
*/
|
||||
ready(): boolean {
|
||||
return this.readyFn?.() ?? true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1255,7 +1244,6 @@ export class GL2Program extends EventEmitter<ShaderProgramEvent> {
|
||||
case RenderMode.ElementsInstanced:
|
||||
return this.elementsInstancedParams as DrawParamsMap[T];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { isNil } from 'lodash-es';
|
||||
import { isEqual, isNil } from 'lodash-es';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import { MotaOffscreenCanvas2D } from './canvas2d';
|
||||
import { Ticker, TickerFn } from 'mutate-animate';
|
||||
@ -445,9 +445,9 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
|
||||
canvas.ctx.drawImage(this.cache.canvas, ax, ay, width, height);
|
||||
} else {
|
||||
this.cacheDirty = false;
|
||||
canvas.ctx.translate(ax, ay);
|
||||
this.render(canvas, tran);
|
||||
this.cacheDirty = false;
|
||||
}
|
||||
ctx.restore();
|
||||
this.emit('afterRender', transform);
|
||||
@ -505,6 +505,7 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
* 修改这个对象的大小
|
||||
*/
|
||||
size(width: number, height: number): void {
|
||||
if (width === this.width && height === this.height) return;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
if (this.enableCache) {
|
||||
@ -1186,7 +1187,7 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
}
|
||||
case 'filter': {
|
||||
if (!this.assertType(nextValue, 'string', key)) return;
|
||||
this.setFilter(this.filter);
|
||||
this.setFilter(nextValue);
|
||||
return;
|
||||
}
|
||||
case 'hd': {
|
||||
@ -1238,6 +1239,7 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
return;
|
||||
}
|
||||
case 'loc': {
|
||||
if (isEqual(nextValue, prevValue)) return;
|
||||
if (!this.assertType(nextValue, Array, key)) return;
|
||||
if (!isNil(nextValue[0]) && !isNil(nextValue[1])) {
|
||||
this.pos(nextValue[0] as number, nextValue[1] as number);
|
||||
@ -1254,6 +1256,7 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
return;
|
||||
}
|
||||
case 'anc': {
|
||||
if (isEqual(nextValue, prevValue)) return;
|
||||
if (!this.assertType(nextValue, Array, key)) return;
|
||||
this.setAnchor(nextValue[0] as number, nextValue[1] as number);
|
||||
return;
|
||||
@ -1264,6 +1267,7 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
return;
|
||||
}
|
||||
case 'scale': {
|
||||
if (isEqual(nextValue, prevValue)) return;
|
||||
if (!this.assertType(nextValue, Array, key)) return;
|
||||
this._transform.setScale(
|
||||
nextValue[0] as number,
|
||||
|
@ -26,8 +26,10 @@ void main() {
|
||||
}
|
||||
`;
|
||||
const DEFAULT_FS = /* glsl */ `
|
||||
out vec4 color;
|
||||
|
||||
void main() {
|
||||
gl_FragColor = texture2D(u_sampler, v_texCoord);
|
||||
color = texture(u_sampler, v_texCoord);
|
||||
}
|
||||
`;
|
||||
|
||||
@ -55,7 +57,7 @@ export class Shader<E extends EShaderEvent = EShaderEvent> extends GL2<
|
||||
}
|
||||
|
||||
export class ShaderProgram extends GL2Program {
|
||||
protected override readonly prefix: IGL2ProgramPrefix = SHADER_PREFIX;
|
||||
protected readonly prefix: IGL2ProgramPrefix = SHADER_PREFIX;
|
||||
|
||||
constructor(gl2: GL2, vs?: string, fs?: string) {
|
||||
super(gl2, vs, fs);
|
||||
@ -66,6 +68,11 @@ export class ShaderProgram extends GL2Program {
|
||||
}
|
||||
}
|
||||
|
||||
ready(): boolean {
|
||||
this.useIndices('defaultIndices');
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override compile() {
|
||||
const success = super.compile();
|
||||
if (!success) return false;
|
||||
@ -76,7 +83,7 @@ export class ShaderProgram extends GL2Program {
|
||||
const tex = this.defineAttribArray('a_texCoord');
|
||||
const position = this.defineAttribArray('a_position');
|
||||
const sampler = this.defineTexture('u_sampler', 0);
|
||||
const indices = this.defineIndices('defalutIndices');
|
||||
const indices = this.defineIndices('defaultIndices');
|
||||
if (!tex || !position || !sampler || !indices) {
|
||||
return true;
|
||||
}
|
||||
@ -91,6 +98,7 @@ export class ShaderProgram extends GL2Program {
|
||||
tex.enable();
|
||||
indices.buffer(new Uint16Array([0, 1, 2, 2, 3, 1]), gl.STATIC_DRAW);
|
||||
this.useIndices(indices);
|
||||
this.mode(this.element.DRAW_ELEMENTS);
|
||||
this.paramElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
|
||||
|
||||
return true;
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
MotaOffscreenCanvas2D
|
||||
} from '@motajs/render-core';
|
||||
import { logger } from '@motajs/common';
|
||||
import { clamp, isNil } from 'lodash-es';
|
||||
import { clamp, isEqual, isNil } from 'lodash-es';
|
||||
import { CanvasStyle } from './types';
|
||||
|
||||
export type CircleParams = [
|
||||
@ -442,6 +442,7 @@ export class Circle extends GraphicItemBase {
|
||||
this.setAngle(this.start, nextValue);
|
||||
return true;
|
||||
case 'circle': {
|
||||
if (isEqual(nextValue, prevValue)) return true;
|
||||
const value = nextValue as CircleParams;
|
||||
if (!this.assertType(value, Array, key)) return false;
|
||||
const [cx, cy, radius, start, end] = value;
|
||||
@ -531,6 +532,7 @@ export class Ellipse extends GraphicItemBase {
|
||||
this.setAngle(this.start, nextValue);
|
||||
return true;
|
||||
case 'ellipse': {
|
||||
if (isEqual(nextValue, prevValue)) return true;
|
||||
const value = nextValue as EllipseParams;
|
||||
if (!this.assertType(value, Array, key)) return false;
|
||||
const [cx, cy, radiusX, radiusY, start, end] = value;
|
||||
@ -619,6 +621,7 @@ export class Line extends GraphicItemBase {
|
||||
this.setPoint2(this.x2, nextValue);
|
||||
return true;
|
||||
case 'line':
|
||||
if (isEqual(nextValue, prevValue)) return true;
|
||||
if (!this.assertType(nextValue as number[], Array, key)) {
|
||||
return false;
|
||||
}
|
||||
@ -750,6 +753,7 @@ export class BezierCurve extends GraphicItemBase {
|
||||
this.setEnd(this.ex, nextValue);
|
||||
return true;
|
||||
case 'curve':
|
||||
if (isEqual(nextValue, prevValue)) return true;
|
||||
if (!this.assertType(nextValue as number[], Array, key)) {
|
||||
return false;
|
||||
}
|
||||
@ -873,6 +877,7 @@ export class QuadraticCurve extends GraphicItemBase {
|
||||
this.setEnd(this.ex, nextValue);
|
||||
return true;
|
||||
case 'curve':
|
||||
if (isEqual(nextValue, prevValue)) return true;
|
||||
if (!this.assertType(nextValue as number[], Array, key)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -98,6 +98,8 @@ export class Hotkey extends EventEmitter<HotkeyEvent> {
|
||||
|
||||
/** 当前正在按下的按键 */
|
||||
private pressed: Set<KeyCode> = new Set();
|
||||
/** 有哪些单次按下的按键已经触发 */
|
||||
private downEmitted: Set<KeyCode> = new Set();
|
||||
/** 按键按下时的时间 */
|
||||
private pressTime: Map<KeyCode, number> = new Map();
|
||||
/** 按键节流时间 */
|
||||
@ -262,6 +264,10 @@ export class Hotkey extends EventEmitter<HotkeyEvent> {
|
||||
});
|
||||
this.emit('emit', key, assist, type);
|
||||
|
||||
if (type === 'down') {
|
||||
this.downEmitted.add(key);
|
||||
}
|
||||
|
||||
return emitted;
|
||||
}
|
||||
|
||||
@ -284,6 +290,7 @@ export class Hotkey extends EventEmitter<HotkeyEvent> {
|
||||
if (!this.pressed.has(keyCode)) return;
|
||||
this.pressed.delete(keyCode);
|
||||
this.pressTime.delete(keyCode);
|
||||
this.downEmitted.delete(keyCode);
|
||||
this.emit('release', keyCode);
|
||||
}
|
||||
|
||||
@ -306,7 +313,7 @@ export class Hotkey extends EventEmitter<HotkeyEvent> {
|
||||
}
|
||||
if (!config) return false;
|
||||
// 按下单次触发
|
||||
if (config.type === 'down') return !this.pressed.has(keyCode);
|
||||
if (config.type === 'down') return !this.downEmitted.has(keyCode);
|
||||
// 按下重复触发
|
||||
if (config.type === 'down-repeat') return true;
|
||||
if (config.type === 'down-timeout') {
|
||||
|
@ -247,9 +247,6 @@ control.prototype.showStartAnimate = function (noAnimate, callback) {
|
||||
core.flags.startUsingCanvas,
|
||||
callback
|
||||
);
|
||||
Mota.r(() => {
|
||||
Mota.require('@motajs/legacy-ui').fixedUi.open('start');
|
||||
});
|
||||
};
|
||||
|
||||
control.prototype._showStartAnimate_resetDom = function () {
|
||||
@ -526,17 +523,20 @@ control.prototype.setHeroMoveInterval = function (callback) {
|
||||
// render.move(true);
|
||||
// });
|
||||
|
||||
core.interval.heroMoveInterval = window.setInterval(function () {
|
||||
// render.offset += toAdd * 4;
|
||||
core.status.heroMoving += toAdd;
|
||||
if (core.status.heroMoving >= 8) {
|
||||
clearInterval(core.interval.heroMoveInterval);
|
||||
core.status.heroMoving = 0;
|
||||
// render.offset = 0;
|
||||
// render.move(false);
|
||||
if (callback) callback();
|
||||
}
|
||||
}, ((core.values.moveSpeed / 8) * toAdd) / core.status.replay.speed);
|
||||
core.interval.heroMoveInterval = window.setInterval(
|
||||
function () {
|
||||
// render.offset += toAdd * 4;
|
||||
core.status.heroMoving += toAdd;
|
||||
if (core.status.heroMoving >= 8) {
|
||||
clearInterval(core.interval.heroMoveInterval);
|
||||
core.status.heroMoving = 0;
|
||||
// render.offset = 0;
|
||||
// render.move(false);
|
||||
if (callback) callback();
|
||||
}
|
||||
},
|
||||
((core.values.moveSpeed / 8) * toAdd) / core.status.replay.speed
|
||||
);
|
||||
};
|
||||
|
||||
////// 每移动一格后执行的事件 //////
|
||||
|
@ -24,9 +24,6 @@ events.prototype.resetGame = function (hero, hard, floorId, maps, values) {
|
||||
////// 游戏开始事件 //////
|
||||
events.prototype.startGame = function (hard, seed, route, callback) {
|
||||
hard = hard || '';
|
||||
if (!main.replayChecking) {
|
||||
Mota.require('@motajs/legacy-ui').fixedUi.closeByName('start');
|
||||
}
|
||||
|
||||
if (main.mode != 'play') return;
|
||||
Mota.require('@user/data-state').resetSkillLevel();
|
||||
@ -701,12 +698,12 @@ events.prototype.getItem = function (id, num, x, y, isGentleClick, callback) {
|
||||
(id.endsWith('Key')
|
||||
? '(钥匙类道具,遇到对应的门时自动打开)'
|
||||
: itemCls == 'tools'
|
||||
? '(消耗类道具,请按T在道具栏使用)'
|
||||
: itemCls == 'constants'
|
||||
? '(永久类道具,请按T在道具栏使用)'
|
||||
: itemCls == 'equips'
|
||||
? '(装备类道具,请按Q在装备栏进行装备)'
|
||||
: '')
|
||||
? '(消耗类道具,请按T在道具栏使用)'
|
||||
: itemCls == 'constants'
|
||||
? '(永久类道具,请按T在道具栏使用)'
|
||||
: itemCls == 'equips'
|
||||
? '(装备类道具,请按Q在装备栏进行装备)'
|
||||
: '')
|
||||
);
|
||||
}
|
||||
itemHint.push(id);
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 108 KiB |
@ -31,6 +31,7 @@ body {
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#curtain {
|
||||
|
Loading…
Reference in New Issue
Block a user