角色头顶信息

发布时间 2023-10-27 00:26:40作者: yanghui01

实现原理就是类似shader中的广告牌(billboard),让ui始终和相机的朝向一致。

效果:

因为类似广告牌,所以脚本的命名也叫BillboardUI了

using System.Collections.Generic;
using UnityEngine;

public class BillboardUIRoot : MonoBehaviour
{

    class Info
    {
        public Transform uiTransform; //广告牌ui
        public Transform targetTransform; //ui的关联对象
    }

    private static BillboardUIRoot s_inst;
    public static BillboardUIRoot Instance
    {
        get { return s_inst; }
    }
    public Transform m_PlayerTransform;

    public Transform m_WorldCamTransform;
    public float m_MaxScale = 0.018f;
    public float m_ScaleFixFactor = 0.0018f; //缩放参数, 相机很近的时候, 确保看着和普通Cavnas一致

    private List<Info> m_List = new List<Info>();

    void Awake()
    {
        if (null == s_inst)
        {
            s_inst = this;
        }
        else
        {
            Debug.LogError($"multi inst: {this.name}");
        }
    }

    void Start()
    {
        if (null == m_WorldCamTransform)
            m_WorldCamTransform = Camera.main.transform;
    }

    void LateUpdate()
    {
        var camForward = m_WorldCamTransform.forward;
        var camPos = m_WorldCamTransform.position;

        for (var i = 0; i < m_List.Count; ++i)
        {
            var info = m_List[i];
            var targetPos = info.targetTransform.position;
            if (Vector3.Distance(targetPos, m_PlayerTransform.position) > 30) //超出范围忽略
                continue;

            info.uiTransform.forward = camForward; //RenderMode为ScreenSapce-Camera时不需要手动设置, 但存在抖动的情况

            var overheadPos = GetUiBindPos(info.targetTransform); //头顶坐标
            info.uiTransform.position = overheadPos;

            var camToRoleVec = targetPos - camPos; //GameObject与相机的距离向量
            var projDistance = Vector3.Dot(camToRoleVec, camForward); //相机方向上的距离
            float scale = projDistance * m_ScaleFixFactor;
            if (scale > m_MaxScale)
                scale = m_MaxScale;
            info.uiTransform.localScale = new Vector3(scale, scale, scale);
        }
    }

    //广告牌ui放在关联对象的哪里
    Vector3 GetUiBindPos(Transform tf)
    {
        var findTf = tf.Find("uiBindPos");
        if (null != findTf)
            return findTf.position;

        return tf.position;
    }

    //关联一个广告牌ui
    public void BindUI(Transform targetTransform, Transform uiTransform)
    {
        var info = new Info();
        info.targetTransform = targetTransform;
        info.uiTransform = uiTransform;
        m_List.Add(info);
    }

    //删除广告牌ui
    public void RemoveUI(Transform targetTransform)
    {
        for (var i = m_List.Count - 1; i >= 0; --i)
        {
            if (m_List[i].targetTransform == targetTransform)
                m_List.RemoveAt(i);
        }
    }

}

逻辑脚本

public class WorldUITest : MonoBehaviour
{
    public GameObject m_NpcPrefab;
    public GameObject m_NpcInfoPrefab;

    void Start()
    {
        
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Alpha1))
        {
            var npcGo = GameObject.Instantiate(m_NpcPrefab);
            npcGo.SetActive(true);
            npcGo.transform.localPosition = new Vector3(0, 0.01f, 0);

            var npcUIGo = GameObject.Instantiate(m_NpcInfoPrefab);
            npcUIGo.SetActive(true);
            npcUIGo.transform.SetParent(BillboardUIRoot.Instance.transform, false);

            BillboardUIRoot.Instance.BindUI(npcGo.transform, npcUIGo.transform);
        }
    }
}

 

一些相关设置

Canvas要用World Space模式

角色控制,相机跟随相关的直接用的这边的

阴影实现 - 准备工作:场景中行走的角色 - yanghui01 - 博客园 (cnblogs.com)

 

参考

unity 3D对象上面显示文字_天涯过客TYGK的博客-CSDN博客_unity在3d模型上加文字