import { _decorator, Component, Node, ProgressBar, tween, UITransform, Vec3, BoxCollider2D, Collider2D, CircleCollider2D } from 'cc'; const { ccclass, property } = _decorator; /** * 血条动画组件 * 使用两个重叠的血条实现黄色血皮滑动动画效果 * 红色血条显示当前血量,黄色血条用于滑动动画 * 血条默认隐藏,只在受伤时显示并播放动画 */ @ccclass('HPBarAnimation') export class HPBarAnimation extends Component { @property({ type: Node, tooltip: '红色血条节点' }) public redBarNode: Node = null; @property({ type: Node, tooltip: '黄色血条节点' }) public yellowBarNode: Node = null; @property({ type: Node, tooltip: 'HPBar根节点' }) public hpBarRootNode: Node = null; @property({ tooltip: '血条相对敌人顶部的额外偏移(像素)' }) public offsetY: number = 18; @property({ tooltip: '优先使用碰撞体高度定位' }) public useColliderForTop: boolean = true; @property({ tooltip: '打印调试日志' }) public debugLogs: boolean = true; private redProgressBar: ProgressBar = null; private yellowProgressBar: ProgressBar = null; private currentProgress: number = 1.0; private targetProgress: number = 1.0; private currentTween: any = null; // 当前正在运行的动画 private hideTimer: any = null; // 隐藏血条的定时器 start() { this.initializeComponents(); this.updateBarPosition(true); if (this.debugLogs) console.log(`[HPBarAnimation] start: node=${this.getNodePath(this.node)}`); } /** * 初始化组件 */ private initializeComponents() { // 获取组件所属的敌人节点信息 const enemyNode = this.node; const enemyName = enemyNode ? enemyNode.name : 'Unknown'; if (this.debugLogs) console.log(`[HPBarAnimation] initialize on ${this.getNodePath(this.node)} enemy=${enemyName}`); // 如果没有设置HPBar根节点,尝试自动查找或使用当前节点 if (!this.hpBarRootNode) { const child = this.node.getChildByName('HPBar'); if (child) { this.hpBarRootNode = child; if (this.debugLogs) console.log(`[HPBarAnimation] 自动设置 hpBarRootNode 为子节点: ${this.getNodePath(child)}`); } else { this.hpBarRootNode = this.node; if (this.debugLogs) console.log(`[HPBarAnimation] 未找到子节点 HPBar,hpBarRootNode 使用当前节点: ${this.getNodePath(this.node)}`); } } // 获取红色血条组件 if (this.redBarNode) { this.redProgressBar = this.redBarNode.getComponent(ProgressBar); if (this.redProgressBar) { this.currentProgress = this.redProgressBar.progress; this.targetProgress = this.currentProgress; } else { console.error(`[HPBarAnimation] [${enemyName}] 红色血条节点上未找到ProgressBar组件!`); } } else { console.error(`[HPBarAnimation] [${enemyName}] 红色血条节点未设置!`); } // 获取黄色血条组件 if (this.yellowBarNode) { this.yellowProgressBar = this.yellowBarNode.getComponent(ProgressBar); if (this.yellowProgressBar) { // 黄色血条初始进度与红色血条同步 this.yellowProgressBar.progress = this.currentProgress; } else { console.error(`[HPBarAnimation] [${enemyName}] 黄色血条节点上未找到ProgressBar组件!`); } } else { console.error(`[HPBarAnimation] [${enemyName}] 黄色血条节点未设置!`); } // 初始化时隐藏血条 this.hideHealthBar(); } /** * 获取节点路径 */ private getNodePath(node: Node): string { if (!node) return 'null'; let path = node.name; let current = node; while (current.parent) { current = current.parent; path = current.name + '/' + path; } return path; } /** * 更新血条显示 */ private updateBarDisplay() { if (!this.redProgressBar || !this.yellowProgressBar) { return; } // 红色血条显示当前血量 if (this.redProgressBar && this.redProgressBar.isValid) { this.redProgressBar.progress = this.currentProgress; } } /** * 显示血条 */ public showHealthBar() { // 仅在首次显示时进行定位计算,避免每次受击重复计算 const barNode = (this.hpBarRootNode && this.hpBarRootNode.isValid) ? this.hpBarRootNode : this.node; const wasActive = barNode.active; if (!wasActive) { this.updateBarPosition(); } if (this.hpBarRootNode && this.hpBarRootNode.isValid) { this.hpBarRootNode.active = true; if (this.debugLogs) console.log(`[HPBarAnimation] showHealthBar: active=true path=${this.getNodePath(this.hpBarRootNode)}`); } else { if (this.debugLogs) console.warn(`[HPBarAnimation] showHealthBar: hpBarRootNode 无效,使用当前节点 path=${this.getNodePath(this.node)}`); this.node.active = true; } // 清除隐藏定时器 if (this.hideTimer) { clearTimeout(this.hideTimer); this.hideTimer = null; } } /** * 隐藏血条 */ public hideHealthBar() { if (this.hpBarRootNode && this.hpBarRootNode.isValid) { this.hpBarRootNode.active = false; if (this.debugLogs) console.log(`[HPBarAnimation] hideHealthBar: active=false path=${this.getNodePath(this.hpBarRootNode)}`); } else { if (this.debugLogs) console.warn(`[HPBarAnimation] hideHealthBar: hpBarRootNode 无效,使用当前节点 path=${this.getNodePath(this.node)}`); this.node.active = false; } } /** * 延迟隐藏血条 * @param delay 延迟时间(秒) */ private scheduleHideHealthBar(delay: number = 3.0) { if (this.debugLogs) console.log(`[HPBarAnimation] scheduleHideHealthBar: delay=${delay}s path=${this.getNodePath(this.hpBarRootNode || this.node)}`); // 清除之前的定时器 if (this.hideTimer) { clearTimeout(this.hideTimer); } // 设置新的定时器 this.hideTimer = setTimeout(() => { this.hideHealthBar(); this.hideTimer = null; }, delay * 1000); } /** * 更新血条进度并播放动画 * @param newProgress 新的血量百分比 (0-1) * @param showBar 是否显示血条(默认为true) */ public updateProgress(newProgress: number, showBar: boolean = true) { const enemyName = this.node ? this.node.name : 'Unknown'; if (this.debugLogs) console.log(`[HPBarAnimation] updateProgress(showBar=${showBar}) ${this.currentProgress} -> ${newProgress}`); if (!this.redProgressBar || !this.yellowProgressBar) { console.warn(`[HPBarAnimation] [${enemyName}] 红色或黄色血条组件未初始化`); return; } newProgress = Math.max(0, Math.min(1, newProgress)); if (showBar) { this.showHealthBar(); // 定位仅在首次显示时执行,避免受击重复计算 } // 如果血量增加或没有变化,直接更新 if (newProgress >= this.currentProgress) { this.currentProgress = newProgress; this.targetProgress = newProgress; // 同步更新两个血条 if (this.redProgressBar && this.redProgressBar.isValid) { this.redProgressBar.progress = newProgress; } if (this.yellowProgressBar && this.yellowProgressBar.isValid) { this.yellowProgressBar.progress = newProgress; } this.updateBarDisplay(); // 如果血量满了,立即隐藏血条 if (newProgress >= 1.0) { this.scheduleHideHealthBar(1.0); // 1秒后隐藏 } else if (showBar) { this.scheduleHideHealthBar(3.0); // 3秒后隐藏 } return; } // 血量减少时播放黄色血皮滑动动画 this.playDamageAnimation(newProgress); } /** * 播放伤害动画 * @param newProgress 新的血量百分比 */ private playDamageAnimation(newProgress: number) { console.log(`[HPBarAnimation] 开始播放伤害动画:${this.currentProgress} -> ${newProgress}`); // 停止当前正在运行的动画,避免动画冲突 if (this.currentTween) { this.currentTween.stop(); this.currentTween = null; } this.targetProgress = newProgress; // 保存原始血量作为黄色血条起始位置 const originalProgress = this.currentProgress; // 1. 立即更新红色血条到新的血量值 this.currentProgress = newProgress; if (this.redProgressBar && this.redProgressBar.isValid) { this.redProgressBar.progress = newProgress; } // 2. 黄色血条从原血量滑动到新血量位置 // 黄色血条保持在原始位置 if (this.yellowProgressBar && this.yellowProgressBar.isValid) { this.yellowProgressBar.progress = originalProgress; } this.updateBarDisplay(); // 创建滑动动画,先暂停0.4秒再滑动 this.currentTween = tween({ progress: originalProgress }) .delay(0.4) // 暂停0.4秒 .to(0.3, { progress: newProgress }, { onUpdate: (target) => { // 动画过程中更新黄色血条进度 if (this.yellowProgressBar && this.yellowProgressBar.isValid) { this.yellowProgressBar.progress = target.progress; this.updateBarDisplay(); } }, onComplete: () => { // 动画完成后黄色血条进度等于当前血量 if (this.yellowProgressBar && this.yellowProgressBar.isValid) { this.yellowProgressBar.progress = newProgress; this.updateBarDisplay(); } // 清除动画引用 this.currentTween = null; // 动画完成后延迟隐藏血条 if (newProgress > 0) { this.scheduleHideHealthBar(3.0); // 3秒后隐藏血条 } } }) .start(); } /** * 获取当前血量百分比 */ public getCurrentProgress(): number { return this.currentProgress; } /** * 重置血条到满血状态 */ public resetToFull() { this.updateProgress(1.0, false); // 重置时不显示血条 } /** * 重置血条到满血状态并隐藏 */ public resetToFullAndHide() { this.currentProgress = 1.0; this.targetProgress = 1.0; // 同步更新两个血条到满血状态 if (this.redProgressBar && this.redProgressBar.isValid) { this.redProgressBar.progress = 1.0; } if (this.yellowProgressBar && this.yellowProgressBar.isValid) { this.yellowProgressBar.progress = 1.0; } this.updateBarDisplay(); // 立即隐藏血条 this.hideHealthBar(); } onDestroy() { // 清理正在运行的动画,防止内存泄漏 if (this.currentTween) { this.currentTween.stop(); this.currentTween = null; } // 清理隐藏血条的定时器,防止内存泄漏 if (this.hideTimer) { clearTimeout(this.hideTimer); this.hideTimer = null; } } public updateBarPosition(force: boolean = false) { // 以血条节点作为基准(兼容脚本挂在 Enemy 或 HPBar 的两种情况) const barNode = (this.hpBarRootNode && this.hpBarRootNode.isValid) ? this.hpBarRootNode : this.node; const barPath = this.getNodePath(barNode); const enemyNode = barNode.parent; if (!enemyNode) { console.warn(`[HPBarAnimation] ${barPath} 未找到父节点(敌人根节点),定位失败`); return; } // 先在同级父节点(敌人根)查找 EnemySprite,不存在则向上最多3级尝试 let spriteNode: Node | null = enemyNode.getChildByName('EnemySprite'); if (!spriteNode) { let ancestor = enemyNode.parent; let level = 1; while (!spriteNode && ancestor && level <= 3) { spriteNode = ancestor.getChildByName('EnemySprite'); if (spriteNode) { console.warn(`[HPBarAnimation] ${barPath} 在更高层级找到 EnemySprite: ${this.getNodePath(spriteNode)} (levelUp=${level})`); break; } ancestor = ancestor.parent; level++; } } if (!spriteNode) { console.warn(`[HPBarAnimation] ${barPath} 未找到 EnemySprite(向上3级查找仍失败),敌人根: ${this.getNodePath(enemyNode)}`); return; } // 同时计算 UITransform 顶部与碰撞体顶部,最终取不低于图像顶部的值 let colliderTopY: number | null = null; let uiTopY: number | null = null; let method = ''; // 计算 UI 顶部 const ui = spriteNode.getComponent(UITransform); if (ui) { const height = ui.contentSize.height * Math.abs(spriteNode.scale.y); uiTopY = spriteNode.position.y + (1 - ui.anchorPoint.y) * height; } // 计算碰撞体顶部(如有) const anyCollider = spriteNode.getComponent(Collider2D); if (anyCollider) { if (anyCollider instanceof BoxCollider2D) { const col = anyCollider as BoxCollider2D; colliderTopY = spriteNode.position.y + col.offset.y + col.size.height / 2; } else if (anyCollider instanceof CircleCollider2D) { const circle = anyCollider as CircleCollider2D; colliderTopY = spriteNode.position.y + circle.offset.y + circle.radius; } } let topY: number | null = null; if (this.useColliderForTop && colliderTopY !== null) { // 优先碰撞体,但不低于UI顶部 topY = uiTopY !== null ? Math.max(colliderTopY, uiTopY) : colliderTopY; method = `ColliderPreferred(${anyCollider ? anyCollider.constructor.name : 'none'}) + clampToUI`; } else if (uiTopY !== null) { // 仅使用UITransform topY = uiTopY; method = `UITransform(height=${ui ? ui.contentSize.height : 'N/A'}, anchorY=${ui ? ui.anchorPoint.y : 'N/A'}, scaleY=${spriteNode.scale.y})`; } else if (colliderTopY !== null) { // 回退到碰撞体 topY = colliderTopY; method = `ColliderOnly(${anyCollider ? anyCollider.constructor.name : 'none'})`; } if (topY === null) { console.warn(`[HPBarAnimation] ${barPath} EnemySprite 缺少 UITransform/Collider,无法计算顶部`); return; } // 将位置设置到血条节点自身(不要移动 Enemy 根节点) const current = barNode.position; const targetY = topY + this.offsetY; const moved = force || Math.abs(current.y - targetY) > 0.5; if (this.debugLogs) console.log(`[HPBarAnimation] 定位HPBar`, { barPath, enemyPath: this.getNodePath(enemyNode), spritePath: this.getNodePath(spriteNode), method, uiTopY, colliderTopY, topY, offsetY: this.offsetY, currentY: current.y, targetY, force, moved }); if (moved) { barNode.setPosition(new Vec3(current.x, targetY, current.z)); } } }