Unity,如何获取“实际”的当前地形?

6
Unity有一个函数Terrain.sampleHeight(point)非常好用,它可以立即给出脚下地形的高度,而无需进行转换。
然而,任何复杂项目都不止一个地形。(事实上,任何大型场景都不可避免会涉及地形拼接。)
Unity有一个函数Terrain.activeTerrain,这个函数竟然提供了“第一个已加载的”地形。
显然,这完全没用。
实际上,有没有一种快速的方法来获取“你下方”的地形?那么就可以使用快速函数.sampleHeight
请注意,当然,你可以通过投射找到你下方的地形!但是你已经拥有了海拔高度,所以不需要担心.sampleHeight
简而言之,是否有与sampleHeight相匹配的函数,可以让该函数知道给定xyz使用哪个地形?
(或者说,sampleHeight只是一个相对无用的演示函数,仅在具有一个地形的演示中可用吗?)
6个回答

4

实际上有一种快速的方法可以让地形“在你下面” - 这样就可以使用快速函数.sampleHeight了吗?

是的,可以做到。

(或者说,sampleHeight只是一个相当无用的演示函数,仅在具有一个地形的演示中可用?)

不是。


Terrain.activeTerrain可以返回场景中的主要地形。还有Terrain.activeTerrains(注意结尾处的"s")可以返回场景中的活动地形

使用Terrain.activeTerrains获取地形数组Terrain,然后使用Terrain.GetPosition函数获取其位置。通过从玩家位置查找最近的地形来获取当前地形。您可以通过对地形位置进行排序,使用Vector3.DistanceVector3.sqrMagnitude(更快)来完成此操作。

Terrain GetClosestCurrentTerrain(Vector3 playerPos)
{
    //Get all terrain
    Terrain[] terrains = Terrain.activeTerrains;

    //Make sure that terrains length is ok
    if (terrains.Length == 0)
        return null;

    //If just one, return that one terrain
    if (terrains.Length == 1)
        return terrains[0];

    //Get the closest one to the player
    float lowDist = (terrains[0].GetPosition() - playerPos).sqrMagnitude;
    var terrainIndex = 0;

    for (int i = 1; i < terrains.Length; i++)
    {
        Terrain terrain = terrains[i];
        Vector3 terrainPos = terrain.GetPosition();

        //Find the distance and check if it is lower than the last one then store it
        var dist = (terrainPos - playerPos).sqrMagnitude;
        if (dist < lowDist)
        {
            lowDist = dist;
            terrainIndex = i;
        }
    }
    return terrains[terrainIndex];
}

用法:

假设玩家位置为 transform.position

//Get the current terrain
Terrain terrain = GetClosestCurrentTerrain(transform.position);
Vector3 point = new Vector3(0, 0, 0);
//Can now use SampleHeight
float yHeight = terrain.SampleHeight(point);

虽然可以使用 Terrain.SampleHeight 来实现,但是通过从玩家位置向下进行简单的光线投射到地形上,可以更加简化此过程。

Vector3 SampleHeightWithRaycast(Vector3 playerPos)
{
    float groundDistOffset = 2f;
    RaycastHit hit;
    //Raycast down to terrain
    if (Physics.Raycast(playerPos, -Vector3.up, out hit))
    {
        //Get y position
        playerPos.y = (hit.point + Vector3.up * groundDistOffset).y;
    }
    return playerPos;
}

我不确定Terrain.GetPosition的工作原理,因为文档上没有更多的信息,而且我也没有设置场景来验证,但我相信它是地形的中心点。 "猜测在你下面的那个将会是最近的吗?" 不是的。由于我正在与Vector3进行比较,因此使用了x、y、z三个分量来确定这一点。它只是检查最近的地形。 - Programmer
如果你只想检查y轴,请将(terrains[0].GetPosition() - playerPos).sqrMagnitude;var dist = (terrainPos - playerPos).sqrMagnitude;这一部分更改为使用y分量。其余代码应保持不变。 - Programmer
我在我的回答中提到了排序或Vector3.DistanceVector3.sqrMagnitude,但最终决定使用Vector3.sqrMagnitude因为它更快。此外,你提到了快速方式,所以使用linq函数会相反。 - Programmer
我还没有测试过它的工作方式,但是根据你的描述,它听起来是可行的。 - Programmer
(.activeTerrain与此问题完全无关) - Fattie
Terrain.GetPosition() = Terrain.transform.position = 世界坐标系中的位置,而非地形中心。如果要获取地形中心,可以使用以下代码:例如:var center = new Vector3(_terrains[0].transform.position.x + _terrains[0].terrainData.size.x / 2, playerPos.y, _terrains[0].transform.position.z + _terrains[0].terrainData.size.z / 2); - miralong

4

Terrain.GetPosition() = Terrain.transform.position = 世界坐标系中的位置
工作方法:

Terrain[] _terrains = Terrain.activeTerrains;

