【Unity2D好项目分享】用全是好活制作横版卷轴射击游戏③制作血条和能量条UI以及生命值系统和能量值系统

学习目标:

 上期我们学习怎么进行后处理渲染以及制作玩家子弹,但我们添加了碰撞体,为子弹设置了伤害,但却没有制作我们的血条和能量条以及属于玩家和敌人的生命值系统和能量值系统。这一期就讲一下怎么实现这两部分功能


制作生命值系统:

首先我们写个Character总类让Player和Enemy继承它。

先简单写写Character受到伤害的TakeDmaage和死亡Die和一些属性,以及一些协程包括在没受到伤害的时候持续回血

using System.Collections;   
using UnityEngine;

public class Character : MonoBehaviour
{

    [Header("--- HEALTH ---")]
    [SerializeField] protected float maxHealth;
    protected float health;
    [SerializeField] StateBar onHeadHealthBar;
    [SerializeField] bool showOnHealthBar = true;

    protected virtual void OnEnable()
    {
        health = maxHealth;

        if (showOnHealthBar)
        {
            ShowOnHealthBar();
        }
        else
        {
            HideOnHealthBar();
        }
    }

    public void ShowOnHealthBar()
    {
        onHeadHealthBar.gameObject.SetActive(true);
        onHeadHealthBar.Initialize(health, maxHealth);
    }
    public void HideOnHealthBar()
    {
        onHeadHealthBar.gameObject.SetActive(false);
    }
    public virtual void TakeDamage(float damage)
    {
        health -= damage;

        if (showOnHealthBar && gameObject.activeSelf)
        {
            onHeadHealthBar.UpdateStates(health, maxHealth);
        }

        if(health <= 0)
        {
            Die();
        }
    }
    public virtual void Die()
    {
        health = 0;
        gameObject.SetActive(false);
    }

    public virtual void RestoreHealth(float value)
    {
        if (health == maxHealth)
            return;

        health = Mathf.Clamp(health + value, 0, maxHealth);

        if (showOnHealthBar)
        {
            onHeadHealthBar.UpdateStates(health, maxHealth);
        }
    }

    protected IEnumerator HealthPercentageCoroutine(WaitForSeconds waitTime,float percent)
    {
        while(health < maxHealth)
        {
            yield return waitTime;

            RestoreHealth(percent * maxHealth);
        }
    }

    protected IEnumerator DamageOverTime(WaitForSeconds waitTime, float percent)
    {
        while (health > 0)
        {
            yield return waitTime;
             
            RestoreHealth(percent * maxHealth);
        }
    }
}

接着我们让Player和ENEMY脚本继承它

public class Player : Character
{
    [SerializeField] StateBar_HUD stateBar_HUD;

    [SerializeField] bool genearatedHealth = true;
    [SerializeField] float healthGenerateTime;
    [SerializeField,Range(0f,1f)] float healthRegeneatePercent;
    
    [Header("--- COROUTINE ---")]

    Coroutine moveCoroutine;
    Coroutine healthRegenerateCoroutine;

    WaitForSeconds waitForFireInterval;
    WaitForSeconds waitHealthGeneratedTime;
}

 void Start()
    {
        rigi2D.gravityScale = 0f;

        waitForFireInterval = new WaitForSeconds(fireInterval);
        waitHealthGeneratedTime = new WaitForSeconds(healthGenerateTime);

        stateBar_HUD.Initialize(health, maxHealth);

        input.EnableGamePlay(); //激活动作表
    }

public override void TakeDamage(float damage)
    {
        base.TakeDamage(damage);
        stateBar_HUD.UpdateStates(health, maxHealth);

        if (gameObject.activeSelf)
        {
            if(healthRegenerateCoroutine != null)
            {
                StopCoroutine(healthRegenerateCoroutine);
            }

            healthRegenerateCoroutine = StartCoroutine(HealthPercentageCoroutine(waitHealthGeneratedTime, healthRegeneatePercent));

        }
    }

    public override void RestoreHealth(float value)
    {
        base.RestoreHealth(value);
        stateBar_HUD.UpdateStates(health, maxHealth);
    }
    public override void Die()
    {
        stateBar_HUD.UpdateStates(0f, maxHealth);
        base.Die();
    }

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

public class Enemy : Character
{
    [SerializeField] int deathEnergyBouns = 3;

    public override void Die()
    {
        base.Die();
    }
}

因此我们可以在Projectile的OnCollisionEnter2D来让继承Character的脚本受到伤害

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

public class Projectile : MonoBehaviour
{
    [SerializeField] float moveSpeed = 10f;

