Unity2D游戏制作入门 | 12(之人物受伤和死亡的逻辑动画)

news2025/1/12 23:11:05

上期链接:Unity2D游戏制作入门 | 11(之人物属性及伤害计算)-CSDN博客

上期我们聊到了人物的自身属性和受伤时的计算,我们先给人物和野猪挂上属性和攻击属性的代码,然后通过触发器触发受伤的事件。物体(人物也好敌人也行)受伤时通常是在被攻击者的内部进行计算的(这是有好处的,比如使用人物更复杂的血量计算方式,我们在人物内部让它自己自动计算就好了),我们使用关键词this来将Attacker内部的变量的访问权限赋给Character,然后我们又使用了计时器,让被攻击的物体在受到攻击后可以有短暂的无敌时间,计时器的写法你还知道吗?先创建三个变量一个布尔二个浮点,创建新的能赋给人物无敌的函数,然后判断人物如果不如那么人物的无敌状态就是True,然后赋给无敌时间(一个浮点变量我们是在Unity窗口手动赋给的,另一个浮点变量是内部进行使用它,在函数判断人物是否处在无敌时才用公共无敌时间赋给那个私有的浮点变量,这样就在内部激活了人物的无敌时间),我们在update中计算人物无敌的倒计时,到点了就切换人物无敌状态为flase,然后在人物的Character中的TakeDamage函数中,我们判断人物如果不是无敌则进入扣血规则,然后如果人物的当前血量足够扣除一次血量则正常减血(如果人物血量为2敌人攻击为5,那么人物直接判断血量归零),不然人物死亡,而无敌状体的赋给与否全在人物扣血那部分功能中执行,如果人物血量归零那么将无法触发无敌状态。总的来说,上次就是多了两份通用的代码文件,分别是物体的属性和攻击(仅对需要这些的物体有用,如果是场景的死物一般不给属性如血量,也可以给看你设定,攻击应该是不要加上去的,除了陷阱啥的),虽然多了两份代码,我们还是要弄清两份代码之间的访问和运行的逻辑,以及什么时候用它们。这期我们看看角色的 受伤和死亡的逻辑和动画 。代码先下方:

public class PlayerAnimations : MonoBehaviour
{
    private Animator anim;//创建好这个组件变量后,如果不知道如何通关代码控制组件,可以去看代码手册
    private Rigidbody2D rb;//这个2D不能忽略,不然不报错但是人物跑不起来。
    private Player_control playercon;//人物控制
    private physicsCheck physicsCheck;

    private void Awake()
    {
        physicsCheck = GetComponent<physicsCheck>();
        anim = GetComponent<Animator>();
        rb= GetComponent<Rigidbody2D>();
        playercon = GetComponent<Player_control>();
    }

    private void Update()
    {
        SetAnimatons();//每帧时时检测,判断是否需要切换动画。
    }

    public void SetAnimatons()//需要做很多动画的切换,我们用这个函数来执行所有的动画切换。
    {
        anim.SetBool("isGround", physicsCheck.isGround);
        anim.SetFloat("velocityX",math.abs(rb.velocity.x));
        anim.SetFloat("velocityY",rb.velocity.y);
        anim.SetBool("shift", playercon.isShift);
        anim.SetBool("isDead", playercon.isDead);
    }

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

}
public class Player_control : MonoBehaviour
{
    public PlayerInputControl inputControl;
    public Vector2 inputDirection;//public变量表示一个公开的方法,它代表我在Unity窗口中可以查看得到。
    private Rigidbody2D rb;
    private physicsCheck physicsCheck;//创建物理检测变量

    private SpriteRenderer rbSprite;
    [Header("基本参数")]
    public float speed;
    public float walkSpeed;
    public float jumpForce;
    public float hurtForce;

    [Header("按键按下")]
    public bool isShift=false;

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


    private void Awake()
    {
        physicsCheck = GetComponent<physicsCheck>();
        rb= GetComponent<Rigidbody2D>();
        inputControl = new PlayerInputControl();
        inputControl.GamePlayer.Jump.started += Jump;//按下空格那一瞬间执行事件Jump
        //inputControl.GamePlayer.Walk.performed += Walk;
        


        rbSprite = GetComponent<SpriteRenderer>();

    }

