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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM