使 UI 元素跟随相机

Make UI element to follow camera

我想让菜单跟随相机,这样当用户注视菜单时,就可以触发菜单。

我在我的Update函数中参考FirstPersonCamera主摄像头设置菜单的位置,

menuCanvas.transform.position = FirstPersonCamera.transform.position 
                               +(FirstPersonCamera.transform.forward * 4);

不幸的是,菜单总是粘在相机中心,因此它总是被触发。如何正确定位上下文菜单?

你可以,例如使用Vector3.Lerp以使运动更平滑(远距离物体移动速度更快,近距离物体移动更慢)。

然后您可以将目标位置限制在显示器的中心,但允许在每个方向上进行偏移。一个非常简单的例子可能看起来像

// how far to stay away fromt he center
public float offsetRadius = 0.3f;
public float distanceToHead = 4;

public Camera FirstPersonCamera;

// This is a value between 0 and 1 where
// 0 object never moves
// 1 object jumps to targetPosition immediately
// 0.5 e.g. object is placed in the middle between current and targetPosition every frame
// you can play around with this in the Inspector
[Range(0, 1)]
public float smoothFactor = 0.5f;

private void Update()
{
    // make the UI always face towards the camera
    transform.rotation = FirstPersonCamera.transform.rotation;

    var cameraCenter = FirstPersonCamera.transform.position + FirstPersonCamera.transform.forward * distanceToHead;

    var currentPos = transform.position;
    
    // in which direction from the center?
    var direction = currentPos - cameraCenter;

    // target is in the same direction but offsetRadius
    // from the center
    var targetPosition = cameraCenter + direction.normalized * offsetRadius;
    
    // finally interpolate towards this position
    transform.position = Vector3.Lerp(currentPos, targetPosition, smoothFactor);
}

当然这远非完美,但我希望这是一个好的起点。


一个萌 complex/complete 解决方案可以,例如在 HoloToolkit: SphereBasedTagalong 中找到(它被称为 MixedRealityToolkit 2.x.x 之前的任何版本)

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using UnityEngine;

namespace HoloToolkit.Unity
{
    /// <summary>
    /// A Tagalong that stays at a fixed distance from the camera and always
    /// seeks to stay on the edge or inside a sphere that is straight in front of the camera.
    /// </summary>
    public class SphereBasedTagalong : MonoBehaviour
    {
        [Tooltip("Sphere radius.")]
        public float SphereRadius = 1.0f;

        [Tooltip("How fast the object will move to the target position.")]
        public float MoveSpeed = 2.0f;

        [Tooltip("When moving, use unscaled time. This is useful for games that have a pause mechanism or otherwise adjust the game timescale.")]
        public bool UseUnscaledTime = true;

        [Tooltip("Display the sphere in red wireframe for debugging purposes.")]
        public bool DebugDisplaySphere = false;

        [Tooltip("Display a small green cube where the target position is.")]
        public bool DebugDisplayTargetPosition = false;

        private Vector3 targetPosition;
        private Vector3 optimalPosition;
        private float initialDistanceToCamera;

        void Start()
        {
            initialDistanceToCamera = Vector3.Distance(this.transform.position, Camera.main.transform.position);
        }

        void Update()
        {
            optimalPosition = Camera.main.transform.position + Camera.main.transform.forward * initialDistanceToCamera;

            Vector3 offsetDir = this.transform.position - optimalPosition;
            if (offsetDir.magnitude > SphereRadius)
            {
                targetPosition = optimalPosition + offsetDir.normalized * SphereRadius;

                float deltaTime = UseUnscaledTime
                    ? Time.unscaledDeltaTime
                    : Time.deltaTime;

                this.transform.position = Vector3.Lerp(this.transform.position, targetPosition, MoveSpeed * deltaTime);
            }
        }

        public void OnDrawGizmos()
        {
            if (Application.isPlaying == false) return;

            Color oldColor = Gizmos.color;

            if (DebugDisplaySphere)
            {
                Gizmos.color = Color.red;
                Gizmos.DrawWireSphere(optimalPosition, SphereRadius);
            }

            if (DebugDisplayTargetPosition)
            {
                Gizmos.color = Color.green;
                Gizmos.DrawCube(targetPosition, new Vector3(0.1f, 0.1f, 0.1f));
            }

            Gizmos.color = oldColor;
        }
    }
}