欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Unity UGUI学习系列(四) ------ 多层血条实现

程序员文章站 2022-03-26 23:19:47
...

本系列文章是学习siki学院UGUI整体解决方案-案例篇笔记
GitHub地址:https://github.com/BlueMonk1107/UGUISolution

本文实现的是多层血条,效果如下 :

Unity UGUI学习系列(四) ------ 多层血条实现

不过这里血条是image,update每帧跟随目标,个人觉得用SpriteRender实现好像更好一点,不过也当学习记录了

一.血条预制体

Unity UGUI学习系列(四) ------ 多层血条实现
结构

LifeBar上添加LifeBar脚本,CurrentBar / NextBar / AdditionBar 上添加LifeBarItem脚本, AdditionBar出现在这里的目的是为了加减血量渐隐过渡动画

二.脚本解析

Controller脚本 : 实例化LifeBar,按键响应

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Controller : MonoBehaviour
{
    private LifeBar _bar;
    // Start is called before the first frame update
    void Start()
    {
        Canvas canvas = FindObjectOfType<Canvas>();

        if (canvas == null)
        {
            Debug.LogError("场景中没有Canvas组件");
            return;
        }
        SpwanLifeBar(canvas);
    }
    //实例化血条
    private void SpwanLifeBar(Canvas canvas)
    {
        GameObject prefab = Resources.Load<GameObject>("LifeBar");
        _bar = Instantiate(prefab, canvas.transform).AddComponent<LifeBar>();
        List<LifeBarData> data = new List<LifeBarData>();
        data.Add(new LifeBarData(null,Color.green));
        data.Add(new LifeBarData(null, Color.red));
        data.Add(new LifeBarData(null, Color.yellow));
        _bar.Init(transform,350, data);
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKey(KeyCode.A))
        {
            Move(Vector3.right);
        }

        if (Input.GetKey(KeyCode.D))
        {
            Move(Vector3.left);
        }

        if (Input.GetKey(KeyCode.S))
        {
            Move(Vector3.down);
        }

        if (Input.GetKey(KeyCode.W))
        {
            Move(Vector3.up);
        }

        if (Input.GetMouseButtonDown(0))
        {
            _bar.ChangeLife(-50);
        }

        if (Input.GetMouseButtonDown(1))
        {
            _bar.ChangeLife(50);
        }
    }

    private void Move(Vector3 direction)
    {
        transform.Translate(direction * Time.deltaTime * 5);
    }
}

LifeBar脚本 : 跟随目标物体,根据索引与data控制LifeBarItem加减血量以及变化

using System.Collections.Generic;
using DG.Tweening;
using UnityEngine;

public class LifeBar : MonoBehaviour
{
    private Transform _target;
    private Vector3 _offset;
    private List<LifeBarData> _data;
    private LifeBarItem _nextBar;
    private LifeBarItem _currentBar;
    private float _unitLifeScale;
    private int _currentIndex;

    public void Init(Transform target, int lifeMax, List<LifeBarData> data)
    {
        _currentIndex = 0;
        _target = target;
        _offset = GetOffset(target);
        _data = data;
        _nextBar = transform.Find("NextBar").gameObject.AddComponent<LifeBarItem>();
        _currentBar = transform.Find("CurrentBar").gameObject.AddComponent<LifeBarItem>();
        _nextBar.Init();
        _currentBar.Init();

        RectTransform rect = GetComponent<RectTransform>();
        _unitLifeScale = rect.rect.width * data.Count / lifeMax;

        SetBarData(_currentIndex,data);
    }

    private Vector3 GetOffset(Transform target)
    {
        Renderer renderer = target.GetComponent<Renderer>();
        if (renderer == null)
            return Vector3.zero;
        return Vector3.up * renderer.bounds.max.y;
    }

