mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-04-18 17:48:52 +08:00
refactor: MotaOffscreenCanvas2D 的垃圾回收机制
This commit is contained in:
parent
e79c530c48
commit
ac5eefdb02
@ -3149,7 +3149,7 @@ control.prototype.resize = function () {
|
|||||||
core.domStyle.scale = target - 0.25;
|
core.domStyle.scale = target - 0.25;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pw = (480 + 180) * core.domStyle.scale;
|
const pw = (480 + 180 * 2) * core.domStyle.scale;
|
||||||
const ph = 480 * core.domStyle.scale;
|
const ph = 480 * core.domStyle.scale;
|
||||||
core.dom.gameDraw.style.width = `${pw}px`;
|
core.dom.gameDraw.style.width = `${pw}px`;
|
||||||
core.dom.gameDraw.style.height = `${ph}px`;
|
core.dom.gameDraw.style.height = `${ph}px`;
|
||||||
|
@ -33,8 +33,14 @@ export class MotaOffscreenCanvas2D extends EventEmitter<OffscreenCanvasEvent> {
|
|||||||
return this._freezed;
|
return this._freezed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _active: boolean = true;
|
||||||
|
get active() {
|
||||||
|
return this._active;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建一个新的离屏画布
|
* 创建一个新的离屏画布\
|
||||||
|
* **注意**:如果你在自定义渲染元素中使用,请避免使用此构造函数,而应该使用 `RenderItem.requireCanvas`
|
||||||
* @param alpha 是否启用透明度通道
|
* @param alpha 是否启用透明度通道
|
||||||
* @param canvas 指定画布,不指定时会自动创建一个新画布
|
* @param canvas 指定画布,不指定时会自动创建一个新画布
|
||||||
*/
|
*/
|
||||||
@ -146,6 +152,27 @@ export class MotaOffscreenCanvas2D extends EventEmitter<OffscreenCanvasEvent> {
|
|||||||
MotaOffscreenCanvas2D.list.delete(this);
|
MotaOffscreenCanvas2D.list.delete(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使此画布生效,使用前请务必调用此函数,生效后会跟随游戏的放缩比例更改大小,但会导致不会被垃圾回收
|
||||||
|
*/
|
||||||
|
activate() {
|
||||||
|
if (this._active || this._freezed) return;
|
||||||
|
MotaOffscreenCanvas2D.list.add(this);
|
||||||
|
if (this.autoScale) {
|
||||||
|
this.size(this.width, this.height);
|
||||||
|
this.symbol++;
|
||||||
|
this.emit('resize');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使此画布失效,当这个画布暂时不会被使用时请务必调用此函数,失效后若没有对此画布的引用,那么会自动垃圾回收
|
||||||
|
*/
|
||||||
|
deactivate() {
|
||||||
|
if (!this._active || this._freezed) return;
|
||||||
|
MotaOffscreenCanvas2D.list.delete(this);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 复制一个离屏Canvas2D对象,一般用于缓存等操作
|
* 复制一个离屏Canvas2D对象,一般用于缓存等操作
|
||||||
* @param canvas 被复制的MotaOffscreenCanvas2D对象
|
* @param canvas 被复制的MotaOffscreenCanvas2D对象
|
||||||
|
@ -316,13 +316,15 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
|
|
||||||
//#region 渲染配置与缓存
|
//#region 渲染配置与缓存
|
||||||
/** 渲染缓存信息 */
|
/** 渲染缓存信息 */
|
||||||
protected cache: MotaOffscreenCanvas2D = new MotaOffscreenCanvas2D();
|
protected cache: MotaOffscreenCanvas2D;
|
||||||
/** 是否需要更新缓存 */
|
/** 是否需要更新缓存 */
|
||||||
protected cacheDirty: boolean = false;
|
protected cacheDirty: boolean = false;
|
||||||
/** 是否启用缓存机制 */
|
/** 是否启用缓存机制 */
|
||||||
readonly enableCache: boolean = true;
|
readonly enableCache: boolean = true;
|
||||||
/** 是否启用transform下穿机制,即画布的变换是否会继续作用到下一层画布 */
|
/** 是否启用transform下穿机制,即画布的变换是否会继续作用到下一层画布 */
|
||||||
readonly transformFallThrough: boolean = false;
|
readonly transformFallThrough: boolean = false;
|
||||||
|
/** 这个渲染元素使用到的所有画布 */
|
||||||
|
protected readonly canvases: Set<MotaOffscreenCanvas2D> = new Set();
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region 交互事件
|
//#region 交互事件
|
||||||
@ -356,6 +358,7 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
this.type = type;
|
this.type = type;
|
||||||
|
|
||||||
this._transform.bind(this);
|
this._transform.bind(this);
|
||||||
|
this.cache = this.requireCanvas();
|
||||||
this.cache.withGameScale(true);
|
this.cache.withGameScale(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -414,6 +417,16 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
this.emit('afterRender', transform);
|
this.emit('afterRender', transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 申请一个 `MotaOffscreenCanvas2D`,即申请一个画布
|
||||||
|
* @param alpha 是否启用画布的 alpha 通道
|
||||||
|
*/
|
||||||
|
protected requireCanvas(alpha: boolean = true) {
|
||||||
|
const canvas = new MotaOffscreenCanvas2D(alpha);
|
||||||
|
this.canvases.add(canvas);
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
|
||||||
//#region 修改元素属性
|
//#region 修改元素属性
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -625,6 +638,7 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
this.update();
|
this.update();
|
||||||
this.checkRoot();
|
this.checkRoot();
|
||||||
this._root?.connect(this);
|
this._root?.connect(this);
|
||||||
|
this.canvases.forEach(v => v.activate());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -638,6 +652,7 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
this._parent = void 0;
|
this._parent = void 0;
|
||||||
parent.requestSort();
|
parent.requestSort();
|
||||||
parent.update();
|
parent.update();
|
||||||
|
this.canvases.forEach(v => v.deactivate());
|
||||||
if (!success) return false;
|
if (!success) return false;
|
||||||
this._root?.disconnect(this);
|
this._root?.disconnect(this);
|
||||||
this._root = void 0;
|
this._root = void 0;
|
||||||
|
@ -498,8 +498,7 @@ export class Damage extends RenderItem<EDamageEvent> {
|
|||||||
this.emit('dirtyUpdate', v);
|
this.emit('dirtyUpdate', v);
|
||||||
|
|
||||||
// 否则依次渲染并写入缓存
|
// 否则依次渲染并写入缓存
|
||||||
const temp =
|
const temp = block.cache.get(v)?.canvas ?? this.requireCanvas();
|
||||||
block.cache.get(v)?.canvas ?? new MotaOffscreenCanvas2D();
|
|
||||||
temp.clear();
|
temp.clear();
|
||||||
temp.setHD(true);
|
temp.setHD(true);
|
||||||
temp.setAntiAliasing(true);
|
temp.setAntiAliasing(true);
|
||||||
|
@ -10,7 +10,6 @@ import { Transform } from '../transform';
|
|||||||
import { LayerFloorBinder, LayerGroupFloorBinder } from './floor';
|
import { LayerFloorBinder, LayerGroupFloorBinder } from './floor';
|
||||||
import { RenderAdapter } from '../adapter';
|
import { RenderAdapter } from '../adapter';
|
||||||
import { ElementNamespace, ComponentInternalInstance } from 'vue';
|
import { ElementNamespace, ComponentInternalInstance } from 'vue';
|
||||||
import { Camera } from '../camera';
|
|
||||||
import { IAnimateFrame, renderEmits } from '../frame';
|
import { IAnimateFrame, renderEmits } from '../frame';
|
||||||
|
|
||||||
export interface ILayerGroupRenderExtends {
|
export interface ILayerGroupRenderExtends {
|
||||||
@ -358,7 +357,7 @@ export class LayerGroup
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case 'camera':
|
case 'camera':
|
||||||
if (!this.assertType(nextValue, Camera, key)) return;
|
if (!this.assertType(nextValue, Transform, key)) return;
|
||||||
this.camera = nextValue;
|
this.camera = nextValue;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -571,11 +570,11 @@ export class Layer extends Container<ELayerEvent> {
|
|||||||
static readonly FRAME_ALL = 15;
|
static readonly FRAME_ALL = 15;
|
||||||
|
|
||||||
/** 静态层,包含除大怪物及正在移动的内容外的内容 */
|
/** 静态层,包含除大怪物及正在移动的内容外的内容 */
|
||||||
protected staticMap: MotaOffscreenCanvas2D = new MotaOffscreenCanvas2D();
|
protected staticMap: MotaOffscreenCanvas2D = this.requireCanvas();
|
||||||
/** 移动层,包含大怪物及正在移动的内容 */
|
/** 移动层,包含大怪物及正在移动的内容 */
|
||||||
protected movingMap: MotaOffscreenCanvas2D = new MotaOffscreenCanvas2D();
|
protected movingMap: MotaOffscreenCanvas2D = this.requireCanvas();
|
||||||
/** 背景图层 */
|
/** 背景图层 */
|
||||||
protected backMap: MotaOffscreenCanvas2D = new MotaOffscreenCanvas2D();
|
protected backMap: MotaOffscreenCanvas2D = this.requireCanvas();
|
||||||
|
|
||||||
/** 最终渲染至的Sprite */
|
/** 最终渲染至的Sprite */
|
||||||
main: Sprite = new Sprite('absolute', false, true);
|
main: Sprite = new Sprite('absolute', false, true);
|
||||||
@ -783,12 +782,12 @@ export class Layer extends Container<ELayerEvent> {
|
|||||||
if (!data) return;
|
if (!data) return;
|
||||||
|
|
||||||
const frame = data.frame;
|
const frame = data.frame;
|
||||||
const temp = new MotaOffscreenCanvas2D();
|
const temp = this.requireCanvas();
|
||||||
temp.setHD(false);
|
temp.setHD(false);
|
||||||
temp.setAntiAliasing(false);
|
temp.setAntiAliasing(false);
|
||||||
temp.withGameScale(false);
|
temp.withGameScale(false);
|
||||||
for (let i = 0; i < frame; i++) {
|
for (let i = 0; i < frame; i++) {
|
||||||
const canvas = new MotaOffscreenCanvas2D();
|
const canvas = this.requireCanvas();
|
||||||
const ctx = canvas.ctx;
|
const ctx = canvas.ctx;
|
||||||
const tempCtx = temp.ctx;
|
const tempCtx = temp.ctx;
|
||||||
const [sx, sy, w, h] = data.render[i];
|
const [sx, sy, w, h] = data.render[i];
|
||||||
@ -1206,7 +1205,7 @@ export class Layer extends Container<ELayerEvent> {
|
|||||||
const ex = Math.min(sx + blockSize, this.mapWidth);
|
const ex = Math.min(sx + blockSize, this.mapWidth);
|
||||||
const ey = Math.min(sy + blockSize, this.mapHeight);
|
const ey = Math.min(sy + blockSize, this.mapHeight);
|
||||||
|
|
||||||
const temp = new MotaOffscreenCanvas2D();
|
const temp = this.requireCanvas();
|
||||||
temp.setAntiAliasing(false);
|
temp.setAntiAliasing(false);
|
||||||
temp.setHD(false);
|
temp.setHD(false);
|
||||||
temp.withGameScale(false);
|
temp.withGameScale(false);
|
||||||
@ -1478,7 +1477,7 @@ export class Layer extends Container<ELayerEvent> {
|
|||||||
return;
|
return;
|
||||||
case 'floorImage':
|
case 'floorImage':
|
||||||
if (!this.assertType(nextValue, Array, key)) return;
|
if (!this.assertType(nextValue, Array, key)) return;
|
||||||
this.setFloorImage(nextValue);
|
this.setFloorImage(nextValue as FloorAnimate[]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
super.patchProp(key, prevValue, nextValue, namespace, parentComponent);
|
super.patchProp(key, prevValue, nextValue, namespace, parentComponent);
|
||||||
|
@ -19,7 +19,7 @@ export class Text extends RenderItem<ETextEvent> {
|
|||||||
|
|
||||||
fillStyle?: CanvasStyle = '#fff';
|
fillStyle?: CanvasStyle = '#fff';
|
||||||
strokeStyle?: CanvasStyle;
|
strokeStyle?: CanvasStyle;
|
||||||
font?: string = '';
|
font: string = '16px Verdana';
|
||||||
strokeWidth: number = 1;
|
strokeWidth: number = 1;
|
||||||
|
|
||||||
private length: number = 0;
|
private length: number = 0;
|
||||||
@ -42,7 +42,7 @@ export class Text extends RenderItem<ETextEvent> {
|
|||||||
ctx.textBaseline = 'bottom';
|
ctx.textBaseline = 'bottom';
|
||||||
ctx.fillStyle = this.fillStyle ?? 'transparent';
|
ctx.fillStyle = this.fillStyle ?? 'transparent';
|
||||||
ctx.strokeStyle = this.strokeStyle ?? 'transparent';
|
ctx.strokeStyle = this.strokeStyle ?? 'transparent';
|
||||||
ctx.font = this.font ?? '';
|
ctx.font = this.font;
|
||||||
ctx.lineWidth = this.strokeWidth;
|
ctx.lineWidth = this.strokeWidth;
|
||||||
|
|
||||||
if (this.strokeStyle) {
|
if (this.strokeStyle) {
|
||||||
@ -59,7 +59,7 @@ export class Text extends RenderItem<ETextEvent> {
|
|||||||
measure() {
|
measure() {
|
||||||
const ctx = Text.measureCanvas.ctx;
|
const ctx = Text.measureCanvas.ctx;
|
||||||
ctx.textBaseline = 'bottom';
|
ctx.textBaseline = 'bottom';
|
||||||
ctx.font = this.font ?? '';
|
ctx.font = this.font;
|
||||||
const res = ctx.measureText(this.text);
|
const res = ctx.measureText(this.text);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
42
src/module/render/components/misc.tsx
Normal file
42
src/module/render/components/misc.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { ElementLocator, Sprite } from '@/core/render';
|
||||||
|
import { defineComponent, onMounted, ref, watch } from 'vue';
|
||||||
|
import { SetupComponentOptions } from './types';
|
||||||
|
|
||||||
|
interface ProgressProps {
|
||||||
|
/** 进度条的位置 */
|
||||||
|
loc: ElementLocator;
|
||||||
|
/** 进度条的进度,1表示完成,0表示未完成 */
|
||||||
|
progress: number;
|
||||||
|
/** 已完成部分的样式,默认为绿色(green) */
|
||||||
|
success?: CanvasStyle;
|
||||||
|
/** 未完成部分的样式,默认为灰色(gray) */
|
||||||
|
background?: CanvasStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
const progressProps = {
|
||||||
|
props: ['loc', 'progress', 'success', 'background']
|
||||||
|
} satisfies SetupComponentOptions<ProgressProps>;
|
||||||
|
|
||||||
|
export const Progress = defineComponent<ProgressProps>(props => {
|
||||||
|
const element = ref<Sprite>();
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
element.value?.setRenderFn(canvas => {
|
||||||
|
const { ctx } = canvas;
|
||||||
|
const width = props.loc[2] ?? 200;
|
||||||
|
const height = props.loc[3] ?? 200;
|
||||||
|
ctx.fillStyle = props.background ?? 'gray';
|
||||||
|
ctx.fillRect(0, 0, width, height);
|
||||||
|
ctx.fillStyle = props.success ?? 'green';
|
||||||
|
ctx.fillRect(0, 0, width * props.progress, height);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(props, () => {
|
||||||
|
element.value?.update();
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
return <sprite ref={element} loc={props.loc}></sprite>;
|
||||||
|
};
|
||||||
|
}, progressProps);
|
@ -6,5 +6,5 @@ export const ENABLE_RIGHT_STATUS_BAR = true;
|
|||||||
export const MAP_WIDTH = 480;
|
export const MAP_WIDTH = 480;
|
||||||
export const MAP_HEIGHT = 480;
|
export const MAP_HEIGHT = 480;
|
||||||
|
|
||||||
export const MAIN_WIDTH = 480 + 180;
|
export const MAIN_WIDTH = 480 + 180 * 2;
|
||||||
export const MAIN_HEIGHT = 480;
|
export const MAIN_HEIGHT = 480;
|
||||||
|
@ -26,7 +26,12 @@ import {
|
|||||||
STATUS_BAR_HEIGHT,
|
STATUS_BAR_HEIGHT,
|
||||||
STATUS_BAR_WIDTH
|
STATUS_BAR_WIDTH
|
||||||
} from '../shared';
|
} from '../shared';
|
||||||
import { IHeroStatus, StatusBar } from './statusBar';
|
import {
|
||||||
|
ILeftHeroStatus,
|
||||||
|
IRightHeroStatus,
|
||||||
|
LeftStatusBar,
|
||||||
|
RightStatusBar
|
||||||
|
} from './statusBar';
|
||||||
import { onLoaded } from '../use';
|
import { onLoaded } from '../use';
|
||||||
|
|
||||||
const MainScene = defineComponent(() => {
|
const MainScene = defineComponent(() => {
|
||||||
@ -71,25 +76,58 @@ const MainScene = defineComponent(() => {
|
|||||||
weather.bind(map.value);
|
weather.bind(map.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
const status: IHeroStatus = reactive({
|
const leftStatus: ILeftHeroStatus = reactive({
|
||||||
hp: 0,
|
hp: 0,
|
||||||
atk: 0,
|
atk: 0,
|
||||||
def: 0,
|
def: 0,
|
||||||
mdef: 0
|
mdef: 0,
|
||||||
|
money: 0,
|
||||||
|
exp: 0,
|
||||||
|
yellowKey: 0,
|
||||||
|
blueKey: 0,
|
||||||
|
redKey: 0,
|
||||||
|
floor: 'MT0',
|
||||||
|
lv: '',
|
||||||
|
regen: 0,
|
||||||
|
exAtk: 0,
|
||||||
|
magicDef: 0
|
||||||
});
|
});
|
||||||
|
const rightStatus: IRightHeroStatus = reactive({});
|
||||||
|
|
||||||
|
const { getHeroStatusOn } = Mota.requireAll('fn');
|
||||||
|
|
||||||
|
const updateStatus = () => {
|
||||||
|
const hero = core.status.hero;
|
||||||
|
leftStatus.atk = getHeroStatusOn('atk');
|
||||||
|
leftStatus.hp = getHeroStatusOn('hp');
|
||||||
|
leftStatus.def = getHeroStatusOn('def');
|
||||||
|
leftStatus.mdef = getHeroStatusOn('mdef');
|
||||||
|
leftStatus.money = getHeroStatusOn('money');
|
||||||
|
leftStatus.exp = core.getNextLvUpNeed() ?? 0;
|
||||||
|
leftStatus.yellowKey = core.itemCount('yellowKey');
|
||||||
|
leftStatus.blueKey = core.itemCount('blueKey');
|
||||||
|
leftStatus.redKey = core.itemCount('redKey');
|
||||||
|
leftStatus.floor = core.status.floorId;
|
||||||
|
leftStatus.lv = core.getLvName(hero.lv);
|
||||||
|
leftStatus.regen = getHeroStatusOn('hpmax');
|
||||||
|
leftStatus.exAtk = getHeroStatusOn('mana');
|
||||||
|
leftStatus.magicDef = getHeroStatusOn('magicDef');
|
||||||
|
};
|
||||||
|
|
||||||
const loaded = ref(false);
|
const loaded = ref(false);
|
||||||
onLoaded(() => {
|
onLoaded(() => {
|
||||||
loaded.value = true;
|
loaded.value = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Mota.require('var', 'hook').on('statusBarUpdate', updateStatus);
|
||||||
|
|
||||||
return () => (
|
return () => (
|
||||||
<container id="main-scene" width={MAIN_WIDTH} height={MAIN_HEIGHT}>
|
<container id="main-scene" width={MAIN_WIDTH} height={MAIN_HEIGHT}>
|
||||||
{loaded.value && (
|
{loaded.value && (
|
||||||
<StatusBar
|
<LeftStatusBar
|
||||||
loc={[0, 0, STATUS_BAR_WIDTH, STATUS_BAR_HEIGHT]}
|
loc={[0, 0, STATUS_BAR_WIDTH, STATUS_BAR_HEIGHT]}
|
||||||
status={status}
|
status={leftStatus}
|
||||||
></StatusBar>
|
></LeftStatusBar>
|
||||||
)}
|
)}
|
||||||
<container id="map-draw" {...mapDrawProps} x={180} zIndex={10}>
|
<container id="map-draw" {...mapDrawProps} x={180} zIndex={10}>
|
||||||
<layer-group id="layer-main" ex={layerGroupExtends} ref={map}>
|
<layer-group id="layer-main" ex={layerGroupExtends} ref={map}>
|
||||||
@ -103,6 +141,12 @@ const MainScene = defineComponent(() => {
|
|||||||
<Textbox id="main-textbox" {...mainTextboxProps}></Textbox>
|
<Textbox id="main-textbox" {...mainTextboxProps}></Textbox>
|
||||||
<FloorChange id="floor-change" zIndex={50}></FloorChange>
|
<FloorChange id="floor-change" zIndex={50}></FloorChange>
|
||||||
</container>
|
</container>
|
||||||
|
{loaded.value && (
|
||||||
|
<RightStatusBar
|
||||||
|
loc={[480 + 180, 0, STATUS_BAR_WIDTH, STATUS_BAR_HEIGHT]}
|
||||||
|
status={rightStatus}
|
||||||
|
></RightStatusBar>
|
||||||
|
)}
|
||||||
{mainUIController.render()}
|
{mainUIController.render()}
|
||||||
</container>
|
</container>
|
||||||
);
|
);
|
||||||
|
@ -3,54 +3,181 @@ import { defineComponent } from 'vue';
|
|||||||
import { SetupComponentOptions } from '../components';
|
import { SetupComponentOptions } from '../components';
|
||||||
import { ElementLocator } from '@/core/render';
|
import { ElementLocator } from '@/core/render';
|
||||||
|
|
||||||
export interface IHeroStatus {
|
export interface ILeftHeroStatus {
|
||||||
hp: number;
|
hp: number;
|
||||||
atk: number;
|
atk: number;
|
||||||
def: number;
|
def: number;
|
||||||
mdef: number;
|
mdef: number;
|
||||||
|
money: number;
|
||||||
|
exp: number;
|
||||||
|
yellowKey: number;
|
||||||
|
blueKey: number;
|
||||||
|
redKey: number;
|
||||||
|
floor: FloorIds;
|
||||||
|
lv: string;
|
||||||
|
/** 生命回复 */
|
||||||
|
regen: number;
|
||||||
|
/** 额外攻击 */
|
||||||
|
exAtk: number;
|
||||||
|
/** 魔法防御 */
|
||||||
|
magicDef: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StatusBarProps {
|
export interface IRightHeroStatus {}
|
||||||
|
|
||||||
|
interface StatusBarProps<T> {
|
||||||
loc: ElementLocator;
|
loc: ElementLocator;
|
||||||
status: IHeroStatus;
|
status: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
const statusBarProps = {
|
const statusBarProps = {
|
||||||
props: ['loc', 'status']
|
props: ['loc', 'status']
|
||||||
} satisfies SetupComponentOptions<StatusBarProps>;
|
} satisfies SetupComponentOptions<StatusBarProps<unknown>>;
|
||||||
|
|
||||||
export const StatusBar = defineComponent<StatusBarProps>(p => {
|
export const LeftStatusBar = defineComponent<StatusBarProps<ILeftHeroStatus>>(
|
||||||
const hpIcon = core.material.images.images['hp.png'];
|
p => {
|
||||||
const atkIcon = core.material.images.images['atk.png'];
|
const hpIcon = core.material.images.images['hp.png'];
|
||||||
const defIcon = core.material.images.images['def.png'];
|
const atkIcon = core.material.images.images['atk.png'];
|
||||||
const mdefIcon = core.material.images.images['IQ.png'];
|
const defIcon = core.material.images.images['def.png'];
|
||||||
|
const mdefIcon = core.material.images.images['IQ.png'];
|
||||||
|
const moneyIcon = core.material.images.images['money.png'];
|
||||||
|
const expIcon = core.material.images.images['exp.png'];
|
||||||
|
|
||||||
const s = p.status;
|
const s = p.status;
|
||||||
const f = core.formatBigNumber;
|
const f = core.formatBigNumber;
|
||||||
|
|
||||||
const iconLoc = (n: number): ElementLocator => {
|
const floorName = core.floors[s.floor].title;
|
||||||
return [16, 16 + 48 * n, 32, 32];
|
|
||||||
};
|
|
||||||
|
|
||||||
const textLoc = (n: number): ElementLocator => {
|
const key = (num: number) => {
|
||||||
return [64, 32 + 48 * n, void 0, void 0, 0.5, 0.5];
|
return num.toString().padStart(2, '0');
|
||||||
};
|
};
|
||||||
|
|
||||||
return () => {
|
const font1 = '18px normal';
|
||||||
return (
|
const font2 = 'bold 18px normal';
|
||||||
<container loc={p.loc}>
|
const font3 = 'bold 14px normal';
|
||||||
<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);
|
const iconLoc = (n: number): ElementLocator => {
|
||||||
|
return [16, 76 + 44 * n, 32, 32];
|
||||||
|
};
|
||||||
|
|
||||||
|
const textLoc = (n: number): ElementLocator => {
|
||||||
|
return [60, 92 + 44 * n, void 0, void 0, 0, 0.5];
|
||||||
|
};
|
||||||
|
|
||||||
|
const central = (y: number): ElementLocator => {
|
||||||
|
const width = p.loc[2] ?? 200;
|
||||||
|
return [width / 2, y, void 0, void 0, 0.5, 0.5];
|
||||||
|
};
|
||||||
|
|
||||||
|
const right = (y: number): ElementLocator => {
|
||||||
|
const width = p.loc[2] ?? 200;
|
||||||
|
return [width - 16, y, void 0, void 0, 1, 0.5];
|
||||||
|
};
|
||||||
|
|
||||||
|
const keyCount = 3;
|
||||||
|
const keyY = 92 + 44 * 6;
|
||||||
|
const keyLoc = (n: number): ElementLocator => {
|
||||||
|
const width = p.loc[2] ?? 200;
|
||||||
|
const per = width / (keyCount + 1);
|
||||||
|
return [per * (n + 1), keyY, 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>
|
||||||
|
<text
|
||||||
|
text={floorName}
|
||||||
|
loc={central(24)}
|
||||||
|
font={font1}
|
||||||
|
cursor="pointer"
|
||||||
|
></text>
|
||||||
|
<text text={s.lv} loc={central(54)} font={font1}></text>
|
||||||
|
<image image={hpIcon} loc={iconLoc(0)}></image>
|
||||||
|
<text text={f(s.hp)} loc={textLoc(0)} font={font1}></text>
|
||||||
|
<text
|
||||||
|
text={`+${f(s.regen)}/t`}
|
||||||
|
loc={right(110)}
|
||||||
|
font={font3}
|
||||||
|
fillStyle="#a7ffa7"
|
||||||
|
></text>
|
||||||
|
<image image={atkIcon} loc={iconLoc(1)}></image>
|
||||||
|
<text text={f(s.atk)} loc={textLoc(1)} font={font1}></text>
|
||||||
|
<text
|
||||||
|
text={`+${f(s.exAtk)}`}
|
||||||
|
loc={right(154)}
|
||||||
|
font={font3}
|
||||||
|
fillStyle="#ffd3d3"
|
||||||
|
></text>
|
||||||
|
<image image={defIcon} loc={iconLoc(2)}></image>
|
||||||
|
<text text={f(s.def)} loc={textLoc(2)} font={font1}></text>
|
||||||
|
{s.magicDef > 0 && (
|
||||||
|
<text
|
||||||
|
text={`+${f(s.magicDef)}`}
|
||||||
|
loc={right(198)}
|
||||||
|
font={font3}
|
||||||
|
fillStyle="#b0bdff"
|
||||||
|
></text>
|
||||||
|
)}
|
||||||
|
<image image={mdefIcon} loc={iconLoc(3)}></image>
|
||||||
|
<text text={f(s.mdef)} loc={textLoc(3)} font={font1}></text>
|
||||||
|
<image image={moneyIcon} loc={iconLoc(4)}></image>
|
||||||
|
<text
|
||||||
|
text={f(s.money)}
|
||||||
|
loc={textLoc(4)}
|
||||||
|
font={font1}
|
||||||
|
></text>
|
||||||
|
<image image={expIcon} loc={iconLoc(5)}></image>
|
||||||
|
<text text={f(s.exp)} loc={textLoc(5)} font={font1}></text>
|
||||||
|
<text
|
||||||
|
text={key(s.yellowKey)}
|
||||||
|
loc={keyLoc(0)}
|
||||||
|
font={font2}
|
||||||
|
fillStyle="#fca"
|
||||||
|
></text>
|
||||||
|
<text
|
||||||
|
text={key(s.blueKey)}
|
||||||
|
loc={keyLoc(1)}
|
||||||
|
font={font2}
|
||||||
|
fillStyle="#aad"
|
||||||
|
></text>
|
||||||
|
<text
|
||||||
|
text={key(s.redKey)}
|
||||||
|
loc={keyLoc(2)}
|
||||||
|
font={font2}
|
||||||
|
fillStyle="#f88"
|
||||||
|
></text>
|
||||||
|
<text
|
||||||
|
text="技能树"
|
||||||
|
loc={central(396)}
|
||||||
|
font={font1}
|
||||||
|
cursor="pointer"
|
||||||
|
></text>
|
||||||
|
<text
|
||||||
|
text="查看技能"
|
||||||
|
loc={central(428)}
|
||||||
|
font={font1}
|
||||||
|
cursor="pointer"
|
||||||
|
></text>
|
||||||
|
</container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
statusBarProps
|
||||||
|
);
|
||||||
|
|
||||||
|
export const RightStatusBar = defineComponent<StatusBarProps<IRightHeroStatus>>(
|
||||||
|
p => {
|
||||||
|
return () => {
|
||||||
|
return (
|
||||||
|
<container loc={p.loc}>
|
||||||
|
<g-rect loc={[0, 0, p.loc[2], p.loc[3]]} stroke></g-rect>
|
||||||
|
</container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
statusBarProps
|
||||||
|
);
|
||||||
|
|
||||||
|
export const leftStatusBarUI = new GameUI('left-status-bar', LeftStatusBar);
|
||||||
|
export const rightStatusBarUI = new GameUI('right-status-bar', RightStatusBar);
|
||||||
|
@ -262,7 +262,9 @@ onUnmounted(() => {
|
|||||||
font-size: 200%;
|
font-size: 200%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 14px;
|
margin-bottom: 14px;
|
||||||
text-shadow: 3px 2px 3px #000, 0px 0px 3px #111;
|
text-shadow:
|
||||||
|
3px 2px 3px #000,
|
||||||
|
0px 0px 3px #111;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -303,7 +305,9 @@ onUnmounted(() => {
|
|||||||
font-size: 200%;
|
font-size: 200%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
text-shadow: 3px 2px 3px #000, 0px 0px 3px #111;
|
text-shadow:
|
||||||
|
3px 2px 3px #000,
|
||||||
|
0px 0px 3px #111;
|
||||||
}
|
}
|
||||||
|
|
||||||
#status-lv {
|
#status-lv {
|
||||||
@ -311,7 +315,9 @@ onUnmounted(() => {
|
|||||||
font-size: 200%;
|
font-size: 200%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
text-shadow: 3px 2px 3px #000, 0px 0px 3px #111;
|
text-shadow:
|
||||||
|
3px 2px 3px #000,
|
||||||
|
0px 0px 3px #111;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-extra {
|
.status-extra {
|
||||||
|
Loading…
Reference in New Issue
Block a user