Unity里名字牌、血条等头顶挂件的实现

Unity里实现人物头顶的名字牌、血条的实现,网上很多博客有提到过。这里结合自己在项目中的开发,简单总结几点。

宣雨松的热门博客里有提到过直接利用Unity自身的OnGUI()实现人物血条:http://www.xuanyusong.com/archives/1032 。

撇开血条进度条的更新,重点关注头顶物件位置的更新。简化之,实现人物头顶的名字牌的。

核心代码如下:

using UnityEngine;
using System.Collections;

public class Following : MonoBehaviour {

    public Camera m_Camera = null;
    public GameObject m_goFollowing = null;
    public Vector3 m_vOffset;

    void OnGUI()
    {
        Vector3 vPosScreen = m_Camera.WorldToScreenPoint(m_goFollowing.transform.position + m_vOffset);
        GUI.Label(new Rect(vPosScreen.x, Screen.height - vPosScreen.y, 200, 80), "牧羊少年奇幻之旅");
    }
Unity里名字牌、血条等头顶挂件的实现_第1张图片


不过,实际项目中暂时不会用Unity的原生UI,目前的项目多用的是Unity4.x版本,原生UI太难用,多用NGUI等插件。下面主要介绍Unity中借助NGUI实现人物的头顶挂件。

头顶物件用NGUI实现可以根据渲染相机来分为两种方式:
(1)采用渲染场景的MainCamera来渲染头顶物件,这样的头顶物件可以看做普通的3D物体,是可以被场景中3D物体遮挡的。
(2)专门用UI相机渲染,头顶物件作为UI,不会被场景中3D物体遮挡。
一般我们会采用方式2,UI尽量不要用主相机渲染,用专门的UI相机渲染。
不过,还是顺带提一下采用方式(1)处理UI的核心代码:
public class Following : MonoBehaviour {
    public GameObject m_goFollowing = null;
    public float m_fOffset = 0;

    void Update()
    {
        gameObject.transform.position = m_goFollowing.transform.position + new Vector3(0, m_fOffset, 0);
        gameObject.transform.rotation = Camera.main.transform.rotation;
    }
}
如果您在实践中发现UILabel不可见,请注意:
(1)UILabel UISprite不能单独用,一定要附带一个Panel,作为Panel的子节点或者NGUIPanel和UILabel UISprite挂在同一个GameObject下。
(2)同时UILabel的Layer必须和所附带的NGUIPanel保持一致。请保证你的相机没有屏蔽UILabel所在的Layer。
(3)UILabel UISprite无论是在哪个Panel下,尽量放在UIRoot下吧。若有些物体看不见,调整下Scale。

我们一般采用方式2,UI用专门的UI相机渲染,核心是将主相机下的Transform映射到UI相机下的Transform。
using UnityEngine;
using System.Collections;

public class Following : MonoBehaviour {

    public GameObject m_goFollowing = null;
    public float m_fOffset = 0;
    private Camera m_go3DCamera = null;
    private Camera m_go2DCamera = null;

    void Start()
    {
        m_go3DCamera = GameObject.FindWithTag("MainCamera").GetComponent();
        m_go2DCamera = GameObject.Find("UI Root").transform.FindChild("Camera").GetComponent();
    }
void Update () {
        Vector3 vPos = TransPos(m_goFollowing.transform.position + new Vector3(0, m_fOffset, 0));
        gameObject.transform.position = new Vector3(vPos.x, vPos.y, 0);    //get proper z,  z = 0.0f simply
        gameObject.transform.rotation = m_go2DCamera.transform.rotation;   //keep the same rotation with camera, so Parallel with camera        
}

    //Transform WorldPos in 3DCamera to new WorldPos in 2DCamera. They share the same ScreenPos
    Vector3 TransPos(Vector3 worldpos)
    {
        if (m_go3DCamera == null || m_go2DCamera == null)
        {
            return Vector3.zero;
        }
        Vector3 screenPos = m_go3DCamera.WorldToScreenPoint(worldpos);
        Vector3 newWorldPos = m_go2DCamera.ScreenToWorldPoint(screenPos);
        return newWorldPos;
    }
}
这其中的要点都写在注释里了。
游戏场景一般用3DCamera渲染,名字牌血条等属于UI,应该被UI相机渲染。头顶的名字牌血条要跟随着3D场景里的物体,关键是如何将3D相机空间里的位置映射到UI相机相机空间。中间的桥梁是屏幕位置,两个相机看到的头顶物件应该映射到同一个屏幕坐标。
同时,两个细节需要注意:
1. 为保证UI可见,通过UI相机变换得到的坐标深度要设置为合适的值,太远或太近会出视野范围。可以简单地设置为0。
2. 头顶物件要注意和UI相机保持平行,即头顶物件的rotation设置为UI相机的rotation。

实际应用中,还有一些性能问题需要注意。
(1)减少DrawCall:
NGUI内置合并DrawCall机制,同一个Panel下,不被打断的depth区间段,使用同样的Material,这些Widget可以被合并为一个DrawCall。
具体而言,使用同样的Atlas的UISprite,挂在同一个UIPanel下的depth区间,此depth区间在此UIPanel下不被其他的Atlas使用。UILabel也针对每种字体分配一个Depth区间。 当然,前提是不影响功能需求。
以头顶的名字条NameBar、血条HPBar为例,NameBar.prefab, HPBar.prefab本身不要带有UIPanel,我们为头戴物件在NGUI-UIRoot下创建一个共同的UIPanel--HeadWearingPanel,所有实例化的NameBar、HPBar都挂在HeadWearingPanel下。
另外,NGUI3.8.0之前的版本,显示/隐藏UIWdget会导致所有UIDrawCall的销毁重建,对CPU负荷较大。如果用NGUI3.8.0之前的版本,建议不要直接用gameObejct.SetActive(false)来实现UI控件的隐藏,可以令transform.scale = Vector3.zero, 或者将欲隐藏的UI移到视野外。NGUI3.8.0的版本没有这个问题了。
(2)减少GC
对于要频繁创建销毁的UI,做一个资源池。避免频繁创建销毁对象带来的大量gc。
针对每种类型的Prefab创建一个Pool.删除NameBar、HPBar时,并不真正Destroy,将之隐藏即可。常见NameBar、HPBar时,先从Pool里查找是否有可用的NameBar、HPBar,若没有找到才真正实例化创建新对象。








你可能感兴趣的:(NGUI,Unity)