Compare commits
2 Commits
76082d8e1f
...
d54e6ce1d6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d54e6ce1d6 | ||
|
|
d06c99e2f1 |
@@ -38,10 +38,13 @@
|
|||||||
"_components": [
|
"_components": [
|
||||||
{
|
{
|
||||||
"__id__": 110
|
"__id__": 110
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"__id__": 112
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"_prefab": {
|
"_prefab": {
|
||||||
"__id__": 112
|
"__id__": 114
|
||||||
},
|
},
|
||||||
"_lpos": {
|
"_lpos": {
|
||||||
"__type__": "cc.Vec3",
|
"__type__": "cc.Vec3",
|
||||||
@@ -1277,7 +1280,7 @@
|
|||||||
"__id__": 65
|
"__id__": 65
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"_active": true,
|
"_active": false,
|
||||||
"_components": [
|
"_components": [
|
||||||
{
|
{
|
||||||
"__id__": 73
|
"__id__": 73
|
||||||
@@ -2520,8 +2523,8 @@
|
|||||||
},
|
},
|
||||||
"_contentSize": {
|
"_contentSize": {
|
||||||
"__type__": "cc.Size",
|
"__type__": "cc.Size",
|
||||||
"width": 70,
|
"width": 40,
|
||||||
"height": 20
|
"height": 11
|
||||||
},
|
},
|
||||||
"_anchorPoint": {
|
"_anchorPoint": {
|
||||||
"__type__": "cc.Vec2",
|
"__type__": "cc.Vec2",
|
||||||
@@ -2534,6 +2537,34 @@
|
|||||||
"__type__": "cc.CompPrefabInfo",
|
"__type__": "cc.CompPrefabInfo",
|
||||||
"fileId": "cdvV5hAkdEaY6AT6urHKuf"
|
"fileId": "cdvV5hAkdEaY6AT6urHKuf"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"__type__": "c4181/FEWhKbY0D0PLdCyKi",
|
||||||
|
"_name": "",
|
||||||
|
"_objFlags": 0,
|
||||||
|
"__editorExtras__": {},
|
||||||
|
"node": {
|
||||||
|
"__id__": 1
|
||||||
|
},
|
||||||
|
"_enabled": true,
|
||||||
|
"__prefab": {
|
||||||
|
"__id__": 113
|
||||||
|
},
|
||||||
|
"hpProgressBar": {
|
||||||
|
"__id__": 53
|
||||||
|
},
|
||||||
|
"lvLabel": null,
|
||||||
|
"shieldProgressBar": {
|
||||||
|
"__id__": 77
|
||||||
|
},
|
||||||
|
"hpBarSprite": {
|
||||||
|
"__id__": 36
|
||||||
|
},
|
||||||
|
"_id": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"__type__": "cc.CompPrefabInfo",
|
||||||
|
"fileId": "3eCKBFOJhLHKqcnak/mi3d"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"__type__": "cc.PrefabInfo",
|
"__type__": "cc.PrefabInfo",
|
||||||
"root": {
|
"root": {
|
||||||
|
|||||||
@@ -1398,7 +1398,7 @@
|
|||||||
"_lpos": {
|
"_lpos": {
|
||||||
"__type__": "cc.Vec3",
|
"__type__": "cc.Vec3",
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 21.504,
|
"y": -31.947,
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"_lrot": {
|
"_lrot": {
|
||||||
@@ -1875,7 +1875,7 @@
|
|||||||
"_lpos": {
|
"_lpos": {
|
||||||
"__type__": "cc.Vec3",
|
"__type__": "cc.Vec3",
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 21.504,
|
"y": -50.813,
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"_lrot": {
|
"_lrot": {
|
||||||
@@ -2352,7 +2352,7 @@
|
|||||||
"_lpos": {
|
"_lpos": {
|
||||||
"__type__": "cc.Vec3",
|
"__type__": "cc.Vec3",
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 0,
|
"y": -150,
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"_lrot": {
|
"_lrot": {
|
||||||
@@ -2618,6 +2618,8 @@
|
|||||||
"__id__": 0
|
"__id__": 0
|
||||||
},
|
},
|
||||||
"fileId": "6bE0ieRMBGyYHdxvuJ03FA",
|
"fileId": "6bE0ieRMBGyYHdxvuJ03FA",
|
||||||
|
"instance": null,
|
||||||
|
"targetOverrides": null,
|
||||||
"nestedPrefabInstanceRoots": null
|
"nestedPrefabInstanceRoots": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -3432,6 +3434,8 @@
|
|||||||
"__id__": 0
|
"__id__": 0
|
||||||
},
|
},
|
||||||
"fileId": "256TxMVzhHQopA08O7v8nq",
|
"fileId": "256TxMVzhHQopA08O7v8nq",
|
||||||
|
"instance": null,
|
||||||
|
"targetOverrides": null,
|
||||||
"nestedPrefabInstanceRoots": null
|
"nestedPrefabInstanceRoots": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -3471,6 +3475,8 @@
|
|||||||
"__id__": 0
|
"__id__": 0
|
||||||
},
|
},
|
||||||
"fileId": "5dWwAe3IFHRJuoSicXBEs8",
|
"fileId": "5dWwAe3IFHRJuoSicXBEs8",
|
||||||
|
"instance": null,
|
||||||
|
"targetOverrides": null,
|
||||||
"nestedPrefabInstanceRoots": null
|
"nestedPrefabInstanceRoots": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -3710,6 +3716,8 @@
|
|||||||
"__id__": 0
|
"__id__": 0
|
||||||
},
|
},
|
||||||
"fileId": "b9zLdCeJlE5pPiK++dLgBX",
|
"fileId": "b9zLdCeJlE5pPiK++dLgBX",
|
||||||
|
"instance": null,
|
||||||
|
"targetOverrides": null,
|
||||||
"nestedPrefabInstanceRoots": null
|
"nestedPrefabInstanceRoots": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -4388,6 +4396,8 @@
|
|||||||
"__id__": 0
|
"__id__": 0
|
||||||
},
|
},
|
||||||
"fileId": "9d2p/canpFGof2O/w8JyTj",
|
"fileId": "9d2p/canpFGof2O/w8JyTj",
|
||||||
|
"instance": null,
|
||||||
|
"targetOverrides": null,
|
||||||
"nestedPrefabInstanceRoots": null
|
"nestedPrefabInstanceRoots": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -4427,6 +4437,8 @@
|
|||||||
"__id__": 0
|
"__id__": 0
|
||||||
},
|
},
|
||||||
"fileId": "6bkL20JyRDOI6ycVYKl/ie",
|
"fileId": "6bkL20JyRDOI6ycVYKl/ie",
|
||||||
|
"instance": null,
|
||||||
|
"targetOverrides": null,
|
||||||
"nestedPrefabInstanceRoots": null
|
"nestedPrefabInstanceRoots": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -4495,6 +4507,8 @@
|
|||||||
"__id__": 0
|
"__id__": 0
|
||||||
},
|
},
|
||||||
"fileId": "14GnCNU65BbJPIzLWE/Oiz",
|
"fileId": "14GnCNU65BbJPIzLWE/Oiz",
|
||||||
|
"instance": null,
|
||||||
|
"targetOverrides": null,
|
||||||
"nestedPrefabInstanceRoots": null
|
"nestedPrefabInstanceRoots": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -7363,6 +7377,8 @@
|
|||||||
"__id__": 0
|
"__id__": 0
|
||||||
},
|
},
|
||||||
"fileId": "c4IqMKgXNExa5w4EAciRxE",
|
"fileId": "c4IqMKgXNExa5w4EAciRxE",
|
||||||
|
"instance": null,
|
||||||
|
"targetOverrides": null,
|
||||||
"nestedPrefabInstanceRoots": null
|
"nestedPrefabInstanceRoots": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -8893,6 +8909,8 @@
|
|||||||
"__id__": 0
|
"__id__": 0
|
||||||
},
|
},
|
||||||
"fileId": "35iAcUUYpE8akTtTCX1ITo",
|
"fileId": "35iAcUUYpE8akTtTCX1ITo",
|
||||||
|
"instance": null,
|
||||||
|
"targetOverrides": null,
|
||||||
"nestedPrefabInstanceRoots": null
|
"nestedPrefabInstanceRoots": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -8932,6 +8950,8 @@
|
|||||||
"__id__": 0
|
"__id__": 0
|
||||||
},
|
},
|
||||||
"fileId": "68Q5vEQWBINrggngtTZ7wh",
|
"fileId": "68Q5vEQWBINrggngtTZ7wh",
|
||||||
|
"instance": null,
|
||||||
|
"targetOverrides": null,
|
||||||
"nestedPrefabInstanceRoots": null
|
"nestedPrefabInstanceRoots": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -232,7 +232,7 @@
|
|||||||
"_top": 0,
|
"_top": 0,
|
||||||
"_bottom": 546.004,
|
"_bottom": 546.004,
|
||||||
"_horizontalCenter": 0,
|
"_horizontalCenter": 0,
|
||||||
"_verticalCenter": 143,
|
"_verticalCenter": -7,
|
||||||
"_isAbsLeft": true,
|
"_isAbsLeft": true,
|
||||||
"_isAbsRight": true,
|
"_isAbsRight": true,
|
||||||
"_isAbsTop": true,
|
"_isAbsTop": true,
|
||||||
@@ -276,7 +276,7 @@
|
|||||||
"value": {
|
"value": {
|
||||||
"__type__": "cc.Vec3",
|
"__type__": "cc.Vec3",
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 783,
|
"y": 633,
|
||||||
"z": 0
|
"z": 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export enum BoxSet {
|
|||||||
LETF_END = -360,
|
LETF_END = -360,
|
||||||
RIGHT_END = 360,
|
RIGHT_END = 360,
|
||||||
//游戏地平线
|
//游戏地平线
|
||||||
GAME_LINE = 160,
|
GAME_LINE = 10,
|
||||||
/** 技能/装备各自独立的上限 */
|
/** 技能/装备各自独立的上限 */
|
||||||
BOX_MAX = 6,
|
BOX_MAX = 6,
|
||||||
}
|
}
|
||||||
|
|||||||
184
assets/script/game/hero/HeroTopBarComp.ts
Normal file
184
assets/script/game/hero/HeroTopBarComp.ts
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
import { _decorator, Label, ProgressBar, Sprite, UIOpacity, Vec3, v3, tween, Tween, Color, clamp } from "cc";
|
||||||
|
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||||
|
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
|
||||||
|
import { HeroAttrsComp } from "./HeroAttrsComp";
|
||||||
|
import { FacSet, FightSet } from "../common/config/GameSet";
|
||||||
|
|
||||||
|
const { ccclass, property } = _decorator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 英雄头顶状态栏组件
|
||||||
|
*
|
||||||
|
* 挂载在英雄节点下的 "top" 子节点上,独立管理:
|
||||||
|
* - 血条进度(ProgressBar)
|
||||||
|
* - 护盾数值条(ProgressBar,top/shield 节点,显示 shield / SHIELD_MAX)
|
||||||
|
* - 等级数字(Label)
|
||||||
|
* - 透明度激活 / 闲置
|
||||||
|
* - 受击抖动动画
|
||||||
|
*
|
||||||
|
* 数据流:HeroViewComp 将 HeroAttrsComp 引用传入,本组件通过 model 读取属性。
|
||||||
|
* 注意:英雄根节点下的 "shielded" 是护盾特效动画节点,由 HeroViewComp 管理,不在本组件职责内。
|
||||||
|
*/
|
||||||
|
@ccclass('HeroTopBarComp')
|
||||||
|
@ecs.register('HeroTopBar', false)
|
||||||
|
export class HeroTopBarComp extends CCComp {
|
||||||
|
|
||||||
|
// ==================== @property 绑定(编辑器拖拽) ====================
|
||||||
|
@property({ type: ProgressBar, tooltip: "血条进度条" })
|
||||||
|
private hpProgressBar: ProgressBar = null!;
|
||||||
|
|
||||||
|
@property({ type: Label, tooltip: "等级数字" })
|
||||||
|
private lvLabel: Label = null!;
|
||||||
|
|
||||||
|
@property({ type: ProgressBar, tooltip: "护盾数值条(top 下的 shield 节点)" })
|
||||||
|
private shieldProgressBar: ProgressBar = null!;
|
||||||
|
|
||||||
|
@property({ type: Sprite, tooltip: "血条填充 Sprite(用于设置阵营颜色)" })
|
||||||
|
private hpBarSprite: Sprite = null!;
|
||||||
|
|
||||||
|
// ==================== 内部引用 ====================
|
||||||
|
private topOpacity: UIOpacity = null!;
|
||||||
|
private basePos: Vec3 = v3();
|
||||||
|
|
||||||
|
// ==================== 配置 ====================
|
||||||
|
private hp_height: number = 90;
|
||||||
|
private boss_hp_height: number = 160;
|
||||||
|
|
||||||
|
// ==================== 透明度 ====================
|
||||||
|
private readonly barIdleOpacity: number = 153;
|
||||||
|
private readonly barActiveOpacity: number = 255;
|
||||||
|
private readonly idleOpacityDelay: number = 0.25;
|
||||||
|
|
||||||
|
/** 外部赋值:英雄数据模型 */
|
||||||
|
private _model: HeroAttrsComp = null!;
|
||||||
|
public get model(): HeroAttrsComp { return this._model; }
|
||||||
|
public set model(value: HeroAttrsComp) { this._model = value; }
|
||||||
|
|
||||||
|
private readonly restoreBarIdleOpacity = () => {
|
||||||
|
this.setTopBarOpacity(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化 / 对象池复用时重置
|
||||||
|
* @param model 英雄属性组件
|
||||||
|
* @param direction 显示方向(1 或 -1)
|
||||||
|
*/
|
||||||
|
public init(model: HeroAttrsComp, direction: number = 1): void {
|
||||||
|
this._model = model;
|
||||||
|
|
||||||
|
// UIOpacity 无 @property 绑定,运行时获取或添加
|
||||||
|
this.topOpacity = this.node.getComponent(UIOpacity) || this.node.addComponent(UIOpacity);
|
||||||
|
|
||||||
|
// 方向取 abs,避免对象池复用时 scale 累乘导致血条反转
|
||||||
|
this.node.setScale(direction * Math.abs(this.node.scale.x), 1 * this.node.scale.y);
|
||||||
|
|
||||||
|
// 位置
|
||||||
|
const height = model.is_boss ? this.boss_hp_height : this.hp_height;
|
||||||
|
this.node.setPosition(0, height, 0);
|
||||||
|
this.basePos = this.node.position.clone();
|
||||||
|
|
||||||
|
// 血条颜色(英雄阵营为绿色)
|
||||||
|
if (model.fac === FacSet.HERO && this.hpBarSprite) {
|
||||||
|
this.hpBarSprite.color = new Color("#2ECC71");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保血条等 UI 始终在最上层
|
||||||
|
this.node.setSiblingIndex(999);
|
||||||
|
|
||||||
|
// 重置显示状态
|
||||||
|
this.node.getChildByName("hp")!.active = true;
|
||||||
|
this.node.getChildByName("cd")!.active = false;
|
||||||
|
this.node.getChildByName("lv")!.active = true;
|
||||||
|
this.node.active = true;
|
||||||
|
|
||||||
|
this.setTopBarOpacity(false);
|
||||||
|
this.hpShow();
|
||||||
|
this.lvShow();
|
||||||
|
this.shieldShow(model.shield);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 更新血量进度条 */
|
||||||
|
public hpShow(): void {
|
||||||
|
if (!this._model || !this.hpProgressBar) return;
|
||||||
|
const hp = this._model.hp;
|
||||||
|
const hpMax = this._model.hp_max;
|
||||||
|
let targetProgress = hpMax > 0 ? hp / hpMax : 0;
|
||||||
|
targetProgress = clamp(targetProgress, 0, 1);
|
||||||
|
|
||||||
|
const prevProgress = this.hpProgressBar.progress;
|
||||||
|
if (targetProgress < prevProgress) {
|
||||||
|
this.activateTopBar();
|
||||||
|
this.playHpBarShake();
|
||||||
|
}
|
||||||
|
this.hpProgressBar.progress = targetProgress;
|
||||||
|
if (targetProgress > prevProgress) {
|
||||||
|
this.setTopBarOpacity(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 更新护盾数值条(百分比 = shield / SHIELD_MAX) */
|
||||||
|
public shieldShow(shield: number = 0): void {
|
||||||
|
if (!this.shieldProgressBar) return;
|
||||||
|
const progress = FightSet.SHIELD_MAX > 0 ? shield / FightSet.SHIELD_MAX : 0;
|
||||||
|
this.shieldProgressBar.progress = clamp(progress, 0, 1);
|
||||||
|
// 护盾节点显隐
|
||||||
|
this.shieldProgressBar.node.active = shield > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 更新等级数字 */
|
||||||
|
public lvShow(): void {
|
||||||
|
if (!this.lvLabel || !this._model) return;
|
||||||
|
this.lvLabel.string = String(this._model.lv);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 激活顶栏(高亮 + 延迟恢复闲置透明度) */
|
||||||
|
public activateTopBar(): void {
|
||||||
|
this.setTopBarOpacity(true);
|
||||||
|
this.unschedule(this.restoreBarIdleOpacity);
|
||||||
|
this.scheduleOnce(this.restoreBarIdleOpacity, this.idleOpacityDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 设置顶栏透明度 */
|
||||||
|
public setTopBarOpacity(isActive: boolean): void {
|
||||||
|
if (!this.node || !this.node.isValid) return;
|
||||||
|
if (this.topOpacity) {
|
||||||
|
this.topOpacity.opacity = isActive ? this.barActiveOpacity : this.barIdleOpacity;
|
||||||
|
}
|
||||||
|
this.node.active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 显示顶栏 */
|
||||||
|
public show(): void {
|
||||||
|
if (this.node && this.node.isValid) this.node.active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 隐藏顶栏 */
|
||||||
|
public hide(): void {
|
||||||
|
if (this.node && this.node.isValid) this.node.active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 血条受击抖动动画 */
|
||||||
|
private playHpBarShake(): void {
|
||||||
|
if (!this.node || !this.node.isValid) return;
|
||||||
|
Tween.stopAllByTarget(this.node);
|
||||||
|
this.node.setPosition(this.basePos);
|
||||||
|
tween(this.node)
|
||||||
|
.by(0.04, { position: v3(-3, 0, 0) })
|
||||||
|
.by(0.04, { position: v3(6, 0, 0) })
|
||||||
|
.by(0.04, { position: v3(-5, 0, 0) })
|
||||||
|
.by(0.04, { position: v3(2, 0, 0) })
|
||||||
|
.call(() => {
|
||||||
|
this.node.setPosition(this.basePos);
|
||||||
|
})
|
||||||
|
.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 对象池回收 / 组件重置时调用 */
|
||||||
|
public reset(): void {
|
||||||
|
this.unscheduleAllCallbacks();
|
||||||
|
if (this.node && this.node.isValid) {
|
||||||
|
Tween.stopAllByTarget(this.node);
|
||||||
|
this.node.setPosition(this.basePos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
assets/script/game/hero/HeroTopBarComp.ts.meta
Normal file
9
assets/script/game/hero/HeroTopBarComp.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ver": "4.0.24",
|
||||||
|
"importer": "typescript",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "c4181fc5-1168-4a6d-8d03-d0f2dd0b22a2",
|
||||||
|
"files": [],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import { Vec3, _decorator, v3, Collider2D, Contact2DType, Label, Node, Prefab, instantiate, ProgressBar, Component, Material, Sprite, math, clamp, Game, tween, Tween, Color, BoxCollider2D, UITransform, UIOpacity } from "cc";
|
import { Vec3, _decorator, v3, Collider2D, Contact2DType, Node, Prefab, instantiate, tween, Tween, BoxCollider2D, UITransform } from "cc";
|
||||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||||
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
|
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
|
||||||
import { mLogger } from "../common/Logger";
|
import { mLogger } from "../common/Logger";
|
||||||
import { HeroSpine } from "./HeroSpine";
|
import { HeroSpine } from "./HeroSpine";
|
||||||
|
import { HeroTopBarComp } from "./HeroTopBarComp";
|
||||||
import { BoxSet, FacSet, FightSet, NumberFormatter, TooltipTypes } from "../common/config/GameSet";
|
import { BoxSet, FacSet, FightSet, NumberFormatter, TooltipTypes } from "../common/config/GameSet";
|
||||||
import { smc } from "../common/SingletonModuleComp";
|
import { smc } from "../common/SingletonModuleComp";
|
||||||
import { SkillSet, } from "../common/config/SkillSet";
|
import { SkillSet, } from "../common/config/SkillSet";
|
||||||
@@ -37,21 +38,10 @@ export class HeroViewComp extends CCComp {
|
|||||||
realDeadTime: number = 0.1
|
realDeadTime: number = 0.1
|
||||||
deadCD: number = 0
|
deadCD: number = 0
|
||||||
monDeadTime: number = 0.1
|
monDeadTime: number = 0.1
|
||||||
hp_height: number = 90
|
|
||||||
boss_hp_height: number = 160
|
|
||||||
// 血条显示相关
|
// 血条显示相关
|
||||||
lastBarUpdateTime: number = 0; // 最后一次血条/蓝条/护盾更新时间
|
lastBarUpdateTime: number = 0; // 最后一次血条/蓝条/护盾更新时间
|
||||||
// ==================== UI 节点引用 ====================
|
// ==================== UI 节点引用 ====================
|
||||||
private top_node: Node = null!;
|
private topBar: HeroTopBarComp = null!;
|
||||||
private topOpacity: UIOpacity = null!;
|
|
||||||
private topBasePos: Vec3 = v3();
|
|
||||||
private lvLabel: Label | null = null; // 等级显示文本
|
|
||||||
private readonly barIdleOpacity: number = 153;
|
|
||||||
private readonly barActiveOpacity: number = 255;
|
|
||||||
private readonly idleOpacityDelay: number = 0.25;
|
|
||||||
private readonly restoreBarIdleOpacity = () => {
|
|
||||||
this.setTopBarOpacity(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
// ==================== 直接访问 HeroAttrsComp ====================
|
// ==================== 直接访问 HeroAttrsComp ====================
|
||||||
get model() {
|
get model() {
|
||||||
@@ -96,67 +86,26 @@ export class HeroViewComp extends CCComp {
|
|||||||
this.lastBarUpdateTime = 0;
|
this.lastBarUpdateTime = 0;
|
||||||
this.status_change("idle");
|
this.status_change("idle");
|
||||||
|
|
||||||
// 初始化 UI 节点
|
// 初始化头顶状态栏
|
||||||
this.initUINodes();
|
const topNode = this.node.getChildByName("top");
|
||||||
|
this.topBar = topNode.getComponent(HeroTopBarComp) || topNode.addComponent(HeroTopBarComp);
|
||||||
|
this.topBar.init(this.model, this.scale);
|
||||||
|
|
||||||
/** 方向 */
|
/** 方向 */
|
||||||
this.node.setScale(this.scale * Math.abs(this.node.scale.x), 1 * this.node.scale.y); // 确保 scale.x 为正后再乘方向
|
this.node.setScale(this.scale * Math.abs(this.node.scale.x), 1 * this.node.scale.y); // 确保 scale.x 为正后再乘方向
|
||||||
// top_node 取 abs 避免对象池复用时 scale 累乘导致血条方向反转
|
|
||||||
this.top_node.setScale(this.scale * Math.abs(this.top_node.scale.x), 1 * this.top_node.scale.y);
|
|
||||||
/* 显示角色血*/
|
|
||||||
this.top_node.getChildByName("hp").active = true;
|
|
||||||
this.top_node.getChildByName("cd").active = false;
|
|
||||||
this.top_node.getChildByName("shield").active = false;
|
|
||||||
this.top_node.getChildByName("lv").active = true;
|
|
||||||
this.top_node.active = true;
|
|
||||||
this.setTopBarOpacity(false);
|
|
||||||
|
|
||||||
// 初始化等级显示
|
// 🔥 重置描边
|
||||||
this.lv_show();
|
|
||||||
|
|
||||||
// 🔥 重置血条 UI 显示状态
|
|
||||||
if (this.model) {
|
if (this.model) {
|
||||||
this.hp_show();
|
|
||||||
|
|
||||||
// 根据英雄模型中的等级数据设置描边
|
|
||||||
// 注意:HeroViewComp 是挂在 node 上的,而 FlashSprite 可能挂在子节点(如 Sprite/anm 节点)
|
|
||||||
let flashSprite = this.node.getComponent(FlashSprite);
|
let flashSprite = this.node.getComponent(FlashSprite);
|
||||||
if (!flashSprite) {
|
if (!flashSprite) {
|
||||||
// 如果当前节点没有,尝试在所有子节点中查找
|
|
||||||
flashSprite = this.node.getComponentInChildren(FlashSprite);
|
flashSprite = this.node.getComponentInChildren(FlashSprite);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flashSprite) {
|
if (flashSprite) {
|
||||||
flashSprite.setOutlineByLevel(this.model.lv);
|
flashSprite.setOutlineByLevel(this.model.lv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 UI 节点引用 */
|
|
||||||
private initUINodes() {
|
|
||||||
this.top_node = this.node.getChildByName("top");
|
|
||||||
this.topOpacity = this.top_node.getComponent(UIOpacity) || this.top_node.addComponent(UIOpacity);
|
|
||||||
this.top_node.setPosition(0, this.hp_height, 0);
|
|
||||||
if (this.model.is_boss) this.top_node.setPosition(0, this.boss_hp_height, 0);
|
|
||||||
this.topBasePos = this.top_node.position.clone();
|
|
||||||
const hpNode = this.top_node.getChildByName("hp");
|
|
||||||
if (this.model.fac == FacSet.HERO) {
|
|
||||||
hpNode.getChildByName("Bar").getComponent(Sprite).color = new Color("#2ECC71")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确保血条等UI始终在最上层显示
|
|
||||||
this.top_node.setSiblingIndex(999);
|
|
||||||
|
|
||||||
// 缓存等级显示 Label(lv 节点下的 Label 子节点)
|
|
||||||
const lvNode = this.top_node.getChildByName("lv");
|
|
||||||
if (lvNode) {
|
|
||||||
this.lvLabel = lvNode.getChildByName("Label")?.getComponent(Label) ?? null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View 层每帧更新
|
* View 层每帧更新
|
||||||
* 注意:数据更新逻辑已移到 HeroAttrSystem,这里只负责显示
|
* 注意:数据更新逻辑已移到 HeroAttrSystem,这里只负责显示
|
||||||
@@ -181,18 +130,18 @@ export class HeroViewComp extends CCComp {
|
|||||||
|
|
||||||
// ✅ 按需更新 UI(脏标签模式)- 只在属性变化时更新
|
// ✅ 按需更新 UI(脏标签模式)- 只在属性变化时更新
|
||||||
if (this.model.dirty_hp) {
|
if (this.model.dirty_hp) {
|
||||||
this.hp_show();
|
this.topBar.hpShow();
|
||||||
this.model.dirty_hp = false;
|
this.model.dirty_hp = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (this.model.dirty_shield) {
|
if (this.model.dirty_shield) {
|
||||||
this.show_shield(this.model.shield);
|
this.topBar.shieldShow(this.model.shield);
|
||||||
this.model.dirty_shield = false;
|
this.model.dirty_shield = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.model.dirty_lv) {
|
if (this.model.dirty_lv) {
|
||||||
this.lv_show();
|
this.topBar.lvShow();
|
||||||
this.model.dirty_lv = false;
|
this.model.dirty_lv = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -201,83 +150,6 @@ export class HeroViewComp extends CCComp {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 显示护盾 */
|
|
||||||
private show_shield(shield: number = 0) {
|
|
||||||
this.lastBarUpdateTime = Date.now() / 1000;
|
|
||||||
this.node.getChildByName("shielded").active = shield > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 显示血量 */
|
|
||||||
private hp_show() {
|
|
||||||
this.lastBarUpdateTime = Date.now() / 1000;
|
|
||||||
// 不再基于血量是否满来决定显示状态,只更新进度条
|
|
||||||
let hp = this.model.hp;
|
|
||||||
let hp_max = this.model.hp_max;
|
|
||||||
// mLogger.log(this.debugMode, 'HeroViewComp', "hp_show",hp,hp_max)
|
|
||||||
|
|
||||||
let targetProgress = hp_max > 0 ? hp / hp_max : 0;
|
|
||||||
targetProgress = clamp(targetProgress, 0, 1);
|
|
||||||
let hpNode = this.top_node.getChildByName("hp");
|
|
||||||
let hpProgressBar = hpNode.getComponent(ProgressBar);
|
|
||||||
|
|
||||||
const prevProgress = hpProgressBar.progress;
|
|
||||||
if (targetProgress < hpProgressBar.progress) {
|
|
||||||
this.activateTopBar();
|
|
||||||
this.playHpBarShake();
|
|
||||||
}
|
|
||||||
hpProgressBar.progress = targetProgress;
|
|
||||||
if (targetProgress > prevProgress) {
|
|
||||||
this.setTopBarOpacity(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private isFullHpAndNoShield(): boolean {
|
|
||||||
if (!this.model) return false;
|
|
||||||
if (this.model.hp_max <= 0) return false;
|
|
||||||
return this.model.hp >= this.model.hp_max;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 更新等级显示 */
|
|
||||||
private lv_show() {
|
|
||||||
if (!this.lvLabel || !this.model) return;
|
|
||||||
this.lvLabel.string = String(this.model.lv);
|
|
||||||
}
|
|
||||||
|
|
||||||
private setTopBarOpacity(isActive: boolean) {
|
|
||||||
if (!this.top_node || !this.top_node.isValid) return;
|
|
||||||
if (this.topOpacity) {
|
|
||||||
this.topOpacity.opacity = this.barActiveOpacity;
|
|
||||||
}
|
|
||||||
if (isActive) {
|
|
||||||
this.top_node.active = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.top_node.active = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private activateTopBar() {
|
|
||||||
this.setTopBarOpacity(true);
|
|
||||||
this.unschedule(this.restoreBarIdleOpacity);
|
|
||||||
this.scheduleOnce(this.restoreBarIdleOpacity, this.idleOpacityDelay);
|
|
||||||
}
|
|
||||||
|
|
||||||
private playHpBarShake() {
|
|
||||||
if (!this.top_node || !this.top_node.isValid) return;
|
|
||||||
Tween.stopAllByTarget(this.top_node);
|
|
||||||
this.top_node.setPosition(this.topBasePos);
|
|
||||||
tween(this.top_node)
|
|
||||||
.by(0.04, { position: v3(-3, 0, 0) })
|
|
||||||
.by(0.04, { position: v3(6, 0, 0) })
|
|
||||||
.by(0.04, { position: v3(-5, 0, 0) })
|
|
||||||
.by(0.04, { position: v3(2, 0, 0) })
|
|
||||||
.call(() => {
|
|
||||||
this.top_node.setPosition(this.topBasePos);
|
|
||||||
})
|
|
||||||
.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** 升级特效 */
|
/** 升级特效 */
|
||||||
public lv_up() {
|
public lv_up() {
|
||||||
this.spawnTimedFx("game/skill/buff/buff_lvup", this.node, 1.0);
|
this.spawnTimedFx("game/skill/buff/buff_lvup", this.node, 1.0);
|
||||||
@@ -432,7 +304,7 @@ export class HeroViewComp extends CCComp {
|
|||||||
}
|
}
|
||||||
add_shield(shield: number) {
|
add_shield(shield: number) {
|
||||||
// 护盾数据更新由 Model 层处理,这里只负责视图表现
|
// 护盾数据更新由 Model 层处理,这里只负责视图表现
|
||||||
if (this.model && this.model.shield > 0) this.show_shield(this.model.shield);
|
if (this.model && this.model.shield > 0) this.topBar.shieldShow(this.model.shield);
|
||||||
}
|
}
|
||||||
|
|
||||||
health(hp: number = 0) {
|
health(hp: number = 0) {
|
||||||
@@ -458,7 +330,7 @@ export class HeroViewComp extends CCComp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 恢复UI
|
// 恢复UI
|
||||||
this.top_node.active = true;
|
this.topBar.show();
|
||||||
|
|
||||||
this.status_change("idle");
|
this.status_change("idle");
|
||||||
|
|
||||||
@@ -504,7 +376,7 @@ export class HeroViewComp extends CCComp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 隐藏UI
|
// 隐藏UI
|
||||||
this.top_node.active = false;
|
this.topBar.hide();
|
||||||
|
|
||||||
// 在销毁实体前先禁用碰撞体,从源头减少"尸体"参与碰撞
|
// 在销毁实体前先禁用碰撞体,从源头减少"尸体"参与碰撞
|
||||||
const collider = this.node.getComponent(Collider2D);
|
const collider = this.node.getComponent(Collider2D);
|
||||||
@@ -528,7 +400,7 @@ export class HeroViewComp extends CCComp {
|
|||||||
/** 受击表现(伤害数字、受击动画、击退) */
|
/** 受击表现(伤害数字、受击动画、击退) */
|
||||||
do_atked(damage: number, isCrit: boolean, s_uuid: number, knockbackExtra: number = 0) {
|
do_atked(damage: number, isCrit: boolean, s_uuid: number, knockbackExtra: number = 0) {
|
||||||
// 受到攻击时更新最后更新时间
|
// 受到攻击时更新最后更新时间
|
||||||
this.activateTopBar();
|
this.topBar.activateTopBar();
|
||||||
this.lastBarUpdateTime = Date.now() / 1000;
|
this.lastBarUpdateTime = Date.now() / 1000;
|
||||||
|
|
||||||
if (damage <= 0) return;
|
if (damage <= 0) return;
|
||||||
@@ -630,7 +502,7 @@ export class HeroViewComp extends CCComp {
|
|||||||
if (!this.model) return;
|
if (!this.model) return;
|
||||||
|
|
||||||
const damageText = NumberFormatter.formatNumber(Math.max(0, Math.floor(damage)));
|
const damageText = NumberFormatter.formatNumber(Math.max(0, Math.floor(damage)));
|
||||||
this.hp_show();
|
this.topBar.hpShow();
|
||||||
if (isCrit) {
|
if (isCrit) {
|
||||||
this.hp_tip(TooltipTypes.crit, damageText);
|
this.hp_tip(TooltipTypes.crit, damageText);
|
||||||
} else {
|
} else {
|
||||||
@@ -641,10 +513,7 @@ export class HeroViewComp extends CCComp {
|
|||||||
// 清理残留的定时器和缓动
|
// 清理残留的定时器和缓动
|
||||||
this.unscheduleAllCallbacks();
|
this.unscheduleAllCallbacks();
|
||||||
Tween.stopAllByTarget(this.node);
|
Tween.stopAllByTarget(this.node);
|
||||||
if (this.top_node && this.top_node.isValid) {
|
this.topBar.reset();
|
||||||
Tween.stopAllByTarget(this.top_node);
|
|
||||||
this.top_node.setPosition(this.topBasePos);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清理碰撞器事件监听
|
// 清理碰撞器事件监听
|
||||||
const collider = this.getComponent(Collider2D);
|
const collider = this.getComponent(Collider2D);
|
||||||
@@ -653,7 +522,6 @@ export class HeroViewComp extends CCComp {
|
|||||||
}
|
}
|
||||||
this.deadCD = 0
|
this.deadCD = 0
|
||||||
this.lastBarUpdateTime = 0
|
this.lastBarUpdateTime = 0
|
||||||
this.unschedule(this.restoreBarIdleOpacity);
|
|
||||||
|
|
||||||
// 清理伤害队列
|
// 清理伤害队列
|
||||||
this.damageQueue.length = 0;
|
this.damageQueue.length = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user