    private void OnEnable()
    {
        inputControl.Enable();
    }

    private void OnDisable()
    {
        inputControl.Disable();
    }

    private void Update()
    {
        inputDirection = inputControl.GamePlayer.Move.ReadValue<Vector2>();
        isShift= Keyboard.current.shiftKey.isPressed;//检测按键

    }

    private void FixedUpdate()//固定频率运行,即0.02秒执行一次。跟物理有关的放在这执行
    {
        if (!isHurt)
        {
            if (isShift && physicsCheck.isGround)
                Walk();
            else
                Move();
        }
        
    }

    public void Move()
    {
        rb.velocity = new Vector2(inputDirection.x*speed*Time.deltaTime,rb.velocity.y);


        //人物翻转
        if(inputDirection.x>0)
            rbSprite.flipX = false;
        if(inputDirection.x<0)
            rbSprite.flipX = true;
    }

    private void Jump(InputAction.CallbackContext obj)
    {
        if (physicsCheck.isGround)
        {
            rb.AddForce(jumpForce * transform.up, ForceMode2D.Impulse);//第一个参数表示给哪个方向施加力}
        }
    }

    private void Walk()
    {
        rb.velocity = new Vector2(inputDirection.x * walkSpeed * Time.deltaTime, rb.velocity.y);

        //人物翻转
        if (inputDirection.x > 0)
            rbSprite.flipX = false;
        if (inputDirection.x < 0)
            rbSprite.flipX = true;
    }

    public void GetHurt(Transform attcker)
    {
        isHurt = true;//受伤了
        //接下来执行反弹,不过前提是先把人物的速度停下来。
        rb.velocity = Vector2.zero;//表示物体的xy轴速度全设置为了。
        //接下来计算受伤的方向,然后ta要朝反方向弹射。
        Vector2 dir=new Vector2((transform.position.x-attcker.position.x),0).normalized;
        /*
         这里对上行代码进行说明,dir是direction方向的简写。计算方向我们就用人物坐标减去
        野猪或是其他的attacker的坐标对于x轴来说,如果人物在敌人左边那么它应该是负数,所以
        我们根据正负数作为它的方向乘上它的反冲力hurtForce(现实加速的效果)。dir算完后,我们
        需要不是数值的大小,而是方向值(我们按下键盘时dir有-1和1两种数值)。如果人物离野猪很远,那么x的值会非常大,乘反弹力会更大
        反而离得近数值会很小,所以我们要把数值归一化,即用到normalized的方法。
         
         */
        rb.AddForce(dir*hurtForce,ForceMode2D.Impulse);//添加瞬时的力反弹人物

    }

    public void PlayerDead()
    {
        isDead = true;
        inputControl.GamePlayer.Disable();
    }
}
public class Character : MonoBehaviour
{
    [Header("基本属性")]
    public float maxHp;//最大血量
    public float currentHp;//当前血量


    [Header("受伤无敌")]
    public float invincibleTime;//无敌时间
    private float invincibleCounter;//一个计数器,内部计算即可,不需要在窗口可以看得到
    public bool invincible;//为了能看见无敌,我们创建一个布尔值

    public UnityEvent<Transform> Ontakedamage;
    public UnityEvent Ondie;
    private void Start()//开始游戏时,要满血
    {
        currentHp = maxHp;
    }



    private void Update()
    {
        if(invincible)
        {
            invincibleCounter-=Time.deltaTime;
            if(invincibleCounter <= 0)
            {
                invincible = false;
            }
        }
    }


    //受到伤害
    public void TakeDamage(Attack attcker)
    {

        if (invincible)
            return;
        //Debug.Log(attcker.damage);

        if (currentHp - attcker.damage > 0)//血量健康才能减血
        {
        currentHp -= attcker.damage;//如果学过python,你会知道这行代码等于currentHp = currentHp - attcker.damage;
        TriggerInvincible();//触发无敌
            //执行受伤
        Ontakedamage?.Invoke(attcker.transform);

        }
        else
        {
            currentHp=0;
            //触发死亡
            Ondie?.Invoke();
        }

    }
    
