Unity问题:Animator窗口动画正常,协程切换时角色场景内冻结
问题:Unity角色切换动画时冻结,Animator窗口显示正常但场景仅播放默认动画
需求:通过协程随机播放walk、idle2、sniff等布尔参数对应的动画,玩家靠近时触发攻击动画。实际运行时仅默认动画能正常播放,切换其他动画时角色冻结,但Animator窗口内动画运行正常。
用户提供的脚本:
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using Random = UnityEngine.Random; public class CreatureController : MonoBehaviour { public Animator animator; public float speed = 3.0f; //public float turnSpeed = 200.0f; public Vector3 boundaryMin; public Vector3 boundaryMax; private bool isWalking = false; private Vector3 targetDirection; private readonly string[] animationParameters = { "walk", "idle2", "sniff", "roar", "crouch" }; void Start() { if (animator == null) { animator = GetComponent<Animator>(); } targetDirection = transform.forward; SetRandomStartingPosition(); StartCoroutine(PlayRandomAnimation()); } void Update() { isWalking = animator.GetCurrentAnimatorStateInfo(0).IsName("Creep|Walk1_Action") || animator.GetCurrentAnimatorStateInfo(0).IsName("Creep|Crouch_Action"); if (isWalking) { MoveCreature(); } } void SetRandomStartingPosition() { float randomX = Random.Range(boundaryMin.x, boundaryMax.x); float randomZ = Random.Range(boundaryMin.z, boundaryMax.z); transform.position = new Vector3(randomX, transform.position.y, randomZ); } void MoveCreature() { transform.Translate(Vector3.forward * speed * Time.deltaTime); Quaternion targetRotation = Quaternion.LookRotation(targetDirection); //transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * turnSpeed); Vector3 position = transform.position; if (position.x < boundaryMin.x || position.x > boundaryMax.x || position.z < boundaryMin.z || position.z > boundaryMax.z) { Vector3 directionToCenter = (new Vector3((boundaryMin.x + boundaryMax.x) / 2, position.y, (boundaryMin.z + boundaryMax.z) / 2) - position).normalized; targetDirection = directionToCenter; } } IEnumerator PlayRandomAnimation() { while (true) { int randomIndex = Random.Range(0, animationParameters.Length); string chosenAnimation = animationParameters[randomIndex]; foreach (string parameter in animationParameters) { animator.SetBool(parameter, false); } animator.SetBool(chosenAnimation, true); Debug.Log($"Playing animation: {chosenAnimation}"); float waitTime = Random.Range(2.0f, 5.0f); yield return new WaitForSeconds(waitTime); if (chosenAnimation == "Creep|Walk1_Action" || chosenAnimation == "Creep|Crouch_Action") { targetDirection = new Vector3(Random.Range(-1.0f, 1.0f), 0, Random.Range(-1.0f, 1.0f)).normalized; } } } private void OnTriggerEnter(Collider other) { Debug.Log("Triggered by: " + other.name); if (other.CompareTag("Player")) { animator.SetBool("attack", true); } } }
问题分析与修复方案
1. 布尔参数与动画状态名混淆
协程中错误地将布尔参数名(如walk)和动画状态名(如Creep|Walk1_Action)做比较,导致切换到walk/crouch动画时无法更新移动方向,修正代码:
// 原错误代码 // if (chosenAnimation == "Creep|Walk1_Action" || chosenAnimation == "Creep|Crouch_Action") // 修正后 if (chosenAnimation == "walk" || chosenAnimation == "crouch")
2. Animator状态机过渡配置检查
- 确保所有布尔参数对应的动画状态,都有从默认状态(如idle)的过渡条件:对应参数为
true,其余参数为false。 - 关闭过渡的
Has Exit Time选项,避免等待当前动画播放完毕才切换。 - 确认每个动画状态都配置了回到其他状态的过渡逻辑,保证参数切换时能触发状态变化。
3. 根运动与手动移动冲突处理
角色冻结但Animator窗口动画正常,大概率是根运动设置问题:
- 如果动画自带根运动(如walk动画包含位移):勾选Animator组件的
Apply Root Motion,同时注释掉MoveCreature中的transform.Translate代码,避免手动移动覆盖根运动。 - 如果动画无自带根运动:确认
isWalking的状态名判断正确,非walk/crouch动画时角色不动是预期行为;若动画本身无动作显示,检查动画是否正确赋值给状态机,模型的动画组件是否正常。
4. 攻击状态的逻辑补充
当前协程会在攻击时继续切换动画,覆盖攻击参数,需添加攻击状态标记暂停协程:
- 新增
isAttacking变量,在触发攻击时设置为true,并重置所有其他动画参数。 - 修改协程循环条件为
while (!isAttacking),攻击时停止随机动画切换。
修复后的完整代码
using System.Collections; using UnityEngine; using Random = UnityEngine.Random; public class CreatureController : MonoBehaviour { public Animator animator; public float speed = 3.0f; public Vector3 boundaryMin; public Vector3 boundaryMax; private bool isWalking = false; private Vector3 targetDirection; private bool isAttacking = false; private readonly string[] animationParameters = { "walk", "idle2", "sniff", "roar", "crouch" }; void Start() { animator ??= GetComponent<Animator>(); targetDirection = transform.forward; SetRandomStartingPosition(); StartCoroutine(PlayRandomAnimation()); } void Update() { if (isAttacking) return; isWalking = animator.GetCurrentAnimatorStateInfo(0).IsName("Creep|Walk1_Action") || animator.GetCurrentAnimatorStateInfo(0).IsName("Creep|Crouch_Action"); if (isWalking) { MoveCreature(); } } void SetRandomStartingPosition() { float randomX = Random.Range(boundaryMin.x, boundaryMax.x); float randomZ = Random.Range(boundaryMin.z, boundaryMax.z); transform.position = new Vector3(randomX, transform.position.y, randomZ); } void MoveCreature() { // 若使用根运动,注释此行 transform.Translate(Vector3.forward * speed * Time.deltaTime); Quaternion targetRotation = Quaternion.LookRotation(targetDirection); //transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * turnSpeed); Vector3 position = transform.position; if (position.x < boundaryMin.x || position.x > boundaryMax.x || position.z < boundaryMin.z || position.z > boundaryMax.z) { Vector3 directionToCenter = (new Vector3((boundaryMin.x + boundaryMax.x) / 2, position.y, (boundaryMin.z + boundaryMax.z) / 2) - position).normalized; targetDirection = directionToCenter; } } IEnumerator PlayRandomAnimation() { while (!isAttacking) { int randomIndex = Random.Range(0, animationParameters.Length); string chosenAnimation = animationParameters[randomIndex]; foreach (string parameter in animationParameters) { animator.SetBool(parameter, false); } animator.SetBool(chosenAnimation, true); Debug.Log($"Playing animation: {chosenAnimation}"); float waitTime = Random.Range(2.0f, 5.0f); yield return new WaitForSeconds(waitTime); if (chosenAnimation == "walk" || chosenAnimation == "crouch") { targetDirection = new Vector3(Random.Range(-1.0f, 1.0f), 0, Random.Range(-1.0f, 1.0f)).normalized; } } } private void OnTriggerEnter(Collider other) { if (other.CompareTag("Player")) { isAttacking = true; foreach (string parameter in animationParameters) { animator.SetBool(parameter, false); } animator.SetBool("attack", true); // 可选:监听攻击动画结束后恢复随机动画 // StartCoroutine(ResumeRandomAnimationAfterAttack()); } } // 可选:攻击动画结束后恢复随机逻辑 // IEnumerator ResumeRandomAnimationAfterAttack() // { // yield return new WaitForSeconds(animator.GetCurrentAnimatorStateInfo(0).length); // isAttacking = false; // animator.SetBool("attack", false); // StartCoroutine(PlayRandomAnimation()); // } }
内容的提问来源于stack exchange,提问作者Beliz Yazici
相关产品推荐
相关产品推荐

