Files
pixelheros/assets/script/game/map/HlistComp.ts
panw 197e913c53 feat(英雄列表): 添加前后英雄预览并改进动画管理
- 新增 phero_icon 和 nhero_icon 节点用于显示前后英雄
- 将 iconVisualToken 改为 Map 结构以分别管理多个节点的动画令牌
- 在更新显示时加载并播放当前、前一个及后一个英雄的动画
- 优化动画加载的取消逻辑,避免令牌不匹配导致的动画错误
2026-04-01 16:54:16 +08:00

190 lines
6.5 KiB
TypeScript

import { _decorator, Animation, AnimationClip, Button, Event, Label, Node, NodeEventType, Sprite, resources } 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 { HeroInfo, HeroList } from "../common/config/heroSet";
import { IType, SkillSet } from "../common/config/SkillSet";
import { oops } from "db://oops-framework/core/Oops";
import { mLogger } from "../common/Logger";
const {property, ccclass } = _decorator;
/** 视图层对象 */
@ccclass('HListComp')
@ecs.register('HListComp', false)
export class HListComp extends CCComp {
@property(Node)
hero_icon=null!
@property(Node)
phero_icon=null!
@property(Node)
nhero_icon=null!
@property(Node)
ap_node=null!
@property(Node)
hp_node=null!
@property(Node)
info_node=null!
@property(Node)
name_node=null!
@property(Node)
pre_btn=null!
@property(Node)
next_btn=null!
huuid:number=null!
private currentIndex: number = 0;
private iconVisualTokens: Map<Node, number> = new Map();
debugMode: boolean = false;
onLoad() {
this.pre_btn?.on(NodeEventType.TOUCH_END, this.onPreClick, this);
this.next_btn?.on(NodeEventType.TOUCH_END, this.onNextClick, this);
}
start() {
if (HeroList && HeroList.length > 0) {
this.currentIndex = 0;
this.updateHeroView();
}
}
private onPreClick() {
if (!HeroList || HeroList.length === 0) return;
this.currentIndex = (this.currentIndex - 1 + HeroList.length) % HeroList.length;
this.updateHeroView();
}
private onNextClick() {
if (!HeroList || HeroList.length === 0) return;
this.currentIndex = (this.currentIndex + 1) % HeroList.length;
this.updateHeroView();
}
private updateHeroView() {
this.huuid = HeroList[this.currentIndex];
const hero = HeroInfo[this.huuid];
if (!hero) return;
// 获取前后英雄的 uuid
const preIndex = (this.currentIndex - 1 + HeroList.length) % HeroList.length;
const nextIndex = (this.currentIndex + 1) % HeroList.length;
const pUuid = HeroList[preIndex];
const nUuid = HeroList[nextIndex];
// 更新基础属性标签
this.setLabelText(this.name_node, hero.name);
this.setLabelText(this.ap_node, `攻击力: ${hero.ap}`);
this.setLabelText(this.hp_node, `生命值: ${hero.hp}`);
// 更新动画
this.updateHeroAnimation(this.hero_icon, this.huuid);
this.updateHeroAnimation(this.phero_icon, pUuid);
this.updateHeroAnimation(this.nhero_icon, nUuid);
// 更新技能列表
this.updateSkillInfo(hero);
}
private setLabelText(node: Node, text: string) {
if (!node) return;
const label = node.getComponent(Label) || node.getComponentInChildren(Label);
if (label) {
label.string = text;
}
}
private updateHeroAnimation(node: Node, uuid: number) {
if (!node) return;
const sprite = node.getComponent(Sprite) || node.getComponentInChildren(Sprite);
if (sprite) sprite.spriteFrame = null;
const hero = HeroInfo[uuid];
if (!hero) return;
const anim = node.getComponent(Animation) || node.addComponent(Animation);
this.clearAnimationClips(anim);
let token = (this.iconVisualTokens.get(node) || 0) + 1;
this.iconVisualTokens.set(node, token);
const path = `game/heros/hero/${hero.path}/idle`;
resources.load(path, AnimationClip, (err, clip) => {
if (err || !clip) {
mLogger.log(this.debugMode, "HListComp", `load hero animation failed ${uuid}`, err);
return;
}
if (token !== this.iconVisualTokens.get(node)) {
return;
}
this.clearAnimationClips(anim);
anim.addClip(clip);
anim.play("idle");
});
}
private clearAnimationClips(anim: Animation) {
const clips = anim.clips;
if (clips && clips.length > 0) {
for (let i = clips.length - 1; i >= 0; i--) {
const clip = clips[i];
if (clip) anim.removeClip(clip, true);
}
}
}
private findNodeByName(root: Node, name: string): Node | null {
if (!root) return null;
if (root.name === name) return root;
for (let i = 0; i < root.children.length; i++) {
const res = this.findNodeByName(root.children[i], name);
if (res) return res;
}
return null;
}
private updateSkillInfo(hero: any) {
if (!this.info_node) return;
const skills = Object.values(hero.skills || {});
for (let i = 1; i <= 5; i++) {
let line = this.findNodeByName(this.info_node, `Line${i}`) || this.findNodeByName(this.info_node, `line${i}`);
if (!line) continue;
const skill: any = skills[i - 1];
if (skill) {
line.active = true;
const skillId = skill.uuid;
const config = SkillSet[skillId];
const text = config ? `${config.name} Lv.${skill.lv} CD:${skill.cd}s ${config.info}` : `未知技能 CD:${skill.cd}s`;
const noteNode = this.findNodeByName(line, "note");
const label = noteNode?.getComponent(Label) || noteNode?.getComponentInChildren(Label) || line.getComponentInChildren(Label);
if (label) {
label.string = text;
}
this.updateLineTypeIcon(line, config?.IType);
} else {
line.active = false;
}
}
}
private updateLineTypeIcon(line: Node, iType?: IType) {
const meleeNode = this.findNodeByName(line, "Melee");
const remoteNode = this.findNodeByName(line, "remote");
const supportNode = this.findNodeByName(line, "support");
if (meleeNode) meleeNode.active = iType === IType.Melee;
if (remoteNode) remoteNode.active = iType === IType.remote;
if (supportNode) supportNode.active = iType === IType.support;
}
/** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */
reset() {
this.node.destroy();
}
}