    [SerializeField] protected Vector2 moveDirection;

    [SerializeField] float damage;

    [SerializeField] AudioData[] hitSFX;

    [SerializeField] GameObject hitVFX;
    protected GameObject target;
    protected virtual void OnEnable()
    {
        StartCoroutine(MoveDirectly());
    }

    IEnumerator MoveDirectly()
    {
        while (gameObject.activeSelf)
        {
            transform.Translate(moveDirection * moveSpeed * Time.deltaTime);

            yield return null;
        }
    }

    protected virtual void OnCollisionEnter2D(Collision2D collision)
    {
        if(collision.gameObject.TryGetComponent<Character>(out Character character)) //判断碰撞的游戏对象是否为Character类,所消耗的性能更少
        {
            character.TakeDamage(damage);

            var contactPoint = collision.GetContact(0); //得到collision的第一个接触点
            PoolManager.Release(hitVFX, contactPoint.point, Quaternion.LookRotation(contactPoint.normal)); //contactPoint.normal法线方向,某个顶点在3D空间中的朝向
            gameObject.SetActive(false);
        }
    }
}

这样我们可以为Enemy和Player设置生命值了。

制作能量值:

我们先创建一个PlayerEnergy挂载给Player。

因为能量条是不同场景中不会销毁的,因此使用单例模式。

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

public class Singleton<T> : MonoBehaviour where T : Component //约定泛型类型必须是Component
{
    public static T Instance { get; private set; } //声明一个只读类型的单例