int GetClosestCurrentTerrain(Vector3 playerPos)
{
    //Get the closest one to the player
    var center = new Vector3(_terrains[0].transform.position.x + _terrains[0].terrainData.size.x / 2, playerPos.y, _terrains[0].transform.position.z + _terrains[0].terrainData.size.z / 2);
    float lowDist = (center - playerPos).sqrMagnitude;
    var terrainIndex = 0;

    for (int i = 0; i < _terrains.Length; i++)
    {
        center = new Vector3(_terrains[i].transform.position.x + _terrains[i].terrainData.size.x / 2, playerPos.y, _terrains[i].transform.position.z + _terrains[i].terrainData.size.z / 2);

        //Find the distance and check if it is lower than the last one then store it
        var dist = (center - playerPos).sqrMagnitude;
        if (dist < lowDist)
        {
            lowDist = dist;
            terrainIndex = i;
        }
    }
    return terrainIndex;
}


2

事实证明答案很简单,Unity不提供这样的功能。


0
public static Terrain GetClosestTerrain(Vector3 position)
{
    return Terrain.activeTerrains.OrderBy(x =>
    {
        var terrainPosition = x.transform.position;
        var terrainSize = x.terrainData.size * 0.5f;
        var terrainCenter = new Vector3(terrainPosition.x + terrainSize.x, position.y, terrainPosition.z + terrainSize.z);
        return Vector3.Distance(terrainCenter, position);
    }).First();
}

0

射线投射解决方案:(虽然这不是要求,但对于那些寻找使用射线投射的解决方案的人来说)

从玩家向下进行射线投射,忽略所有没有“地形”层的物体(可以在检查器中轻松设置层)。

代码:

    void Update() {
    // Put this on Player! Raycast's down (raylength=10f), if we hit something, check if the Layers name is "Terrain", if yes, return its instanceID
    RaycastHit hit;
    if (Physics.Raycast (transform.localPosition, transform.TransformDirection (Vector3.down), out hit, 10f, 1 << LayerMask.NameToLayer("Terrain"))) {
        Debug.Log(hit.transform.gameObject.GetInstanceID());
    }
}

现在你已经通过"hit.transform.gameObject"获得了地形的引用。

对于我的情况,我想通过它的instanceID来引用这个地形:

    // any other script
    public static UnityEngine.Object FindObjectFromInstanceID(int goID) {
    return (UnityEngine.Object)typeof(UnityEngine.Object)
            .GetMethod("FindObjectFromInstanceID", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)
            .Invoke(null, new object[] { goID });
}

但如上所述,如果您想要地形本身(作为地形对象),而不是instanceID,则“hit.transform.gameObject”已经给您提供了参考。

输入和代码片段取自以下链接:


Haxel,虽然您的示例代码非常完美,但请注意,问题的整个重点在于:请注意,当然,您可以...强制转换以找到您下面的地形!基于sampleHeight存在的事实,他们已经在持续使用处理器功率来查找“sampleHeight”,因此他们必须-逻辑上-已经找到了“你下面的地形”和/或“最近的地形”;因此,他们没有公开这些信息是疯狂的。该页面上关于该问题的答案似乎确实是“没有-令人惊讶的是,Unity不公开它,您必须复制他们的工作”。 - Fattie
WTF对吧?顺便提一下,在某些情况下,“最近”的地形(请参见其他答案)更适合/可靠和/或更便宜来计算它。 - Fattie
1
嗨,抱歉啊兄弟,我一开始没看到原帖中关于Raycast的部分,现在我已经调整了我的回答标题。谢谢。 - Haxel0rd
没问题!这段代码对于谷歌搜索引擎来说非常棒! - Fattie

0
您可以使用此函数获取最接近您当前位置的地形:
int GetClosestTerrain(Vector3 CheckPos)
{
    int terrainIndex = 0;
    float lowDist = float.MaxValue;

    for (int i = 0; i < _terrains.Length; i++)
    {
      var center = new Vector3(_terrains[i].transform.position.x + _terrains[i].terrainData.size.x / 2, CheckPos.y, _terrains[i].transform.position.z + _terrains[i].terrainData.size.z / 2);

      float dist = Vector3.Distance(center, CheckPos);

      if (dist < lowDist)
      {
        lowDist = dist;
        terrainIndex = i;
      }
    }
    return terrainIndex;
}

然后你可以像这样使用该函数:

private Terrain[] _terrains;

void Start()
  {
    _terrains = Terrain.activeTerrains;

    Vector3 start_pos = Vector3.zero;

    start_pos.y = _terrains[GetClosestTerrain(start_pos)].SampleHeight(start_pos);

  }

顺便提一下,按距离排序的真正简单方法只需使用 OrderBy( .. distance .. ).ToList();。示例:http://answers.unity.com/questions/341065/sort-a-list-of-gameobjects-by-distance.html - Fattie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接