refactor: 新状态栏

This commit is contained in:
unanmed 2025-02-20 21:57:49 +08:00
parent 6ba0b4a762
commit 2e3c368354
12 changed files with 316 additions and 77 deletions

View File

@ -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`;

View File

@ -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) {

View File

@ -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;

View File

@ -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);
},

View File

@ -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 {

View File

@ -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;

View File

@ -1,87 +1,22 @@
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}>
<container width={MAIN_WIDTH} height={MAIN_HEIGHT}>
{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>
);
});
@ -100,3 +35,5 @@ export function create() {
}
export * from './components';
export * from './ui';
export * from './use';

View 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;

View File

@ -0,0 +1,2 @@
export * from './main';
export * from './statusBar';

View 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');

View 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
View 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();
}
}