mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-05-01 20:03:24 +08:00
refactor: 新状态栏
This commit is contained in:
parent
6ba0b4a762
commit
2e3c368354
@ -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`;
|
||||
|
||||
|
@ -895,6 +895,18 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
* @param expected 期望类型
|
||||
* @param key 键名
|
||||
*/
|
||||
protected assertType(value: any, expected: string, key: string): boolean;
|
||||
/**
|
||||
* 判断一个prop是否是期望类型
|
||||
* @param value 实际值
|
||||
* @param expected 期望类型
|
||||
* @param key 键名
|
||||
*/
|
||||
protected assertType<T>(
|
||||
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<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
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) {
|
||||
|
@ -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;
|
||||
|
@ -49,7 +49,11 @@ export const { createApp, render } = createRenderer<RenderItem, RenderItem>({
|
||||
},
|
||||
|
||||
createText: function (text: string): RenderItem<ETextEvent> {
|
||||
if (!/^\s*$/.test(text)) logger.warn(38);
|
||||
if (/^\s*$/.test(text)) {
|
||||
return new Comment();
|
||||
} else {
|
||||
logger.warn(38);
|
||||
}
|
||||
return new Text(text);
|
||||
},
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -19,6 +19,17 @@ export type Props<
|
||||
? InstanceType<T>['$props'] & InstanceType<T>['$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<FloorViewport>('viewport');
|
||||
if (!adapter) return;
|
||||
|
@ -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<typeof Textbox> = {
|
||||
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<LayerGroup>();
|
||||
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 () => (
|
||||
<container id="map-draw" {...mapDrawProps}>
|
||||
<container width={480} height={480}>
|
||||
{ui.render()}
|
||||
</container>
|
||||
<layer-group id="layer-main" ex={layerGroupExtends} ref={map}>
|
||||
<layer layer="bg" zIndex={10}></layer>
|
||||
<layer layer="bg2" zIndex={20}></layer>
|
||||
<layer layer="event" zIndex={30} ex={eventExtends}></layer>
|
||||
<layer layer="fg" zIndex={40}></layer>
|
||||
<layer layer="fg2" zIndex={50}></layer>
|
||||
<PopText id="pop-main" zIndex={80}></PopText>
|
||||
</layer-group>
|
||||
<Textbox id="main-textbox" {...mainTextboxProps}></Textbox>
|
||||
<FloorChange id="floor-change" zIndex={50}></FloorChange>
|
||||
<container width={MAIN_WIDTH} height={MAIN_HEIGHT}>
|
||||
{ui.render()}
|
||||
</container>
|
||||
);
|
||||
});
|
||||
@ -100,3 +35,5 @@ export function create() {
|
||||
}
|
||||
|
||||
export * from './components';
|
||||
export * from './ui';
|
||||
export * from './use';
|
||||
|
10
src/module/render/shared.ts
Normal file
10
src/module/render/shared.ts
Normal file
@ -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;
|
2
src/module/render/ui/index.ts
Normal file
2
src/module/render/ui/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './main';
|
||||
export * from './statusBar';
|
112
src/module/render/ui/main.tsx
Normal file
112
src/module/render/ui/main.tsx
Normal file
@ -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<typeof Textbox> = {
|
||||
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<LayerGroup>();
|
||||
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 () => (
|
||||
<container id="main-scene" width={MAIN_WIDTH} height={MAIN_HEIGHT}>
|
||||
{loaded.value && (
|
||||
<StatusBar
|
||||
loc={[0, 0, STATUS_BAR_WIDTH, STATUS_BAR_HEIGHT]}
|
||||
status={status}
|
||||
></StatusBar>
|
||||
)}
|
||||
<container id="map-draw" {...mapDrawProps} x={180} zIndex={10}>
|
||||
<layer-group id="layer-main" ex={layerGroupExtends} ref={map}>
|
||||
<layer layer="bg" zIndex={10}></layer>
|
||||
<layer layer="bg2" zIndex={20}></layer>
|
||||
<layer layer="event" zIndex={30} ex={eventExtends}></layer>
|
||||
<layer layer="fg" zIndex={40}></layer>
|
||||
<layer layer="fg2" zIndex={50}></layer>
|
||||
<PopText id="pop-main" zIndex={80}></PopText>
|
||||
</layer-group>
|
||||
<Textbox id="main-textbox" {...mainTextboxProps}></Textbox>
|
||||
<FloorChange id="floor-change" zIndex={50}></FloorChange>
|
||||
</container>
|
||||
{mainUIController.render()}
|
||||
</container>
|
||||
);
|
||||
});
|
||||
|
||||
export const mainSceneUI = new GameUI('main-scene', MainScene);
|
||||
export const mainUIController = new UIController('main-ui');
|
56
src/module/render/ui/statusBar.tsx
Normal file
56
src/module/render/ui/statusBar.tsx
Normal file
@ -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<StatusBarProps>;
|
||||
|
||||
export const StatusBar = defineComponent<StatusBarProps>(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 (
|
||||
<container loc={p.loc}>
|
||||
<g-rect loc={[0, 0, p.loc[2], p.loc[3]]} stroke></g-rect>
|
||||
<image image={hpIcon} loc={iconLoc(0)}></image>
|
||||
<text text={f(s.hp)} loc={textLoc(0)}></text>
|
||||
<image image={atkIcon} loc={iconLoc(1)}></image>
|
||||
<text text={f(s.atk)} loc={textLoc(1)}></text>
|
||||
<image image={defIcon} loc={iconLoc(2)}></image>
|
||||
<text text={f(s.atk)} loc={textLoc(2)}></text>
|
||||
<image image={mdefIcon} loc={iconLoc(3)}></image>
|
||||
<text text={f(s.atk)} loc={textLoc(3)}></text>
|
||||
</container>
|
||||
);
|
||||
};
|
||||
}, statusBarProps);
|
||||
|
||||
export const statusBarUI = new GameUI('status-bar', StatusBar);
|
60
src/module/render/use.ts
Normal file
60
src/module/render/use.ts
Normal file
@ -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<OrientationHook>();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user