【问题标题】:How can i prevent from moving the camera view under the terrain?如何防止在地形下移动相机视图?
【发布时间】:2021-11-08 07:35:12
【问题描述】:

我正在使用这个附加到相机的 mouseorbit 脚本。 问题是当我用鼠标移动相机并旋转它以使相机位于地形下方时。 我希望当它到达地形高度时停止不要向下移动我的意思是不要在字符最大值下到达这个视图以达到地形高度..

要在地形高度上停下来,我的意思是当它到达这个高度时:

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

public class MouseOrbit : MonoBehaviour
{
    /* These variables are what tell the camera how its going to function by
      * setting the viewing target, collision layers, and other properties
      * such as distance and viewing angles */
    public Transform viewTarget;
    public LayerMask collisionLayers;
    public float distance = 6.0f;
    public float distanceSpeed = 150.0f;
    public float collisionOffset = 0.3f;
    public float minDistance = 4.0f;
    public float maxDistance = 12.0f;
    public float height = 1.5f;
    public float horizontalRotationSpeed = 250.0f;
    public float verticalRotationSpeed = 150.0f;
    public float rotationDampening = 0.75f;
    public float minVerticalAngle = -60.0f;
    public float maxVerticalAngle = 60.0f;
    public bool useRMBToAim = false;

    /* These variables are meant to store values given by the script and
     * not the user */
    private float h, v, smoothDistance;
    private Vector3 newPosition;
    private Quaternion newRotation, smoothRotation;
    private Transform cameraTransform;

    /* This is where we initialize our script */
    void Start()
    {
        Initialize();
    }

    /* This is where we set our private variables, check for null errors,
     * and anything else that needs to be called once during startup */
    void Initialize()
    {
        h = this.transform.eulerAngles.x;
        v = this.transform.eulerAngles.y;

        cameraTransform = this.transform;
        smoothDistance = distance;

        NullErrorCheck();
    }

    /* We check for null errors or warnings and notify the user to fix them */
    void NullErrorCheck()
    {
        if (!viewTarget)
        {
            Debug.LogError("Please make sure to assign a view target!");
            Debug.Break();
        }
        if (collisionLayers == 0)
        {
            Debug.LogWarning("Make sure to set the collision layers to the layers the camera should collide with!");
        }
    }

    /* This is where we do all our camera updates. This is where the camera
     * gets all of its functionality. From setting the position and rotation,
     * to adjusting the camera to avoid geometry clipping */
    void LateUpdate()
    {
        if (!viewTarget)
            return;

        /* We check for right mouse button functionality, set the rotation
         * angles, and lock the mouse cursor */
        if (!useRMBToAim)
        {
            /* Check to make sure the game isn't paused and lock the mouse cursor*/
            if (Time.timeScale > 0.0f)
                Cursor.lockState = CursorLockMode.Locked;

            h += Input.GetAxis("Mouse X") * horizontalRotationSpeed * Time.deltaTime;
            v -= Input.GetAxis("Mouse Y") * verticalRotationSpeed * Time.deltaTime;

            h = ClampAngle(h, -360.0f, 360.0f);
            v = ClampAngle(v, minVerticalAngle, maxVerticalAngle);

            newRotation = Quaternion.Euler(v, h, 0.0f);
        }
        else
        {
            if (Input.GetMouseButton(1))
            {
                /* Check to make sure the game isn't paused and lock the mouse cursor */
                if (Time.timeScale > 0.0f)
                    Cursor.lockState = CursorLockMode.Locked;

                h += Input.GetAxis("Mouse X") * horizontalRotationSpeed * Time.deltaTime;
                v -= Input.GetAxis("Mouse Y") * verticalRotationSpeed * Time.deltaTime;

                h = ClampAngle(h, -360.0f, 360.0f);
                v = ClampAngle(v, minVerticalAngle, maxVerticalAngle);

                newRotation = Quaternion.Euler(v, h, 0.0f);
            }
            else
            {
                Cursor.lockState = CursorLockMode.Confined;
            }
        }

        /* We set the distance by moving the mouse wheel and use a custom
         * growth function as the time value for linear interpolation */
        distance = Mathf.Clamp(distance - Input.GetAxis("Mouse ScrollWheel") * 10, minDistance, maxDistance);
        smoothDistance = Mathf.Lerp(smoothDistance, distance, TimeSignature(distanceSpeed));

        /*We give the rotation some smoothing for a nicer effect */
        smoothRotation = Quaternion.Slerp(smoothRotation, newRotation, TimeSignature((1 / rotationDampening) * 100.0f));

        newPosition = viewTarget.position;
        newPosition += smoothRotation * new Vector3(0.0f, height, -smoothDistance);

        /* Calls the function to adjust the camera position to avoid clipping */
        CheckSphere();

        smoothRotation.eulerAngles = new Vector3(smoothRotation.eulerAngles.x, smoothRotation.eulerAngles.y, 0.0f);

        cameraTransform.position = newPosition;
        cameraTransform.rotation = smoothRotation;
    }

