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

“敌人野猪”受伤和死亡的逻辑和动画

我们来制作野猪的受伤和死亡的这个动画,以及要实现后边的逻辑

我们查看一下野猪身上挂载的Character脚本,我们可以留意到下边的OnTakeDamage和OnDie我们并没有添加任何的函数,我们还没有写,这个视频我们来实现一下

首先我们现在运行游戏的时候,我们的玩家是可以一边跳跃一边攻击的,如果需要有这样的功能,可以保留;如果不需要,我们可以到PlayerController代码中编辑一下,限制它跳跃的时候是不能攻击的。

我们找到攻击相关的代码PlayerAttack,在这个代码中我们播放了我们攻击动画,而攻击的动画会产生实际的攻击,在这里边我们可以添加一个约束;如果不在地面上,那么我们就不能攻击,通过return这个关键词,可以忽略下边要执行的内容;所以他执行这一步只要满足条件的话,直接返回,不执行下面的内容。

 private void PlayerAttack(InputAction.CallbackContext context){if (!physicsCheck.isGround)return;playerAnimation.PlayAttack();isAttack = true;

另外一种写法,我们就可以判断只有他在地面上的时候,他才能进行攻击

 private void PlayerAttack(InputAction.CallbackContext context){if (physicsCheck.isGround){playerAnimation.PlayAttack();isAttack = true;}

 这两种写法都可以

保存代码,返回unity

我们继续来考虑野猪的内容。我们的Player现在限制只能在地上攻击。

野猪现在还没有受伤的动画,也没有受伤的逻辑

我们先来创建一下野猪的受伤动画,我们找到受伤动画的图集,将图片先切割好

 创建新的动画,保存到相同的文件夹,命名为boarHurt

图片拖拽进去,帧数设置为10

现在我们就有了受伤闪烁的效果

接下来我们创建一个野猪死亡的动画,不过我们的素材没有提供野猪死亡的动画

我们希望这个死亡的动画,是他再次执行受伤的闪烁动画之后,然后消失掉,最后我们把它销毁,我们来学习一个快速制作这种动画的方法。

我们找到Animation的文件夹,打开野猪的文件夹,找到受伤的动画片段,将这个动画片段复制一份出来,按ctrl+D,复制后重命名boarDead

然后我们选中我们的野猪,在Animator的窗口中,我们先把boarDead的动画拖拽进来,然后我们就可以在Animation里面选择对应的这个死亡的动画了

当前的死亡动画和我们受伤的动画完全一样,我希望他有一个渐变的效果慢慢的消失,然后最后把它销毁,我们再次使用Sprite Renderer color这样的方法

在Animation中Add Poperty--Sprite Renderer color

在color的下拉菜单当中我们修改阿尔法值

可以看到第一帧和最后一帧都全部为1,我们将最后一帧的阿尔法值改为0

当前我们的动画就变为了逐渐消失,最后一帧就没有了

这样我们就成功制作好了死亡的动画

现在我们有了这些动画,我们到Animator窗口当中来连接一下他们之间的逻辑

受伤的动画,我们在任何状态下都会执行这个受伤的动画,连接一条线Any Atate指向boarHurt

添加条件,添加一个trigger的方式,命名为hurt

留意,当前我们在左侧设置的所有的Animator Parameters参数值都是希望我们所有的敌人都能调用,因为我们敌人的代码是整个父类的继承的方式,所以我们写在这里,就代表所以的敌人都要有相同的动画里面的参数,这样才不会出错

当这个Tigger执行的时候,我们就来播放一下受伤画面,没有修正时间,取消勾选Can Transition To Self

然后我希望这个受伤的动画执行之后要退出,退出的意思就是返回先前的这个动画的逻辑当中,所以退出的条件就是完整播放一次之后就退出好了

死亡的动画也是在任何时候都可以发生,建立和Any State直接的连续;我们要创建一个状态条件bool值,来表示我们这个野猪已经死亡了

给Any State 到boarDead添加条件,同样取消勾选Can Transition To Self

之前我们可能反复播放死亡动画,我们要修正一下这个动画,project窗口选择boarDead,让他只能单次播放,取消勾选Loop Time

boarHurt动画的Loop Time,我们也同样取消一下

这样我们的动画逻辑就已经完成了,也就代表我们野猪受伤的时候,我们要Tigger一下受伤动画,死亡的话就播放死亡动画

有了动画逻辑,基本上代码的逻辑我们也就清楚了

代码的逻辑,我们要用继承的方法来写,所以我们要把它写到父类当中,所有的敌人都会执行相同的代码

打开Enemy代码

我们来创建受伤的代码,受伤的时候,我们的人物也要在函数当中添加一个参数,就是tansform的类型。

创建函数OnTakeDamage,transform的类型中传进来的参数attackTrans(其实是攻击我们的Player的),我们希望我们的这个野猪受到攻击的时候,可以朝着攻击的相反方向有一个击退的效果,然后我们的野猪也要转身播放我们的受伤动画。

 public void OnTakaeDamage(Transform attackTrans){}

我们要记录一下攻击我们的对象,它接下来会变为我们攻击的对象,所以我们创建一个变量,在上面来记录一下我们的这个attacker

在我们刚才创建的OnTakeDamage函数当中,一开始传进去的变量,就应该等于我们的attacker

然后我希望我们的这个野猪可以转身,那我们就要判断一下当前的坐标,野猪跟攻击野猪的对象的坐标之间的关系,然后来翻转野猪。

如果攻击对象的坐标减去被攻击对象的坐标x>0,那么就代表攻击我的这个Player是在野猪的右侧,要翻转野猪(当前野猪的localScale为1,面朝左边)野猪要面朝右侧localScale为-1;在这里边我们就直接把它写死,因为我们的翻转是固定的,(三个敌人,默认状态都是面朝左侧)

然后相反,另外一种情况,但是在这里边我们不能用else来写,因为我们是同时判断两种情况,所以一旦我们的玩家跑到左侧,或者突然又跑到右侧,我们是需要同时进行两层判断的,所以在这样的判断过程当中,我们不应该用else,而是直接再写一个if语句

x<0,Player在野猪左侧。我们把localScale改为1

 public void OnTakaeDamage(Transform attackTrans){attacker = attackTrans;//翻转if (attackTrans.position.x - transform.position.x > 0)transform.localScale = new Vector3(-1, 1, 1);if (attackTrans.position.x - transform.position.x < 0)transform.localScale = new Vector3(1, 1, 1);}

保存代码,返回unity

选中Boar,找到Character组件,我们在OnTakeDamage当中点击加号,然后将我们的Boar代码拖拽进来,上面选中Rutime Only,然后我们选中父类当中的这个代码方法了Boar,OnTakeDamage

接下来,我们来测试运行一下

如果野猪受到攻击,这个野猪可以马上翻转方向

在游戏的设计过程当中,我们希望野猪即便是在后边被攻击了,它要马上转身并且切换到冲锋状态,在后边我们会写状态机,一旦受到攻击,我们就要切换状态了,暂时我们先保留这个,只要受到攻击就会转回来攻击我们的玩家,往我们的玩家方向移动。

接下来我们来实现受击,然后执行受到攻击的动画并且向后被击退的效果

继续来写Enemy代码

我们需要标记应该一个受伤的状态,有了这个受伤的状态,阻止我们当前的野猪继续前进,那么我们才可以有一个反馈,首先我在上面标记一下他的状态,起名为isHurt

[Header("状态")]
public bool isHurt;

在受伤被击退的时候,我们就可以写上isHurt=true,然后播放动画

这就是为什么我们希望我们所有的敌人在动画里面都使用相同的变量参数,这样的话我们设置SetTigger--hurt;无论是蜜蜂蜗牛还是野猪,都会执行受伤的动画

 public void OnTakaeDamage(Transform attackTrans)//接收方向,传递进来的攻击方向{attacker = attackTrans;//翻转if (attackTrans.position.x - transform.position.x > 0)transform.localScale = new Vector3(-1, 1, 1);if (attackTrans.position.x - transform.position.x < 0)transform.localScale = new Vector3(1, 1, 1);//受伤被击退isHurt = true;anim.SetTrigger("hurt");}

我们在移动这个代码当中,我们有这个持续不断的朝着面朝方向去移动,所以一旦受伤了,就要停止移动,我们有了isHurt之后,要在上面来添加一个约束,如果不是在受伤的情况下,才可以执行移动。

受伤的时候,不执行代码

 private void FixedUpdate(){if(!isHurt)Move();}

接下来,我们希望野猪被击退。我们要给他添加一个反方向的力,首先要计算一下攻击野猪的方向,我们创建一个临时变量,来记录攻击的方向,定义为Vector2的变量(攻击野猪的x减去当前的x,取一个归一的数值,计算出方向,由于我们当前localScale=1,面朝左,所以我们直接用当前的坐标减去攻击者的坐标)后边直接写上.normalized

 public void OnTakaeDamage(Transform attackTrans)//接收方向,传递进来的攻击方向{attacker = attackTrans;//翻转if (attackTrans.position.x - transform.position.x > 0)transform.localScale = new Vector3(-1, 1, 1);if (attackTrans.position.x - transform.position.x < 0)transform.localScale = new Vector3(1, 1, 1);//受伤被击退isHurt = true;anim.SetTrigger("hurt");Vector2 dir = new Vector2(transform.position.x - attackTrans.position.x, 0).normalized;}

在上方写一下添加的力的基本参数

 [Header("基本参数")]public float normalSpeed;//普通速度public float chaseSpeed;//加速冲public float currentSpeed;//当前速度public Vector3 faceDir;//面朝方向public float hurtForce;//受伤带来的冲击力public Transform attacker;

然后用刚体的方法来添加一个这个方向对应的力AddForce,添加一个受伤的模式,力的模式为impose冲击力

 public void OnTakaeDamage(Transform attackTrans)//接收方向,传递进来的攻击方向{attacker = attackTrans;//翻转if (attackTrans.position.x - transform.position.x > 0)transform.localScale = new Vector3(-1, 1, 1);if (attackTrans.position.x - transform.position.x < 0)transform.localScale = new Vector3(1, 1, 1);//受伤被击退isHurt = true;anim.SetTrigger("hurt");Vector2 dir = new Vector2(transform.position.x - attackTrans.position.x, 0).normalized;rb.AddForce(dir * hurtForce, ForceMode2D. Impulse);}

保存代码,返回unity

添加一下这个力,Boar组件中的Boar代码,把HurtForce改为4.5,运行测试一下

注意观察野猪被击退的距离,被击退后野猪会停在那里不动,没有重新恢复到走路的状态

回到Enemy代码中,我们的isHurt一直等于 true,设置一下受到冲击力后isHurt=false

保存代码,运行查看

我们会发现突然没有不能击退野猪,原因是我们这个代码执行的时候是非常快速的从上往下来执行,它中间不会有任何的等待,不会等待上面的代码先执行完在执行最后一句。让它停下来,等待他被击退之后,然后再切换为false

我们使用一个新的代码的写法,携程的方法

IEnumerator携程的一个返回值(迭代器;协同程序返回值),返回一个迭代器,这个迭代器中包含,按住ctrl键点击IEnumerator一下,会出现代码,这里边是可以按照一步一步的顺序来执行的,我们可以看到现在有一个当前执行的内容,这里面有一个布尔值叫做MoveNext(移到下一个步骤),所以用携程的方式,可以帮助我们按照一定的顺序来逐一执行,而且在中间我们可以添加等待的内容,让他等待一段时间或者等待上面执行完成之后,再执行后面的内容。这就是c#的迭代器的一个好处

下面进行编写,IEnumerator其实是一个返回值,这个函数中我们要返回一个迭代器;迭代器中的内容我们也要返回,返回的关键词是yield return,这个return后面需要有一定的值(什么都不返回,只要执行完上一帧,执行下一帧,可以return null)

这样我们就有了顺序的迭代器

 private IEnumerator OnHurt(){yield return null;}

我们在迭代器中执行我们受伤被击退,击退之后,我希望它等待一段时间之后再执行isHurt=false,在这里面它提示我们没有办法访问dir,因为他是一个局部变量,私有的区域性变量,它没有办法在另外一个函数当中访问,我们把这个变量当作一个参数传递进来就可以了

 private IEnumerator OnHurt(Vector2 dir){rb.AddForce(dir * hurtForce, ForceMode2D. Impulse);yield return null;isHurt = false;}

我们可以设置一个等待时间,return new wait(我们可以看到提示信息),有很多种类,WaitUntil(这是一个dedicate委托,它可以等待一定的函数执行完成之后,才来执行下一帧的内容,也可以等待固定的秒数,等待FIxedUpdate的帧数,等待上一帧的结束等等)

在这里面我们使用一个等待描述waitForSecond,等待0.5秒。

private IEnumerator OnHurt(Vector2 dir)
{rb.AddForce(dir * hurtForce, ForceMode2D. Impulse);yield return new WaitForSeconds(0.45f);isHurt = false;
}

我们还需要在OnTakeDanage函数当中执行一下这个携程方法,一旦我播放了受伤之后,拿到了方向,然后就要开始这个携程,它是一个固定的写法,叫做StartCoroutine启动携程,将携程的函数名传递进来,加上我们传递过去的参数,这样一个携程就写好了

public void OnTakaeDamage(Transform attackTrans)//接收方向,传递进来的攻击方向
{attacker = attackTrans;//翻转if (attackTrans.position.x - transform.position.x > 0)transform.localScale = new Vector3(-1, 1, 1);if (attackTrans.position.x - transform.position.x < 0)transform.localScale = new Vector3(1, 1, 1);//受伤被击退isHurt = true;anim.SetTrigger("hurt");Vector2 dir = new Vector2(transform.position.x - attackTrans.position.x, 0).normalized;StartCoroutine(OnHurt(dir));//启动携程迭代器
}private IEnumerator OnHurt(Vector2 dir)//迭代器
{rb.AddForce(dir * hurtForce, ForceMode2D. Impulse);yield return new WaitForSeconds(0.45f);isHurt = false;
}

保存代码,返回unity

运行测试,当前我们成功实现了击退效果,如果希望击退距离更远,可以调整一下受击的力或者调整一下等待时间,这两个参数都可以影响野猪被击退的距离。

由于我们野猪有无敌时间,所以连续攻击的话是没有办法实现两次攻击的

二,野猪死亡代码

接下来,我们调整野猪死亡要执行的内容,当前我们还没有这个函数方法

我们需要在Animator中播放一下死亡isDead=true,然后播放死亡动画,播放完动画后,我们就要把它销毁,所以一会我们会在Animation当中,在结束的时候,执行一个方法,让我们的野猪被销毁。

我们打开Enemy代码

我们同样像受伤一样,我们先创建一个死亡状态的布尔值

 [Header("状态")]public bool isHurt;public bool isDead;

野猪死亡后,我们也不需要在FixedUpdate当中做任何判断了,所以不是受伤不是死亡,野猪可以一直移动

 private void FixedUpdate(){if(!isHurt && !isDead)Move();}

创建这个函数,起名为OnDie,在这个函数里面播放我们的动画,dead=true,播放动画后,我们还要改变它的状态isDead=true;

public void OnDie()
{anim.SetBool("dead", true);isDead = true;
}

在设置一个函数DestoryAfterAnimation,执行动画完成之后,销毁野猪的效果

销毁物体,固定的函数方法Destory,在这里面有一些重载的方法,你可以立刻将一个物体销毁,也可以等待一段时间。

我们要立刻销毁,直接在里面输入物体就行了this.gameObject当前物体

 public void DestoryAfterAnimation(){Destroy(this.gameObject);}

保存代码,返回unity。来调用这个函数

2.学习如何在动画里面添加一个函数事件

选中野猪,在Animation窗口当中选择Dead死亡动画,我们拉到最后一帧,最后一帧执行销毁的函数方法。

两种方法,一按鼠标右键--Add Aniamtion Event;二直接在左侧选择AddEvent

存在一个问题,一旦鼠标连击,就很可能在最后一帧的位置出现两个白杠 但是看不出来他们重叠在一起。要确保只添加了一个Animation Event

添加好之后,在右侧的Inspector窗口当中选择当前要执行什么函数,选择Boar--DeatoryAfterAnimation

这样执行动画直到最后一帧,就会销毁掉这个物体

我们把Boar的无敌时间改小一点

还要再Boar的Charactor组件当中,把函数方法添加进去

保存项目,运行测试一下,看看效果

实现了野猪被击退,野猪死亡,野猪被销毁

还要一种情况我们要考虑,野猪死亡动画播放的时候,我们的人物跑过去的时候还是受伤了,原因是野猪播放死亡动画的过程当中,野猪的碰撞体仍然存在。设置取消死亡状态下,野猪的碰撞体,

(我们可以通过调整碰撞图层来解决这个问题,在右上角有Ignore Raycast,我们把敌人改成Ignore Raycast,这样就不会和Player产生任何的碰撞了)

具体修改这些碰撞的关系,在Edit--Project Settings--Physics 2D--Layer Collision Matrix碰撞图层的矩阵图,在这里面可以看到哪两个Layer可以产生碰撞,就会被打勾,我们不希望Player和Ignore Raycast产生碰撞,取消勾选

野猪死亡后,我们要第一时间把图层改到Ignore Raycast,不会和玩家产生碰撞

注意这个Ignore Raycast的层级为2

我们来改一下代码,打开Enemy

在OnDie刚开始执行的时候,我们把当前的Layer改为2,直接输入数字就可以了

  public void OnDie(){gameObject.layer = 2;anim.SetBool("dead", true);isDead = true;}

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

实现了我们的效果


http://www.mrgr.cn/news/54479.html

相关文章:

  • 接口测试(三)jmeter——连接mysql数据库
  • iOS IPA上传到App Store Connect的三种方案详解
  • nbsaas vue3管理后台框架
  • 【Python网络编程】学习Socket编程,打造网络应用!
  • 蒙特卡洛法面波频散曲线反演(matlab)
  • Docker 笔记
  • xtu oj 不定方程的正整数解
  • yjs机器学习数据操作01——数据的获取、可视化
  • 民宿预订新纪元:SpringBoot实现的在线平台
  • 昇思MindSpore进阶教程--AOE调优工具
  • 大幅降低人工核验遗漏的概率,降低出错风险的智慧能源开源了
  • QT的事件
  • SpringBoot技术在汽车票预订领域的应用
  • 状态空间表达式的求解与转化【现代控制理论】
  • 第6天:Intent和页面导航
  • 管家婆财贸ERP BB007.销售订单明细批量采购
  • 大数据治理--技术平台与工具
  • 深入探索 APKTool:Android 应用的反编译与重打包工具
  • 2024软考网络工程师笔记 - 第12章.网络规划设计
  • 【软件运行类文档】项目试运行方案,试运行计划书(word原件)
  • Windows环境下安装jdk8,含配置环境变量全过程
  • 基于MATLAB车牌识别系统设计
  • mysql连接池简单原理介绍+高并发场景下的设计
  • 发动机拆解可视化:精细化呈现机械内部结构
  • 安装 Git
  • 二层交换机的工作原理与局域网设备通信详解