当前位置: 首页 > article >正文

血量更新逻辑的实现

来实现一下减血的逻辑

首先我们来看一下,我们再HealthBar当中的填充,填充的时候,我们会调用FillAmount这个变量,

学习调用组件,我们可以选择右上角的问号,跳转到代码手册当中,快速了解这些功能如何使用

我们要调用的FillAmount,这个数值是从0~1,中间有很多的小数,所以其实他就是一个百分比的意思,我们要记录这个值就是当前的血量除以最大血量,来获得的百分比;就可以实现让这个滑动条跟随我们的人物的血量来走了

想要控制image里面的变量,先要获得这些imgae组件的使用,使用我们在Player Stat Bar上面来创建一个相同名字的代码,用来控制所有跟人物有关的血量,能量相关的所有的内容。我们来创建这个代码,

在Script文件夹当中,创建一个新的文件夹UI,在UI中创建c#脚本PlayerStatBar(人物属性数值UI控制)

加载好之后,我们把这个代码添加到PlayerStatBar物体身上

打开代码,进行编写

如果我们想使用UI相关的组件,比如说image,button等等,

我们要先调用这个命名空间Using UnityEngine.UI,

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

然后我们就可以创建和图片有关的内容了,我们来创建一下这些image类型的变量

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

public class PlayStatBar : MonoBehaviour
{
    public Image healthImage;//绿色血条
    public Image healthDelayImage;//红色有延迟的血条
    public Image powerImage;//能量条
}

保存代码,返回unity

我们先来添加一下这些变量,拖拽给这些变量进行赋值

目前我们就获得了Image的这些组件,然后我们就可以调用他的Image的Fill Amount了

打开刚才代码继续编写

创建一个函数OnHealthChange来实现血量变化的时候,我们要执行的内容,我们希望能够传递进来这个百分比,然后把这个部分给到我们的healthImage,就能够实现实时同步我们的血量;我们调用Fill Amount,将他和我们的persentage连接在一起就可以了

 public void OnHealthChange(float persentage)
 {
     healthImage.fillAmount = persentage;
 }

我们调用这个函数,让我们人物的数值能够传递进来

先返回unity来看一下这个逻辑,在之前我们设置的血量减少的逻辑,我们可以找到Player的Character代码,用于计算我们的伤害,所以在这里面有我们的maxHealth和current Health,所以我们希望通过Player当前的这个Character,把我们现在所有的数值传递出去,由PlayerStatBar来接收这个数据

(之后我们还会做Power,Power可能也放在我们的Character当中来记录这个基本的数值和属性)

我们希望这些东西都能从这里传出去,直接通过Character把所有的东西都传过去

我们如何跨物体,跨场景来传递一些数值;

我们可以添加一些引用,我们在Enemy代码当中也调用过我们的PhysicsCheck,Player代码当中也调用过我们的PhysicsCheck,这样跨代码操作是可以的

不过我们跨物体和场景,就可能会出现一些问题;比如说丢失了引用,场景切换的时候就没有办法找到这些物体了,我们要来学习一个新的方法。

在整个过程当中,我们使用ScriptableObject来接收所有的事件的方法;ScriptableObjectt是一个可以持久化储存在项目当中的一个资产性的文件类型(例如上次添加的ColorPalette插件,他其实就是一个ScriptableObject,我们的inputSystem下的inputControl,也是一个资产性文件,我们可以找到这个资产性文件对应的所有的子类,每一个都是一个单独的ScriptableObject,所以它可以持久性的存储在我们的项目当中,是一个.asset的文件)

可以在下方看到他是一个.asset文件,所以他是一个资产

资产就会一直在我们的项目当中,甚至我们打包成真实的游戏之后,他也会存在在我们的项目当中

这样的话,他就不会丢失了,而且跨场景也可以使用

接下来我们来学习如何写这个事件的方法,我们可以通过这个事件远程传递一个参数过去,(有点类似于我们Character当中的事件,只不过那样的事件是固定在我们代码当中的,没有办法传递到别的地方)

我们在Script文件夹当中创建一个新的文件夹ScriptObject,在此文件夹当中创建c#代码,(命名的时候注意,这一次我们都使用ScriptableObject去做Event事件的方式,所以这些命名的最后我们都加上event,方便我们来识别这是一个什么东西;我们传递的数据类型就放在前面;ScriptableObject的缩写就是SO文件,在最后加上大写的SO)命名为CharacterEventSO

