在Unity中创建和优化Head Health Bars

孙广东  2017.3.27

http://blog.csdn.NET/u010019717


翻译自:http://www.theappguruz.com/blog/create-and-optimize-on-head-health-bars-in-unity  



目的

这篇博客的主要目的是要给你一个想法, 关于创建和优化你游戏 (特别是第三人游戏)中的 Head Health Bars。

 

如果你想要学习如何优化你的游戏,你可以查阅这篇博客︰

 

Head Health Bars?是真的没什么大不了?

理想的例子使用Head Health Bars的地方就是帝国时代(如下图所示的屏幕)。

Health Bars Age of Empires


会出现的问题就是 FPS 很低!!!!!

很多人的做法可能是在 每个角色上添加 world space canvas 这个UGUI !

 

容易吗?

让我们理解这一些案例,然后最后讨论出一个理想的解决方案。

案例测试 1

让我们生成 100 个角色。(在我的例子中使用球体)

创建角色与world canvas作为角色的子对象(像下图一样设置Canvas 和 Image)

下面的图像显示相同︰

Health Bars on world Canvas


在这里有一个  PlayerGenerate.cs    脚本,生成的100个角色的代码


using UnityEngine;
using System.Collections;

public class PlayerGenerator : MonoBehaviour {

public GameObject playerPrefab;
public GameObject healthBarPrefab;

public Transform playersParent;
public RectTransform healthPanelRect;

void Start()
{
GeneratePlayers();
}

private void GeneratePlayers()
{
Vector3 position = new Vector3(3.75f,6.5f,5.0f);
for (int i = 0; i < 10; i++)
{
position -= new Vector3(0,0.6f,0.90f);
for (int j = 0; j < 10; j++)
{
GeneratePlayerAt(position);
position -= new Vector3(0.85f,0,0);
}
position = new Vector3(3.75f,position.y,position.z);
}
}

private void GeneratePlayerAt(Vector3 position)
{
GameObject player = Instantiate(playerPrefab, position, Quaternion.identity) as GameObject;
player.transform.parent = playersParent;
}
}


此外让每个玩家运动起来, 把以下脚本附加到角色上。

PlayerMovement.cs 


using UnityEngine;
using System.Collections;

public class PlayerMovement : MonoBehaviour {

    public float speed=5;
    public Vector3 direction=new Vector3(0,1,0);
    // Update is called once per frame
    void Update () {
        transform.Translate(direction * speed * Time.deltaTime);
    }
}

我们要看场景当前的FPS ,  工具脚本如下:

            同时为脚本赋值(在编辑器中)

HUDFPS.cs 

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class HUDFPS : MonoBehaviour
{

    // Attach this to a GUIText to make a frames/second indicator.
    //
    // It calculates frames/second over each updateInterval,
    // so the display doesz not keep changing wildly.
    //
    // It is also fairly accurate at very low FPS counts (<10).
    // We do this not by simply counting frames per interval, but
    // by accumulating FPS for each frame. This way we end up with
    // correct overall FPS even if the interval renders something like
    // 5.5 frames.

    public float updateInterval = 0.5F;
    public Text t;
    private float accum = 0; // FPS accumulated over the interval
    private int frames = 0; // Frames drawn over the interval
    private float timeleft; // Left time for current interval

    void Start()
    {
        if (!GetComponent<Text>())
        {
            Debug.Log("UtilityFramesPerSecond needs a GUIText component!");
            enabled = false;
            return;
        }
        timeleft = updateInterval;
        t = GetComponent<Text>();
    }

    void Update()
    {
        timeleft -= Time.deltaTime;
        accum += Time.timeScale / Time.deltaTime;
        ++frames;

        // Interval ended - update GUI text and start new interval
        if (timeleft <= 0.0)
        {
            // display two fractional digits (f2 format)
            float fps = accum / frames;
            string format = System.String.Format("{0:F2} FPS", fps);
            t.text = format;

            if (fps < 30)
                t.color = Color.yellow;
            else
                if (fps < 10)
                t.color = Color.red;
            else
                t.color = Color.green;
            //DebugConsole.Log(format, level);
            timeleft = updateInterval;
            accum = 0.0F;
            frames = 0;
        }
    }
}



我的手机显示 fps 约 30 FPS。


Unoptimized Multiple world Canvases


让我们看一看探查器

Unoptimized case profiler reading

Canvas.SendWillRenderCanvases()占用 14 毫秒,

Canvas.builtBatch                               需要 6.76ms,

Canas.Render                                      需要 5.88ms。


 

解决方法?

如何使用single overlay canvas

计算出的位置,只需将health bar图像放在每个角色?



案例测试 2

1) 创建一个 health bar 预制体  内容就是一个简单的图像。

