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

Unity中3D地形球体沿2D路径点移动穿模问题求助

解决Unity球体沿路径移动时穿地形及Rigidbody无法移动的问题

一、球体穿入地形的核心原因

  1. 高度采样逻辑错误:你当前用SampleHeight(transform.position)采样球体当前位置的地形高度,但如果球体已经穿入地形,采样值会偏低;且移动目标点的高度应基于路径点的X/Z坐标计算,而非球体当前位置。
  2. 固定偏移量不合理:硬编码的+2f没有考虑球体自身半径,导致高度计算无法精准贴合地面。

二、添加Rigidbody后无法移动的原因

直接修改transform.position会绕过Unity物理引擎,和Rigidbody的控制逻辑冲突,导致球体无法响应移动指令。若要使用物理交互,必须采用引擎提供的物理移动方法。

三、两种可行的实现方案

方案1:不使用Rigidbody(纯动画式移动)

修改高度计算逻辑,用球体半径作为偏移量,基于路径点坐标采样地形高度:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

public class WaypointMover : MonoBehaviour
{
    [SerializeField]
    private float _WaypointDistance = 0.01f;    // 到达下一个路点的判定距离
    [SerializeField]
    private float _MovementSpeed = 6f;          // 移动速度

    private List<Vector2> _Waypoints;           // 路点集合
    private int _CurrentWaypointIndex;          // 当前路点索引
    private Vector2 _CurrentWaypoint;           // 当前目标路点
    private float _sphereRadius;                // 球体碰撞体半径

    void Start()
    {
        _Waypoints = new List<Vector2>();
        // 补充你的路点加载逻辑(从Python导入数据)
        
        _CurrentWaypointIndex = 0;
        // 确保路点列表不为空再初始化
        if (_Waypoints.Count > 0)
        {
            _CurrentWaypoint = _Waypoints[_CurrentWaypointIndex];
            // 获取球体碰撞体实际半径(考虑缩放)
            _sphereRadius = GetComponent<SphereCollider>().radius * transform.localScale.y;
            // 初始化位置到第一个路点的正确高度
            float terrainHeight = Terrain.activeTerrain.SampleHeight(new Vector3(_CurrentWaypoint.x, 0, _CurrentWaypoint.y));
            transform.position = new Vector3(_CurrentWaypoint.x, terrainHeight + _sphereRadius, _CurrentWaypoint.y);
            _CurrentWaypointIndex++;
        }
    }

    void Update()
    {
        if (_Waypoints.Count == 0) return;

        // 计算当前球体在XZ平面到目标路点的距离
        Vector2 currentXZ = new Vector2(transform.position.x, transform.position.z);
        if (Vector2.Distance(_CurrentWaypoint, currentXZ) < _WaypointDistance)
        {
            _CurrentWaypointIndex++;
            // 循环路点
            _CurrentWaypointIndex %= _Waypoints.Count;
            _CurrentWaypoint = _Waypoints[_CurrentWaypointIndex];
        }
        else
        {
            // 基于目标路点坐标采样地形高度
            float targetTerrainHeight = Terrain.activeTerrain.SampleHeight(new Vector3(_CurrentWaypoint.x, 0, _CurrentWaypoint.y));
            Vector3 targetPosition = new Vector3(_CurrentWaypoint.x, targetTerrainHeight + _sphereRadius, _CurrentWaypoint.y);
            // 平滑移动到目标位置
            transform.position = Vector3.MoveTowards(transform.position, targetPosition, _MovementSpeed * Time.deltaTime);
        }

        // 让球体水平看向目标路点(避免上下摆动)
        Vector3 lookTarget = new Vector3(_CurrentWaypoint.x, transform.position.y, _CurrentWaypoint.y);
        transform.LookAt(lookTarget);
    }
}

方案2:使用Rigidbody(支持物理交互)

改用Rigidbody.MovePosition控制移动,在FixedUpdate中处理物理逻辑:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

public class WaypointMover : MonoBehaviour
{
    [SerializeField]
    private float _WaypointDistance = 0.01f;    // 到达下一个路点的判定距离
    [SerializeField]
    private float _MovementSpeed = 6f;          // 移动速度

    private List<Vector2> _Waypoints;           // 路点集合
    private int _CurrentWaypointIndex;          // 当前路点索引
    private Vector2 _CurrentWaypoint;           // 当前目标路点
    private float _sphereRadius;                // 球体碰撞体半径
    private Rigidbody _rb;                      // 刚体组件

    void Start()
    {
        _rb = GetComponent<Rigidbody>();
        _Waypoints = new List<Vector2>();
        // 补充你的路点加载逻辑(从Python导入数据)
        
        _CurrentWaypointIndex = 0;
        if (_Waypoints.Count > 0)
        {
            _CurrentWaypoint = _Waypoints[_CurrentWaypointIndex];
            _sphereRadius = GetComponent<SphereCollider>().radius * transform.localScale.y;
            // 初始化刚体位置
            float terrainHeight = Terrain.activeTerrain.SampleHeight(new Vector3(_CurrentWaypoint.x, 0, _CurrentWaypoint.y));
            _rb.position = new Vector3(_CurrentWaypoint.x, terrainHeight + _sphereRadius, _CurrentWaypoint.y);
            _CurrentWaypointIndex++;
        }
        // 可选:不需要重力则关闭
        // _rb.useGravity = false;
    }

    // 物理逻辑需在FixedUpdate中处理
    void FixedUpdate()
    {
        if (_Waypoints.Count == 0) return;

        Vector2 currentXZ = new Vector2(_rb.position.x, _rb.position.z);
        if (Vector2.Distance(_CurrentWaypoint, currentXZ) < _WaypointDistance)
        {
            _CurrentWaypointIndex++;
            _CurrentWaypointIndex %= _Waypoints.Count;
            _CurrentWaypoint = _Waypoints[_CurrentWaypointIndex];
        }
        else
        {
            float targetTerrainHeight = Terrain.activeTerrain.SampleHeight(new Vector3(_CurrentWaypoint.x, 0, _CurrentWaypoint.y));
            Vector3 targetPosition = new Vector3(_CurrentWaypoint.x, targetTerrainHeight + _sphereRadius, _CurrentWaypoint.y);
            // 用刚体方法移动,兼容物理引擎
            _rb.MovePosition(Vector3.MoveTowards(_rb.position, targetPosition, _MovementSpeed * Time.fixedDeltaTime));
        }

        // 水平看向目标路点
        Vector3 lookTarget = new Vector3(_CurrentWaypoint.x, _rb.position.y, _CurrentWaypoint.y);
        transform.LookAt(lookTarget);
    }
}

额外注意事项

  • 确保地形已启用Terrain Collider(Unity地形默认自带,无需额外添加)
  • 若路点密度低,可对相邻路点做线性插值,提升移动平滑度
  • 地形凹凸区域可适当增大_WaypointDistance,避免频繁切换路点导致抖动

内容的提问来源于stack exchange,提问作者Xerrum

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 05:40:35