    /* This is where the camera checks for a collsion hit within a specified radius,
     * and then moves the camera above the location it hit with an offset value */
    void CheckSphere()
    {
        /* Add height to our spherecast origin */
        Vector3 tmpVect = viewTarget.position;
        tmpVect.y += height;

        RaycastHit hit;

        /* Get the direction from the camera position to the origin */
        Vector3 dir = (newPosition - tmpVect).normalized;

        /* Check a radius for collision hits and then set the new position for
         * the camera */
        if (Physics.SphereCast(tmpVect, 0.3f, dir, out hit, distance, collisionLayers))
        {
            newPosition = hit.point + (hit.normal * collisionOffset);
        }
    }

    /* Keeps the angles values within their specificed minimum and maximum
     * inputs while at the same time putting the values back to 0 if they
     * go outside of the 360 degree range */
    private float ClampAngle(float angle, float min, float max)
    {
        if (angle < -360)
            angle += 360;

        if (angle > 360)
            angle -= 360;

        return Mathf.Clamp(angle, min, max);
    }

    /* This is our custom logistic growth time signature with speed as input */
    private float TimeSignature(float speed)
    {
        return 1.0f / (1.0f + 80.0f * Mathf.Exp(-speed * 0.02f));
    }
}

看起来我抓取的脚本已经处理了地形碰撞...我只需要确保在其上设置碰撞层以包含地形。但不知道怎么做。

我尝试了什么:

我现在在检查器中添加了一个名为 Terrain 的新层。 然后在地形上的层次结构中,我将其图层更改为地形。 同样在脚本中我选择了地形。但是还是不行。

在屏幕截图中,顶部是相机的检查器,在 Collision Layers Terrain 中选择了脚本。 在底部,地形检查器选择 Terrain 作为:Layer

【问题讨论】:

  • 我在一个测试项目中尝试了这个脚本。它与您设置的设置很好地配合。您是否有可能在场景中的多个摄像机上使用相同的脚本?或者您可能从 Edit->Project Settings->Physics 中关闭了默认图层和地形图层之间的碰撞?
  • @ZayedUpal 我的相机是空游戏对象的子对象,脚本附加到游戏对象而不是相机。 GameObject 和子相机都没有任何碰撞器。地形有它自己的对撞机 Terrain Collider。所以我必须将脚本直接附加到相机上,或者将它附加到父 GameObject 上?您可以在我的问题的底部屏幕截图中看到 FollowCamera 游戏对象,而相机是它的子对象。我是否需要向空的 GameObject 或 Camera 添加任何对撞机?也许我需要添加到游戏对象或相机刚体?
  • @ZayedUpal 你能否展示一下相机层次结构的屏幕截图和检查员你做了什么?
  • 一般情况下,您会直接将脚本添加到相机中。但是,如果相机的本地位置、旋转设置为 0 并缩放为 1,则将它放到它的父级上也可以。这是我的测试截图:imgur.com/a/nti61
  • @ZayedUpal 很棒的工作。谢谢。

