Unity引擎2D游戏开发,受伤和死亡的逻辑和动画

发布时间 2023-12-19 16:18:37作者: 心霖の雨

一、创建受伤动画

找到相关受伤的动画素材,然后在Animation窗口创建动画。此处不再赘述。

image

image

此时在Animator窗口删除刚才创建的两个动画,因为现在要采用另一种方式创建动画:采用Animator的Layer方式

使用Animator→Layer创建受伤闪烁动画

受伤闪烁动画意为受伤后短暂无敌时间

点击右边号,创建新的动画图层,并命名为Hurt Layer
image

点击创建的新的图层的齿轮,将Weight设置为1,Blending设置为Additive
image
Weight为权重,即权重越高,动画播放优先级越高。
Blending为混合模式,Overrides为覆盖模式,Additive为叠加模式

在Animator窗口,右击选中Create State创建新的状态
image

创建新的Animation动画,更名为blueHurt2。将用此动画创建闪烁动画
image

在此动画下方点击Add Property,选中Sprite Renderer.Material._Color
image
Add Property可以使此动画片段修改其中任意一个被添加的属性

展开被添加的属性,可以看到其中有多个从属性,可以在动画时间轴添加关键帧修改这些从属性
image

将Samples修改为6,并在0:2、0:4、1:0处打上关键帧,分别将Material._Color.a设置为0.2、1、0.2
image
可以将鼠标点击对应的时间轴位置,然后修改需要修改的从属性,即可自动打上关键帧。
Material._Color.a为Alpha通道

在Hurt Layer动画图层中,将blueHurt2动画添加进来
image

由于此受伤后无敌动画需要触发进行,因此添加一个Trigger,命名为hurt
image

选中连线,在右下角将此Trigger添加进Condition中。并在上方设置好连线参数。
image

连接退出动画,选中连线,设置好退出参数
image

编写受伤闪烁触发器相关代码

打开PlayerAnimation代码。由于添加的触发器(Trigger)是一次执行,因此不能写在Update()方法内,另外新写一个方法。

public void PlayerHurt()
{
    animator.SetTrigger("hurt");
}

由于是写的关于受伤相关的代码,并且根据正常游戏需求,受伤后需要处理的操作非常庞大,比如UI抖动、人物受击动画、人物受击位移、扣血、削减属性等等。因此,此时需要利用Unity自带的“事件”(Event)进行处理。
打开Character代码。在头部添加UnityEvent包。

using UnityEngine.Events;

添加新的全局变量

// 将Transform坐标传入事件中
public UnityEvent<Transform> OnTakeDamage;

由于此处打算编写受击位移,需要坐标值,所以将Transform传入UnityEvent

此时,在Player的组件列表中,Character内多了一个On Take Damage(Transform)事件处理器。
image
只要此事件触发,会执行下方添加的所有方法。

将PlayAnimation拖入下面小格中
image

点击+号,添加PlayHurt()方法
image

将触发条件设置为Runtime Only
image
Editor And Runtime为编辑器运行和执行时
Runtime Only为只在执行时

在Character的TakeDamage()方法内,添加新的代码

// 执行受伤
OnTakeDamage?.Invoke(attacker.transform);

OnTakeDamage?用于判断此组件是否绑定了方法,如果绑定了则进一步处理。否则不进行处理
Invoke()方法为开始执行
attacker.transform将攻击者的坐标传入

此时开始Play,可以发现人物受伤后,无论怎么操作,受击闪烁都会执行。这就是Blending中叠加模式(Additive)的效果

创建受击后撤动画

选择中blueHurt2,然后在右侧Motion中,选择blueHurt
image

在Animation中,选择blueHurt,添加Color的Property
image

在时间轴中打关键帧,调整颜色数值(可根据自己的需要进行更改)
image

编写受击后撤的代码

受击后撤需要两个变量,一个受伤时反弹施加的力,一个是否受伤的状态

// 受伤时反弹施加的力
public float hurtForce;
// 是否受伤的状态
public bool isHurt;

