mirror of
				https://github.com/unanmed/HumanBreak.git
				synced 2025-10-31 12:12:58 +08:00 
			
		
		
		
	feat: 标题界面换用新渲染系统
This commit is contained in:
		
							parent
							
								
									58d2b5eb36
								
							
						
					
					
						commit
						f832d75f50
					
				| @ -501,3 +501,184 @@ export const StepForward = defineComponent<IconsProps>(props => { | |||||||
|         ); |         ); | ||||||
|     }; |     }; | ||||||
| }, iconsProps); | }, 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} |                 loc={rectLoc.value} | ||||||
|                 circle={[props.corner ?? 4]} |                 circle={[props.corner ?? 4]} | ||||||
|                 fill |                 fill | ||||||
|                 fillStyle="rgba(40,40,40,0.8)" |                 fillStyle="rgba(0,0,0,0.8)" | ||||||
|             /> |             /> | ||||||
|             <icon |             <icon | ||||||
|                 hidden={!showIcon.value} |                 hidden={!showIcon.value} | ||||||
|  | |||||||
| @ -2,18 +2,27 @@ import { Shader, ShaderProgram } from '@motajs/render-core'; | |||||||
| 
 | 
 | ||||||
| export abstract class EffectBase<T> { | 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 vs = this.getVertex(options); | ||||||
|         const fs = this.getFragment(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(); |         program.requestCompile(); | ||||||
| 
 | 
 | ||||||
|         this.program = program; |         this.program = program; | ||||||
|  |         this.shader = shader; | ||||||
|  | 
 | ||||||
|  |         this.initProgram(program, options); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -39,13 +48,14 @@ export abstract class EffectBase<T> { | |||||||
|      * 更新着色器渲染 |      * 更新着色器渲染 | ||||||
|      */ |      */ | ||||||
|     requestUpdate() { |     requestUpdate() { | ||||||
|         this.shader.update(); |         this.shader?.update(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * 使用此着色器 |      * 使用此着色器 | ||||||
|      */ |      */ | ||||||
|     use() { |     use() { | ||||||
|  |         if (!this.program || !this.shader) return; | ||||||
|         this.shader.useProgram(this.program); |         this.shader.useProgram(this.program); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -29,29 +29,33 @@ export class Image3DEffect | |||||||
| 
 | 
 | ||||||
|     protected getFragment(): string { |     protected getFragment(): string { | ||||||
|         return /* glsl */ ` |         return /* glsl */ ` | ||||||
|  |             out vec4 color; | ||||||
|  | 
 | ||||||
|             void main() { |             void main() { | ||||||
|                 gl_FragColor = texture2D(u_sampler, v_texCoord); |                 color = texture(u_sampler, v_texCoord); | ||||||
|             } |             } | ||||||
|         `;
 |         `;
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     initProgram(program: ShaderProgram): void { |     initProgram(program: ShaderProgram): void { | ||||||
|  |         if (!this.shader) return; | ||||||
|         program.defineUniformMatrix( |         program.defineUniformMatrix( | ||||||
|             'u_imageTransform', |             'u_imageTransform', | ||||||
|             this.shader.U_MATRIX_4x4 |             this.shader.U_MATRIX_4x4 | ||||||
|         ); |         ); | ||||||
|         const shader = this.shader; |         this.proj.perspective(Math.PI / 2, 1, 0.01, 1000); | ||||||
|         const aspect = shader.width / shader.height; |         this.view.lookAt([0, 0, 1], [0, 0, 0], [0, 1, 0]); | ||||||
|         this.proj.perspective((Math.PI * 2) / 3, aspect, 0.01, 1000); |  | ||||||
|         this.model.bind(this); |         this.model.bind(this); | ||||||
|         this.view.bind(this); |         this.view.bind(this); | ||||||
|         this.proj.bind(this); |         this.proj.bind(this); | ||||||
|  |         this.updateTransform(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     updateTransform(): void { |     updateTransform(): void { | ||||||
|  |         if (!this.shader || !this.program) return; | ||||||
|         const matrix = this.program.getMatrix('u_imageTransform'); |         const matrix = this.program.getMatrix('u_imageTransform'); | ||||||
|         if (!matrix) return; |         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); |         matrix.set(false, trans.mat); | ||||||
|         this.requestUpdate(); |         this.requestUpdate(); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -1,24 +1,21 @@ | |||||||
| import { createApp } from '@motajs/render'; | import { createApp } from '@motajs/render'; | ||||||
| import { defineComponent } from 'vue'; | import { defineComponent } from 'vue'; | ||||||
| import { UIController } from '@motajs/system-ui'; |  | ||||||
| import { mainSceneUI } from './ui/main'; |  | ||||||
| import { MAIN_HEIGHT, MAIN_WIDTH } from './shared'; | import { MAIN_HEIGHT, MAIN_WIDTH } from './shared'; | ||||||
| import { hook } from '@user/data-base'; | import { loading } from '@user/data-base'; | ||||||
| import { createLoopMap } from './loopMap'; | import { createLoopMap } from './loopMap'; | ||||||
| import { createElements } from './elements'; | import { createElements } from './elements'; | ||||||
| import { mainRenderer } from './renderer'; | import { mainRenderer } from './renderer'; | ||||||
| import { createUI } from './ui'; | import { createUI } from './ui'; | ||||||
| import { createAction } from './action'; | import { createAction } from './action'; | ||||||
| import { createLegacy } from './legacy'; | import { createLegacy } from './legacy'; | ||||||
|  | import { sceneController } from './scene'; | ||||||
|  | import { GameTitleUI } from './ui/title'; | ||||||
| 
 | 
 | ||||||
| export function createGameRenderer() { | export function createGameRenderer() { | ||||||
|     const App = defineComponent(_props => { |     const App = defineComponent(_props => { | ||||||
|         const ui = new UIController('root-ui'); |  | ||||||
|         ui.open(mainSceneUI, {}); |  | ||||||
| 
 |  | ||||||
|         return () => ( |         return () => ( | ||||||
|             <container width={MAIN_WIDTH} height={MAIN_HEIGHT}> |             <container width={MAIN_WIDTH} height={MAIN_HEIGHT}> | ||||||
|                 {ui.render()} |                 {sceneController.render()} | ||||||
|             </container> |             </container> | ||||||
|         ); |         ); | ||||||
|     }); |     }); | ||||||
| @ -26,14 +23,6 @@ export function createGameRenderer() { | |||||||
|     mainRenderer.hide(); |     mainRenderer.hide(); | ||||||
|     createApp(App).mount(mainRenderer); |     createApp(App).mount(mainRenderer); | ||||||
| 
 | 
 | ||||||
|     hook.on('reset', () => { |  | ||||||
|         mainRenderer.show(); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     hook.on('restart', () => { |  | ||||||
|         mainRenderer.hide(); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     console.log(mainRenderer); |     console.log(mainRenderer); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -43,6 +32,11 @@ export function createRender() { | |||||||
|     createUI(); |     createUI(); | ||||||
|     createAction(); |     createAction(); | ||||||
|     createLoopMap(); |     createLoopMap(); | ||||||
|  | 
 | ||||||
|  |     loading.on('loaded', () => { | ||||||
|  |         sceneController.open(GameTitleUI, {}); | ||||||
|  |         mainRenderer.show(); | ||||||
|  |     }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export * from './components'; | 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(() => { |     onLoaded(() => { | ||||||
|         loaded.value = true; |         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, |     loadDefaultResource, | ||||||
|     LoadTask |     LoadTask | ||||||
| } from '@motajs/legacy-common'; | } from '@motajs/legacy-common'; | ||||||
| import { GameUi } from '../controller'; |  | ||||||
| import { formatSize } from '../utils'; | import { formatSize } from '../utils'; | ||||||
| import { logger } from '@motajs/common'; | import { logger } from '@motajs/common'; | ||||||
| import { sleep } from 'mutate-animate'; | import { sleep } from 'mutate-animate'; | ||||||
| @ -70,10 +69,10 @@ onMounted(async () => { | |||||||
|         core._afterLoadResources(props.callback); |         core._afterLoadResources(props.callback); | ||||||
|         logger.log(`Resource load end.`); |         logger.log(`Resource load end.`); | ||||||
|         loadDiv.style.opacity = '0'; |         loadDiv.style.opacity = '0'; | ||||||
|  |         await sleep(500); | ||||||
|         Mota.require('@user/data-base').loading.emit('loaded'); |         Mota.require('@user/data-base').loading.emit('loaded'); | ||||||
|         await sleep(1000); |         await sleep(500); | ||||||
|         props.controller.close(props.num); |         props.controller.close(props.num); | ||||||
|         props.controller.open('start'); |  | ||||||
|     }); |     }); | ||||||
|     loadDiv = document.getElementById('load') as HTMLDivElement; |     loadDiv = document.getElementById('load') as HTMLDivElement; | ||||||
| }); | }); | ||||||
|  | |||||||
| @ -419,7 +419,8 @@ onUnmounted(() => { | |||||||
|         ); |         ); | ||||||
|         background-clip: text; |         background-clip: text; | ||||||
|         -webkit-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), |             -1px -1px 3px rgba(255, 255, 255, 0.3), | ||||||
|             5px 5px 5px rgba(0, 0, 0, 0.4); |             5px 5px 5px rgba(0, 0, 0, 0.4); | ||||||
|         filter: brightness(1.8); |         filter: brightness(1.8); | ||||||
| @ -442,14 +443,17 @@ onUnmounted(() => { | |||||||
|             position: absolute; |             position: absolute; | ||||||
|             opacity: 0; |             opacity: 0; | ||||||
|             animation: cursor 2.5s linear 0s infinite normal running; |             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; |                 opacity 1.5s ease-out; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         .start-button { |         .start-button { | ||||||
|             position: relative; |             position: relative; | ||||||
|             font: bold 1.5em 'normal'; |             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); |                 0px 0px 1px rgba(255, 255, 255, 0.3); | ||||||
|             background-clip: text; |             background-clip: text; | ||||||
|             -webkit-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(); |     protected framebufferMap: Map<string, WebGLFramebuffer> = new Map(); | ||||||
| 
 | 
 | ||||||
|     constructor(type: RenderItemPosition = 'static') { |     constructor(type: RenderItemPosition = 'static') { | ||||||
|         super(type, !GL2.support); |         super(type, false); | ||||||
| 
 | 
 | ||||||
|         this.canvas = document.createElement('canvas'); |         this.canvas = document.createElement('canvas'); | ||||||
|         this.gl = this.canvas.getContext('webgl2')!; |         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); |             this.gl.useProgram(this.program.program); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (this.cacheDirty) { |         // 清空画布
 | ||||||
|             // 清空画布
 |         const gl = this.gl; | ||||||
|             const gl = this.gl; |         gl.viewport(0, 0, this.canvas.width, this.canvas.height); | ||||||
|             gl.viewport(0, 0, this.canvas.width, this.canvas.height); |         gl.clearColor(0, 0, 0, 0); | ||||||
|             gl.clearColor(0, 0, 0, 0); |         gl.clearDepth(1); | ||||||
|             gl.clearDepth(1); |         gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); | ||||||
|             gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); |         this.program.ready(); | ||||||
|             this.drawScene(canvas, gl, this.program, transform); |         this.drawScene(canvas, gl, this.program, transform); | ||||||
|             this.cacheDirty = false; |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|  |         canvas.clear(); | ||||||
|         canvas.ctx.drawImage(this.canvas, 0, 0, this.width, this.height); |         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; |     private shader: CompiledShader | null = null; | ||||||
|     /** 当前的webgl程序 */ |     /** 当前的webgl程序 */ | ||||||
|     program: WebGLProgram | null = null; |     program: WebGLProgram | null = null; | ||||||
|     /** 准备函数 */ |  | ||||||
|     private readyFn?: () => boolean; |  | ||||||
|     /** 当前正在使用的顶点索引数组 */ |     /** 当前正在使用的顶点索引数组 */ | ||||||
|     usingIndices: IShaderIndices | null = null; |     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 { |     ready(): boolean { | ||||||
|         return this.readyFn?.() ?? true; |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -1255,7 +1244,6 @@ export class GL2Program extends EventEmitter<ShaderProgramEvent> { | |||||||
|             case RenderMode.ElementsInstanced: |             case RenderMode.ElementsInstanced: | ||||||
|                 return this.elementsInstancedParams as DrawParamsMap[T]; |                 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 { EventEmitter } from 'eventemitter3'; | ||||||
| import { MotaOffscreenCanvas2D } from './canvas2d'; | import { MotaOffscreenCanvas2D } from './canvas2d'; | ||||||
| import { Ticker, TickerFn } from 'mutate-animate'; | 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); |             canvas.ctx.drawImage(this.cache.canvas, ax, ay, width, height); | ||||||
|         } else { |         } else { | ||||||
|             this.cacheDirty = false; |  | ||||||
|             canvas.ctx.translate(ax, ay); |             canvas.ctx.translate(ax, ay); | ||||||
|             this.render(canvas, tran); |             this.render(canvas, tran); | ||||||
|  |             this.cacheDirty = false; | ||||||
|         } |         } | ||||||
|         ctx.restore(); |         ctx.restore(); | ||||||
|         this.emit('afterRender', transform); |         this.emit('afterRender', transform); | ||||||
| @ -505,6 +505,7 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent> | |||||||
|      * 修改这个对象的大小 |      * 修改这个对象的大小 | ||||||
|      */ |      */ | ||||||
|     size(width: number, height: number): void { |     size(width: number, height: number): void { | ||||||
|  |         if (width === this.width && height === this.height) return; | ||||||
|         this.width = width; |         this.width = width; | ||||||
|         this.height = height; |         this.height = height; | ||||||
|         if (this.enableCache) { |         if (this.enableCache) { | ||||||
| @ -1186,7 +1187,7 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent> | |||||||
|             } |             } | ||||||
|             case 'filter': { |             case 'filter': { | ||||||
|                 if (!this.assertType(nextValue, 'string', key)) return; |                 if (!this.assertType(nextValue, 'string', key)) return; | ||||||
|                 this.setFilter(this.filter); |                 this.setFilter(nextValue); | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|             case 'hd': { |             case 'hd': { | ||||||
| @ -1238,6 +1239,7 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent> | |||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|             case 'loc': { |             case 'loc': { | ||||||
|  |                 if (isEqual(nextValue, prevValue)) return; | ||||||
|                 if (!this.assertType(nextValue, Array, key)) return; |                 if (!this.assertType(nextValue, Array, key)) return; | ||||||
|                 if (!isNil(nextValue[0]) && !isNil(nextValue[1])) { |                 if (!isNil(nextValue[0]) && !isNil(nextValue[1])) { | ||||||
|                     this.pos(nextValue[0] as number, nextValue[1] as number); |                     this.pos(nextValue[0] as number, nextValue[1] as number); | ||||||
| @ -1254,6 +1256,7 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent> | |||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|             case 'anc': { |             case 'anc': { | ||||||
|  |                 if (isEqual(nextValue, prevValue)) return; | ||||||
|                 if (!this.assertType(nextValue, Array, key)) return; |                 if (!this.assertType(nextValue, Array, key)) return; | ||||||
|                 this.setAnchor(nextValue[0] as number, nextValue[1] as number); |                 this.setAnchor(nextValue[0] as number, nextValue[1] as number); | ||||||
|                 return; |                 return; | ||||||
| @ -1264,6 +1267,7 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent> | |||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|             case 'scale': { |             case 'scale': { | ||||||
|  |                 if (isEqual(nextValue, prevValue)) return; | ||||||
|                 if (!this.assertType(nextValue, Array, key)) return; |                 if (!this.assertType(nextValue, Array, key)) return; | ||||||
|                 this._transform.setScale( |                 this._transform.setScale( | ||||||
|                     nextValue[0] as number, |                     nextValue[0] as number, | ||||||
|  | |||||||
| @ -26,8 +26,10 @@ void main() { | |||||||
| } | } | ||||||
| `;
 | `;
 | ||||||
| const DEFAULT_FS = /* glsl */ ` | const DEFAULT_FS = /* glsl */ ` | ||||||
|  | out vec4 color; | ||||||
|  | 
 | ||||||
| void main() { | 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 { | 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) { |     constructor(gl2: GL2, vs?: string, fs?: string) { | ||||||
|         super(gl2, vs, fs); |         super(gl2, vs, fs); | ||||||
| @ -66,6 +68,11 @@ export class ShaderProgram extends GL2Program { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     ready(): boolean { | ||||||
|  |         this.useIndices('defaultIndices'); | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     protected override compile() { |     protected override compile() { | ||||||
|         const success = super.compile(); |         const success = super.compile(); | ||||||
|         if (!success) return false; |         if (!success) return false; | ||||||
| @ -76,7 +83,7 @@ export class ShaderProgram extends GL2Program { | |||||||
|         const tex = this.defineAttribArray('a_texCoord'); |         const tex = this.defineAttribArray('a_texCoord'); | ||||||
|         const position = this.defineAttribArray('a_position'); |         const position = this.defineAttribArray('a_position'); | ||||||
|         const sampler = this.defineTexture('u_sampler', 0); |         const sampler = this.defineTexture('u_sampler', 0); | ||||||
|         const indices = this.defineIndices('defalutIndices'); |         const indices = this.defineIndices('defaultIndices'); | ||||||
|         if (!tex || !position || !sampler || !indices) { |         if (!tex || !position || !sampler || !indices) { | ||||||
|             return true; |             return true; | ||||||
|         } |         } | ||||||
| @ -91,6 +98,7 @@ export class ShaderProgram extends GL2Program { | |||||||
|         tex.enable(); |         tex.enable(); | ||||||
|         indices.buffer(new Uint16Array([0, 1, 2, 2, 3, 1]), gl.STATIC_DRAW); |         indices.buffer(new Uint16Array([0, 1, 2, 2, 3, 1]), gl.STATIC_DRAW); | ||||||
|         this.useIndices(indices); |         this.useIndices(indices); | ||||||
|  |         this.mode(this.element.DRAW_ELEMENTS); | ||||||
|         this.paramElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0); |         this.paramElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0); | ||||||
| 
 | 
 | ||||||
|         return true; |         return true; | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ import { | |||||||
|     MotaOffscreenCanvas2D |     MotaOffscreenCanvas2D | ||||||
| } from '@motajs/render-core'; | } from '@motajs/render-core'; | ||||||
| import { logger } from '@motajs/common'; | import { logger } from '@motajs/common'; | ||||||
| import { clamp, isNil } from 'lodash-es'; | import { clamp, isEqual, isNil } from 'lodash-es'; | ||||||
| import { CanvasStyle } from './types'; | import { CanvasStyle } from './types'; | ||||||
| 
 | 
 | ||||||
| export type CircleParams = [ | export type CircleParams = [ | ||||||
| @ -442,6 +442,7 @@ export class Circle extends GraphicItemBase { | |||||||
|                 this.setAngle(this.start, nextValue); |                 this.setAngle(this.start, nextValue); | ||||||
|                 return true; |                 return true; | ||||||
|             case 'circle': { |             case 'circle': { | ||||||
|  |                 if (isEqual(nextValue, prevValue)) return true; | ||||||
|                 const value = nextValue as CircleParams; |                 const value = nextValue as CircleParams; | ||||||
|                 if (!this.assertType(value, Array, key)) return false; |                 if (!this.assertType(value, Array, key)) return false; | ||||||
|                 const [cx, cy, radius, start, end] = value; |                 const [cx, cy, radius, start, end] = value; | ||||||
| @ -531,6 +532,7 @@ export class Ellipse extends GraphicItemBase { | |||||||
|                 this.setAngle(this.start, nextValue); |                 this.setAngle(this.start, nextValue); | ||||||
|                 return true; |                 return true; | ||||||
|             case 'ellipse': { |             case 'ellipse': { | ||||||
|  |                 if (isEqual(nextValue, prevValue)) return true; | ||||||
|                 const value = nextValue as EllipseParams; |                 const value = nextValue as EllipseParams; | ||||||
|                 if (!this.assertType(value, Array, key)) return false; |                 if (!this.assertType(value, Array, key)) return false; | ||||||
|                 const [cx, cy, radiusX, radiusY, start, end] = value; |                 const [cx, cy, radiusX, radiusY, start, end] = value; | ||||||
| @ -619,6 +621,7 @@ export class Line extends GraphicItemBase { | |||||||
|                 this.setPoint2(this.x2, nextValue); |                 this.setPoint2(this.x2, nextValue); | ||||||
|                 return true; |                 return true; | ||||||
|             case 'line': |             case 'line': | ||||||
|  |                 if (isEqual(nextValue, prevValue)) return true; | ||||||
|                 if (!this.assertType(nextValue as number[], Array, key)) { |                 if (!this.assertType(nextValue as number[], Array, key)) { | ||||||
|                     return false; |                     return false; | ||||||
|                 } |                 } | ||||||
| @ -750,6 +753,7 @@ export class BezierCurve extends GraphicItemBase { | |||||||
|                 this.setEnd(this.ex, nextValue); |                 this.setEnd(this.ex, nextValue); | ||||||
|                 return true; |                 return true; | ||||||
|             case 'curve': |             case 'curve': | ||||||
|  |                 if (isEqual(nextValue, prevValue)) return true; | ||||||
|                 if (!this.assertType(nextValue as number[], Array, key)) { |                 if (!this.assertType(nextValue as number[], Array, key)) { | ||||||
|                     return false; |                     return false; | ||||||
|                 } |                 } | ||||||
| @ -873,6 +877,7 @@ export class QuadraticCurve extends GraphicItemBase { | |||||||
|                 this.setEnd(this.ex, nextValue); |                 this.setEnd(this.ex, nextValue); | ||||||
|                 return true; |                 return true; | ||||||
|             case 'curve': |             case 'curve': | ||||||
|  |                 if (isEqual(nextValue, prevValue)) return true; | ||||||
|                 if (!this.assertType(nextValue as number[], Array, key)) { |                 if (!this.assertType(nextValue as number[], Array, key)) { | ||||||
|                     return false; |                     return false; | ||||||
|                 } |                 } | ||||||
|  | |||||||
| @ -98,6 +98,8 @@ export class Hotkey extends EventEmitter<HotkeyEvent> { | |||||||
| 
 | 
 | ||||||
|     /** 当前正在按下的按键 */ |     /** 当前正在按下的按键 */ | ||||||
|     private pressed: Set<KeyCode> = new Set(); |     private pressed: Set<KeyCode> = new Set(); | ||||||
|  |     /** 有哪些单次按下的按键已经触发 */ | ||||||
|  |     private downEmitted: Set<KeyCode> = new Set(); | ||||||
|     /** 按键按下时的时间 */ |     /** 按键按下时的时间 */ | ||||||
|     private pressTime: Map<KeyCode, number> = new Map(); |     private pressTime: Map<KeyCode, number> = new Map(); | ||||||
|     /** 按键节流时间 */ |     /** 按键节流时间 */ | ||||||
| @ -262,6 +264,10 @@ export class Hotkey extends EventEmitter<HotkeyEvent> { | |||||||
|         }); |         }); | ||||||
|         this.emit('emit', key, assist, type); |         this.emit('emit', key, assist, type); | ||||||
| 
 | 
 | ||||||
|  |         if (type === 'down') { | ||||||
|  |             this.downEmitted.add(key); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         return emitted; |         return emitted; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -284,6 +290,7 @@ export class Hotkey extends EventEmitter<HotkeyEvent> { | |||||||
|         if (!this.pressed.has(keyCode)) return; |         if (!this.pressed.has(keyCode)) return; | ||||||
|         this.pressed.delete(keyCode); |         this.pressed.delete(keyCode); | ||||||
|         this.pressTime.delete(keyCode); |         this.pressTime.delete(keyCode); | ||||||
|  |         this.downEmitted.delete(keyCode); | ||||||
|         this.emit('release', keyCode); |         this.emit('release', keyCode); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -306,7 +313,7 @@ export class Hotkey extends EventEmitter<HotkeyEvent> { | |||||||
|         } |         } | ||||||
|         if (!config) return false; |         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-repeat') return true; | ||||||
|         if (config.type === 'down-timeout') { |         if (config.type === 'down-timeout') { | ||||||
|  | |||||||
| @ -247,9 +247,6 @@ control.prototype.showStartAnimate = function (noAnimate, callback) { | |||||||
|             core.flags.startUsingCanvas, |             core.flags.startUsingCanvas, | ||||||
|             callback |             callback | ||||||
|         ); |         ); | ||||||
|     Mota.r(() => { |  | ||||||
|         Mota.require('@motajs/legacy-ui').fixedUi.open('start'); |  | ||||||
|     }); |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| control.prototype._showStartAnimate_resetDom = function () { | control.prototype._showStartAnimate_resetDom = function () { | ||||||
| @ -526,17 +523,20 @@ control.prototype.setHeroMoveInterval = function (callback) { | |||||||
|     //     render.move(true);
 |     //     render.move(true);
 | ||||||
|     // });
 |     // });
 | ||||||
| 
 | 
 | ||||||
|     core.interval.heroMoveInterval = window.setInterval(function () { |     core.interval.heroMoveInterval = window.setInterval( | ||||||
|         // render.offset += toAdd * 4;
 |         function () { | ||||||
|         core.status.heroMoving += toAdd; |             // render.offset += toAdd * 4;
 | ||||||
|         if (core.status.heroMoving >= 8) { |             core.status.heroMoving += toAdd; | ||||||
|             clearInterval(core.interval.heroMoveInterval); |             if (core.status.heroMoving >= 8) { | ||||||
|             core.status.heroMoving = 0; |                 clearInterval(core.interval.heroMoveInterval); | ||||||
|             // render.offset = 0;
 |                 core.status.heroMoving = 0; | ||||||
|             // render.move(false);
 |                 // render.offset = 0;
 | ||||||
|             if (callback) callback(); |                 // render.move(false);
 | ||||||
|         } |                 if (callback) callback(); | ||||||
|     }, ((core.values.moveSpeed / 8) * toAdd) / core.status.replay.speed); |             } | ||||||
|  |         }, | ||||||
|  |         ((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) { | events.prototype.startGame = function (hard, seed, route, callback) { | ||||||
|     hard = hard || ''; |     hard = hard || ''; | ||||||
|     if (!main.replayChecking) { |  | ||||||
|         Mota.require('@motajs/legacy-ui').fixedUi.closeByName('start'); |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     if (main.mode != 'play') return; |     if (main.mode != 'play') return; | ||||||
|     Mota.require('@user/data-state').resetSkillLevel(); |     Mota.require('@user/data-state').resetSkillLevel(); | ||||||
| @ -701,12 +698,12 @@ events.prototype.getItem = function (id, num, x, y, isGentleClick, callback) { | |||||||
|                     (id.endsWith('Key') |                     (id.endsWith('Key') | ||||||
|                         ? '(钥匙类道具,遇到对应的门时自动打开)' |                         ? '(钥匙类道具,遇到对应的门时自动打开)' | ||||||
|                         : itemCls == 'tools' |                         : itemCls == 'tools' | ||||||
|                         ? '(消耗类道具,请按T在道具栏使用)' |                           ? '(消耗类道具,请按T在道具栏使用)' | ||||||
|                         : itemCls == 'constants' |                           : itemCls == 'constants' | ||||||
|                         ? '(永久类道具,请按T在道具栏使用)' |                             ? '(永久类道具,请按T在道具栏使用)' | ||||||
|                         : itemCls == 'equips' |                             : itemCls == 'equips' | ||||||
|                         ? '(装备类道具,请按Q在装备栏进行装备)' |                               ? '(装备类道具,请按Q在装备栏进行装备)' | ||||||
|                         : '') |                               : '') | ||||||
|             ); |             ); | ||||||
|         } |         } | ||||||
|         itemHint.push(id); |         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; |     box-sizing: border-box; | ||||||
|     -moz-box-sizing: border-box; |     -moz-box-sizing: border-box; | ||||||
|     -webkit-box-sizing: border-box; |     -webkit-box-sizing: border-box; | ||||||
|  |     display: none; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #curtain { | #curtain { | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user