以这样的方式来确定是一个ScriptableObject的事件文件

双击打开代码

这个代码中我们要继承ScriptableObject,代码中原来的内容都删掉

创建了ScriptableObject,他就不能挂载在我们的物体上成为一个组件了,不过他会生成一个资产文件

如何来创建资产文件,我们要在上边添加一个描述CreateAssetMenu

显示了对应的方法

CreateAssetMenu后我们要添加的就是它的名字menuName(在菜单当中显示名字),要创建的这个东西的文件名,打上引号,我们可以创建这个对应的路径,一会写完之后,就可以在Project窗口来创建这个文件了

路径就叫Event,叫做当前的名字CharacterEventSO

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

[CreateAssetMenu(menuName="Event/CharacterEventSO")]
public class CharacterEventSO : ScriptableObject
{
   
}

保存代码,返回unity

我们做了刚才的CreateAssetMenu描述,我们就可以在Project窗口“ + ”,查看到最上方出现Event,里面有CharacterEventSO;点击之后,我们就可以在ScriptableObject文件夹下创建一个SO的文件了

先把这个创建的删掉

返回代码,我们来写一下这里面的事件的方法和逻辑

在这里面,我们想调用的就是unity action一个事件的方式,快速的帮助我们进行订阅和启动

(订阅和启动,我们在Character当中也创建过,叫做UnityEvent,这个Event方法,我们在后边用Invoke来启动它,如何谁调用谁订阅了它的话,就会都被启动;但是我们之前订阅的方法是把它添加到我们的菜单当中)

当前我们要完全在代码当中进行订阅,而不是在inspector窗口当中来调用

接下来写代码

首先要调用这个事件,我们先来调用这个命名空间

然后我们来创建一下,这次我们使用的方法,就是我们的UnityAction的方法,这个UnityAction跟我们c#中的Action非常的像,有一些细微的差别,主要在于里面所引用的参数变量的数量,当前我们只想传递一个内容,就是Character的代码

将Character传递进去,就可以接收到最大血量,当前血量,能量值;给这个事件起名OnEventRaised(在所有事件中,定义的名字都要叫做这个事件被启动,被调用)

当这个事件被调用的时候,所以我们订阅的时候就订阅这个事件,

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

[CreateAssetMenu(menuName="Event/CharacterEventSO")]
public class CharacterEventSO : ScriptableObject
{
    public UnityAction<Character> OnEventRaised;

    
}

创建一个函数RaiseEvent来调用启动这个事件,然后要传进去的是谁想启动这个事件就把自己的Character代码传进去;然后在内部执行的就是把所以订阅到这个事件的内容(先确定是不是有打问号“?”,然后启动这个事件invoke,把对应的参数变量传进去)

这样我们就写好了一个事件订阅的方式了

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

[CreateAssetMenu(menuName="Event/CharacterEventSO")]
public class CharacterEventSO : ScriptableObject
{
    public UnityAction<Character> OnEventRaised;

    public void RaiseEvent(Character character)
    {
        OnEventRaised?.Invoke(character);
    }
}

当前我们可能并不理解,为什么我要把事件的订阅和事件的调用都写在一起,那我们一起来使用一下它,就会更明白了。

保存代码,返回unity

我们在Assets文件夹下创建一个新的文件夹Events,然后在这个文件夹下,我们来添加我们的Events,我们创建一下刚才我们的SO文件,命名为CharacterEventSO

接下来,我们如何订阅,怎么传递;我希望在我的Player身上的Character代码脚本当中,只要我的血量变化,我就调用一下,把它传过去

我们打开Character代码

我们先在这个Character当中来创建一个这个Unity的Invent,我们把它放到上面

我们要传递就是我们的Character,跟下面一样,起名为OnHealthChange

保存代码,返回unity

在Player的inspector窗口中,我们用unityEvent方式来进行广播(就是我们血量变更的时候),我们要把我们刚才创建的SO文件拖拽过来,选择RunTime,然后我们就可以访问我们代码当中刚才写的函数方法RaiseEvent了,就是启动了(这个是呼叫,呼叫谁订阅了,就来接收一下人物血量的变化)它把这个数据广播出去,接下来就轮到我们PlayerStatBar来进行监听

