写在前面(废话):
想用NGUI实现个多层血条,在网上没找到相关信息,思来想去还是自己实现一个吧。最后捣鼓捣鼓还是出来了。写了两个版本的,第一个版本是纯体力制作,就是有几层血就预先放好几个血条,比如说 boss有5层血,就预先摆好5层血,但是要加个透明血条(用来表现减血效果,如下图示例),就需要摆10个血条了,这样就太挫了,于是就有了多层血条2.0 beta,之所以用beta,因为觉得还是不够好。开个贴,大家交流一下,希望多提意见。。。
开篇:
一.简介:
1.简单实现原理。预先放置几个血条,当最上面一层血减完为0(缩小为0)后,把它移动到底层,然后再放到的正常大小,并且改变图片显示或者颜色为指定显示。
2. 本版多层血条,用了3个血条对象,每个血条对象包括一个全色显示的血条和一个半透明的血条,实际上用到了6个sprite,如下图示。先前想用2个血条对象(2个要简单),后来考虑到会有一种情况,就是当本次伤害减血的值大于当前血条所剩余值时,下面的血条也会瞬间减血,因为只有两条血,在最上面这层血减完,还未移动到底层的时候,这样的话显示就会露馅。不过实际上我没有用2层对象来做过测试,具体会不会出现这种情况,也只是处于理论阶段...... : - )
3.这个演示版本的血条从底向上摆放的血条颜色依次为红,绿,蓝。sprite深度depth,依次增加。计数也是从底向上计数,依次为0, 1, 2。如下面层面图演示。
二.代码
1.血条对象。
上面讲到,是用了三个血条对象实现的,每个血条对象包括两个血条sprite,一个是全色的,一个是半透明的,为此就单独写个脚本挂到血条对象上,来做管理。代码如下。
简单说明:
1).血条是用sprite制作的
2).不同层的血条会使用不同颜色的图片.示例使用了三种颜色的图片依次为红,绿,蓝。
3).显示的顺序是从底向上的,即索引值从底向上依次为0,1,2
主要功能:
1),使用TweenScale进行缩小
2).改变depth并切换sprite图片
3).显示或者隐藏
P.S.请不要直接拷贝代码使用,出错了不管哦
using UnityEngine;
using System.Collections;
public class MuHp : MonoBehaviour
{
GameObject _normalHp;
GameObject _transHp;
UISprite _normalSp;
UISprite _transSp;
TweenScale _normalScale;
TweenScale _transScale;
/// <summary>
/// Tween Speed
/// </summary>
float tweenSpeed = 1 / 0.7f;
private int _depth = 0;
private int _picIndex = 0;
/// <summary>
/// 血条对应的图片 依次为蓝色,绿色,红色
/// </summary>
string[] _hpPic = { "Hp03", "Hp02", "Hp01" };
void OnEnable()
{
//1 - 查找对象
_normalHp = transform.Find("normal").gameObject;
_transHp = transform.Find("trans").gameObject;
//2 - 获取normal血条脚本 sprite和TweenScale
_normalSp = _normalHp.GetComponent<UISprite>();
_normalScale = _normalHp.GetComponent<TweenScale>();
//2.1 - 给TweenScale绑定动画结束回调
_normalScale.AddOnFinished(PlayScaleFinished);
//3 - 获取transparent血条脚本 sprite 和 TweenScale
_transSp = _transHp.GetComponent<UISprite>();
_transScale = _transHp.GetComponent<TweenScale>();
}
/// <summary>
/// 血条显示控制
/// </summary>
/// <param name="bIs"></param>
public void SetVisible(bool bIs)
{
_normalHp.SetActive(bIs);
_transHp.SetActive(bIs);
}
/// <summary>
/// 设置血条图片并放大到正常大小
/// </summary>
public void SetShowPic()
{
_normalSp.spriteName = _hpPic[_picIndex];
_transSp.spriteName = _hpPic[_picIndex];
SetScale(Vector3.one);
}
/// <summary>
/// 设置血条depth
/// </summary>
/// <param name="dep"></param>
public void SetHpDepth(int dep)
{
_normalSp.depth = dep;
_transSp.depth = dep - 1;
}
/// <summary>
/// 改变血条depth
/// </summary>
public int HpDepth
{
set
{
_depth = value;
_normalSp.depth = _depth;
_transSp.depth = _depth - 1;
}
get { return _depth; }
}
/// <summary>
/// 血条图片索引
/// </summary>
public int HpPicIndex
{
set { _picIndex = value; }
get { return _picIndex; }
}
/// <summary>
/// 设置血条缩放
/// </summary>
public void SetScale(Vector3 vScale)
{
_normalScale.value = vScale;
_transScale.value = vScale;
}
/// <summary>
/// 获取正常血条对象
/// </summary>
public GameObject NormalHp
{
set
{
_normalHp = value;
}
get { return _normalHp; }
}
/// <summary>
/// 获取透明血条对象
/// </summary>
public GameObject TransHp
{
set
{
_transHp = value;
}
get { return _transHp; }
}
/// <summary>
/// 播放动画
/// </summary>
public void PlayTween(float to)
{
_normalScale.from = _normalScale.value;
_normalScale.to.x = to;
_normalScale.duration = (_normalScale.from.x - _normalScale.to.x) / tweenSpeed;
_normalScale.PlayForward();
}
/// <summary>
/// normal缩放结束,trans开始动画
/// </summary>
void PlayScaleFinished()
{
_transAlpha.from = _initAlpha;
_transAlpha.to = 0;
_transAlpha.delay = 0.4f;
_transAlpha.PlayForward();
}
/// <summary>
/// 淡出结束 缩放到前景一样的长度 并恢复Alpha值
/// </summary>
void PlayAlphaFinished()
{
_transScale.value = _normalScale.value;
_transAlpha.value = _initAlpha;
}
}
2.血条操作对象Controller
说明: 如上所说,本例使用3个血条对象实现多层血条效果。首先根据初始血条层数和最大血量,来初始化血条的显示,然后根据每次剩余血量和总血量来计算相关数据来做显示处理。为了方便说明,会把血条对象的depth说为12,14,16,其实是指代对象里实体血条sprite的depth。
要点:
2.1UI制作。预先在界面中放置好3个血条对象,每个对象里的sprite要有不同的depth。应该从底到高显示。对象里有两个sprite,一个为正常显示的,一个为半透明的,半透明的depth比正常的低一个值。本例为第一个对象中半透明血条depth为11,正常血条depth为12,第二个对象半透明血条depth为13,正常血条depth为14,第二个对象半透明血条depth为15,正常血条depth为16。
2.2.几个参数变量
1).血条对象脚本MuHp中的血条图片名字数组 - _hpPic.这个数组放的是血条所用图片的名字,图片颜色依次为蓝,绿,红,对应初始的血条摆放显示顺序,即从底向上依次为红,绿,蓝。在HpController脚本里,当一个血条消耗完毕,移动到底部时,会根据计算的index来从_hpPic里取图片来做显示改变。
2).HpController中名为_muHp的List。这个list中存放的是事先摆放好的三个血条对象,从index 0开始计数,依次为 0, 1, 2,分别对应的血条深度依次为12, 14, 16。即从0开始计数,深度依次增加。
3).HpController中名为_listQueue的Queue。这个Queue存放的是int 值,初始值为 0, 1, 2分别对应于_muHp中的index,当最高index = 2的血条消耗完下降到底部时,这个队列会Dequeue最上面的值,然后Enqueue到自身,依此来一一对应记录_muHp中血条目前真实的index,可以得到当前Top血层在_muHp中的index。
4).其他参数如记录最大血层数_ulayerNum,记录当前血层数的值_layerCount,在血条初始化显示和血条变化过程中会用到。
2.3.主要方法
1).是初始化显示方法。CalculateInitValue用来做初始化数值和图片显示,关键是大于3层血的显示,本例显示规则是从底向上依次为红绿蓝红绿蓝........这样循环显示。
2).根据数值计数显示。CalculateHp。这个方法根据传来的剩余血量和总血量,来计算当前血层是第几层血(从0开始计数),以及当前血层的血量百分比。用两个计算就可以搞定了。还有一个就是当前血层消耗完的判断。具体看代码。
3).循环遍历显示调整深度和图片显示。AdjustDepth。这个方法会在有一层血消耗完并且剩余血层数量大于等3的时候运行。用来改变每个血条对象的深度,并且改变下降到底层血条的显示。
P.S.请不要直接拷贝代码使用,出错了不管哦
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class HpController : MonoBehaviour
{
/// <summary>
/// 使用三个血条来实现多层血条效果
/// List中的血条对象index依此为0, 1, 2.深度依此为 12, 14, 16 !!!! - 重要
/// </summary>
List<MuHp> _muHp = new List<MuHp>();
float fTest = 1.0f;
/// <summary>
/// 血条List中index,调整其depth后对应的index
/// 初始添加为 2, 1, 0,对应_muHp里血条对象的 index !!!! - 重要
/// </summary>
Queue<int> _listQueue = new Queue<int>();
/// <summary>
/// 总层数
/// </summary>
int _ulayerNum = 3;
/// <summary>
/// 当前还有几层血
/// </summary>
int _layerCount = 0;
/// <summary>
/// 每层的血量
/// </summary>
float _layerHpValue = 0;
/// <summary>
/// 上一层血是属于那一层 - 总血层减去1
/// </summary>
int _lastLayer = 0;
//血条颜色排列 从底向上 !!!! - 重要
//蓝色 - 2
//绿色 - 1
//红色 - 0
void Awake()
{
FindUI();
}
//void Start()
//{
// CalculateInitValue(5, 1200);
//}
//float maxHp = 1200;
//float curHp = 1200;
//void Update()
//{
// if (Input.GetKeyDown(KeyCode.W))
// {
// int test = Random.Range(20, 60);
// curHp -= test;
// CalculateHp(curHp, maxHp);
// }
//}
/// <summary>
/// 查找血条对象并绑定MuHp脚本
/// </summary>
void FindUI()
{
for (int i = 0; i < 3; i++)
{
string strHp = string.Format("Hp{0}", i);
//找到3个血条对象
GameObject hp = transform.Find(strHp).gameObject;
//添加MuHp.cs脚本
MuHp hpSc = hp.AddComponent<MuHp>();
//从depth低到高,依此添加到List中 !!!! - 重要
_muHp.Add(hpSc);
}
}
#region 血条
/// <summary>
/// 根据配置计算相关初始化数据
/// 说明:每层血的血量相同 !!!! - 重要
/// </summary>
/// <param name="layer">血条的总层数</param>
/// <param name="maxHp">最大血量</param>
public void CalculateInitValue(int layer, float maxHp)
{
_listQueue.Clear();
//血条总数
_ulayerNum = layer;
//总数计数
_layerCount = layer;
//每层血的血量
_layerHpValue = maxHp / layer;
//用来判断某一层的血条是否减完了,初始值为最大层数
//层数计数和数组计数一样,最小从0开始记
_lastLayer = layer - 1;
//处理血条初始图片显示
HandleInitShow(_ulayerNum);
//初始index 队列
_listQueue.Enqueue(2);
_listQueue.Enqueue(1);
_listQueue.Enqueue(0);
}
/// <summary>
/// 处理血条初始化显示
/// 主要做初始化图片显示,或者隐藏多余的
/// 要点:
/// 1.MuHp.cs 中的 _hpPic 图片名字数组
/// 2.总数大于三条的显示。举例说明,比如现在总血层为4条
/// 则当前3个血条从底向上的显示应该依次为 绿 蓝 红,二不是红 绿 蓝了,本例依此规则显示。 !!!!! - 重要
/// </summary>
/// <param name="totalLayer"></param>
void HandleInitShow(int totalLayer)
{
_muHp[0].HpDepth = 12;
_muHp[1].HpDepth = 14;
_muHp[2].HpDepth = 16;
if (totalLayer < 3)//总数不足3条的,隐藏多余的
{
for (int i = 2; i > totalLayer - 1; i--)
{
_muHp[i].SetVisible(false);
}
}
else if (totalLayer > 3)//总数大于3条的
{
//从低到高 0 1 2 0 1 2
for (int i = 0; i < 3; i++)
{
int picIn = (totalLayer - 3 + i) % 3;
_muHp[i].HpPicIndex = picIn;
_muHp[i].SetVisible(true);
_muHp[i].SetShowPic();
}
}
else//总数正好3条
{
for (int i = 0; i < 3; i++)
{
_muHp[i].HpPicIndex = i;
_muHp[i].SetVisible(true);
_muHp[i].SetShowPic();
}
Debug.Log("The Three Hp is Good");
}
}
/// <summary>
/// 血条更新
/// </summary>
/// <param name="hp">当前血量</param>
/// <param name="maxhp">最大血量</param>
void UpdateBossHp(float hp, float maxhp)
{
float hpRate = Mathf.Clamp(hp / maxhp, 0, 1);
Debug.Log("hp = " + hp);
Debug.Log("maxHP = " + maxhp);
CalculateHp(hp, maxhp);
}
/// <summary>
/// 计算数值改变显示
/// </summary>
/// <param name="hp">当前剩余血量</param>
/// <param name="maxhp">满血血量</param>
void CalculateHp(float hp, float maxhp)
{
//hp 为 0,Boss挂掉了
if (hp <= 0)
{
hp = 0;
_layerCount = 0;
}
if (_layerCount <= 0)
{
_layerNum.text = "X0";
foreach (var hpSc in _muHp)
{
hpSc.SetVisible(false);
}
}
else
{
_layerNum.text = "X" + _layerCount.ToString();
}
//1 - 计算当前血条时第几层血 计数从0开始
//真实层数 计数减一 比如有三层血,则最高层计数为 2
//- 剩余除以每层的平均血量取得当前所处的层数
int curLayer = Mathf.FloorToInt(hp / _layerHpValue);
//2 - 计算当前血条剩余比值
//求余数算法
//1).求余之后就是当前层所剩余的血
//2).然后再和平均血量做比就是比值了
float layerRatio = hp % _layerHpValue / _layerHpValue;
//取得当前血条在三个真实血条中的索引值
int curIndex = _listQueue.Peek();
//缩放动画
_muHp[curIndex].PlayTween(layerRatio);
//通过与last的值比较,来判断这层血是不是减完了
if (curLayer != _lastLayer)
{
if (_layerCount == 0)
{
return;
}
//计数减一
_layerCount -= 1;
//取出这个index
int downIndex = _listQueue.Dequeue();
//本层血结束了,如果剩余血层的数量在3之上,则重新排下深度
if (_ulayerNum > 3 && _layerCount >= 3)
{
AdjustDepth(downIndex);
//并把耗完的这层血的index放到队列里
_listQueue.Enqueue(downIndex);
}
else//不足3层血了,直接缩小为0即可
{
_muHp[curIndex].PlayTween(0);
}
//记录下当前层
_lastLayer = curLayer;
}
}
/// <summary>
/// 调整深度
/// 重点说明:每消耗完一层血,则运行此方法。方法的主要功能就是遍历三个血条对象
/// 1.每个血条对象深度加2,如果有血条深度大于最高depth 16,则把它下降到最低depth 12
/// 并放大的正常大小
/// 2.降到底部血条的sprite图片在图片数组中的index减3,如果index小于0,则再加3.即图片显示循环了一道.
/// 然后再改变血条sprite的图片
/// </summary>
void AdjustDepth(int downIndex)
{
for (int i = 0; i < _muHp.Count; i++)
{
_muHp[i].HpDepth += 2;
if (_muHp[i].HpDepth > 16)
{
_muHp[i].HpDepth = 12;
//放大到正常长度
_muHp[i].SetScale(Vector3.one);
_muHp[i].HpPicIndex -= 3;
if (_muHp[i].HpPicIndex < 0)
{
_muHp[i].HpPicIndex += 3;
}
_muHp[i].SetShowPic();
}
}
}
#endregion
}
结束:
以上为多层血条的具体实现,细看一下还是有很多要点要注意,而且自我感觉写的还是有点小复杂。在此贴个帖子,希望大家多提提意见,我做做改进。
希望不要直接复制上面的两个脚本使用,因为绝对会报错。贴两个脚本主要是方便讲解,让大家看看实现过程,原理大致就是这样。有意见,有改进的地方,多提提哈。谢谢了。