Files
pixelheros/assets/script/game/skill/TooltipCom.ts
panFD 667ec1abfb refactor(skill&atk): 优化伤害飘字与debuff图标显示逻辑
1. 调整fb-1、fb-3、fb-5、fb-6技能预制体的攻击偏移与跑动类型参数
2. 重构debuff图标映射逻辑,提取公共常量供多模块复用
3. 为DoT伤害飘字添加debuff图标显示功能,统一伤害与debuff展示流程
4. 修复tooltip预制体默认激活状态问题
2026-08-15 14:55:51 +08:00

257 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { _decorator, Collider2D, Contact2DType, v3, IPhysics2DContact, Vec3, tween, Label, resources, SpriteFrame, Sprite, UIOpacity, Color, math, Tween,Node } 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 { SkillSet } from "../common/config/SkillSet";
import { FacSet, TooltipTypes } from "../common/config/GameSet";
import { SkillTriggerType } from "../common/config/heroSet";
import { Tooltip } from "./Tooltip";
const { ccclass, property } = _decorator;
/** 视图层对象 */
@ccclass('TooltipCom')
@ecs.register('TooltipView', false)
export class TooltipCom extends CCComp {
@property(Node)
atking_bg: Node = null;
@property(Node)
atked_bg: Node = null;
@property(Node)
fstart_bg: Node = null;
@property(Node)
dead_bg: Node = null;
@property(Node)
revive_bg: Node = null;
stype: number = 1; // 1:减少生命值2增加生命值3技能图标
value: string = "";
s_uuid: number = 1001;
fac: number = FacSet.MON;
/** 当前技能喊话对应的触发类型(空字符串表示普通主动技能) */
triggerType: string = "";
/** DoT 飘字需激活的 debuff 图标节点名loss_life/hp/debuff 下的子节点,如 FireDamage空串=普通伤害 */
debuffIcon: string = "";
// 动画参数配置
private readonly popDuration = 0.15;
private readonly driftDuration = 0.5;
private readonly fadeDuration = 0.2;
private _uiOpacity: UIOpacity | null = null;
start() {
// 首次加载时如果未手动调用 init则自动初始化防止直接挂载场景的情况
// 但在当前架构下Tooltip 都是通过 Tooltip.load 创建的,所以 start 可以留空或仅做基本检查
}
/** 初始化并播放动画 */
init(type: number, value: string, uuid: number, fac: number = FacSet.MON, triggerType: string = "", debuffIcon: string = "") {
this.stype = type;
this.value = value;
this.s_uuid = uuid;
this.fac = fac;
this.triggerType = triggerType;
this.debuffIcon = debuffIcon;
// 初始化或获取 UIOpacity 组件
this._uiOpacity = this.node.getComponent(UIOpacity);
if (!this._uiOpacity) {
this._uiOpacity = this.node.addComponent(UIOpacity);
}
this._uiOpacity.opacity = 255;
// 检测父节点翻转
let sx = 1;
if (this.node.parent && this.node.parent.scale.x < 0) {
sx = -1;
}
// 🔥 关键修复:停止当前节点和 UIOpacity 上的所有缓动
Tween.stopAllByTarget(this.node);
if (this._uiOpacity) {
Tween.stopAllByTarget(this._uiOpacity);
this._uiOpacity.opacity = 255;
}
this.node.setScale(v3(0, 0, 1)); // 初始缩放为0
this.node.active = true; // 确保节点激活
// 重置所有子节点状态
this.node.children.forEach(child => child.active = false);
let scaleMax = 1.5;
let isCrit = false;
let isHeal = false;
// 初始随机偏移,防止完全重叠
const offsetX = (Math.random() - 0.5) * 40;
// 设置极高的渲染层级,防止被怪物遮挡
const topSiblingIndex = 9999;
// 临时变量存储当前Y坐标方便统一处理。增加60的Y轴基础偏移防止伤害飘字等遮挡血条
let currentY = this.node.position.y + 30;
switch (this.stype) {
case TooltipTypes.life: // 普通伤害
this.node.setPosition(v3(this.node.position.x + offsetX, currentY));
this.node.setSiblingIndex(topSiblingIndex);
this.setupLabel(this.fac === FacSet.HERO ? "hloss" : "loss_life", "hp", this.value);
this.setupDebuffIcon(); // DoT 伤害时点亮 loss_life/hp/debuff 下的对应图标
scaleMax = 1.5;
break;
case TooltipTypes.health: // 治疗
this.node.setPosition(v3(this.node.position.x + offsetX, currentY));
this.node.setSiblingIndex(topSiblingIndex);
this.setupLabel("add_life", "hp", this.value);
isHeal = true;
break;
case TooltipTypes.addmp: // 回蓝
this.node.setPosition(v3(this.node.position.x + offsetX, currentY));
this.node.setSiblingIndex(topSiblingIndex);
this.setupLabel("add_mp", "mp", this.value);
isHeal = true;
break;
case TooltipTypes.crit: // 暴击
this.node.setPosition(v3(this.node.position.x + offsetX, currentY));
this.node.setSiblingIndex(topSiblingIndex + 100); // 暴击层级更高
this.setupLabel("bloss", "hp", this.value);
scaleMax = 3.0; // 暴击放大
isCrit = true;
break;
case TooltipTypes.skill:
const skillConfig = SkillSet[this.s_uuid];
const skillName = skillConfig ? skillConfig.name : "";
this.setupLabel("skill", "name", skillName+this.value);
// this.node.setPosition(v3(this.node.position.x, currentY));
this.node.setSiblingIndex(topSiblingIndex);
// 根据触发类型激活对应的背景标识(追击/反击/起手/生息/亡语/复活)
this.setupTriggerBg(this.triggerType);
break;
case TooltipTypes.uskill:
this.setupLabel("uskill", "name", this.value);
this.node.setPosition(v3(this.node.position.x, currentY + 30));
this.node.setSiblingIndex(topSiblingIndex);
break;
case TooltipTypes.lvup:
this.node.getChildByName("lvup").active = true;
this.node.setPosition(v3(this.node.position.x, currentY - 30));
this.node.setSiblingIndex(topSiblingIndex);
break;
case TooltipTypes.apup:
this.setupLabel("apup", "num", "+" + this.value);
this.node.setPosition(v3(this.node.position.x, currentY - 60));
this.node.setSiblingIndex(topSiblingIndex);
break;
case TooltipTypes.hpup:
this.setupLabel("hpup", "num", "+" + this.value);
this.node.setPosition(v3(this.node.position.x, currentY - 60));
this.node.setSiblingIndex(topSiblingIndex);
break;
case TooltipTypes.shield: // 护盾减免
this.node.setPosition(v3(this.node.position.x + offsetX, currentY));
this.node.setSiblingIndex(topSiblingIndex);
this.setupLabel("add_life", "hp", "减免");
isHeal = true;
break;
}
this.playAnimation(scaleMax, isCrit, isHeal, sx);
}
private setupLabel(nodeName: string, labelNodeName: string, text: string) {
const node = this.node.getChildByName(nodeName);
if (node) {
node.active = true;
const label = node.getChildByName(labelNodeName)?.getComponent(Label);
if (label) label.string = text;
}
}
/**
* DoT 伤害飘字:激活 loss_life/hp/debuff 下指定的 debuff 图标子节点。
* 仅怪物普通伤害loss_life路径调用debuffIcon 为空或未找到节点时静默跳过。
*/
private setupDebuffIcon() {
if (!this.debuffIcon) return;
const debuffNode = this.node.getChildByName("loss_life")?.getChildByName("hp")?.getChildByName("debuff");
if (!debuffNode) return;
// 先全部隐藏(对象池复用防残留),再点亮目标图标
for (const child of debuffNode.children) {
child.active = child.name === this.debuffIcon;
}
}
/**
* 根据技能触发类型激活对应的背景标识节点
* 仅 TooltipTypes.skill 走此分支,其他飘字类型不受影响
* 对象池复用场景下先统一关闭所有 _bg避免上一次的状态残留
*/
private setupTriggerBg(triggerType: string) {
// 先关闭所有触发类型背景,防止节点池复用时残留
this.atking_bg && (this.atking_bg.active = false);
this.atked_bg && (this.atked_bg.active = false);
this.fstart_bg && (this.fstart_bg.active = false);
this.dead_bg && (this.dead_bg.active = false);
this.revive_bg && (this.revive_bg.active = false);
if (!triggerType) return;
const bgMap: Record<string, Node | null> = {
[SkillTriggerType.Atking]: this.atking_bg,
[SkillTriggerType.Atked]: this.atked_bg,
[SkillTriggerType.FStart]: this.fstart_bg,
[SkillTriggerType.Dead]: this.dead_bg,
[SkillTriggerType.Revive]: this.revive_bg,
};
const bg = bgMap[triggerType];
if (bg) bg.active = true;
}
playAnimation(scaleMax: number, isCrit: boolean, isHeal: boolean, sx: number = 1) {
// 随机 X 轴偏移 (防止重叠)
const randomX = (Math.random() - 0.5) * 60;
const moveY = 80;
const t = tween(this.node);
// 1. 爆发阶段 (Pop)
t.to(this.popDuration, { scale: v3(sx * scaleMax, scaleMax, 1) }, { easing: 'backOut' });
const isSkill = this.stype === TooltipTypes.skill;
if (isSkill) {
// 技能类型的提示:回弹后稍作停留,然后原地快速淡出
t.to(this.popDuration, { scale: v3(sx * 1.2, 1.2, 1) });
t.delay(0.2);
t.parallel(
tween(this._uiOpacity).to(this.fadeDuration, { opacity: 0 })
);
} else {
// 2. 漂移阶段 (Drift) & 3. 淡出 (Fade)
// 其他类型提示:并行执行恢复缩放、位移和淡出
t.parallel(
tween(this.node).to(this.popDuration, { scale: v3(sx * 1.2, 1.2, 1) }), // 回弹到正常大小 (稍微放大)
tween(this.node).by(this.driftDuration, { position: v3(isHeal ? 0 : randomX, moveY, 0) }, { easing: 'sineOut' }), // 治疗垂直飘,其他随机飘
tween(this._uiOpacity).delay(this.driftDuration - this.fadeDuration).to(this.fadeDuration, { opacity: 0 }) // 最后阶段淡出
);
}
// 结束销毁
t.call(() => {
// this.ent.destroy(); // 不要销毁实体,而是直接回收节点
Tooltip.put(this.node);
}).start();
}
update(deltaTime: number) {
}
/** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */
reset() {
// 使用对象池回收
Tooltip.put(this.node);
}
}