一、效果展示

二、实现过程

2.1 准备工作

首先在场景中使用「Image」创建如下结构并命名为「LifeBar」。需要注意的是内部的「Image」都需要将锚点设置到最左侧,高度设置为自适应。在父元素上挂载同名脚本,将「LifeBar」制作为预制体。

之所以创建了「OuterBar」和「InnerBar」两个血条,是为了做出多层血条的效果。

然后在场景中随便创建一个敌人(需要有「MeshRenderer」),然后挂载一个控制脚本。我这里的脚本命名为「KeLiController」

2.2 动态创建血条

接下来需要在游戏运行时,将血条动态创建出来。在「KeLiController」中添加如下代码

private LifeBar _lifeBar;
void Start()
{Canvas canvas = FindObjectOfType<Canvas>();if (canvas == null){Debug.LogError("场景中没有Canvas控件");return;}SpawnLifeBar(canvas);
}private void SpawnLifeBar(Canvas canvas)
{GameObject lifeBar = Resources.Load<GameObject>("LifeBar");_lifeBar = Instantiate(lifeBar, canvas.transform).AddComponent<LifeBar>();// 初始化操作_lifeBar.Init(transform);
}

然后需要将创建出来的代码移动到敌人头顶上。我们知道模型的位置就是脚下中心点的位置,但是如何获取到模型的高度呢?其实模型上挂载的「Mesh Renderer」组件中就有相关的属性。我们可以在「LifeBar」进行初始化时获取到「Mesh Renderer」最高点的Y值作为偏移量,然后在Update中不断更新血条的位置(别忘了把世界坐标转换为屏幕坐标)

public class LifeBar : MonoBehaviour
{private Transform _target;private float _offsetY;public void Init(Transform target){_target = target;_offsetY = GetOffsetY(target);}/// <summary>/// 获取Renderer最高点的Y值/// </summary>/// <param name="target"></param>/// <returns></returns>private float GetOffsetY(Transform target){Renderer ren = target.GetComponentInChildren<Renderer>();if (ren == null)return 0;return ren.bounds.max.y;}private void Update(){if(_target == null)return;transform.position = Camera.main.WorldToScreenPoint(_target.position + Vector3.up*_offsetY);}
}

运行游戏可以看到,血条已经正确生成在了敌人头顶上。随便给敌人加点移动的控制逻辑,可以发现血条也会跟随角色移动。

2.3 初始化血条

血条创建出来后还需要对其进行初始化,包括设置血条的颜色、图片等。新建一个「LifeBarData」类用来对血条的数据进行封装。

public class LifeBarData
{public Sprite BarSprite;public Color BarColor;public LifeBarData(Sprite barSprite, Color barColor){BarSprite = barSprite;BarColor = barColor;}
}

再创建一个「LifeBarItem」类用来控制单个的血条。「InnerBar」、「OuterBar」和它们下面的「AdditionBar」都需要挂载这个脚本。在「LifeBarItem」类中定义出必要的字段并暴露出初始化API

public class LifeBarItem : MonoBehaviour
{private Image _img;private Image Img{get{if (_img == null)_img = GetComponent<Image>();return _img;}}private RectTransform _rect;private RectTransform Rect{get{if (_rect == null)_rect = GetComponent<RectTransform>();return _rect;}}private LifeBarItem _child;public void Init(){var additionBar = transform.Find("AdditionBar");if (additionBar != null)_child = additionBar.AddComponent<LifeBarItem>();}public void SetData(LifeBarData data){Img.color = data.BarColor;if (data.BarSprite != null){Img.sprite = data.BarSprite;}if (_child != null){_child.SetData(data);}}
}

这里「AdditionBar」之所以设置成与父元素相同,是为了制作扣血时的过渡特效。后面会进行说明。

接下来在「LifeBar」中持有「OuterBar」和「InnerBar」并对其进行初始化设置即可。另外,初始化所需的数据可以通过一个集合传入。集合有多少条数据,就代表有多少行血条。再传入一个整型参数表示总血量,就可以计算出单位血量占多少宽度。