    //受伤触发无敌
    private void TriggerInvincible()
    {
        if(!invincible)
        {
            invincible= true;
            invincibleCounter = invincibleTime;
        }
    }
}
public class HurtAninmation : StateMachineBehaviour
{
    override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        animator.GetComponent<Player_control>().isHurt = false;
    }
}

正文:

玩家如果受到伤害,那么应该有对应的受伤动画,甚至血量归零时人物播放死亡的动画。找到我们之前的素材,我们可以先看看guid指导的动画内容,找到受伤的内容。我们先点击player,然后创建人物的动画文件(一定放在人物动画管理的文件下,这样方便找),然后创建人物的hurt动画,用右边的那四张图片(我们之前切割好的),然后Samples采样率我们调整到合适的数值(14的话感觉还是有点快了所以我选了9)。
在这里插入图片描述

死亡动画编号为42-53。
在这里插入图片描述

先将Animator中出现的我们刚刚创建的两个动画删去,我们注意到我们的人物在任何情况下都可能受伤,如我们跑步撞到野猪,在空中碰到敌人,这些都可能使人物受伤。如果人物受伤,应该凌驾在所有动画之上(我们先前制作的所有动画,如跳跃、闲置、跑步等),接下来我们创建新的方法。先不调用受伤动画,来看一个有意思的方法(UP话)。玩家如果进行受伤的状态, 那么玩家应该有闪烁的状态来表示玩家在这个阶段是受伤无敌的状态。我们在下图的这个地方创建新的动画的Layer图层注意这些图层的名字下有一条线
在这里插入图片描述

我们点击动画图层右边的齿轮可以发现,有一个名为Weight权重的东西,这个权重会影响当前的这一层的动画的播放,你可以理解为:它的优先级是多少。下面的Blending为混合模式,Override是完全覆盖的意思。我们先将HurtLayer动画图层的权重拉到1,然后混合模式切换成Additive(叠加)模式即当前层动画不会覆盖之前层(BaseLayer)的动画,在之前层的基础上为它进行添加。

在这里插入图片描述

我们在这个新地方创建新的空的状态,然后创建新的受伤动画即bule_hurt2。在这里我们不调用任何的图片,现在录制我们的动画片段。

在这里插入图片描述

这个地方关键,我们去到人物的右边的组件Sprite Renderer,看到Color我们修改它RGB的通道,也可以修改它的阿尔法值,怎么说呢就是修改物体的透明程度(1的话就显示,0就隐身了),如果是改RGB中的值人物就会变色如变成红色等。然后我们在动画管理下的Add Property,找到对应组件的功能,即我们找到Sprite Renderer下的控制color的选项即可。
在这里插入图片描述

我们先在BaseLayer中删除我们创建的bule_hurt2动画,然后添加到HurtLayer中去。对了,hutr2一个六帧,采样率设置6即可,然后可以可以拖拽右边的6个点到1:0的位置,然后再0:2、 0:4 、1:0的位置分别设置阿尔法值为0.5、1、0.5。弄好后链接一根线到bule_hurt2,再创建一个新的参数为trigger类型的名称取hurt(你会注意到它右边是圆形的图案这是触发器)。在触发hurt2的条件中添加hurt参数这个条件(其他的条件如退出时间、转换时间都弄为无或是0,和之前的那些动画差不多就行),一旦hurt(左边红框的圆圈内)被勾选了即启动了,那么会自动播放这个受伤的动画。
在这里插入图片描述

如果要由播放受伤的动画播放完了,我们需要回到之间的动画所以设置受伤播放完后回到初始的状态,这里返回的条件就设置为无,即什么都不加。

在这里插入图片描述

我们进入到代码把它测试一下,我们打开代码PlayerAnimations。trigger的方法我们不能在update中执行,它是单次执行的函数,所以创建一个新的函数。

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

那么我们时候执行调用PlayerHurt()呢?应该在Character代码中去执行:受伤我们需要播放人物的动画,以及人物被弹走不能再往前走(当然,我们受伤被弹走一般是不允许进行键盘操作的),甚至我们需要播放受伤的声音。这里又很关键了,试想一下,除了刚刚提到要被弹飞等事件,也可能还要调整一下我们ui的显示,就在受伤后有很多事情需要去执行。想让一次受伤去触发其他各种各样的代码或以后做UI都要来执行对应的方法,我们需要使用Unity的事件的方式。