在这里面,我们不希望我们的Player单独的去调用这个监听

我们希望有一个总体的管理类型的代码,去管理所有跟UI相关的内容

在Hierarchy窗口创建一个新的Game Object,命名为UI Manager

再创建一个分栏来归类我们的$manager类型,调整一下位置

再Project的UI的文件夹下,我们创建UIManger代码(管理所有UI的逻辑)

加载好之后,在Hierarchy窗口挂载这个脚本

挂载好之后,打开代码,进行编写

在上面做一个分类“监听事件”

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

public class UIManager : MonoBehaviour
{
    [Header("监听事件")]
    public CharacterEventSO healthEvent;//创建事件
}

创建好了这个事件,保存代码,回到unity,添加一下

拖拽过来

这样我们就监听了这个事件,通过ScriptableObject作为一个中间件,来进行传递一些数据

再次留意一下,我们在Player的Character当中创建了一个新的Event事件,每次血量变化的时候,我们启动它,把这个数据传递进去,然后让UIManager在这边接收,接收之后,就可以执行相关内容了

我们还需要来注册一下这个事件

打开UIManger代码,在OnEnable中注册一下事件,取消注册在OnDisable(这是一个固定的写法)

订阅的方法是要订阅OnEventRaised这个名字,

我们可以同时有多个代码订阅同一个事件,这样在执行的时候,这些所有代码都会同时响应,我们用到的订阅符号就是“+=”,想把上面函数加到这个定位里边,在后面写上名字,起名为OnHealthEvent;目前我们还没写OnHealthEvent这个函数方法

在注销的时候就要用“-=”,将现在的函数从这个事件当中取消

public class UIManager : MonoBehaviour
{
    [Header("监听事件")]
    public CharacterEventSO healthEvent;//创建事件

    private void OnEnable()
    {
        healthEvent.OnEventRaised += OnHealthEvent;
    }

    private void OnDisable()
    {
        healthEvent.OnEventRaised -= OnHealthEvent;
    }
}

让编译器自动帮我们生成OnHealthEvent这个函数方法

在这个函数当中设置百分比

public class UIManager : MonoBehaviour
{
    [Header("监听事件")]
    public CharacterEventSO healthEvent;//创建事件

    private void OnEnable()
    {
        healthEvent.OnEventRaised += OnHealthEvent;
    }


    private void OnDisable()
    {
        healthEvent.OnEventRaised -= OnHealthEvent;
    }

    private void OnHealthEvent(Character character)
    {
        var persentage = character.currentHealth / character.maxHealth;
    }
}

我们拿到了这个百分比,接下来要做的就是把这个百分比传递到我们的PlayerStatBar当中,调用这个的函数方法,把数值传递进去

在上面获得我们的PlayerStatBar的这个物体,一会我们返回unity进行赋值

public class UIManager : MonoBehaviour
{
    public PlayStatBar playerStatBar;

    [Header("监听事件")]
    public CharacterEventSO healthEvent;//创建事件

    private void OnEnable()
    {
        healthEvent.OnEventRaised += OnHealthEvent;
    }


    private void OnDisable()
    {
        healthEvent.OnEventRaised -= OnHealthEvent;
    }

    private void OnHealthEvent(Character character)
    {
        var persentage = character.currentHealth / character.maxHealth;
    }
}

然后我们在OnHealthEvent当中就可以执行了,直接把persentage传递进去,这样就搞定了

 private void OnHealthEvent(Character character)
 {
     var persentage = character.currentHealth / character.maxHealth;
     playerStatBar.OnHealthChange(persentage);//接收Health的百分比变化
 }

然后在PlayerStatBar当中就会有数值的变化和填充了

返回unity,给这些变量赋值

首先我们的UIManager现在没有PlayerStatBar,我们可以打开我们的MainCanvas,把PlayerStatBar拖拽进来

再返回到Character当中,在Character当中我们创建了OnHealthChange这个事件,但是我们并没有传递任何的数据进来,所以我们在受伤的时候调用这个事件方法,血量产生了变化,这个时候我们要传递这个数值进去;无论受伤了还是最后死亡了血量变成0了,都要最后传递一下数据;所以我们在整体的最下方添加这个函数方法;传递的类型就是Character类型,就是把自己传递进去;这样只要我们受伤了之后,我们的血量就会传递进去,然后每一次都会执行

