血量更新逻辑的实现

news/2024/10/27 18:53:47/

来实现一下减血的逻辑

首先我们来看一下,我们再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.ppmy.cn/news/1542403.html

相关文章

昇思25天学习打卡营第1天|快速入门

昇思25天学习打卡营第1天|快速入门 目录 昇思25天学习打卡营第1天|快速入门实操教程 一、MindSpore内容简介 主要特点&#xff1a; MindSpore的组成部分&#xff1a; 二、入门实操步骤 1. 安装必要的依赖包 2. 下载并处理数据集 3. 构建网络模型 4. 训练模型 5. 测试…

Nova-Admin:基于Vue3、Vite、TypeScript和NaiveUI的开源简洁灵活管理模板

嗨&#xff0c;大家好&#xff0c;我是小华同学&#xff0c;关注我们获得“最新、最全、最优质”开源项目和工作学习方法 Nova Admin是一个基于Vue3、Vite、TypeScript和NaiveUI的简洁灵活的管理模板。这个项目旨在为开发者提供一个现代化、易于定制的后台管理界面解决方案。无…

算法汇总整理篇——贪心与动态规划学习及框架思考

算法的知识储备 动态规划算法(重中之重) 如果某⼀问题有很多重叠⼦问题&#xff0c;使⽤动态规划是最有效的动规是由前⼀个状态推导出来的&#xff0c;⽽贪⼼是局部直接选最优的 1. 确定dp数组&#xff08;dp table&#xff09;以及下标的含义 2. 确定递推公式 3. dp数组如何初…

DiffusionDet: Diffusion Model for Object Detection—用于对象检测的扩散模型论文解析

DiffusionDet: Diffusion Model for Object Detection—用于对象检测的扩散模型论文解析 这是一篇发表在CVPR 2023的一篇论文&#xff0c;因为自己本身的研究方向是目标跟踪&#xff0c;之前看了一点使用扩散模型进行多跟踪的论文&#xff0c;里面提到了DiffusionDet因此学习一…

LabVIEW共享变量通信故障

问题概述&#xff1a; 在LabVIEW项目中&#xff0c;使用IO服务器创建共享变量&#xff0c;并通过LabVIEW作为从站进行数据通信。通讯在最初运行时正常&#xff0c;但在经过一段时间或几个小时后&#xff0c;VI前面板出现错误输出&#xff0c;导致数据传输失败。虽然“分布式系统…

u盘装win10系统提示“windows无法安装到这个磁盘,选中的磁盘采用GPT分区形式”解决方法

我们在u盘安装原版win10 iso镜像时&#xff0c;发现在选择硬盘时提示了“windows无法安装到这个磁盘,选中的磁盘采用GPT分区形式”&#xff0c;直接导致了无法继续安装下去。出现这种情况要怎么解决呢&#xff1f;下面小编分享u盘安装win10系统提示“windows无法安装到这个磁盘…

【nginx-openssl证书过期替换证书】

1 备份原有的证书和key 备份好原来的文件 cd /usr/local/nginx/conf mv cacert.pem cacert.pem.bak mv privkey.pem privkey.bak 2 将申请好的证书和key解压后更换名称&#xff1b; 将文件上传到nginx目录下 cd /usr/local/nginx/conf/ 将test.bergengine.com_bundle.crt重命…

七、数据库服务器(MySQL、PostgreSQL)的搭建

Linux 数据库服务器&#xff08;MySQL、PostgreSQL&#xff09;搭建全攻略 在当今的信息技术领域&#xff0c;数据库服务器的搭建是许多开发者和系统管理员必须掌握的技能。本文将详细介绍在 Linux 系统上搭建 MySQL 和 PostgreSQL 数据库服务器的步骤&#xff0c;并包括数据导…