实现原理就是类似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模型上加文字