Files
pixelheros/assets/script/game/hero/HeroAnmComp.ts
panFD d951896799 refactor(hero): 优化英雄动画状态同步逻辑
移除测试用的英雄成长模拟数据,修复动画状态与实际表现不一致的问题

1. 重构HeroSpine的idle/move方法,新增返回值标记动画是否被拦截
2. 重构HeroViewComp的status_change逻辑,仅在动画切换成功时更新状态
3. 重构HeroAnmComp的动画播放逻辑,修复动画结束后默认播放的异常
4. 移除SingletonModuleComp中的测试用英雄成长数据
5. 简化MoveSystem中状态切换的冗余判断逻辑
2026-09-12 14:04:18 +08:00

110 lines
4.2 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, Animation, AnimationState, CCClass, Component, } from "cc";
import { HeroViewComp } from "./HeroViewComp";
import { FacSet } from "../common/config/GameSet";
import { FlashSprite } from "./hit-flash-white/scripts/FlashSprite";
const { ccclass, property } = _decorator;
@ccclass('HeroAnmComp')
export default class HeroAnmComp extends Component {
fsSprite: FlashSprite = null!;
private anmcon: any = null
private _hasStop = true;
private _atkIndex = 0;
anms: any[] = ["idle", "move", "atk0", "max0", "max1"]
onLoad() {
this.anmcon = this.node.getComponent(Animation)
this.fsSprite = this.node.getComponent(FlashSprite);
this.anmcon.on(Animation.EventType.FINISHED, this.onAnimationFinished, this);
}
onDestroy() {
if (this.anmcon) {
this.anmcon.off(Animation.EventType.FINISHED, this.onAnimationFinished, this);
}
}
/** 大招是否播放中(最高优先级,期间禁止一切动画切换) */
private get maxPlaying(): boolean {
return !!(this.anmcon.getState("max0")?.isPlaying || this.anmcon.getState("max1")?.isPlaying);
}
/** 普攻是否播放中(次优先级,仅可被大招打断) */
private get atkPlaying(): boolean {
return !!this.anmcon.getState("atk0")?.isPlaying;
}
/** buff 是否播放中(可被大招/普攻打断,但拦截 idle/move) */
private get buffPlaying(): boolean {
return !!this.anmcon.getState("buff")?.isPlaying;
}
stop() {
this._hasStop = true;
}
onAnimationFinished(type: Animation.EventType, state: AnimationState) {
// HeroAnmComp 与 HeroViewComp 挂在同一节点上,直接 getComponent 即可,
// 不需要走父节点查找(且 Component 没有 getComponentInParent,必须在 Node 上调用)
const viewComp = this.node.getComponent(HeroViewComp);
if (state.name === "max0" || state.name === "max1") {
// 大招播放结束,清除 HeroViewComp 的置顶标记,恢复 MoveSystem 统一排序
if (viewComp) {
viewComp.isCastingMax = false;
}
}
if (state.name != "idle" && state.name != "move") {
// 攻击/buff/大招结束:按视图层记录的真实状态回放,而非过期的缓存动画,
// 避免"位置已停但还在播走路"的原地走步(view.status 由 status_change 成功切换时写入)
const target = viewComp?.status === "move" ? "move" : "idle";
if (!this.maxPlaying && !this.atkPlaying && !this.buffPlaying) {
if (!this.anmcon.getState(target)?.isPlaying) {
this.anmcon.play(target);
}
}
}
}
atked() {
this.fsSprite.clickFlash();
}
/**
* 播放移动动画。
* Why 返回 boolean:普攻/buff/大招播放期间会被拦截(return false),
* 调用方(HeroSpine/HeroViewComp)据此不记录状态,保证记录状态与实际动画一致,
* 下一帧重试时去重条件不成立可再次尝试,避免"位置在动但动画卡死"。
* @returns true = 动画已处于目标状态(新播或原本在播);false = 被高优先级动画拦截
*/
move(): boolean {
if (this.maxPlaying || this.atkPlaying || this.buffPlaying) return false
if (this.anmcon.getState("move").isPlaying) return true
this.anmcon.play("move")
return true
}
atk() {
if (this.maxPlaying) return
this.anmcon.play(`atk0`)
}
max() {
if (this.maxPlaying) return
this.anmcon.play("max0")
}
play(anm: string = "") {
if (anm === "") return
if (this.maxPlaying) return
this.anmcon.play(anm)
}
/**
* 播放待机动画(返回值语义与 move() 一致:被高优先级动画拦截时返回 false)
*/
idle(): boolean {
if (this.maxPlaying || this.atkPlaying || this.buffPlaying) return false
if (this.anmcon.getState("idle").isPlaying) return true
this.anmcon.play("idle")
return true
}
buff() {
if (this.maxPlaying) return
this.anmcon.play("buff")
}
dead() {
}
}