// 血条数据
private List<LifeBarData> _data;
// 外层血条
private LifeBarItem _outerBar;
// 内层血条
private LifeBarItem _innerBar;
// 单位血量所占宽度
private float _unitLifeScale;
// 当前血条下标
private int _index;/// <summary>
/// 初始化
/// </summary>
/// <param name="target">目标物体</param>
/// <param name="lifeMax">最大血量</param>
/// <param name="data">血条数据</param>
public void Init(Transform target,int lifeMax,List<LifeBarData> data)
{_target = target;_offsetY = GetOffsetY(target);_data = data;_outerBar = transform.Find("OuterBar").AddComponent<LifeBarItem>();_innerBar = transform.Find("InnerBar").AddComponent<LifeBarItem>();_outerBar.Init();_innerBar.Init();_unitLifeScale = GetComponent<RectTransform>().rect.width * data.Count / lifeMax;SetBarData(_index, data);
}
/// <summary>
/// 设置内外血条数据
/// </summary>
/// <param name="index"></param>
/// <param name="data"></param>
private void SetBarData(int index, List<LifeBarData> data)
{if(index < 0 || index >= data.Count)return;_outerBar.SetData(data[index]);if (index + 1 >= data.Count){_innerBar.SetData(new LifeBarData(null,Color.white));}else{_innerBar.SetData(data[index+1]);}
}

最后在「KeLiController」中传入一组测试数据,运行看下效果

List<LifeBarData> data = new();
data.Add(new LifeBarData(null,Color.blue));
data.Add(new LifeBarData(null,Color.green));
data.Add(new LifeBarData(null,Color.yellow));
_lifeBar.Init(transform,350,data);

2.4 扣血逻辑

下面编写扣血逻辑。首先对于「AdditionBar」来说,可以直接使用DOTween做一个渐隐动画。对于外层血条,可以传入一个宽度的改变值。根据这个改变值调整血条的宽度。不过要考虑到如果加血/扣血超出了当前血条的范围,就需要把超出的值返回出去,以便处理后续血条的扣血逻辑。

