refactor(tooltip): 优化提示系统使用对象池并改进动画效果

重构提示系统,引入对象池管理节点提升性能,改进动画效果包括缩放、位移和淡出,调整提示位置和层级防止重叠,修复父节点翻转时的显示问题
This commit is contained in:
walkpan
2026-01-02 17:27:53 +08:00
parent ebd67472c7
commit c40414173d
5 changed files with 266 additions and 212 deletions

View File

@@ -1,8 +1,9 @@
import { _decorator,Collider2D ,Contact2DType,v3,IPhysics2DContact,Vec3, tween, Label,resources,SpriteFrame,Sprite} from "cc";
import { _decorator, Collider2D, Contact2DType, v3, IPhysics2DContact, Vec3, tween, Label, resources, SpriteFrame, Sprite, UIOpacity, Color, math, Tween } 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 { TooltipTypes } from "../common/config/GameSet";
import { Tooltip } from "./Tooltip";
const { ccclass, property } = _decorator;
@@ -10,130 +11,168 @@ const { ccclass, property } = _decorator;
@ccclass('TooltipCom')
@ecs.register('TooltipView', false)
export class TooltipCom extends CCComp {
/** 视图层逻辑代码分离演示 */
// start() {
// // var entity = this.ent as ecs.Entity; // ecs.Entity 可转为当前模块的具体实体对象
// // this.on(ModuleEvent.Cmd, this.onHandler, this);
// }
stype:number = 1; // 1:减少生命值2增加生命值3技能图标
value:string = "";
s_uuid:number = 1001;
alive_time:number = 0.3;
skill_name_time=0.5;
scale:number = 1;
start() {
switch(this.stype){
case TooltipTypes.life:
this.node.setPosition(v3(this.node.position.x,this.node.position.y))
this.node.setSiblingIndex(100);
this.node.getChildByName("loss_life").getChildByName("hp").getComponent(Label).string = this.value;
this.node.getChildByName("loss_life").active=true;
this.scheduleOnce(()=>{
this.ent.destroy()
},0.5)
break
case TooltipTypes.health:
this.node.setSiblingIndex(110);
this.node.getChildByName("add_life").getChildByName("hp").getComponent(Label).string = this.value;
this.node.getChildByName("add_life").active=true;
this.scheduleOnce(()=>{
this.ent.destroy()
},0.5)
break
case TooltipTypes.addmp:
this.node.setSiblingIndex(110);
this.node.getChildByName("add_mp").getChildByName("mp").getComponent(Label).string = this.value;
this.node.getChildByName("add_mp").active=true;
this.scheduleOnce(()=>{
this.ent.destroy()
},0.5)
break
case TooltipTypes.crit:
this.node.setPosition(v3(this.node.position.x,this.node.position.y))
this.node.setSiblingIndex(200);
this.node.getChildByName("bloss").getChildByName("hp").getComponent(Label).string = this.value;
this.node.getChildByName("bloss").active=true;
this.scheduleOnce(()=>{
this.ent.destroy()
},0.5)
break
case TooltipTypes.skill:
const skillConfig = SkillSet[this.s_uuid];
if (skillConfig) {
this.node.getChildByName("skill").getChildByName("name").getComponent(Label).string = "<"+skillConfig.name+">";
} else {
this.node.getChildByName("skill").getChildByName("name").getComponent(Label).string = "";
}
this.node.getChildByName("skill").active=true;
this.node.setPosition(v3(this.node.position.x,this.node.position.y+30))
this.scheduleOnce(()=>{
this.ent.destroy()
},0.5)
break
case TooltipTypes.uskill:
this.node.getChildByName("uskill").getChildByName("name").getComponent(Label).string = this.value;
this.node.getChildByName("uskill").active=true;
this.node.setPosition(v3(this.node.position.x,this.node.position.y+30))
this.scheduleOnce(()=>{
this.ent.destroy()
},0.5)
break
case TooltipTypes.lvup:
// this.node.getChildByName("lvup").getChildByName("name").getComponent(Label).string = this.value;
this.node.getChildByName("lvup").active=true;
this.node.setPosition(v3(this.node.position.x,this.node.position.y-30))
this.scheduleOnce(()=>{
this.ent.destroy()
},0.5)
break
case TooltipTypes.apup:
this.node.getChildByName("apup").getChildByName("num").getComponent(Label).string = "+"+this.value;
this.node.getChildByName("apup").active=true;
this.node.setPosition(v3(this.node.position.x,this.node.position.y-60))
this.scheduleOnce(()=>{
this.ent.destroy()
},0.5)
break
case TooltipTypes.hpup:
this.node.getChildByName("hpup").getChildByName("num").getComponent(Label).string = "+"+this.value;
this.node.getChildByName("hpup").active=true;
this.node.setPosition(v3(this.node.position.x,this.node.position.y-60))
this.scheduleOnce(()=>{
this.ent.destroy()
},0.5)
break
case TooltipTypes.shield:
this.node.setSiblingIndex(110);
this.node.getChildByName("add_life").getChildByName("hp").getComponent(Label).string = this.value;
this.node.getChildByName("add_life").active=true;
this.scheduleOnce(()=>{
this.ent.destroy()
},0.5)
break
}
// console.log("TooltipView start:",this.node.getSiblingIndex());
stype: number = 1; // 1:减少生命值2增加生命值3技能图标
value: string = "";
s_uuid: number = 1001;
// 动画参数配置
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) {
this.stype = type;
this.value = value;
this.s_uuid = uuid;
// 初始化或获取 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坐标方便统一处理
let currentY = this.node.position.y;
switch (this.stype) {
case TooltipTypes.life: // 普通伤害
this.node.setPosition(v3(this.node.position.x + offsetX, currentY));
this.node.setSiblingIndex(topSiblingIndex);
this.setupLabel("loss_life", "hp", this.value);
scaleMax = 1.5;
break;
case TooltipTypes.health: // 治疗
this.node.setSiblingIndex(topSiblingIndex);
this.setupLabel("add_life", "hp", this.value);
isHeal = true;
break;
case TooltipTypes.addmp: // 回蓝
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.node.setPosition(v3(this.node.position.x, currentY));
this.node.setSiblingIndex(topSiblingIndex);
break;
case TooltipTypes.uskill:
this.setupLabel("uskill", "name", this.value);
this.node.setPosition(v3(this.node.position.x, this.node.position.y + 30));
this.node.setSiblingIndex(topSiblingIndex);
break;
case TooltipTypes.lvup:
this.node.getChildByName("lvup").active = true;
this.node.setPosition(v3(this.node.position.x, this.node.position.y - 30));
this.node.setSiblingIndex(topSiblingIndex);
break;
case TooltipTypes.apup:
this.setupLabel("apup", "num", "+" + this.value);
this.node.setPosition(v3(this.node.position.x, this.node.position.y - 60));
this.node.setSiblingIndex(topSiblingIndex);
break;
case TooltipTypes.hpup:
this.setupLabel("hpup", "num", "+" + this.value);
this.node.setPosition(v3(this.node.position.x, this.node.position.y - 60));
this.node.setSiblingIndex(topSiblingIndex);
break;
case TooltipTypes.shield:
this.node.setSiblingIndex(topSiblingIndex);
this.setupLabel("add_life", "hp", this.value);
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;
}
}
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' });
// 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) {
}
do_up(){
tween(this.node).to(
this.alive_time,
{position:v3(this.node.position.x,this.node.position.y+60), },
{
onComplete:()=>{ this.ent.destroy()},
easing:"linear"
}
).start()
}
to_destroy() {
this.ent.destroy()
}
/** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */
reset() {
this.node.destroy();
// 使用对象池回收
Tooltip.put(this.node);
}
}
}