2) 现在创建   HealthBar.cs    脚本,如下所示,并将其附加到刚刚的预制体上:

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class HealthBar : MonoBehaviour {

    #region PRIVATE_VARIABLES
    private Vector2 positionCorrection = new Vector2(0, 100);
    #endregion

    #region PUBLIC_REFERENCES
    public RectTransform targetCanvas;
    public RectTransform healthBar;
    public Transform objectToFollow;

    #endregion

    #region PUBLIC_METHODS
    public void SetHealthBarData(Transform targetTransform,RectTransform healthBarPanel)
    {
        this.targetCanvas = healthBarPanel;
        healthBar = GetComponent<RectTransform>();
        objectToFollow = targetTransform;
        RepositionHealthBar();
        healthBar.gameObject.SetActive(true);
    }

    public void OnHealthChanged(float healthFill)
    {
        healthBar.GetComponent<Image>().fillAmount = healthFill;
    }
    #endregion

    #region UNITY_CALLBACKS
    void Update()
    {
        RepositionHealthBar();
    }
    #endregion

    #region PRIVATE_METHODS

    private void RepositionHealthBar()
    {
        Vector2 ViewportPosition = Camera.main.WorldToViewportPoint(objectToFollow.position);
        Vector2 WorldObject_ScreenPosition = new Vector2(
        ((ViewportPosition.x * targetCanvas.sizeDelta.x) - (targetCanvas.sizeDelta.x * 0.5f)),
        ((ViewportPosition.y * targetCanvas.sizeDelta.y) - (targetCanvas.sizeDelta.y * 0.5f)));

        //now you can set the position of the ui element
        healthBar.anchoredPosition = WorldObject_ScreenPosition;
    }
    #endregion
}

3) 修改以前的PlayerGenerator.cs脚本,如下所示。

using UnityEngine;
using System.Collections;

public class PlayerGenerator : MonoBehaviour {

    public GameObject playerPrefab;
    public GameObject healthBarPrefab;

    public Transform playersParent;
    public RectTransform healthPanelRect;

    void Start()
    {
        GeneratePlayers();
    }

    private void GeneratePlayers()
    {
        Vector3 position = new Vector3(3.75f,6.5f,5.0f);
        for (int i = 0; i < 10; i++)
        {
            position -= new Vector3(0,0.6f,0.90f);
            for (int j = 0; j < 10; j++)
            {
                GeneratePlayerAt(position);
                position -= new Vector3(0.85f,0,0);
            }
            position = new Vector3(3.75f,position.y,position.z);
        }
    }

    private void GeneratePlayerAt(Vector3 position)
    {
        GameObject player = Instantiate(playerPrefab, position, Quaternion.identity) as GameObject;
        player.transform.parent = playersParent;
    }

    private void GeneratePlayerHealthBar(Transform player)
    {
        GameObject healthBar = Instantiate(healthBarPrefab) as GameObject;
        healthBar.GetComponent<HealthBar>().SetHealthBarData(player, healthPanelRect);
        healthBar.transform.SetParent(healthPanelRect, false);
    }
}



   每创建一个角色,同时也创建一个 health bar 然后位置放到头顶位置!


现在,让我们在移动设备上测试它。

Multiple Health Bar images on single canvas

提升了  20 fps !!!!!



让我们现在检查探查器。

Optimized profiler reading





Unity创建和使用后台Service通常涉及以下几个步骤: 1. 定义Service接口:首先定义一个接口,这个接口将包含你希望Service实现的方法。这样可以确保不同的Service实现者遵循相同的方法签名。 2. 创建Service实现:然后,创建一个或多个类来实现你定义的Service接口。在这个实现类,你可以编写具体的后台逻辑,如网络请求、数据处理等。 3. 使用Unity的依赖注入系统:Unity提供了依赖注入(Dependency Injection)的功能,你可以使用它来将Service注入到需要它的组件。这通常通过构造函数注入或者属性注入来实现。 4. 启动和管理Service:在Unity的生命周期管理,如在`Start`或者`Awake`方法,你可以初始化Service并启动需要的后台任务。 5. 使用异步处理:在后台Service处理如网络请求这样的异步操作时,确保使用`async`和`await`关键字来避免阻塞Unity的主线程。Unity的`Task`和`IProgress`接口也可以用来处理异步任务。 示例代码框架如下: ```csharp // Service Interface public interface IMyService { void PerformTask(); } // Service Implementation public class MyService : IMyService { public void PerformTask() { // 实现后台任务的代码 } } // 使用Service的组件 public class MyComponent : MonoBehaviour { private IMyService _myService; // 通过构造函数注入Service public MyComponent(IMyService myService) { _myService = myService; } void Start() { // 启动后台Service _myService.PerformTask(); } } // 在Unity的某个初始化点(例如UnityEditor或者MonoBehaviour的某个生命周期方法)进行Service的注册和实例化 void ConfigureServices() { // 创建Service实例并将其注册到Unity的依赖注入容器 var service = new MyService(); UnityDIContainer.Instance.Register<IMyService>(service); } ``` 在实际操作时,你可能需要根据Unity的版本和使用的具体框架(如UniRx、Zenject等)来调整具体实现细节。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值