标签: c# unity3d unity5


【解决方案1】:

这个问题有很多答案。
但这是我的看法,因为与您在网上找到的与统一第三人称控制器相关的所有解决方案相比,我相当肯定这个解决方案很有趣。


摘要
在 Unity 中,您可以采用通常的方法,即拥有一个空对象并将其作为相机的父对象,等等……但业内人士通常会使用名为 Spherical coordinates 的数学公式。 到目前为止,根据我的经验,这是第三人称控制器的最佳方法,也是最优雅、最漂亮的方法。实施此方法后,您需要做的就是从相机向下射一米,如果它检测到任何东西,然后更改球坐标公式中的单个值,这将使玩家周围的球体更小,从而产生效果碰撞

实施
这是一段 sn-p 代码,它显示了创建球坐标公式所需的单个函数。

private Vector3 sphericalCoordinates()
{
    float m = distanceFromPlayer;
    v = mouseY;
    h = mouseX;

    x = m * Mathf.Sin(v) * Mathf.Cos(h);
    z = m * Mathf.Sin(v) * Mathf.Sin(h);
    y = m * Mathf.Cos(v);

    Vector3 pos = new Vector3(x, y, z);
    return pos;
}

m = 幅度。 你可以把它想象成球体的半径,这就是你改变它来给出碰撞效果。

我稍后使用此 sn-p 将播放器与触发器中的相机绑定。
[注意:非常不干净的代码,我只是想给你看一个基本概念,以后一定要更干净]

Vector3 getCamRotWORLD()
{
    Vector3 camRotWORLD = new Vector3(0, transform.rotation.eulerAngles.y, 0);
    return camRotWORLD;
}

void adjustPlayerRot()
{
    if (Input.GetKey(KeyCode.W))
    {
        player.transform.rotation = Quaternion.Euler(player.transform.rotation.x, getCamRotWORLD().y, player.transform.rotation.z);
        player.transform.Translate(0, 0, playerScript.speed * Time.deltaTime);
    }

    if (Input.GetKey(KeyCode.A))
    {
        player.transform.rotation = Quaternion.Euler(player.transform.rotation.x, getCamRotWORLD().y - 90, player.transform.rotation.z);
        player.transform.Translate(0, 0, playerScript.speed * Time.deltaTime);
    }

    if (Input.GetKey(KeyCode.S))
    {
        player.transform.rotation = Quaternion.Euler(player.transform.rotation.x, getCamRotWORLD().y - 180, player.transform.rotation.z);
        player.transform.Translate(0, 0, playerScript.speed * Time.deltaTime);
    }

    if (Input.GetKey(KeyCode.D))
    {
        player.transform.rotation = Quaternion.Euler(player.transform.rotation.x, getCamRotWORLD().y + 90, player.transform.rotation.z);
        player.transform.Translate(0, 0, playerScript.speed * Time.deltaTime);
    }
}

这是我的更新的样子,以防您真的想抢夺实现而不是尝试自己尝试。

private void Update()
{
    mouseX -= Input.GetAxis("Mouse X") * (sensitivity * Time.deltaTime);
    mouseY -= Input.GetAxis("Mouse Y") * (sensitivity * Time.deltaTime);

    playerCam.transform.LookAt(player.transform);
    playerCam.transform.position = new Vector3(
        sphericalCoordinates().x + player.transform.position.x,
        sphericalCoordinates().y,
        sphericalCoordinates().z + player.transform.position.z
        );
}

【讨论】:

  • 欢迎来到社区。这是一个深思熟虑的答案。此外,这实际上似乎是这个问题的 first 答案!另一个建议作为评论发布。 (承认这种区别对于像您这样的新贡献者来说可能有点微妙;答案在 Stack Overflow 上会得到特殊处理。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-01-30
  • 1970-01-01
  • 2020-04-09
  • 1970-01-01
  • 1970-01-01
  • 2018-05-31
  • 1970-01-01
相关资源
最近更新 更多