- 新增两个英雄图标节点用于扩展轮播视图 - 实现平滑的轮播动画,点击前后按钮时图标会滑动切换 - 添加动画状态锁防止动画冲突 - 重构英雄信息更新逻辑,支持五节点轮播布局 - 使用tween实现位置动画,优化视觉流畅度
244 lines
9.2 KiB
TypeScript
244 lines
9.2 KiB
TypeScript
import { _decorator, Animation, AnimationClip, Button, Event, Label, Node, NodeEventType, Sprite, resources, tween, Vec3 } 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)
|
|
phero1_icon=null!
|
|
@property(Node)
|
|
nhero1_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();
|
|
private isAnimating: boolean = false;
|
|
private carouselNodes: Node[] = [];
|
|
private fixedPositions: Vec3[] = [];
|
|
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 (this.phero1_icon && this.phero_icon && this.hero_icon && this.nhero_icon && this.nhero1_icon) {
|
|
this.carouselNodes = [this.phero1_icon, this.phero_icon, this.hero_icon, this.nhero_icon, this.nhero1_icon];
|
|
this.fixedPositions = this.carouselNodes.map(n => n.position.clone());
|
|
}
|
|
|
|
if (HeroList && HeroList.length > 0) {
|
|
this.currentIndex = 0;
|
|
this.initAllNodes();
|
|
this.updateHeroInfo();
|
|
}
|
|
}
|
|
|
|
private onPreClick() {
|
|
if (!HeroList || HeroList.length === 0 || this.isAnimating || this.carouselNodes.length < 5) return;
|
|
this.isAnimating = true;
|
|
|
|
this.currentIndex = (this.currentIndex - 1 + HeroList.length) % HeroList.length;
|
|
|
|
const [n0, n1, n2, n3, n4] = this.carouselNodes;
|
|
|
|
// n4 instantly jumps from rightmost to leftmost position to get ready to slide in
|
|
n4.setPosition(new Vec3(this.fixedPositions[0].x, n4.position.y, n4.position.z));
|
|
this.updateNodeAnimationByOffset(n4, -2);
|
|
this.updateHeroInfo();
|
|
|
|
tween(n0).to(0.2, { position: new Vec3(this.fixedPositions[1].x, n0.position.y, n0.position.z) }).start();
|
|
tween(n1).to(0.2, { position: new Vec3(this.fixedPositions[2].x, n1.position.y, n1.position.z) }).start();
|
|
tween(n2).to(0.2, { position: new Vec3(this.fixedPositions[3].x, n2.position.y, n2.position.z) }).start();
|
|
tween(n3).to(0.2, { position: new Vec3(this.fixedPositions[4].x, n3.position.y, n3.position.z) })
|
|
.call(() => {
|
|
this.carouselNodes = [n4, n0, n1, n2, n3];
|
|
this.isAnimating = false;
|
|
})
|
|
.start();
|
|
}
|
|
|
|
private onNextClick() {
|
|
if (!HeroList || HeroList.length === 0 || this.isAnimating || this.carouselNodes.length < 5) return;
|
|
this.isAnimating = true;
|
|
|
|
this.currentIndex = (this.currentIndex + 1) % HeroList.length;
|
|
|
|
const [n0, n1, n2, n3, n4] = this.carouselNodes;
|
|
|
|
// n0 instantly jumps from leftmost to rightmost position to get ready to slide in
|
|
n0.setPosition(new Vec3(this.fixedPositions[4].x, n0.position.y, n0.position.z));
|
|
this.updateNodeAnimationByOffset(n0, 2);
|
|
this.updateHeroInfo();
|
|
|
|
tween(n1).to(0.2, { position: new Vec3(this.fixedPositions[0].x, n1.position.y, n1.position.z) }).start();
|
|
tween(n2).to(0.2, { position: new Vec3(this.fixedPositions[1].x, n2.position.y, n2.position.z) }).start();
|
|
tween(n3).to(0.2, { position: new Vec3(this.fixedPositions[2].x, n3.position.y, n3.position.z) }).start();
|
|
tween(n4).to(0.2, { position: new Vec3(this.fixedPositions[3].x, n4.position.y, n4.position.z) })
|
|
.call(() => {
|
|
this.carouselNodes = [n1, n2, n3, n4, n0];
|
|
this.isAnimating = false;
|
|
})
|
|
.start();
|
|
}
|
|
|
|
private getHeroUuid(offset: number): number {
|
|
const len = HeroList.length;
|
|
return HeroList[(this.currentIndex + offset + len * 5) % len];
|
|
}
|
|
|
|
private updateNodeAnimationByOffset(node: Node, offset: number) {
|
|
const uuid = this.getHeroUuid(offset);
|
|
this.updateHeroAnimation(node, uuid);
|
|
}
|
|
|
|
private updateHeroInfo() {
|
|
this.huuid = this.getHeroUuid(0);
|
|
const hero = HeroInfo[this.huuid];
|
|
if (!hero) return;
|
|
|
|
this.setLabelText(this.name_node, hero.name);
|
|
this.setLabelText(this.ap_node, `攻击力: ${hero.ap}`);
|
|
this.setLabelText(this.hp_node, `生命值: ${hero.hp}`);
|
|
this.updateSkillInfo(hero);
|
|
}
|
|
|
|
private initAllNodes() {
|
|
if (this.carouselNodes.length < 5) return;
|
|
this.updateNodeAnimationByOffset(this.carouselNodes[0], -2);
|
|
this.updateNodeAnimationByOffset(this.carouselNodes[1], -1);
|
|
this.updateNodeAnimationByOffset(this.carouselNodes[2], 0);
|
|
this.updateNodeAnimationByOffset(this.carouselNodes[3], 1);
|
|
this.updateNodeAnimationByOffset(this.carouselNodes[4], 2);
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|