    protected virtual void Awake()
    {
        Instance = this as T;
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerEnergy : Singleton<PlayerEnergy>
{
    [SerializeField] EnergyBar energyBar;
    public const int MAX = 100;
    public const int PERCENT = 1;
    int energy;

    void Start()
    {
        energyBar.Initialize(energy, MAX);
        Obtain(75);
    }

    public void Obtain(int value) //获得能量
    {
        if (energy == MAX) return;

        energy = Mathf.Clamp(energy + value, 0, MAX);
        energyBar.UpdateStates(energy, MAX); //更新能量条
    }

    public void Use(int value)
    {
        energy -= value;
        energyBar.UpdateStates(energy, MAX);
    }

    public bool IsEnough(int value) => energy >= value; //判断能量是否能使用
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerProjectile : Projectile
{
    TrailRenderer trail;

    private void Awake()
    {
        trail = GetComponentInChildren<TrailRenderer>();
        if (moveDirection != Vector2.right)
        {
            transform.GetChild(0).rotation = Quaternion.FromToRotation(Vector2.right, moveDirection);
        }
    }

    private void OnDisable()
    {
        trail.Clear();
    }

    protected override void OnCollisionEnter2D(Collision2D collision)
    {
        base.OnCollisionEnter2D(collision);
        PlayerEnergy.Instance.Obtain(PlayerEnergy.PERCENT);
    }
}

首先要在攻击到敌人的时候增加能量。

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

public class Enemy : Character
{
    [SerializeField] int deathEnergyBouns = 3;

    public override void Die()
    {
        PlayerEnergy.Instance.Obtain(deathEnergyBouns);
        EnemyManager.Instance.RemoveList(gameObject);
        base.Die();
    }
}

制作生命条和能量条:

先创建一个UI作为所以ui的父对象,然后将创建一个HUD Player Health Bar的Canvas并作为子对象然后设置好它的参数。 

 在进行接下来的操作之前,让我们创建好他们的Material材质。

 接着我们添加一个Image背景,依次添加以下组件。

 

 

 EnergyBar也类似可以直接复制粘贴HealthBar,只需要稍微更改一下它们的位置即可

 

为了管理这些状态能量条,我们同样也需要一个总类StateBar

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class StateBar : MonoBehaviour
{
    [SerializeField] Image fillImageBack;
    [SerializeField] Image fillImageFront;
    [SerializeField] float fillSpeed = 0.1f;

    [SerializeField] bool delayFill = true;
    [SerializeField] float fillDelay = 0.5f;

    Canvas canvas;

    float previousFillAmount;

    float currentFillAmount;
    protected float targetFillAmount;

    float t;

    Coroutine bufferdFillingCoroutine;
    WaitForSeconds waitForDelayFill;

    private void Awake()
    {
        canvas = GetComponent<Canvas>();
        canvas.worldCamera = Camera.main;

        waitForDelayFill = new WaitForSeconds(fillDelay);
    }
    private void OnDisable()
    {
        StopAllCoroutines();
    }

    public virtual void Initialize(float currentValue,float maxValue)
    {
        currentFillAmount = currentValue / maxValue;
        targetFillAmount = currentFillAmount;
        fillImageBack.fillAmount = currentFillAmount;
        fillImageFront.fillAmount = currentFillAmount;
    }

    public void UpdateStates(float currentValue,float maxValue)
    {
        targetFillAmount = currentValue / maxValue;

        if(bufferdFillingCoroutine != null)
        {
            StopCoroutine(bufferdFillingCoroutine);
        }

        //如果状态值在减少的时候
        //让front的图片立即减少到目标值
        //back的图片则根据fillSpeed慢慢减少到目标值
        if(currentFillAmount > targetFillAmount)
        {
            fillImageFront.fillAmount = targetFillAmount;
            bufferdFillingCoroutine = StartCoroutine(BufferedFillingCoroutine(fillImageBack));

            return;
        }

        //如果状态值在增加的时候
        //让back的图片立即减少到目标值
        //front的图片则根据fillSpeed慢慢减少到目标值
        if (currentFillAmount < targetFillAmount)
        {
            fillImageBack.fillAmount = targetFillAmount;
            bufferdFillingCoroutine = StartCoroutine(BufferedFillingCoroutine(fillImageFront));
        }
    }

    protected virtual IEnumerator BufferedFillingCoroutine(Image image)
    {
        if (delayFill)
        {
            yield return waitForDelayFill;
        }

        t = 0f;
        previousFillAmount = currentFillAmount;

        while (t < 1f)
        {
            t += Time.deltaTime * fillSpeed;
            currentFillAmount = Mathf.Lerp(previousFillAmount, targetFillAmount, t);
            image.fillAmount = currentFillAmount;

            yield return null;
        }
    }
}

为HealthBar的Canvas添加一个脚本 

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

public class StateBar_HUD : StateBar
{
    [SerializeField] Text percentText;

    void SetPercent()
    {
        percentText.text = Mathf.RoundToInt(targetFillAmount * 100f).ToString();
    }

    public override void Initialize(float currentValue, float maxValue)
    {
        base.Initialize(currentValue, maxValue);
        SetPercent();
    }
    protected override IEnumerator BufferedFillingCoroutine(Image image)
    {
        SetPercent();
        return base.BufferedFillingCoroutine(image);
    }
}

再为我们的能量条

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

public class EnergyBar : StateBar_HUD
{
    
}

最后我们再制作一个玩家头顶的血量条。

为什么我们频繁使用前后两张照片呢?因为我们脚本设计的时候使用的让收到伤害的时候血条不是立刻减少到相应的值,而是调用协程让后面的图片先减少到相应的值,再将前面的血条缓慢减少到相应的值。

注意!它们前后两张的图片的材质并不相同。

学习产出:

玩家和敌人都正常掉血了。

Unity中使用Rigidbody2D制作2D横版跳跃游戏时,控制玩家的精准移动和跳跃主要涉及到对玩家角色的Rigidbody2D组件进行编程操作。以下是一个基础的控制逻辑,它包括了水平移动和跳跃的基本实现: 1. 水平移动:通过检测玩家的输入(例如,使用左右箭头键或A/D键),你可以获取一个水平方向的移动,并应用到Rigidbody2D的velocity属性上,以实现水平移动。例如: ```csharp void Update() { float move = Input.GetAxis("Horizontal"); // 获取水平轴上的输入 rb2d.velocity = new Vector2(move * speed, rb2d.velocity.y); // 设置水平速度 } ``` 2. 跳跃:为了实现跳跃,你需要检测跳跃键(例如空格键)的按下,并在件满足的情况下给角色施加向上的力。你还需要判断角色是否站在地面上,这通常涉及到碰撞检测。以下是跳跃的简单实现: ```csharp public float jumpForce = 700f; // 跳跃力 public Transform groundCheck; // 地面检测点的位置 public float checkRadius = 0.3f; // 检测半径 public LayerMask whatIsGround; // 什么层被认为是地面 bool isGrounded; // 角色是否在地面上 void Update() { // 检测是否在地面 isGrounded = Physics2D.OverlapCircle(groundCheck.position, checkRadius, whatIsGround); if (isGrounded && Input.GetButtonDown("Jump")) // 如果玩家按下跳跃键且在地面 { rb2d.AddForce(new Vector2(0f, jumpForce)); // 向上施加力 } } ``` 3. 跳跃缓冲:为了使跳跃更加真实,可以加入一个短暂的时间窗口,在这个窗口内,即使玩家松开跳跃键,角色仍然可以继续上升,这被称为跳跃缓冲。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值