You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity使用Transform.LookAt仅绕Y轴旋转锁定XZ轴及敌人飞行实现问询

敌人仅绕Y轴旋转修复方案

你的问题根源是Transform.LookAt()会默认对准目标的全部坐标,当玩家Y轴高度和敌人不一致时,就会导致敌人X/Z轴发生旋转,出现扎地/悬空问题,两种修复方案选一种即可:

  • 方案1:修改LookAt的目标点,抹平Y轴高度差
    把所有transform.LookAt(player)替换为如下代码:
    Vector3 lookTarget = new Vector3(player.position.x, transform.position.y, player.position.z);
    transform.LookAt(lookTarget);
    
    这个方案会让敌人始终只看向和自己同一水平高度的玩家位置,不会产生X/Z轴的旋转。
  • 方案2:强制锁定欧拉角
    每次旋转后手动重置X、Z轴的旋转值为0:
    transform.LookAt(player);
    transform.eulerAngles = new Vector3(0, transform.eulerAngles.y, 0);
    

额外优化:如果你的NavMeshAgent开启了updateRotation属性,可以直接删掉手动LookAt的代码,导航系统会自动控制敌人沿Y轴转向移动目标,适配性更好。

敌人随机飞起下落效果实现方案

你可以通过协程实现该效果,和现有逻辑兼容的实现步骤如下:

  1. 新增两个公共变量控制效果参数:
public float flyForce = 5f; // 飞起的力度
public float flyInterval = 10f; // 随机触发飞起的间隔时间
private bool isFlying = false;
  1. 在Awake里启动随机触发协程:
private void Awake()
{
    player = GameObject.FindGameObjectWithTag("Player").transform;
    agent = GetComponent<NavMeshAgent>();
    StartCoroutine(RandomFlyCoroutine());
}
  1. 新增协程和落地检测逻辑:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 05:39:02