Unity使用Transform.LookAt仅绕Y轴旋转锁定XZ轴及敌人飞行实现问询
敌人仅绕Y轴旋转修复方案
你的问题根源是Transform.LookAt()会默认对准目标的全部坐标,当玩家Y轴高度和敌人不一致时,就会导致敌人X/Z轴发生旋转,出现扎地/悬空问题,两种修复方案选一种即可:
- 方案1:修改LookAt的目标点,抹平Y轴高度差
把所有transform.LookAt(player)替换为如下代码:
这个方案会让敌人始终只看向和自己同一水平高度的玩家位置,不会产生X/Z轴的旋转。Vector3 lookTarget = new Vector3(player.position.x, transform.position.y, player.position.z); transform.LookAt(lookTarget); - 方案2:强制锁定欧拉角
每次旋转后手动重置X、Z轴的旋转值为0:transform.LookAt(player); transform.eulerAngles = new Vector3(0, transform.eulerAngles.y, 0);
额外优化:如果你的NavMeshAgent开启了updateRotation属性,可以直接删掉手动LookAt的代码,导航系统会自动控制敌人沿Y轴转向移动目标,适配性更好。
敌人随机飞起下落效果实现方案
你可以通过协程实现该效果,和现有逻辑兼容的实现步骤如下:
- 新增两个公共变量控制效果参数:
public float flyForce = 5f; // 飞起的力度 public float flyInterval = 10f; // 随机触发飞起的间隔时间 private bool isFlying = false;
- 在Awake里启动随机触发协程:
private void Awake() { player = GameObject.FindGameObjectWithTag("Player").transform; agent = GetComponent<NavMeshAgent>(); StartCoroutine(RandomFlyCoroutine()); }
- 新增协程和落地检测逻辑:
private IEnumerator RandomFlyCoroutine() { while (true) { // 随机等待时间,在0.5倍到1.5倍的设定间隔之间 yield return new WaitForSeconds(Random.Range(flyInterval * 0.5f, flyInterval * 1.5f)); // 仅在非攻击、非飞行状态下触发飞起 if (!isFlying && !playerInAttackRange) { isFlying = true; agent.enabled = false; // 暂时关闭导航避免冲突 Rigidbody rb = GetComponent<Rigidbody>(); rb.isKinematic = false; rb.AddForce(Vector3.up * flyForce, ForceMode.Impulse); // 等待落地 yield return new WaitUntil(() => Physics.Raycast(transform.position, Vector3.down, 1.1f, whatIsGround)); // 恢复状态 rb.isKinematic = true; agent.enabled = true; isFlying = false; } } }
注意需要给敌人挂载Rigidbody组件,并且勾选Constraints里的X、Z轴旋转锁定,避免飞起过程中敌人歪掉。
修改后的完整代码
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; public class EnemyBehaviour : MonoBehaviour { public NavMeshAgent agent; public Transform player; public LayerMask whatIsGround, whatIsPlayer; //Patrol public Vector3 walkPoint; bool walkPointSet; public float walkPointRange; //Attacking public float timeBetweenAttack; bool alreadyAttacked; public GameObject projectile; public float hitPoints; //states public float sightRange, attackRange; public bool playerInSightRange, playerInAttackRange; // 飞起效果参数 public float flyForce = 5f; public float flyInterval = 10f; private bool isFlying = false; private void Awake() { player = GameObject.FindGameObjectWithTag("Player").transform; agent = GetComponent<NavMeshAgent>(); StartCoroutine(RandomFlyCoroutine()); } private void Update() { if (isFlying) return; // 飞行状态下跳过原有逻辑 //Check sight and attack range playerInSightRange = Physics.CheckSphere(transform.position, sightRange, whatIsPlayer); playerInAttackRange = Physics.CheckSphere(transform.position, attackRange, whatIsPlayer); if (!playerInSightRange && !playerInAttackRange) Patroling(); if (playerInSightRange && !playerInAttackRange) ChasePlayer(); if (playerInSightRange && playerInAttackRange) AttackPlayer(); } private void Patroling() { if (!walkPointSet) SearchWalkPoint(); if (walkPointSet) { agent.SetDestination(walkPoint); Vector3 distanceToWalkPoint = transform.position - walkPoint; //walk point reached if(distanceToWalkPoint.magnitude < 1f) { walkPointSet = false; } } } private void SearchWalkPoint() { float randomZ = Random.Range(-walkPointRange, walkPointRange); float randomX = Random.Range(-walkPointRange, walkPointRange); walkPoint = new Vector3(transform.position.x + randomX, transform.position.y, transform.position.z + randomZ); if(Physics.Raycast(walkPoint, -transform.up, 2f, whatIsGround)) { walkPointSet = true; } } private void ChasePlayer() { agent.SetDestination(player.position); Vector3 lookTarget = new Vector3(player.position.x, transform.position.y, player.position.z); transform.LookAt(lookTarget); } private void AttackPlayer() { agent.SetDestination(transform.position); Vector3 lookTarget = new Vector3(player.position.x, transform.position.y, player.position.z); transform.LookAt(lookTarget); if (!alreadyAttacked) { Rigidbody rb = Instantiate(projectile, transform.position, Quaternion.identity).GetComponent<Rigidbody>(); rb.AddForce(transform.forward * 32f, ForceMode.Impulse); rb.AddForce(transform.up * 32f, ForceMode.Impulse); alreadyAttacked = true; Invoke(nameof(ResetAttack), timeBetweenAttack); } } private void ResetAttack() { alreadyAttacked = false; } public void TakeDamage(int damage) { hitPoints -= damage; if (hitPoints <= 0) Invoke(nameof(DestroyEnemy), 2f); } private void DestroyEnemy() { Destroy(gameObject); } private IEnumerator RandomFlyCoroutine() { while (true) { yield return new WaitForSeconds(Random.Range(flyInterval * 0.5f, flyInterval * 1.5f)); if (!isFlying && !playerInAttackRange) { isFlying = true; agent.enabled = false; Rigidbody rb = GetComponent<Rigidbody>(); rb.isKinematic = false; rb.AddForce(Vector3.up * flyForce, ForceMode.Impulse); yield return new WaitUntil(() => Physics.Raycast(transform.position, Vector3.down, 1.1f, whatIsGround)); rb.isKinematic = true; agent.enabled = true; isFlying = false; } } } }
内容的提问来源于stack exchange,提问作者Multiverse3snowflake Artist
相关产品推荐
相关产品推荐

