From 2e3c3683546cb11494a6799f2a081ccf4c976d38 Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Thu, 20 Feb 2025 21:57:49 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=96=B0=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E6=A0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/libs/control.js | 4 +- src/core/render/item.ts | 33 +++++++++ src/core/render/render.ts | 7 +- src/core/render/renderer/index.ts | 6 +- src/core/render/renderer/props.ts | 9 +++ src/core/render/utils.ts | 11 +++ src/module/render/index.tsx | 83 +++------------------ src/module/render/shared.ts | 10 +++ src/module/render/ui/index.ts | 2 + src/module/render/ui/main.tsx | 112 +++++++++++++++++++++++++++++ src/module/render/ui/statusBar.tsx | 56 +++++++++++++++ src/module/render/use.ts | 60 ++++++++++++++++ 12 files changed, 316 insertions(+), 77 deletions(-) create mode 100644 src/module/render/shared.ts create mode 100644 src/module/render/ui/index.ts create mode 100644 src/module/render/ui/main.tsx create mode 100644 src/module/render/ui/statusBar.tsx create mode 100644 src/module/render/use.ts diff --git a/public/libs/control.js b/public/libs/control.js index af95525..25a6f9f 100644 --- a/public/libs/control.js +++ b/public/libs/control.js @@ -3149,8 +3149,8 @@ control.prototype.resize = function () { core.domStyle.scale = target - 0.25; } - const pw = core._PX_ * core.domStyle.scale; - const ph = core._PY_ * core.domStyle.scale; + const pw = (480 + 180) * core.domStyle.scale; + const ph = 480 * core.domStyle.scale; core.dom.gameDraw.style.width = `${pw}px`; core.dom.gameDraw.style.height = `${ph}px`; diff --git a/src/core/render/item.ts b/src/core/render/item.ts index 6d012f9..99de344 100644 --- a/src/core/render/item.ts +++ b/src/core/render/item.ts @@ -895,6 +895,18 @@ export abstract class RenderItem * @param expected 期望类型 * @param key 键名 */ + protected assertType(value: any, expected: string, key: string): boolean; + /** + * 判断一个prop是否是期望类型 + * @param value 实际值 + * @param expected 期望类型 + * @param key 键名 + */ + protected assertType( + value: any, + expected: new (...params: any[]) => T, + key: string + ): value is T; protected assertType( value: any, expected: string | (new (...params: any[]) => any), @@ -1030,6 +1042,27 @@ export abstract class RenderItem this.setComposite(nextValue); return; } + case 'loc': { + if (!this.assertType(nextValue, Array, key)) return; + if (!isNil(nextValue[0]) && !isNil(nextValue[1])) { + this.pos(nextValue[0] as number, nextValue[1] as number); + } + if (!isNil(nextValue[2]) && !isNil(nextValue[3])) { + this.size(nextValue[2] as number, nextValue[3] as number); + } + if (!isNil(nextValue[4]) && !isNil(nextValue[5])) { + this.setAnchor( + nextValue[4] as number, + nextValue[5] as number + ); + } + return; + } + case 'anc': { + if (!this.assertType(nextValue, Array, key)) return; + this.setAnchor(nextValue[0] as number, nextValue[1] as number); + return; + } } const ev = this.parseEvent(key); if (ev) { diff --git a/src/core/render/render.ts b/src/core/render/render.ts index ee2a2cd..02c98bf 100644 --- a/src/core/render/render.ts +++ b/src/core/render/render.ts @@ -62,7 +62,6 @@ export class MotaRenderer extends Container implements IRenderTreeRoot { this.target = new MotaOffscreenCanvas2D(true, canvas); this.size(core._PX_, core._PY_); this.target.withGameScale(true); - this.target.size(core._PX_, core._PY_); this.target.setAntiAliasing(false); this.setAnchor(0.5, 0.5); @@ -81,6 +80,12 @@ export class MotaRenderer extends Container implements IRenderTreeRoot { this.listen(); } + size(width: number, height: number): void { + super.size(width, height); + this.target.size(width, height); + this.transform.setTranslate(width / 2, height / 2); + } + private listen() { // 画布监听 const canvas = this.target.canvas; diff --git a/src/core/render/renderer/index.ts b/src/core/render/renderer/index.ts index 636f821..c6d84e5 100644 --- a/src/core/render/renderer/index.ts +++ b/src/core/render/renderer/index.ts @@ -49,7 +49,11 @@ export const { createApp, render } = createRenderer({ }, createText: function (text: string): RenderItem { - if (!/^\s*$/.test(text)) logger.warn(38); + if (/^\s*$/.test(text)) { + return new Comment(); + } else { + logger.warn(38); + } return new Text(text); }, diff --git a/src/core/render/renderer/props.ts b/src/core/render/renderer/props.ts index c947383..0f155ab 100644 --- a/src/core/render/renderer/props.ts +++ b/src/core/render/renderer/props.ts @@ -7,6 +7,7 @@ import { } from '../preset/layer'; import type { EnemyCollection } from '@/game/enemy/damage'; import { ILineProperty } from '../preset/graphics'; +import { ElementAnchor, ElementLocator } from '../utils'; export interface CustomProps { _item: (props: BaseProps) => RenderItem; @@ -32,6 +33,14 @@ export interface BaseProps { alpha?: number; composite?: GlobalCompositeOperation; cursor?: string; + /** + * 定位属性,可以填 `[横坐标,纵坐标,宽度,高度,x锚点,y锚点]`, + * 对于横坐标与纵坐标、宽度与高度、x锚点与y锚点,两两一组要么都填,要么都不填 + * 是 x, y, width, height, anchorX, anchorY 的简写属性 + */ + loc?: ElementLocator; + /** 锚点属性,可以填 `[x锚点,y锚点]`,是 anchorX, anchorY 的简写属性 */ + anc?: ElementAnchor; } export interface SpriteProps extends BaseProps { diff --git a/src/core/render/utils.ts b/src/core/render/utils.ts index b0dd5db..b1b4308 100644 --- a/src/core/render/utils.ts +++ b/src/core/render/utils.ts @@ -19,6 +19,17 @@ export type Props< ? InstanceType['$props'] & InstanceType['$emits'] : unknown; +export type ElementLocator = [ + x?: number, + y?: number, + width?: number, + height?: number, + anchorX?: number, + anchorY?: number +]; + +export type ElementAnchor = [x: number, y: number]; + export function disableViewport() { const adapter = RenderAdapter.get('viewport'); if (!adapter) return; diff --git a/src/module/render/index.tsx b/src/module/render/index.tsx index 7b399a6..0bfa2d6 100644 --- a/src/module/render/index.tsx +++ b/src/module/render/index.tsx @@ -1,86 +1,21 @@ -import { FloorItemDetail } from '@/plugin/fx/itemDetail'; -import { FloorDamageExtends, LayerGroup } from '@/core/render'; -import { LayerDoorAnimate } from '@/core/render'; -import { HeroRenderer } from '@/core/render'; import { MotaRenderer } from '@/core/render'; -import { LayerShadowExtends } from '@/core/fx/shadow'; -import { LayerGroupFilter } from '@/plugin/fx/gameCanvas'; -import { LayerGroupAnimate } from '@/core/render'; -import { LayerGroupPortal } from '@/plugin/fx/portal'; -import { LayerGroupHalo } from '@/plugin/fx/halo'; -import { FloorViewport } from '@/core/render'; -import { PopText } from '@/plugin/fx/pop'; -import { FloorChange } from '@/plugin/fallback'; import { createApp } from '@/core/render'; -import { defineComponent, onMounted, ref } from 'vue'; -import { Textbox } from './components'; -import { ILayerGroupRenderExtends, ILayerRenderExtends } from '@/core/render'; -import { Props } from '@/core/render'; -import { WeatherController } from '../weather'; +import { defineComponent } from 'vue'; import { UIController } from '@/core/system'; +import { mainSceneUI } from './ui/main'; +import { MAIN_HEIGHT, MAIN_WIDTH } from './shared'; export function create() { const main = new MotaRenderer(); + main.size(MAIN_WIDTH, MAIN_HEIGHT); const App = defineComponent(_props => { - const layerGroupExtends: ILayerGroupRenderExtends[] = [ - new FloorDamageExtends(), - new FloorItemDetail(), - new LayerGroupFilter(), - new LayerGroupPortal(), - new LayerGroupHalo(), - new LayerGroupAnimate(), - new FloorViewport() - ]; - const eventExtends: ILayerRenderExtends[] = [ - new HeroRenderer(), - new LayerDoorAnimate(), - new LayerShadowExtends() - ]; - const mapDrawProps: Props<'container'> = { - width: core._PX_, - height: core._PY_ - }; - const mainTextboxProps: Props = { - text: '', - hidden: true, - width: 480, - height: 150, - y: 330, - zIndex: 30, - fillStyle: '#fff', - titleFill: 'gold', - fontFamily: 'normal', - titleFont: '700 20px normal', - winskin: 'winskin2.png', - interval: 100, - lineHeight: 6 - }; - - const map = ref(); - const weather = new WeatherController('main'); - - onMounted(() => { - weather.bind(map.value); - }); - - const ui = new UIController('main-ui'); + const ui = new UIController('root-ui'); + ui.open(mainSceneUI, {}); return () => ( - - - {ui.render()} - - - - - - - - - - - + + {ui.render()} ); }); @@ -100,3 +35,5 @@ export function create() { } export * from './components'; +export * from './ui'; +export * from './use'; diff --git a/src/module/render/shared.ts b/src/module/render/shared.ts new file mode 100644 index 0000000..2365800 --- /dev/null +++ b/src/module/render/shared.ts @@ -0,0 +1,10 @@ +export const STATUS_BAR_WIDTH = 180; +export const STATUS_BAR_HEIGHT = 480; + +export const ENABLE_RIGHT_STATUS_BAR = true; + +export const MAP_WIDTH = 480; +export const MAP_HEIGHT = 480; + +export const MAIN_WIDTH = 480 + 180; +export const MAIN_HEIGHT = 480; diff --git a/src/module/render/ui/index.ts b/src/module/render/ui/index.ts new file mode 100644 index 0000000..b302aa2 --- /dev/null +++ b/src/module/render/ui/index.ts @@ -0,0 +1,2 @@ +export * from './main'; +export * from './statusBar'; diff --git a/src/module/render/ui/main.tsx b/src/module/render/ui/main.tsx new file mode 100644 index 0000000..348a1b1 --- /dev/null +++ b/src/module/render/ui/main.tsx @@ -0,0 +1,112 @@ +import { LayerShadowExtends } from '@/core/fx/shadow'; +import { + ILayerGroupRenderExtends, + FloorDamageExtends, + LayerGroupAnimate, + FloorViewport, + ILayerRenderExtends, + HeroRenderer, + LayerDoorAnimate, + Props, + LayerGroup +} from '@/core/render'; +import { WeatherController } from '@/module/weather'; +import { FloorChange } from '@/plugin/fallback'; +import { LayerGroupFilter } from '@/plugin/fx/gameCanvas'; +import { LayerGroupHalo } from '@/plugin/fx/halo'; +import { FloorItemDetail } from '@/plugin/fx/itemDetail'; +import { PopText } from '@/plugin/fx/pop'; +import { LayerGroupPortal } from '@/plugin/fx/portal'; +import { defineComponent, onMounted, reactive, ref } from 'vue'; +import { Textbox } from '../components'; +import { GameUI, UIController } from '@/core/system'; +import { + MAIN_HEIGHT, + MAIN_WIDTH, + STATUS_BAR_HEIGHT, + STATUS_BAR_WIDTH +} from '../shared'; +import { IHeroStatus, StatusBar } from './statusBar'; +import { onLoaded } from '../use'; + +const MainScene = defineComponent(() => { + const layerGroupExtends: ILayerGroupRenderExtends[] = [ + new FloorDamageExtends(), + new FloorItemDetail(), + new LayerGroupFilter(), + new LayerGroupPortal(), + new LayerGroupHalo(), + new LayerGroupAnimate(), + new FloorViewport() + ]; + const eventExtends: ILayerRenderExtends[] = [ + new HeroRenderer(), + new LayerDoorAnimate(), + new LayerShadowExtends() + ]; + const mapDrawProps: Props<'container'> = { + width: core._PX_, + height: core._PY_ + }; + const mainTextboxProps: Props = { + text: '', + hidden: true, + width: 480, + height: 150, + y: 330, + zIndex: 30, + fillStyle: '#fff', + titleFill: 'gold', + fontFamily: 'normal', + titleFont: '700 20px normal', + winskin: 'winskin2.png', + interval: 100, + lineHeight: 6 + }; + + const map = ref(); + const weather = new WeatherController('main'); + + onMounted(() => { + weather.bind(map.value); + }); + + const status: IHeroStatus = reactive({ + hp: 0, + atk: 0, + def: 0, + mdef: 0 + }); + + const loaded = ref(false); + onLoaded(() => { + loaded.value = true; + }); + + return () => ( + + {loaded.value && ( + + )} + + + + + + + + + + + + + {mainUIController.render()} + + ); +}); + +export const mainSceneUI = new GameUI('main-scene', MainScene); +export const mainUIController = new UIController('main-ui'); diff --git a/src/module/render/ui/statusBar.tsx b/src/module/render/ui/statusBar.tsx new file mode 100644 index 0000000..ed59321 --- /dev/null +++ b/src/module/render/ui/statusBar.tsx @@ -0,0 +1,56 @@ +import { GameUI } from '@/core/system'; +import { defineComponent } from 'vue'; +import { SetupComponentOptions } from '../components'; +import { ElementLocator } from '@/core/render'; + +export interface IHeroStatus { + hp: number; + atk: number; + def: number; + mdef: number; +} + +interface StatusBarProps { + loc: ElementLocator; + status: IHeroStatus; +} + +const statusBarProps = { + props: ['loc', 'status'] +} satisfies SetupComponentOptions; + +export const StatusBar = defineComponent(p => { + const hpIcon = core.material.images.images['hp.png']; + const atkIcon = core.material.images.images['atk.png']; + const defIcon = core.material.images.images['def.png']; + const mdefIcon = core.material.images.images['IQ.png']; + + const s = p.status; + const f = core.formatBigNumber; + + const iconLoc = (n: number): ElementLocator => { + return [16, 16 + 48 * n, 32, 32]; + }; + + const textLoc = (n: number): ElementLocator => { + return [64, 32 + 48 * n, void 0, void 0, 0.5, 0.5]; + }; + + return () => { + return ( + + + + + + + + + + + + ); + }; +}, statusBarProps); + +export const statusBarUI = new GameUI('status-bar', StatusBar); diff --git a/src/module/render/use.ts b/src/module/render/use.ts new file mode 100644 index 0000000..60a1623 --- /dev/null +++ b/src/module/render/use.ts @@ -0,0 +1,60 @@ +import { onMounted, onUnmounted } from 'vue'; + +export const enum Orientation { + /** 横屏 */ + Landscape, + /** 竖屏 */ + Portrait +} + +export type OrientationHook = ( + orientation: Orientation, + width: number, + height: number +) => void; + +let nowOrientation = Orientation.Landscape; +const orientationHooks = new Set(); + +function checkOrientation() { + const before = nowOrientation; + // 只要宽度大于高度,那么就视为横屏 + if (window.innerWidth >= window.innerHeight) { + nowOrientation = Orientation.Landscape; + } else { + nowOrientation = Orientation.Portrait; + } + if (nowOrientation === before) return; + + orientationHooks.forEach(v => { + v(nowOrientation, window.innerWidth, window.innerHeight); + }); +} +window.addEventListener('resize', checkOrientation); + +/** + * 当屏幕方向改变时执行函数 + * @param hook 当屏幕方向改变时执行的函数 + */ +export function onOrientationChange(hook: OrientationHook) { + onMounted(() => { + orientationHooks.add(hook); + hook(nowOrientation, window.innerWidth, window.innerHeight); + }); + onUnmounted(() => { + orientationHooks.delete(hook); + }); +} + +/** + * 当游戏加载完成时执行函数,如果调用此函数时游戏已经加载,那么会立刻调用传入的钩子函数 + * @param hook 当游戏加载完成时执行的函数 + */ +export function onLoaded(hook: () => void) { + const loading = Mota.require('var', 'loading'); + if (!loading.loaded) { + loading.once('loaded', hook); + } else { + hook(); + } +}