新建GetHurt()方法,在里面写相关逻辑

public void GetHurt(Transform attacker)
{
    isHurt = true;
    // 受伤时强行使人物停止移动
    rb.velocity = Vector2.zero;
    // 用人物的x坐标减去攻击者的x坐标,如果得到一个负数,则人物在攻击者左侧。否则,在攻击者的右侧。
    // 用差得到方向,乘以受伤的力即可得到加速的效果
    Vector2 dir = new Vector2((transform.position.x - attacker.position.x), 0).normalized;

    rb.AddForce(dir * hurtForce, ForceMode2D.Impulse);
}

normalized属性为标准化属性,使数值无论多大,都标准化为1

normalized属性文档:https://docs.unity3d.com/cn/2022.3/ScriptReference/Vector2-normalized.html

修改FixedUpdate()方法,进行进一步的判断

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

在Player的On Take Damage(Transform)中,添加GetHurt()方法
image

在Player的PlayerController中,将Hurt Force设置为自己想要的数值
image

此时执行,可以发现人物受击后,确实发生了后撤,但是后续无法进行操作

解决受击后撤后无法操作的问题

在Animator窗口中,选中blueHurt2。接着,在右侧窗口中,选择Add Behavior
image

添加新的脚本,命名为HurtAnimation,并将新创建的脚本拖入Player文件夹中
image

打开HurtAnimation

public class HurtAnimation : StateMachineBehaviour
{
    // OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
    //override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    //{
    //    
    //}

    // OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks
    //override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    //{
    //    
    //}

    // OnStateExit is called when a transition ends and the state machine finishes evaluating this state
    //override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    //{
    //    
    //}

    // OnStateMove is called right after Animator.OnAnimatorMove()
    //override public void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    //{
    //    // Implement code that processes and affects root motion
    //}

    // OnStateIK is called right after Animator.OnAnimatorIK()
    //override public void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    //{
    //    // Implement code that sets up animation IK (inverse kinematics)
    //}
}

OnStateEnter():当被绑定此代码的动画进入时执行
OnStateUpdate():当被绑定此代码的动画执行时执行
OnStateExit():当被绑定此代码的动画退出时执行

在OnStateExit()中,获取PlayerController组件,修改当中的isHurt为false

override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
    animator.GetComponent<PlayerController>().isHurt = false;
}

二、创建死亡动画

找到相关死亡的动画素材,然后在Animation窗口创建动画。此处不再赘述。

在Animator窗口,创建死亡动画

将Hurt Layer的Blending更改为覆盖模式(Override)
image

将死亡动画拖入Animator窗口中,与Any State连线。因为,任何情况都会进入死亡动画
image

在左侧Parameters窗口中,创建新的参数isDead
image

选择连线,将isDead参数添加进条件,并调整相关参数
image

将BlueDeath与Exit连线,并将isDead参数添加进条件,调整相关参数
image

编写死亡相关的代码

在Character文件中,新建onDie事件属性。由于死亡后也需要处理很多的操作,所以在此创建为UnityEvent事件对象。

public UnityEvent onDie;

TakeDamage中,调用此创建全局变量

onDie?.Invoke();

在PlayerController文件中,创建isDead全局属性

// 是否死亡状态
public bool isDead;

创建PlayerDead()方法,禁止玩家的操作输入

public void PlayerDead()
{
    isDead = true;
    playerInputControl.Gameplay.Disable();
}

在PlayerAnimation文件中,链接Controller中的isDead状态

animator.SetBool("isDead", playerController.isDead);

将PlayerController组件拖入,并绑定PlayerDead()方法
image

此时将玩家血量设置到最低,受伤后就会播放死亡动画。但是,目前会不断反复播放。

解决死亡动画反复播放的问题

选择BlueDeath动画,在右侧将LoopTime取消勾选,即可单次执行
image

只要连接了Any State的动画,基本就不存在反复执行的问题。连接了Any State的动画,即可根据需求,判断是否需要反复执行。