public void TakeDamage(Attack attcker)
    {

        if (invincible)
            return;
        //Debug.Log(attcker.damage);

        if (currentHp - attcker.damage > 0)//血量健康才能减血
        {
        currentHp -= attcker.damage;//如果学过python,你会知道这行代码等于currentHp = currentHp - attcker.damage;
        TriggerInvincible();
        //执行受伤

        }
        else
        {
            currentHp=0;
            //触发死亡
        }
    }

接上面的内容,我们需要调用Unity的命名空间,接下来创建这些事件,我们先创建一个类型为UnityEvent的变量(现在要先弄受伤的事件),然后再public UnityEvent<>的尖括号中,我们可以传入一些参数,例如在受伤时希望每次受伤都能按受伤的这个方向的反方向被击退,无论敌人还是人物都应该是这样的。传递方向进去,其实也就是坐标,那么我们Transform类型的组件传进去。

using UnityEngine.Events;
public UnityEvent<Transform> Ontakedamage;

可能上面代码看得不是很懂,返回unity窗口就能直观地看到了(记得先保存写完的代码):Character组件就多了事情的内容了。这个东西是unity自带的,它的好处是以后我们学习UI时,点击的时候也会有这种事件的处理方式。在这个事件当中,我们可以点击加号来添加各种各样的函数方法,各种各样的功能。那么在一个事件触发时候,这里可以执行所有你添加进去的方法。如我们之前提到的人物受伤,如果触发了很可能我们要调用ui、播放音乐、执行方法,播放动画,所有的内容都可以放在这里。
在这里插入图片描述

应用举例,我们把playerAnimations拖拽下去,我们找到代码中的playerHurt的函数方法,然后可以选择在何时运行它,如在游戏运行或是编辑器里时,这里我们选择只在游戏运行时执行。

在这里插入图片描述

接下来我们会到代码文件Character中,回看这行代码public UnityEvent<Transform> Ontakedamage;,**这个关于事件Ontakedamage,刚才的加号是代表我们把各种各样的方法注册到这个事件当中,接下来我们需要把这个事件启动起来,它其实有一个固定的写法。**注意新写入的代码Ontakedamage?.Invoke(attcker.transform);问号代表判断一下有没有任何方法添加进来,很可能之前的列表是空的,那我们就可能报错了,所以加上问号。**后面接点Invoke表示启动当前事件的内容(不要忘记打上括号)。**然后因为刚刚我们创建时有设置了参数,所以需要传一个transform进去,谁攻击我我就朝反方向移动,那么我们就要获得attcker它的transform了。这样,我们就实现了该函数(TakeDamage(Attack attcker))受伤数值减少、触发无敌,然后执行所以注册过来的受伤的函数方法。(目前我们只注册了受伤时播放闪烁的动画)