private float _defaultWidth;
public void Init()
{  var additionBar = transform.Find("AdditionBar");  if (additionBar != null)  _child = additionBar.AddComponent<LifeBarItem>();  _defaultWidth = Rect.rect.width;
}
/// <summary>
/// 血量改变事件
/// </summary>
/// <param name="changeValue">改变量(宽度)</param>
/// <returns></returns>
public float ChangeLife(float changeValue)
{if (_child != null){// 清除未播放完的动画  _child.DOKill();  _child.Img.color = Img.color;  _child.Rect.sizeDelta = Rect.sizeDelta;  _child.Img.DOFade(0, 0.5f);}Rect.sizeDelta += changeValue * Vector2.right;return GetOutRange();
}
/// <summary>
/// 获取超出部分的宽度
/// </summary>
/// <returns></returns>
private float GetOutRange()
{float offset = 0;var rectWidth = Rect.rect.width;if (rectWidth < 0){offset = rectWidth;ResetToZero();}else if (rectWidth > _defaultWidth){offset = rectWidth - _defaultWidth;ResetToDefault();}return offset;
}public void ResetToZero()
{Rect.sizeDelta = Vector2.zero;
}public void ResetToDefault()
{Rect.sizeDelta = _defaultWidth * Vector2.right;
}

在「LifeBar」中接收到返回的超出值后,需要分情况对血条的层级、宽度等进行重新设置

public void ChangeLife(float changeValue)
{var extraWidth = _outerBar.ChangeLife(changeValue * _unitLifeScale);// 当前血条不够扣if (extraWidth < 0 && ChangeIndex(false)){// 交换前后血条的指针ExChangeBar();// 设置层级,使其显示在前面_outerBar.transform.SetAsLastSibling();// 内层血条恢复成默认大小_innerBar.ResetToDefault();SetBarData(_index,_data);ChangeLife(extraWidth/_unitLifeScale);}// 当前血条不够加else if (extraWidth > 0 && ChangeIndex(true)){// 交换前后血条的指针ExChangeBar();// 设置层级,使其显示在前面_outerBar.transform.SetAsLastSibling();// 外层血条设置为0_outerBar.ResetToZero();SetBarData(_index,_data);ChangeLife(extraWidth/_unitLifeScale);}
}
/// <summary>
/// 更改下标
/// </summary>
/// <param name="isAdd">是否是加血</param>
/// <returns></returns>
private bool ChangeIndex(bool isAdd)
{// 加血往前移,扣血往后移int newIndex = _index + (isAdd ? -1 : 1);if (newIndex >= 0 && newIndex < _data.Count){_index = newIndex;return true;}return false;
}private void ExChangeBar()
{(_outerBar, _innerBar) = (_innerBar, _outerBar);
}

至此,血条加血/扣血的主要逻辑就算完成了。在敌人的脚本上添加两个点击事件,左键为扣血,右键为加血。运行游戏看下效果

if (Input.GetMouseButtonDown(0))
{  _lifeBar.ChangeLife(-70);
}else if (Input.GetMouseButtonDown(1))
{  _lifeBar.ChangeLife(70);
}


源码下载

UGUI学习笔记(十二)自制血条控件相关推荐

  1. VC2008学习笔记(二)——插入WMP控件并创建类

    一.在资源视图加入窗体 二.新加入的窗体 三.插入Widows Media Player控件 右键点击"插入ActiveX 控件"--选择Windows Media Player, ...

  2. Python语言入门这一篇就够了-学习笔记(十二万字)

    Python语言入门这一篇就够了-学习笔记(十二万字) 友情提示:先关注收藏,再查看,12万字保姆级 Python语言从入门到精通教程. 文章目录 Python语言入门这一篇就够了-学习笔记(十二万字 ...

  3. 吴恩达《机器学习》学习笔记十二——机器学习系统

    吴恩达<机器学习>学习笔记十二--机器学习系统 一.设计机器学习系统的思想 1.快速实现+绘制学习曲线--寻找重点优化的方向 2.误差分析 3.数值估计 二.偏斜类问题(类别不均衡) 三. ...

  4. ROS学习笔记十二:使用roswtf

    ROS学习笔记十二:使用roswtf 在使用ROS过程中,roswtf工具可以为我们提供ROS系统是否正常工作的检查作用. 注意:在进行下列操作之前,请确保roscore没有运行. 检查ROS是否安装 ...

  5. Polyworks脚本开发学习笔记(十二)-输出和读取文本文件

    Polyworks脚本开发学习笔记(十二)-输出和读取文本文件 Polyworks作为一个测量工具,将测量的数据方便的导出到文本文件则是一项必须的功能.在DATA_FILE这个命令下提供了很多子命令用 ...

  6. OpenCV学习笔记(十二)——图像分割与提取

    在图像处理的过程中,经常需要从图像中将前景对象作为目标图像分割或者提取出来.例如,在视频监控中,观测到的是固定背景下的视频内容,而我们对背景本身并无兴趣,感兴趣的是背景中出现的车辆.行人或者其他对象. ...

  7. ET6.0服务器框架学习笔记(二、一条登录协议)

    ET6.0服务器框架学习笔记(二.一条登录协议) 上一篇主要记录ET6.0的服务器启动功能,本篇主要记录ET6.0完整的一条协议,从配置到生成协议数据,到从客户端发送给服务端,再发送回客户端的流程 文 ...

  8. LabView学习笔记(三):基本控件

    Labview学习笔记: LabView学习笔记(一):基础介绍 LabView学习笔记(二):滤波器实验 LabView学习笔记(三):基本控件 LabView学习笔记(四):动态数据类型 LabV ...

  9. IOS学习笔记(四)之UITextField和UITextView控件学习

    IOS学习笔记(四)之UITextField和UITextView控件学习(博客地址:http://blog.csdn.net/developer_jiangqq) Author:hmjiangqq ...

最新文章

  1. Martini 中的 Handler
  2. python电商项目源码_Python Django(WEB电商项目构建)
  3. 文巾解题 LCP 07. 传递信息
  4. 笔记本电脑如何强制关机_长按电源键强制关机会损害笔记本硬件吗?联想:不会但不建议...
  5. JVM性能调优监控工具总结
  6. C#和VB的关键字的对照表
  7. jQuery 一些操作
  8. django 分页(2) 使用类 页码显示
  9. android 延时摄影,当「延时摄影」和「镜头慢放」结合,手机视频从未有如此质感……#Android&iOS...
  10. 利用Md2all的自定义CSS,给Markdown一个漂亮的排版
  11. 计算机bios所以的英文翻译,bios界面翻译对照 主板BIOS界面全英文翻译介绍
  12. 删除shipyard
  13. java getmethods_java中Class.getMethods()和Class.getDeclaredMethods()方法的区别
  14. beeline执行sql语句_beeline执行sql脚本交易
  15. sql分组 会计分录_oracle\EBS\常用表\视图\会计分录\mtl_serial_numbers\总账
  16. qbo_listen编译问题
  17. html5情人节贺卡,情人节贺卡创意情话
  18. 信道容量 Channel capacity
  19. 《乞力马扎罗山的雪》——海明威
  20. display属性值:

热门文章

  1. 什么是云服务举例说明_云服务器有什么用?最好举例说明。
  2. 2011-6-13 周一 日志
  3. EasyUEFI 管理配置 Windows EFI 启动项
  4. 推特账户设置推文保护
  5. MySQL进阶篇:深入理解启动项、系统变量、字符集
  6. 小猪o2o生活通v2.82 全开源尊享版+多城市
  7. 统计专用计算机使用年限,『应用』电脑使用时间统计软件--ManicTime
  8. java虚拟机收集器_Java虚拟机(JVM)垃圾回收器G1收集器 - Break易站
  9. css div横屏超长滚动,CSS实现DIV超长截断,并显示...
  10. 2020-2021学年第二学期期末考试《税法》大作业