You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
AdLike2048/assets/script/ui/BattleGame/MonsterBlood.ts

241 lines
8.5 KiB

3 weeks ago
import { _decorator, clamp01, Component, Label, Node, Sprite, UITransform } from 'cc';
import { MonsterType } from '../../manager/MonsterDataManager';
const { ccclass } = _decorator;
/** 白色/黄色滞后血条:掉到主条目标的时长(秒) */
const PROGRESS2_DROP_SEC = 0.3;
@ccclass('MonsterBlood')
export class MonsterBlood extends Component {
/** 小怪主血量进度 */
smallMonsterProgress: Node = null;
/** 小怪滞后血量进度(主条变化后持续跟随掉落) */
smallMonsterProgress2: Node = null;
midBossProgress: Node = null;
bossProgress: Node = null;
/** Boss 滞后血量进度(主条变化后持续跟随掉落) */
bossProgress2: Node = null;
/** Boss 血条总血量数字 */
num: Node = null;
/** 滞后条追逐目标 fill;<0 表示未在掉落 */
private _trailTargetFill = -1;
/** 滞后条掉落速度(fill/秒) */
private _trailDropSpeed = 0;
/** 当前正在掉落的滞后条节点 */
private _trailNode: Node | null = null;
protected onLoad(): void {
this._ensureNodes();
this.enabled = false;
}
protected onDisable(): void {
this._stopTrailDrop();
}
protected update(dt: number): void {
const battle = gg.game?.CurentBattle as { TimeScale?: number; IsGamePause?: boolean; IsGameOver?: boolean } | undefined;
if (!battle || battle.IsGameOver || battle.IsGamePause) return;
const scale = battle.TimeScale ?? 1;
if (!(scale > 0)) return;
this._tickTrailDrop(dt * scale);
}
/** 池化复用时 onLoad 可能已跑过,受击刷新前再绑一次子节点 */
private _ensureNodes(): void {
if (!this.bossProgress?.isValid) this.bossProgress = this.node.find('bossProgress');
if (!this.bossProgress2?.isValid) this.bossProgress2 = this.node.find('bossProgress2');
if (!this.smallMonsterProgress?.isValid) {
this.smallMonsterProgress = this.node.find('smallMonsterProgress');
}
if (!this.smallMonsterProgress2?.isValid) {
this.smallMonsterProgress2 = this.node.find('smallMonsterProgress2');
}
if (!this.midBossProgress?.isValid) this.midBossProgress = this.node.find('midBossProgress');
if (!this.num?.isValid) this.num = this.node.find('num');
}
Data: ITableMonster = null;
/**
* 按当前/最大生命刷新进度。召唤怪与小怪共用「小怪血条底」,禁止走中期条。
* @param hp 当前生命
* @param maxHp 最大生命
* @param shieldHp 护盾当前(仅影响小怪满血是否显示)
* @param shieldMaxHp 护盾上限
*/
updateBloodBar(hp: number, maxHp: number, shieldHp = 0, shieldMaxHp = 0) {
if (!this.Data) return;
this._ensureNodes();
const t = this.Data.type;
const isSmall = t === MonsterType.SmallMonster;
const isSummon = t === MonsterType.SummonMonster;
const useSmallBar = isSmall || isSummon;
const isBoss = t === MonsterType.Boss;
const isMid = !isBoss && !useSmallBar && (
t === MonsterType.SilverBoss
|| t === MonsterType.GoldenBoss
|| t === MonsterType.OrangeBoss
);
const hasShield = shieldMaxHp > 0 && shieldHp > 0;
if (isSmall && !(hp < maxHp || hasShield)) {
this._resetSmallProgressFills();
this.node.active = false;
return;
}
this.node.active = true;
const ratio = maxHp > 0 ? clamp01(hp / maxHp) : 0;
if (this.smallMonsterProgress) {
this.smallMonsterProgress.active = useSmallBar;
if (useSmallBar) {
this._updateFillWithTrail(this.smallMonsterProgress, this.smallMonsterProgress2, ratio);
}
}
if (this.midBossProgress) {
this.midBossProgress.active = isMid;
if (isMid) this._setFill(this.midBossProgress, ratio);
}
if (isBoss) {
this._updateBossBar(hp, maxHp);
}
}
/**
* 主条立即改 fill;滞后条在 0.3s 内持续掉到相同 fill。
* @param main 主进度节点
* @param trail 滞后进度节点
* @param fill 目标填充 0~1
*/
private _updateFillWithTrail(main: Node, trail: Node | null, fill: number): void {
const mainSp = main.getComponent(Sprite);
if (!mainSp) {
console.log('[MonsterBlood] 主进度缺少 Sprite', main.name);
return;
}
this._applyFill(mainSp, fill);
if (!trail?.isValid) {
console.log('[MonsterBlood] 缺少滞后进度', main.name);
return;
}
trail.active = true;
const trailSp = trail.getComponent(Sprite);
if (!trailSp) {
console.log('[MonsterBlood] 滞后进度缺少 Sprite', trail.name);
return;
}
const cur = trailSp.fillRange;
if (Math.abs(cur - fill) < 1e-4 || fill >= cur) {
this._stopTrailDrop();
this._applyFill(trailSp, fill);
return;
}
this._trailNode = trail;
this._trailTargetFill = fill;
this._trailDropSpeed = (cur - fill) / PROGRESS2_DROP_SEC;
this.enabled = true;
}
/** 每帧推进滞后条 */
private _tickTrailDrop(dt: number): void {
if (!(this._trailTargetFill >= 0)) return;
const trail = this._trailNode;
if (!trail?.isValid) {
this._stopTrailDrop();
return;
}
const trailSp = trail.getComponent(Sprite);
if (!trailSp) {
this._stopTrailDrop();
return;
}
const target = this._trailTargetFill;
const next = trailSp.fillRange - this._trailDropSpeed * Math.max(0, dt);
if (next <= target) {
this._applyFill(trailSp, target);
this._stopTrailDrop();
return;
}
this._applyFill(trailSp, next);
}
/** 满血隐藏前复位,避免池化脏 fill */
private _resetSmallProgressFills(): void {
this._stopTrailDrop();
const mainSp = this.smallMonsterProgress?.isValid
? this.smallMonsterProgress.getComponent(Sprite)
: null;
if (mainSp) this._applyFill(mainSp, 1);
const trailSp = this.smallMonsterProgress2?.isValid
? this.smallMonsterProgress2.getComponent(Sprite)
: null;
if (trailSp) this._applyFill(trailSp, 1);
}
/**
* 写入 FILLED 进度并刷新渲染。
* TRIMMED + FILLED 会按贴图像素显示、池化后 contentSize 还可能被填满比例挤扁,须 CUSTOM。
* @param sp 进度 Sprite
* @param fill 0~1
*/
private _applyFill(sp: Sprite, fill: number): void {
if (sp.type !== Sprite.Type.FILLED) {
sp.type = Sprite.Type.FILLED;
sp.fillType = Sprite.FillType.HORIZONTAL;
}
sp.fillStart = 0;
if (sp.sizeMode !== Sprite.SizeMode.CUSTOM) {
const frame = sp.spriteFrame;
const ut = sp.node.getComponent(UITransform);
const w = frame?.rect.width ?? ut?.contentSize.width ?? 0;
const h = frame?.rect.height ?? ut?.contentSize.height ?? 0;
sp.sizeMode = Sprite.SizeMode.CUSTOM;
if (ut && w > 0 && h > 0) ut.setContentSize(w, h);
}
sp.fillRange = fill;
sp.markForUpdateRenderData();
}
/**
* 节点上的进度 Sprite。
* @param node 进度节点
* @param fill 0~1
*/
private _setFill(node: Node, fill: number): void {
const sp = node.getComponent(Sprite);
if (!sp) {
console.log('[MonsterBlood] 进度条缺少 Sprite', node.name);
return;
}
this._applyFill(sp, fill);
}
/** 停止滞后条追逐 */
private _stopTrailDrop(): void {
this._trailTargetFill = -1;
this._trailDropSpeed = 0;
this._trailNode = null;
this.enabled = false;
}
/** Boss 血条:主条立即扣、bossProgress2 滞后掉落,num 显示总血量数字 */
private _updateBossBar(hp: number, maxHp: number): void {
const ratio = maxHp > 0 ? clamp01(hp / maxHp) : 0;
if (this.bossProgress?.isValid) {
this._updateFillWithTrail(this.bossProgress, this.bossProgress2, ratio);
} else {
console.log('[MonsterBlood] 缺少 bossProgress');
}
if (this.num) {
const numLabel = this.num.getComponent(Label);
if (numLabel) numLabel.string = String(Math.max(0, Math.ceil(hp)));
}
}
}