public void TakeDamage(Attack attcker)
    {

        if (invincible)
            return;
        //Debug.Log(attcker.damage);

        if (currentHp - attcker.damage > 0)//血量健康才能减血
        {
        currentHp -= attcker.damage;//如果学过python,你会知道这行代码等于currentHp = currentHp - attcker.damage;
         //触发无敌
        TriggerInvincible();
         //执行受伤
        Ontakedamage?.Invoke(attcker.transform);

        }

运行游戏,如果人物经过野猪会有一个人物闪烁的效果,**我经过测试发现:如果把野猪的Is trigger给关掉或胶囊触发器关闭不使用,那么人物经过野猪,不管是顶着它走还是穿过它都不能能触发受伤闪烁的效果。**这样我们就学习了如何触发事件来执行一些方法。接下来我们给人物受伤切换回我们之前想要的受伤动画,然后按照我们之前给没有素材的动画添加受伤会变红闪烁的效果即可,然后这样我们就有了红色受伤的动画,那么我们还需要让人物反弹回去。
在这里插入图片描述

我们受伤被击退的事件应该在人物控制的代码中被执行,因为控制人物的方向要被打断,如果一直向前跑但是受伤后需要被弹开。所以我们在player_control里面创建新的方法,来执行一个人物反弹的效果。我们需要设置一下玩家的状态,受伤就要阻止我们其他的移动,也要反弹一段距离。所以需要创建是否受伤的布尔值类型的变量isHurt。然后反弹的时候希望有一个力把它弹开一段距离。

	public bool isHurt;
	public float hurtForce;
....
    private void FixedUpdate()//固定频率运行,即0.02秒执行一次。跟物理有关的放在这执行
    {
        if (!isHurt)//没有进入受伤状态才能走动
        {
            if (isShift && physicsCheck.isGround)
                Walk();
            else
                Move();
        }
        
    }
.....
    public void GetHurt(Transform attcker)
    {
        isHurt = true;//受伤了
        //接下来执行反弹,不过前提是先把人物的速度停下来。
        rb.velocity = Vector2.zero;//表示物体的xy轴速度全设置为了。
        //接下来计算受伤的方向,然后ta要朝反方向弹射。
        Vector2 dir=new Vector2((transform.position.x-attcker.position.x),0).normalized;
        /*
        这里对上行代码进行说明,dir是direction方向的简写。计算方向我们就用人物坐标减去
        野猪或是其他的attacker的坐标对于x轴来说,如果人物在敌人左边那么它应该是负数,所以
        我们根据正负数作为它的方向乘上它的反冲力hurtForce(现实加速的效果)。dir算完后,我们
        需要不是数值的大小,而是方向值(我们按下键盘时dir有-1和1两种数值)。如果人物离野猪很远,那么x		  的值会非常大,乘反弹力会更大,反而离得近数值会很小,所以我们要把数值归一化,即用到normalized的		  方法。         
         */
        rb.AddForce(dir*hurtForce,ForceMode2D.Impulse);//添加瞬时的力反弹人物
    }

保存上面写好的代码会回到unity的窗口,我们需要把我们写好的人物受伤的函数方法添加到Ontakedamage这个事件中去,然后我们给人物被攻击的力改成8,我们发现人物被弹走后停不下来,原因是isHurt一直没用变回flase。
在这里插入图片描述

我们留意到animator中,我们看到受伤动画状态的右边,即我们可以添加一个代码,在这个动画执行的过程中,做一些代码的变化,我们先添加一个叫HurtAninmation的代码。输入完名称后,按下回车键,然后它会为你创建这个新的代码,不过代码被放在了我们project下的Assets下了,和一些文件目录同级了。

在这里插入图片描述
在这里插入图片描述

双击打开代码,我们可以看到这是一个unity自动为我们写好的模板,我们可以直接拿来用。它继承了**StateMachineBehaviour,也就是状态机的行为,**在做敌人我们也会单独来写。取消方法前面的注释就可以用了。第一方法是,当进去这个状态时执行,第二方法是当在执行这个动画的过程中我执行函数方法(如果这个状态也可称为动画时间比较久那么函数一直执行),第三个是当状态退出后,我们执行某某方法。So,当我们受伤播完并退出后,我们更改我们的人物isHurt的状态为flase即可。
在这里插入图片描述

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

注意我们用Ontakedamage事件的这个方法,我们即播放了动画,又可以播放脚本里的函数的逻辑。最后一个我们要做的就是人物的死亡,在任何状态下我们的人物都会死亡,所以我们到动画控制器中添加死亡的动画,然后HurtLayer的动画图层我们把混合的模式改为Override覆盖。我们在Parameter中添加死亡参数进行判断isDead(布尔值)。然后给Any State连一根线到死亡动画中去,条件为isDead为true时,其他要么无要么为0。

在这里插入图片描述

注意一下,我们的人物是否会一直进入死亡状态呢?不一定,如果我们重新开始游戏,那么人物要刷新状态并退出死亡的动画(退出的条件大家应该都熟练了,isDead为flse其他要么无要么为0)。
在这里插入图片描述

接下来把死亡动画链接到函数,我们需要在Character代码中添加死亡的事件方法。因为在人物死亡时,可能也要执行很多的事情,比如跳出UI的面板,除了通知你Game Over,还要通知你很多的信息,比如通知你的敌人不要继续攻击,人物不要再移动了。但是这次我们不需要传递任何参数,就是人物只是死亡。

//#Characte.cs
    public UnityEvent Ondie;//新加入,对应代码第22行
..........
public void TakeDamage(Attack attcker)
    {

        if (invincible)
            return;
        //Debug.Log(attcker.damage);

        if (currentHp - attcker.damage > 0)//血量健康才能减血
        {
        currentHp -= attcker.damage;//如果学过python,你会知道这行代码等于currentHp = currentHp - attcker.damage;
        TriggerInvincible();//触发无敌
            //执行受伤
        Ontakedamage?.Invoke(attcker.transform);

        }
        else
        {
            currentHp=0;
            //触发死亡
            Ondie?.Invoke();
        }
	}

那么人物的死亡是一个布尔值,所以它不是单次执行的,没有必要单独去写,我们直接把状态连接到Animation当中即可,并通过人物控制停止我们所有的人物操作如移动。

//#Player_control.cs
public bool isDead;
...
public void PlayerDead()
    {
        isDead = true;
        inputControl.GamePlayer.Disable();//我们之前输入系统的人物操作相关的内容。我们只关闭人物的操作,正常的ui操作还要保留的。
    }

然后在PlaterAnimation中把状态连接起来。

public void SetAnimatons()//需要做很多动画的切换,我们用这个函数来执行所有的动画切换。
    {
   		........
        anim.SetBool("isDead", playercon.isDead);
    }

在这里插入图片描述

然后设置人物的血量为6进行死亡测试,不过你会发现人物会反复死亡,这是因为人物的死亡动画没有设置为只播放一次。把下图人物死亡的动画的循环关去即可,让它单次执行。一般来说很多动画都需要单次播放的,如果不连接到Any State中,它都不会有这样的问题,如果你链接了Any State且只希望播放一次,一定要取消勾选Loop Time。
在这里插入图片描述

总结:

​ 添加事件的方式特别的方便,它能帮助我们添加各种函数去执行一些事件,我们虽然只做了两个动画,即人物的受伤和死亡却操作了很久说明做游戏也不是很容易的。这节可能没那么好理解,记得多多操作并理解这些步骤是为什么要这么做的,就连我自己都要反复看才能理出一点头绪。

​ 为了能记下Unity一些的特殊用法,还是一些来稍微回味一下这期的内容吧。在创建人物受伤和死亡的动画之前,我们先是创建了人物被触发后能够闪烁的动画,这样的动画我们是通过组件Sprite Renderer里面的color功能区实现的。我们创建了一个新的动画图层,修改它显示的权重并用于叠加到我们之前的动画上。触发受伤的动画是通过触发器来实现的,所以只需要单次执行,我们不会把它放入到Update函数中去,我们将在PlayerAnimations类中的函数PlayerHurt()放到Character代码中去执行,因为人物检测到血量减少后应该就执行人物受伤的动画,这很符合直觉。然后因为如果我们的人物受伤了,我们需要做很多的事情,如播放音乐、播放受伤动画等,所以我们使用了命名空间UnityEngine.Events,来给物体添加一些事件的功能,而且我们在Unity窗口的物体的组件上也能看到在对应的代码多出了添加一些注册事件的功能,然后我们需要在代码的对应位置启动某某事件固定格式为:事件名称.Invoke(参数)Ontakedamage?.Invoke(attcker.transform);

​ 运行游戏后发现触发人物的受伤动画是正常的,接下来我们需要人物在进去受伤动画时,人物不应该能执行一些操作,如果移动跑步等,并且受伤被击退的事件应该在人物控制的代码中被执行,即现在我们需要确定的事件就是,人物受伤不能操作,且要被击退,而播放动画是在人物属性的代码中执行的,因为一旦扣血我们才播放受伤动画。在人物控制的代码文件中,我们创建了新的函数方法为GetHurt(Transform attcker),然后我们创建一个布尔值变量判断受伤,和一个受伤应该能弹飞人物的力的变量。然后该事件函数(人物被弹飞,且不能操作)的调用我们放入到刚刚Character的代码中去注册到事件Ontakedamege中去,这个地方就是如果在Character代码中如果事件Ontakedamage被启动了,相应的人物动画代码中的触发的参数hurt将被激活,然后执行人物受伤的动画,再接下来就是人物控制代码中的函数GetHurt(Transform attcker)被调用了,这是直接更新人物的受伤状态isHurt为true,那么,人物进入不能操作且被击飞的状态,这个持续的时间是是无限时间,因为没有设定何时把isHurt改为flase,在人物控制的FixedUpdate()函数中isHurt还是true,达不到我们能操作的条件,所以我们在受伤动画退出的时间即受伤动画播放完后,我们通过在受伤的State中创建新的代码来在退出动画时更新我们人物受伤的状态,该代码文件的关键代码为:animator.GetComponent<Player_control>().isHurt = false;,即我们获取了人物控制的代码,在动画完全退出后我们把isHurt的状态改为flase,这样在我们人物被击飞后我们又获得了人物操作的控制权(是的,人物在受伤获得操控权后又飘了…)。然后就是注入人物死亡的动画,当人物的状态isDead为true时,我们让人物进入死亡并且所有操作都要停止,但是也只是限制人物的操作,像其他的如UI的操作我们没有停止,因为那是结算界面。我们在人物控制的代码文件中创建新的函数为PlayerDead(),人物死亡则isDead为true,而且关闭人物相关操作,并在人物动画中时时检测人物死亡因为重开游戏人物会满血达不到死亡的条件。

​ 大致的流程就是这样了,不知道我有没有说清楚?唉这个部分就是比较混乱的,多看多想吧。

未尽事宜以后可能会补充。

-------------------------结束线

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1803092.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用 Scapy 库编写 ICMP 不可达攻击脚本

一、介绍 ICMP不可达攻击是一种利用ICMP&#xff08;Internet Control Message Protocol&#xff09;不可达消息来干扰或中断目标系统的网络通信的攻击类型。通过发送伪造的ICMP不可达消息&#xff0c;攻击者可以诱使目标系统认为某些网络路径或主机不可达&#xff0c;从而导致…

vue2中如何使用函数式组件

vue2 中如何使用函数式组件 用 render 定义函数式组件如何处理 props如何在函数式组件中触发自定义事件&#xff1f;injection如何使用 computed 和 methods定义一个函数式组件的 MyButton函数式组件有何优势哪种场景适合使用函数式组件函数式组件的问题参考 函数式组件&#x…

WPS JSA 宏脚本入门和样例

1入门 WPS window版本才支持JSA宏的功能。 可以自动化的操作文档中的一些内容。 参考文档&#xff1a; WPS API 参考文档&#xff1a;https://open.wps.cn/previous/docs/client/wpsLoad 微软的Word API文档&#xff1a;Microsoft.Office.Interop.Word 命名空间 | Microsoft …

测试工具链

缺陷管理 bug管理工具 devops---项目管理--缺陷管理 bug管理地址 https://devsecops.mychery.com:8443/chery/project?filterROLE&statusACTIVE bug管理环境 采用公司的devops平台&#xff0c;对每个项目的bug进行管理。目前在使用 接口测试和服务端性能测试 工具…

基础乐理入门

基础概念 乐音&#xff1a;音高&#xff08;频率&#xff09;固定&#xff0c;振动规则的音。钢琴等乐器发出的是乐音&#xff0c;听起来悦耳、柔和。噪音&#xff1a;振动不规则&#xff0c;音高也不明显的音。风声、雨声、机器轰鸣声是噪音&#xff0c;大多数打击乐器&#…

【CS.SE】使用 docker pull confluentinc/cp-kafka 的全面指南

文章目录 1 引言2 准备工作2.1 安装 Docker2.1.1 在 Linux 上安装 Docker2.1.2 在 macOS 上安装 Docker2.1.3 在 Windows 上安装 Docker 2.2 验证 Docker 安装 3 拉取 confluentinc/cp-kafka Docker 镜像3.1 拉取镜像3.2 验证镜像 4 运行 Kafka 容器4.1 启动 ZooKeeper4.2 启动…

Nextjs学习教程

一.手动创建项目 建议看这个中文网站文档,这个里面的案例配置都是手动的,也可以往下看我这个博客一步步操作 1.在目录下执行下面命令,初始化package.json文件 npm init -y2.安装react相关包以及next包 yarn add next react react-dom // 或者 npm install --save next react…

2024最新 Jenkins + Docker实战教程(八)- Jenkins实现集群并发构建

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

转型AI产品经理(4):“认知负荷”如何应用在Chatbot产品

认知负荷理论主要探讨在学习过程中&#xff0c;人脑处理信息的有限容量以及如何优化信息的呈现方式以促进学习。认知负荷定律认为&#xff0c;学习者的工作记忆容量是有限的&#xff0c;而不同类型的认知任务会对工作记忆产生不同程度的负荷&#xff0c;从而影响学习效果。以下…

Java面试八股之什么是自动装箱和自动拆箱

什么是自动装箱和自动拆箱 在Java中&#xff0c;自动装箱&#xff08;Autoboxing&#xff09;和自动拆箱&#xff08;Auto-unboxing&#xff09;是两个与基本数据类型和它们对应的包装类之间的转换相关的特性。这两个概念自Java 5&#xff08;也称为Java SE 5或JDK 5&#xff…

SpringBoot3整合Mybatis-Plus3.5.5出现的问题

主要是由于 mybatis-plus 中 mybatis 的整合包版本不够导致的 排除 mybatis-plus 中自带的 mybatis 整合包&#xff0c;单独引入即可 java.lang.IllegalArgumentException: Invalid value type for attribute factoryBeanObjectType: java.lang.Stringat org.springframework.…

Linux安装Qt5.14.2

下载 qt 5.14.2下载网址 下载qt-opensource-linux-x64-5.14.2.run Linux系统下载.run文件&#xff08;runfile文件&#xff09;&#xff0c;windows系统下载.exe文件&#xff0c;mac系统下载.dmg文件。 md5sums.txt中是各个文件对应的MD5校验码。 验证MD5校验码 md5sum是li…

例54:Draw使用

建立一个控制台工程&#xff0c;输入代码&#xff1a; Screen 13 移动到&#xff08;50,50&#xff09;而不绘图 Draw "BM 50,50" B:移动但不绘制,M:移动到指定位置 将绘图颜色设置为2&#xff08;绿色&#xff09; Draw "C2" C将颜色改为n …

后台管理系统排序混乱,分页出现重复条例

检查了接口和请求参数都没有问题。 查询数据库发现是排序字段create_time 都相同导致的。没有区分度。 解决方案 按照唯一id排序 避免create_time 大批量相同 order by create_time &#xff0c;xxx 两个排序字段

171.二叉树:二叉树的所有路径(力扣)

代码解决 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr, right(nullptr) {}* Tree…

电脑提示msvcp140.dll丢失的解决方法(附带详细msvcp140.dll文件分析)

msvcp140.dll是一个动态链接库&#xff08;DLL&#xff09;文件&#xff0c;属于Microsoft Visual C 2015 Redistributable的一部分。它全称为 "Microsoft C Runtime Library" 或 "Microsoft C Runtime Library"&#xff0c;表明该文件是微软C运行时库的一…

Vue3【十二】09Computed计算属性

Vue3【十二】09Computed计算属性 计算属性 获取全名 这种方式是只读的不能修改 这样定义fullName是一个计算属性&#xff0c;可读可写 案例截图 目录结构 代码 Person.vue <template><div class"person"><h1>我是 Person 组件</h1>姓&…

09.2手工制作docker镜像-kod服务

手工制作docker镜像-kod服务 基于centos6.9系统镜像&#xff0c;搭建kod服务&#xff0c;提交镜像 创建并进入容器 添加centos6系统的yum源和epel源 yum源 curl -o /etc/yum.repos.d/CentOS-Base.repo https://www.xmpan.com/Centos-6-Vault-Aliyun.repo epel源 curl -o /e…

家宽动态公网IP,使用docker+ddns 实现动态域名解析

官方地址&#xff1a;https://github.com/jeessy2/ddns-go 安装docker docker pull jeessy/ddns-godocker run -d --name ddns-go --restartalways --nethost -v /opt/ddns-go:/root jeessy/ddns-go然后访问ip端口 配置时注意如下

基于OpenVINO实现无监督异常检测

异常检测(AD) 在欺诈检测、网络安全和医疗诊断等关键任务应用中至关重要。由于数据的高维性和底层模式的复杂性&#xff0c;图像、视频和卫星图像等视觉数据中的异常检测尤其具有挑战性。然而&#xff0c;视觉异常检测对于检测制造中的缺陷、识别监控录像中的可疑活动以及检测医…