import { _decorator, Component, Node, Label, Vec3, tween, Tween, UITransform, UIOpacity, instantiate, resources, Prefab, Color, find } from 'cc'; import { EnemyController } from '../CombatSystem/EnemyController'; const { ccclass, property } = _decorator; /** * 伤害数字动画组件 * 实现伤害数字从小变大再缩小消失的动画效果 * 可挂载到EnemyController节点上,通过装饰器获取EnemyController组件 */ @ccclass('DamageNumberAni') export class DamageNumberAni extends Component { @property({ type: EnemyController, tooltip: 'EnemyController组件引用' }) private enemyController: EnemyController = null; @property({ type: Node, tooltip: 'GameArea节点引用' }) private gameArea: Node = null; @property({ type: Prefab, tooltip: 'DamageNumber预制体引用' }) private damageNumberPrefab: Prefab = null; onLoad() { // 自动获取同节点上的EnemyController组件 if (!this.enemyController) { this.enemyController = this.getComponent(EnemyController); } // 只有在需要使用EnemyController功能时才报错 // 动态创建的伤害数字节点不需要EnemyController组件 if (!this.enemyController && this.damageNumberPrefab) { console.warn('[DamageNumberAni] 未找到EnemyController组件,某些功能可能不可用'); } } /** * 创建并显示伤害数字 * @param damage 伤害值 * @param worldPosition 世界坐标位置(敌人头顶) * @param isCritical 是否暴击 */ public showDamageNumber(damage: number, worldPosition: Vec3, isCritical: boolean = false) { // 检查预制体引用 if (!this.damageNumberPrefab) { console.error('[DamageNumberAni] DamageNumber预制体引用未设置'); return; } // 实例化伤害数字节点 const damageNode = instantiate(this.damageNumberPrefab); // 使用装饰器引用的GameArea节点 if (!this.gameArea) { console.error('[DamageNumberAni] GameArea节点引用未设置'); damageNode.destroy(); return; } // 添加到GameArea this.gameArea.addChild(damageNode); // 转换世界坐标到GameArea的本地坐标 const gameAreaTransform = this.gameArea.getComponent(UITransform); if (gameAreaTransform) { const localPos = gameAreaTransform.convertToNodeSpaceAR(worldPosition); // 稍微向上偏移,显示在敌人头顶 localPos.y += 30; damageNode.position = localPos; } // 设置伤害数字文本 const label = damageNode.getComponent(Label); if (label) { // 显示精确的伤害值,保留一位小数(如果有小数部分) const damageText = damage % 1 === 0 ? damage.toString() : damage.toFixed(1); label.string = damageText; // 暴击时使用不同颜色 if (isCritical) { label.color = Color.YELLOW; // 暴击显示黄色 label.fontSize = 24; // 暴击字体更大 } else { label.color = Color.WHITE; // 普通伤害白色 label.fontSize = 20; } } // 将DamageNumberAni组件添加到伤害数字节点 const aniComponent = damageNode.addComponent(DamageNumberAni); aniComponent.playAnimation(isCritical); } /** * 静态方法保持兼容性,但推荐使用实例方法 * @param damage 伤害值 * @param worldPosition 世界坐标位置(敌人头顶) * @param isCritical 是否暴击 */ public static showDamageNumber(damage: number, worldPosition: Vec3, isCritical: boolean = false) { // 尝试找到EnemyController节点上的DamageNumberAni组件 const enemyControllerNode = find('Canvas/GameLevelUI/EnemyController'); if (enemyControllerNode) { const damageAni = enemyControllerNode.getComponent(DamageNumberAni); if (damageAni) { damageAni.showDamageNumber(damage, worldPosition, isCritical); return; } } console.warn('[DamageNumberAni] 未找到挂载的DamageNumberAni组件,请确保组件已挂载到EnemyController节点'); } /** * 播放伤害数字动画 * @param isCritical 是否暴击 */ private playAnimation(isCritical: boolean = false) { // 初始状态:缩放为0,透明度为1 this.node.setScale(0, 0, 1); const uiTransform = this.node.getComponent(UITransform); const uiOpacity = this.node.getComponent(UIOpacity); if (uiOpacity) { uiOpacity.opacity = 255; } // 动画参数 const maxScale = isCritical ? 1.3 : 1.0; // 暴击时放大更多 const growDuration = 0.2; // 放大阶段持续时间 const stayDuration = 0.3; // 停留时间 const shrinkDuration = 0.4; // 缩小消失时间 const floatDistance = 50; // 向上漂浮距离 // 第一阶段:从0放大到最大尺寸(从底部开始放大) tween(this.node) .to(growDuration, { scale: new Vec3(maxScale, maxScale, 1) }, { easing: 'backOut' // 弹性效果 }) .call(() => { // 第二阶段:保持大小,开始向上漂浮并逐渐缩小透明 const startPos = this.node.position.clone(); const endPos = startPos.clone(); endPos.y += floatDistance; // 位置动画 tween(this.node) .to(stayDuration + shrinkDuration, { position: endPos }, { easing: 'sineOut' }) .start(); // 延迟后开始缩小和淡出 tween(this.node) .delay(stayDuration) .to(shrinkDuration, { scale: new Vec3(0.3, 0.3, 1) }, { easing: 'sineIn' }) .start(); // 透明度动画 if (uiOpacity) { tween(uiOpacity) .delay(stayDuration) .to(shrinkDuration, { opacity: 0 }, { easing: 'sineIn' }) .call(() => { // 动画完成,销毁节点 if (this.node && this.node.isValid) { this.node.destroy(); } }) .start(); } }) .start(); } onDestroy() { // 清理所有tween动画 Tween.stopAllByTarget(this.node); const uiTransform = this.node.getComponent(UITransform); const uiOpacity = this.node.getComponent(UIOpacity); if (uiTransform) { Tween.stopAllByTarget(uiTransform); } if (uiOpacity) { Tween.stopAllByTarget(uiOpacity); } } }