zoukankan      html  css  js  c++  java
  • Unity3d中NGUI加强版血条(Healthbar)的制作

    这阵子项目中需要用到一种特殊样式的血条。描述如下:

    1. 正常颜色为红色。受到伤害后,即将扣除的血量变暗(暗红色),并有下降动画效果;

    2. 加护盾效果后,增加一部分血量值,该额外部分为白色,护盾效果消失后该部分血量瞬间消失;

    3. 在护盾效果下受到伤害时,首先扣除白色血量。白色血量不足扣除时,余下部分从红色血量中扣除;

    4. 白色血量的扣除效果为变为灰色并有下降动画效果;

    4. 当加护盾效果时,若即将添加的白色血量将使总血条“溢出”,从新计算百分比并排满血条;

    5. 中毒时,将相应的血量(按照伤害扣血优先级,即先扣除护盾,再扣除正常)变为紫色。该紫色血量有递减动画;

    6. 中毒时若受到伤害,不扣除紫色部分血量(实际上该部分已扣除,但有个缓冲时间),而是红色或白色部分;

    7. 若中毒时受到护盾效果?

    8. 血条会自动隐藏,血量产生变化时会自动显示;

    制作普通血条时,我们一般会用UISlider。

    但是这里涉及到护盾和中毒的效果,用UISlider显然是不够的。我首先想到的是用多个血条叠加在一起,分辨为正常血条、中毒血条、护盾血条。但是掉血效果要怎么解决?

    如果只是有下降动画,那很好解决,可是会先变暗,这显然是一个slider做不到的。

    于是我灵机一动想到了:一个血条,多个UISlider!我们可以写一个自定义血条,该血条包含正常血量、中毒值、护盾值,以及相应的状态属性。

    经过实践,果然我的想法是对的。先来看下效果图:

    1.掉血效果

     

    2.加护盾

    2.1 加护盾时掉血

    3. 中毒

    复杂的叠加效果我们稍后再讨论。第一步,先完成UI上的结构设计:

    1. Heathbar为金色的边框(UISprite)

    2. Blank为底色(灰)(UISprite)

    3. ShieldedDmg为加护盾时的减血底色(深灰色)(UISprite, UISlider)

    4. Shielded为护盾颜色(白色)(UISprite, UISlider)

    5. Poisoned为中毒颜色(紫色)(UISprite, UISlider)

    6. NormalDmg为正常情况下的减血底色(暗红色)(UISprite, UISlider)

    7. Normal为正常血条的颜色(红色)(UISprite, UISlider)

    8. thumb为血条末端的小刻度(白色)(UISprite),并设置Normal上Slider的Thumb为它

    如此,我们就完成了初步的UI设计。数一下,一共有5个Slider。我们再添加一个名为UIHealthbar自定义脚本,用来管理这些UISlider的数值变化,以及处理相关逻辑。

    将UIHealthbar绑到Heathbar上。初步脚本如下:

      1 using System;
      2 using UnityEngine;
      3 
      4 public class UIHealthbar : MonoBehaviour
      5 {
      6     #region 
      7 
      8     private UISlider _normal;
      9     private UISlider _normalDmg;
     10     private UISlider _shielded;
     11     private UISlider _shieldedDmg;
     12     private UISlider _poisoned;
     13     private UISprite _barSprite;
     14 
     15     #endregion
     16 
     17 
     18     /// <summary>
     19     /// 全局动画时长
     20     /// </summary>
     21     private const float AnimDuration = 0.2f;
     22 
     23     /// <summary>
     24     /// 渐变类型
     25     /// </summary>
     26     private const iTween.EaseType EaseType = iTween.EaseType.linear;
     27 
     28     /// <summary>
     29     /// 是否正在隐藏或显示(但如或淡出)
     30     /// </summary>
     31     private bool _isFading;
     32 
     33     /// <summary>
     34     /// 用来判断自动隐藏的计时器
     35     /// </summary>
     36     private float _timer;
     37 
     38     /// <summary>
     39     /// 是否自动隐藏
     40     /// </summary>
     41     public bool autoHide = true;
     42 
     43     /// <summary>
     44     /// 是否受到正常伤害
     45     /// </summary>
     46     private bool IsNormalDamaging
     47     {
     48         get { return _normalDmg.gameObject.activeSelf; }
     49         set { _normalDmg.gameObject.SetActive(value); }
     50     }
     51 
     52     /// <summary>
     53     /// 是否在加护盾的情况下受到伤害
     54     /// </summary>
     55     private bool IsShieldedDamaging
     56     {
     57         get { return _shieldedDmg.gameObject.activeSelf; }
     58         set { _shieldedDmg.gameObject.SetActive(value); }
     59     }
     60 
     61     /// <summary>
     62     /// 是否正在掉血
     63     /// </summary>
     64     public bool IsDamaging
     65     {
     66         get { return IsShieldedDamaging || IsNormalDamaging; }
     67     }
     68 
     69     /// <summary>
     70     /// 是否中毒
     71     /// </summary>
     72     public bool IsPoisoned
     73     {
     74         get { return _poisoned.gameObject.activeSelf; }
     75         private set { _poisoned.gameObject.SetActive(value); }
     76     }
     77 
     78     /// <summary>
     79     /// 是否受护盾
     80     /// </summary>
     81     public bool IsShielded
     82     {
     83         get { return _shielded.gameObject.activeSelf; }
     84         private set { _shielded.gameObject.SetActive(value); }
     85     }
     86 
     87     /// <summary>
     88     /// 是否可见(自动隐藏相关隐藏)
     89     /// </summary>
     90     private bool IsVisible
     91     {
     92         get
     93         {
     94             throw
     95                 new NotImplementedException();
     96         }
     97         set
     98         {
     99             
    100         }
    101     }
    102 
    103     private void OnEnable()
    104     {
    105         IsPoisoned = false;
    106         IsShielded = false;
    107         IsShieldedDamaging = false;
    108         IsNormalDamaging = false;
    109     }
    110 
    111     private void Awake()
    112     {
    113         _normal = transform.FindChild("Normal").GetComponent<UISlider>();
    114         _normalDmg = transform.FindChild("NormalDmg").GetComponent<UISlider>();
    115         _shielded = transform.FindChild("Shielded").GetComponent<UISlider>();
    116         _shieldedDmg = transform.FindChild("ShieldedDmg").GetComponent<UISlider>();
    117         _poisoned = transform.FindChild("Poisoned").GetComponent<UISlider>();
    118         _barSprite = transform.GetComponent<UISprite>();
    119     }
    120 
    121     #region 逻辑处理
    122 
    123     /// <summary>
    124     /// 加伤害
    125     /// </summary>
    126     /// <param name="percent">将造成的伤害百分比(小于1)</param>
    127     /// <returns>剩余血量百分比</returns>
    128     public float AddDamage(float percent)
    129     {
    130         return 0;
    131     }
    132 
    133     /// <summary>
    134     /// 加中毒值
    135     /// </summary>
    136     /// <param name="percent">百分比</param>
    137     /// <param name="speed">下降速度(刻度/秒)</param>
    138     public void AddPoison(float percent, float speed)
    139     {
    140     }
    141 
    142     /// <summary>
    143     /// 加护盾值
    144     /// </summary>
    145     /// <param name="percent">百分比</param>
    146     /// <param name="time">持续时间(秒)</param>
    147     public void AddShield(float percent, float time)
    148     {
    149     }
    150 
    151     #endregion
    152 }
    View Code

    接下来我们处理具体的逻辑。

    1. 自动隐藏:

    自动隐藏的需求是,在5秒内未产生任何形式的血量变化,则淡出隐藏。一旦产生血量变化,淡入显示。

    淡入淡出是需要Alpha值来控制的。我们直接改变_barSprite这个字段(即最上层的Healthbar上的UISprite)的alpha值,则其子物体会一起产生Alpha值变化的效果。

    修改IsVisible属性:

     1 private bool IsVisible
     2     {
     3         get { return _barSprite.color.a >= 1; }
     4         set
     5         {
     6             _timer = 0;
     7             if (value != IsVisible && !_isFading)
     8             {
     9                 _isFading = true;
    10                 if (value)
    11                 {
    12                     iTween.ValueTo(gameObject,
    13                         iTween.Hash("from", 0, "to", 1, "time", AnimDuration, "easetype",
    14                             EaseType, "onupdate", "OnFadeIn",
    15                             "onupdatetarget", gameObject, "oncomplete", "OnFadeInComplete", "oncompletetarget", gameObject));
    16                 }
    17                 else
    18                 {
    19                     iTween.ValueTo(gameObject,
    20                         iTween.Hash("from", 1, "to", 0, "time", AnimDuration, "easetype",
    21                             EaseType, "onupdate", "OnFadeOut",
    22                             "onupdatetarget", gameObject, "oncomplete", "OnFadeOutComplete", "oncompletetarget", gameObject));
    23                 }
    24             }
    25         }
    26     }

    再添加iTween中引用的四个方法:

     1 private void OnFadeIn(float value)
     2     {
     3         _barSprite.color = new Color(1, 1, 1, value);
     4     }
     5 
     6     private void OnFadeInComplete()
     7     {
     8         _isFading = false;
     9         _timer = 0;
    10     }
    11 
    12     private void OnFadeOut(float value)
    13     {
    14         _barSprite.color = new Color(1, 1, 1, value);
    15     }
    16 
    17     private void OnFadeOutComplete()
    18     {
    19         _isFading = false;
    20         _timer = 0;
    21     }

    现在,直接改变IsVisible即可控制淡入淡出。我们还需要在Update里检查血量变化和设置隐藏,即修改IsVisible:

     1 private void Update()
     2     {
     3         if (autoHide && !_isFading && IsVisible && !IsPoisoned)
     4         {
     5             _timer += Time.deltaTime;
     6             if (_timer > 5f)
     7             {
     8                 IsVisible = false;
     9             }
    10         }
    11     }

    上面设置的阀值为5秒。实际上这个5该提取出来做属性或字段。在收到伤害,护盾等情况时,我们需要手动改变IsVisible。

    2. 减血:

     1 /// <summary>
     2     /// 加伤害
     3     /// </summary>
     4     /// <param name="percent">将造成的伤害百分比(小于1)</param>
     5     /// <returns>剩余血量百分比</returns>
     6     public float AddDamage(float percent)
     7     {
     8         if (!IsVisible)
     9         {
    10             IsVisible = true;
    11         }
    12         if (percent > 1f)
    13         {
    14             Debug.LogWarning(string.Format("Illegal damage percent: -{0}", percent));
    15             return _normal.value;
    16         }
    17         if (_normal.value <= 0f)
    18         {
    19             Debug.LogWarning(string.Format("Health is already below zero: -{0}", percent));
    20             return _normal.value;
    21         }
    22         if (IsShielded)
    23         {
    24             _shieldedDmg.value = _shielded.value;
    25             _shielded.value -= percent;
    26             _shieldedDmg.gameObject.SetActive(true);
    27             iTween.ValueTo(gameObject,
    28                 iTween.Hash("from", _shieldedDmg.value, "to", _shielded.value, "time", AnimDuration, "easetype",
    29                     EaseType, "onupdate", "OnShieldedDamage",
    30                     "onupdatetarget", gameObject, "oncomplete", "ShieldedDamageDone", "oncompletetarget", gameObject));
    31             //if damage lows the shield value to zero, take health instead
    32             //...
    33         }
    34         else
    35         {
    36             _normalDmg.value = _normal.value;
    37             _normal.value -= percent;
    38             _normalDmg.gameObject.SetActive(true);
    39             iTween.ValueTo(gameObject,
    40                 iTween.Hash("from", _normalDmg.value, "to", _normal.value, "time", AnimDuration, "easetype", EaseType,
    41                     "onupdate", "OnNormalDamage",
    42                     "onupdatetarget", gameObject, "oncomplete", "NormalDamageDone", "oncompletetarget", gameObject));
    43         }
    44 
    45         return _normal.value;
    46     }

    上面有判断护盾。当受到的伤害不大于护盾值时,只会减少护盾,若未大于护盾值,则会从正常血量里扣除剩余的值(此处我未处理这种情况。只标了注释,算是留给大家的一个题目...)。

    注意,血量值得变化是按百分比来算的。所以各种参数应该在折算后传入。比如你满血为100,受到20点伤害,那么应该AddDamage(0.2f);

    添加iTween里的引用:

     1 private void OnNormalDamage(float value)
     2     {
     3         _normalDmg.value = value;
     4     }
     5 
     6     private void NormalDamageDone()
     7     {
     8         _normalDmg.gameObject.SetActive(false);
     9     }
    10 
    11     private void OnShieldedDamage(float value)
    12     {
    13         _shieldedDmg.value = value;
    14     }
    15 
    16     private void ShieldedDamageDone()
    17     {
    18         _shieldedDmg.gameObject.SetActive(false);
    19     }

    注意在变化完成,及时隐藏相关底色。

    3. 护盾:

     1 /// <summary>
     2     /// 加护盾值
     3     /// </summary>
     4     /// <param name="percent">百分比</param>
     5     /// <param name="time">持续时间(秒)</param>
     6     public void AddShield(float percent, float time)
     7     {
     8         //若将增加的护盾值使总值超过100%
     9         if (_normal.value + percent + _shieldMod > 1f)
    10         {
    11             percent = _normal.value + percent + _shieldMod - 1;
    12             _normal.value -= percent;
    13             _shieldMod += percent;
    14         }
    15         //若已中毒
    16         if (IsPoisoned)
    17         {
    18             //若将要增加的护盾值大于中毒(剩余)值
    19             if (percent > _poisoned.value)
    20             {
    21                 percent -= _poisoned.value;
    22             }
    23             else //否则
    24             {
    25                 percent = percent - _poisoned.value;
    26             }
    27         }
    28         _shielded.value = _normal.value + percent;
    29         IsShielded = true;
    30         iTween.ValueTo(gameObject,
    31             iTween.Hash("from", 0, "to", time, "time", time, "onupdate", "OnShield", "oncomplete", "ShieldTimeOut",
    32                 "oncompleteparams", _shieldMod,
    33                 "oncompletetarget", gameObject));
    34     }

    上面出现里一个float型的_shieldMod之前并没有声明。事实上我后来了解到需求里护盾是不能叠加的,后加的护盾只会覆盖之前的护盾。所以这个用来存储多重护盾值得_shieldMod就没用了。移除即可。记得要从iTween.ValueTo里也将其移除并修改对应方法签名(ShieldTimeOut)。

     1 private void OnShield(float value)
     2     {
     3     }
     4 
     5     private void ShieldTimeOut(float modPercent)
     6     {
     7         _shieldMod = Mathf.Max(0, _shieldMod - modPercent);
     8         _normal.value += modPercent;
     9         IsShielded = false;
    10         _shielded.value = _normal.value;
    11         _shieldedDmg.value = _normal.value;
    12     }

    护盾到期是在ShieldTimeOut里处理的。此处并没有渐变消失,而是啪一下没了=。=

    4.中毒:

     1 /// <summary>
     2     /// 加中毒值
     3     /// </summary>
     4     /// <param name="percent">百分比</param>
     5     /// <param name="speed">下降速度(刻度/秒)</param>
     6     public void AddPoison(float percent, float speed)
     7     {
     8         //若已加护盾
     9         if (IsShielded)
    10         {
    11             //若将要增加的中毒值大于护盾值
    12             if (percent > _shielded.value)
    13             {
    14                 percent -= _shielded.value;
    15             }
    16             else //否则
    17             {
    18                 percent = _shielded.value - percent;
    19             }
    20         }
    21         if (percent < 0)
    22         {
    23             return;
    24         }
    25         _normal.value -= percent;
    26         IsPoisoned = true;
    27         iTween.ValueTo(gameObject,
    28             iTween.Hash("from", percent, "to", 0, "speed", speed, "easetype", EaseType, "onupdate", "OnPoison", "onupdatetarget",
    29                 gameObject,
    30                 "oncomplete",
    31                 "PoisonTimeOut",
    32                 "oncompletetarget", gameObject));
    33     }
     1 private void OnPoison(float value)
     2     {
     3         _poisoned.value = _normal.value + value;
     4     }
     5 
     6     private void PoisonTimeOut()
     7     {
     8         IsPoisoned = false;
     9         _poisoned.value = _normal.value;
    10     }

    中毒的递减效果在OnPoison里实现。

    最后添加测试代码:

     1 /// <summary>
     2     /// Debug Testing
     3     /// </summary>
     4     private void OnGUI()
     5     {
     6         if (GUI.Button(new Rect(10, 10, 200, 100), "Hit - 20%"))
     7         {
     8             AddDamage(0.2f);
     9         }
    10         if (GUI.Button(new Rect(10, 120, 200, 100), "Shield + 30%(3s)"))
    11         {
    12             AddShield(0.3f, 3);
    13         }
    14         if (GUI.Button(new Rect(10, 230, 200, 100), "Poison + 10%(5%/s)"))
    15         {
    16             AddPoison(0.1f, 0.05f);
    17         }
    18     }

    到此就差不多完成了。拖出来当预置,就成了一个自定义控件。

    事实上还存在许多bug。尤其是在面临【又加护盾又中毒又受伤害】这类情况下。我没有去处理这样的逻辑因为项目里不需要。这里只是提供一个思路给大家。如果能抛砖引玉当然最好了~

    另外,里面的iTween这样用起来会很麻烦。我会另写一篇,介绍我的iTween自定义扩展。

    源码请见我的github。

    https://github.com/theoxuan/GeneralGame/blob/master/Assets/Resources/Healthbar.prefab

    https://github.com/theoxuan/GeneralGame/blob/master/Assets/Script/UIHealthbar.cs

  • 相关阅读:
    扩展性很好的一个分页存储过程
    SQL中列转行
    Server.MapPath() 方法(摘自互联网)
    容易遗忘のSQL
    Linq读取XML
    字节流和字符流
    Java中" "和 ' '
    Spring常用基本注解
    finally和return
    JS 深度clone
  • 原文地址:https://www.cnblogs.com/seancheung/p/4126352.html
Copyright © 2011-2022 走看看