    public void Update()
    {
        if (_target == null)
            return;
        //跟随目标物体
        transform.position = Camera.main.WorldToScreenPoint(_target.position + _offset);
    }
    //加减血量
    public void ChangeLife(float value)
    {
        //得到当前加减血量后image还需要偏移的量
        float width = _currentBar.ChangeLife(value * _unitLifeScale);
        //减少血量
        if (width < 0 && ChangeIndex(1))
        {
            //交换
            Exchange();
            //使_currentBar成为最后一个子物体以达到遮挡的目的
            _currentBar.transform.SetAsLastSibling();
            //设置_nextBar宽度为满值
            _nextBar.ResetToWidth();
            //设置_currentBar与_nextBar的data,以修改颜色与sprite
            SetBarData(_currentIndex,_data);
            //继续减少剩下的width
            ChangeLife(width / _unitLifeScale);
        }
        else if (width > 0 && ChangeIndex(-1))
        {
            Exchange();
            _currentBar.transform.SetAsLastSibling();
            _currentBar.ResetToZero();
            SetBarData(_currentIndex, _data);
            ChangeLife(width/ _unitLifeScale);
        }
    }

    // -1 代表加血 1代表减血
    private bool ChangeIndex(int symbol)
    {
        int index = _currentIndex + symbol;
        if(_data == null)
            return false;
        if (index >= 0 && index < _data.Count)
        {
            _currentIndex = index;
            return true;
        }

        return false;
    }

    private void Exchange()
    {
        var temp = _nextBar;
        _nextBar = _currentBar;
        _currentBar = temp;
    }
    //设置索引index对应的血条
    private void SetBarData(int index, List<LifeBarData> data)
    {
        if (index < 0 || index >= data.Count)
            return;

        _currentBar.SetData(data[index]);

        if (index + 1 >= data.Count)
        {
            _nextBar.SetData(new LifeBarData(null,Color.white));
        }
        else
        {
            _nextBar.SetData(data[index + 1]);
        }
    }
}
//血条数据,包含Sprite和颜色
public struct LifeBarData
{
    public Sprite BarSprite;
    public Color BarMainColor;

    public LifeBarData(Sprite sprite, Color mainColor)
    {
        BarSprite = sprite;
        BarMainColor = mainColor;
    }
}

LifeBarItem脚本 : 根据索引和data控制image具体宽度,以及加减血量的动画

using System.Collections;
using System.Collections.Generic;
using DG.Tweening;
using UnityEngine;
using UnityEngine.UI;

public class LifeBarItem : MonoBehaviour
{
    private RectTransform _rect;
    public RectTransform Rect
    {
        get
        {
            if (_rect == null)
                _rect = GetComponent<RectTransform>();
            return _rect;
        }
    }
    private Image _image;

    public Image Image
    {
        get
        {
            if (_image == null)
                _image = GetComponent<Image>();
            return _image;
        }
    }
    private LifeBarItem _child;

    private float _defaultWidth;

    public void Init()
    {
        if (transform.Find("AdditionBar") != null)
        //添加LifeBarItem脚本保证两者保持一致
            _child = transform.Find("AdditionBar").gameObject.AddComponent<LifeBarItem>();

        _defaultWidth = Rect.rect.width;
    }

    public void SetData(LifeBarData data)
    {
        Image.color = data.BarMainColor;
        if (data.BarSprite != null)
            Image.sprite = data.BarSprite;

        if(_child != null)
            _child.SetData(data);
    }
    //当前血条加减血量值value
    public float ChangeLife(float value)
    {
        //用子物体做渐隐动画
        if (_child != null)
        {
            _child.DOKill();
            _child.Image.color = Image.color;
            _child.Rect.sizeDelta = Rect.sizeDelta;
            _child.Image.DOFade(0, 0.5f).OnComplete(()=>_child.ChangeLife(value));
        }
        //设置Rect的size
        Rect.sizeDelta += Vector2.right*value;

        return GetOutOfRange();
    }
    //返回加减血量后的偏移量
    private float GetOutOfRange()
    {
        float offset = 0;

        if (Rect.rect.width < 0)
        {
            offset = Rect.rect.width;
            ResetToZero();
        }
        else if(Rect.rect.width > _defaultWidth)
        {
            offset = Rect.rect.width - _defaultWidth;
            ResetToWidth();
        }

        return offset;
    }

    public void ResetToZero()
    {
        Rect.sizeDelta = Vector2.zero;
    }

    public void ResetToWidth()
    {
        Rect.sizeDelta = Vector2.right*_defaultWidth;
    }
}