public void TakeDamage(Attack attacker)//接收伤害,把Attack值传递进来
{
    if (invulnerable)
        return;
    //Debug.Log(attacker.damage);
    if(currentHealth -attacker .damage > 0)
    {
        currentHealth -= attacker.damage;
        TriggerInvulnerable();
        //执行受伤
        OnTakeDamage?.Invoke(attacker.transform);
    }
    else
    {
        currentHealth = 0;
        //触发死亡
        OnDie?.Invoke();
    }

    OnHealthChange?.Invoke(this);
}

那么在游戏的一开始Start这个位置,我也希望调用一下这个,希望获得新游戏的时候,血量自动填满

 private void Start()
 {
     currentHealth = maxHealth;
     OnHealthChange?.Invoke(this);
 }

保存代码,返回unity,测试一下

可以看到,当前我们的血量有减少了,这样我们就成功实现了这个血条根据当然人物的血量来进行变化了

接下来我们来实现后面的红色渐变的效果

打开PlayerStatbar;涉及到渐变,就会有跟随,让我们的HealthDelayImage跟HealthImage的fill Amount保持一致;所以我们放到Update当中来进行执行,检测当前HealthDelayImage跟HealthImage的fill Amount是否保持一致

如果大于,减去时间修正,这样就可以缓慢移动到同一位置;(也可以在后边乘以一个变量参数,来调整它的跟随速度)目前减去时间修正就可以了;

public class PlayStatBar : MonoBehaviour
{
    public Image healthImage;//绿色血条
    public Image healthDelayImage;//红色有延迟的血条
    public Image powerImage;//能量条

    private void Update()
    {
        if(healthDelayImage.fillAmount > healthImage.fillAmount)
        {
            healthDelayImage.fillAmount -= Time.deltaTime;
        }
    }


    //<summary>
    //    接收Health的变更百分比
    //</summary>
    //<param name= "persentage" >百分比:Current/Max<param>
    public void OnHealthChange(float persentage)
    {
        healthImage.fillAmount = persentage;
    }
}

保存代码,返回unity 

首先我们先将红色填满,然后调整一下野猪的伤害值,看的更直观一些,看到红色的渐变跟随

创建了Script ableObject,我们创建了什么名字的事件,就代表我们要传递什么参数进去;接下来创建一个Action的方法,这个事件里边可以被任何一个代码去订阅,只要订阅了,那么接下来我们广播的时候,所以订阅的函数方法都会执行对应的订阅事件;然后我们在Character当中,去添加了这个Event,通过我们在面板上把这个事件广播出去,然后由我们的UIManager来进行事件的监听,监听的时候,一旦他被invoke了之后,就会执行对应的方法,然后我们修改了我们的fill Amount


http://www.kler.cn/news/367836.html

相关文章:

  • P11229 [CSP-J 2024] 小木棍(民间数据)
  • android——渐变色
  • 华为OD机试 - 芯片资源占用(Java 2024 E卷 200分)
  • S-Function
  • echarts:导入excel生成桑葚图
  • 【linux故障处理】【Failed to restart nginx.service: Unit not found.】
  • Windows AD 域的深度解析 第一篇:AD 域原理与多系统联动
  • 考研要求掌握的C语言程度(堆排序)1
  • HBase2.4.17 修改znode后master初始化失败
  • Flutter中使用Cookies
  • 动态库和静态库
  • 第五十三章 安全元素的详细信息 - Signature 详情
  • MySQL8.0.40编译安装
  • Ajax:请求 响应
  • HarmonyOS ArkTS与C++数据类型转换
  • 【前端】实操tips集合
  • 猫头虎 分享:MySQL 中 TEXT 与 LONGTEXT 数据类型详解与使用场景分析
  • ORACLE 11G WINDOWS上面搭建DG,路径对应不起作用
  • Matlab学习03-符号的替换及运算(接上一篇)
  • Python记录-字典
  • 深入解析 MySQL 数据库:防止脏读
  • 获取 Excel 文件中的所有工作表名称,可以通过 OleDbConnection 获取表架构
  • Java 中的正则表达式详解
  • qt EventFilter用途详解
  • 第 24 章 - Elasticsearch 索引生命周期管理
  • k8s知识点总结