[Unity Demo]从零开始制作空洞骑士Hollow Knight第九集:制作小骑士基本的攻击行为Attack以及为敌人制作生命系统和受伤系统

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、制作小骑士基本的攻击行为Attack
    • 1.制作动画以及使用UNITY编辑器编辑
    • 2.使用代码实现扩展新的落地行为和重落地行为
    • 3.使用状态机实现击中敌人造成伤害机制
  • 二、为敌人制作生命系统
    • 1.使用代码制作生命系统
  • 三、为敌人制作受伤系统
    • 1.使用代码制作受伤系统
    • 2.制作受伤特效
  • 总结


前言

        警告:此篇文章难度较高而且复杂繁重,非常不适合刚刚入门的或者没看过我前几期的读者,我做了一整天才把明显的bug给解决了真的快要睁不开眼了,因此请读者如果在阅读后感到身体不适请立刻退出这篇文章,本期主要涉及的内容是:制作小骑士基本的攻击行为Attack以及为敌人制作生命系统和受伤系统,我已经把内容上传到我的github空洞骑士demo资产中,欢迎大家下载后在Unity研究。

GitHub - ForestDango/Hollow-Knight-Demo: A new Hollow Knight Demo after 2 years!


一、制作小骑士基本的攻击行为Attack

1.制作动画以及使用UNITY编辑器编辑 

我们来为小骑士添加几个新的tk2dSprite和tk2dSpriteAnimation:

我们把Knight文件夹中所有和Slash有关的文件夹里面的Sprite全部拖进去,然后点击Apply:

然后到Animation中,我们每一个种类的Slash都只要前六张图像,Clip Time统一设置成0.3

 

 

除此之外我们还做刀光效果也就是SlashEffect,为每一种 SlashEffect创建自己单独的tk2dSprite和tk2dSpriteAnimation:

同样,我们找到 Knight文件夹中所有和Slashffect有关的文件夹一个个拖上去:

在Animation中我们只要0,2,4三张图像,ClipTime设置成0.2:

万事准备OK后我们就给小骑士创建好这样的Attack子对象:

 一定要记得给Attacks下的每一个子对象设置Layer为HeroAttack:

选择好HeroAttack可以交互的层级:

 然后为每一种Slash Effect添加如下图所示的组件:

那些脚本你们先别管(比如里面的NailSlash.cs),我后续都会讲的。我们先设置好PolygonCollider2D的参数,这里有个小技巧,使用tk2dSprite的这个显示刀光的图片,然后对着这个大小调整好碰撞箱大小,注意要勾选isTrigger,调整好后

调整好后记得关上MeshRenderer和 PolygonCollider2D,我们只在需要的时候用到它们:

关于Slash的子对象Clash Tink目前还用不上,我们先把它放一边以后用的时候再扩展:

然后其它三个如上同理,最后打开Gizmos后效果如下所示:

2.使用代码实现扩展新的落地行为和重落地行为 

        我们每添加一个行为就要到HeroActions中添加一个PlayerAction,这次是attack

重大失误!!! 这个moveVector = CreateTwoAxisPlayerAction(left, right, down, up);它的顺序应该是-x,x-y,y。我之前倒数两个参数位置搞反了,所以检测的y轴输入时反的,现在已经更改过来了,只能说还好Debug发现的早。

using System;
using InControl;public class HeroActions : PlayerActionSet
{public PlayerAction left;public PlayerAction right;public PlayerAction up;public PlayerAction down;public PlayerTwoAxisAction moveVector;public PlayerAction attack;public PlayerAction jump;public PlayerAction dash;public HeroActions(){left = CreatePlayerAction("Left");left.StateThreshold = 0.3f;right = CreatePlayerAction("Right");right.StateThreshold = 0.3f;up = CreatePlayerAction("Up");up.StateThreshold = 0.3f;down = CreatePlayerAction("Down");down.StateThreshold = 0.3f;moveVector = CreateTwoAxisPlayerAction(left, right, down, up); //重大失误!!!moveVector.LowerDeadZone = 0.15f;moveVector.UpperDeadZone = 0.95f;attack = CreatePlayerAction("Attack");jump = CreatePlayerAction("Jump");dash = CreatePlayerAction("Dash");}
}

然后就到InputHandler.cs中添加一行代码:    AddKeyBinding(inputActions.attack, "Z");

using System;
using System.Collections;
using System.Collections.Generic;
using GlobalEnums;
using InControl;
using UnityEngine;public class InputHandler : MonoBehaviour
{public InputDevice gameController;public HeroActions inputActions;public void Awake(){inputActions = new HeroActions();}public void Start(){MapKeyboardLayoutFromGameSettings();if(InputManager.ActiveDevice != null && InputManager.ActiveDevice.IsAttached){}else{gameController = InputDevice.Null;}Debug.LogFormat("Input Device set to {0}.", new object[]{gameController.Name});}private void MapKeyboardLayoutFromGameSettings(){AddKeyBinding(inputActions.up, "UpArrow");AddKeyBinding(inputActions.down, "DownArrow");AddKeyBinding(inputActions.left, "LeftArrow");AddKeyBinding(inputActions.right, "RightArrow");AddKeyBinding(inputActions.attack, "Z");AddKeyBinding(inputActions.jump, "X");AddKeyBinding(inputActions.dash, "D");}private static void AddKeyBinding(PlayerAction action, string savedBinding){Mouse mouse = Mouse.None;Key key;if (!Enum.TryParse(savedBinding, out key) && !Enum.TryParse(savedBinding, out mouse)){return;}if (mouse != Mouse.None){action.AddBinding(new MouseBindingSource(mouse));return;}action.AddBinding(new KeyBindingSource(new Key[]{key}));}}

来到HeroControllerState部分,我们添加几个新的状态:

    public bool attacking;
    public bool altAttack;
    public bool upAttacking;
    public bool downAttacking; 

[Serializable]
public class HeroControllerStates
{public bool facingRight;public bool onGround;public bool wasOnGround;public bool attacking;public bool altAttack;public bool upAttacking;public bool downAttacking;public bool inWalkZone;public bool jumping;public bool falling;public bool dashing;public bool backDashing;public bool touchingWall;public bool wallSliding;public bool willHardLand;public bool preventDash;public bool preventBackDash;public bool dashCooldown;public bool backDashCooldown;public bool isPaused;public HeroControllerStates(){facingRight = false;onGround = false;wasOnGround = false;attacking = false;altAttack = false;upAttacking = false;downAttacking = false;inWalkZone = false;jumping = false;falling = false;dashing = false;backDashing = false;touchingWall = false;wallSliding = false;willHardLand = false;preventDash = false;preventBackDash = false;dashCooldown = false;backDashCooldown = false;isPaused = false;}

回到HeroAnimationController.cs中,我们为attack攻击判断哪种攻击类型:

if(cState.attacking)
    {
        if (cState.upAttacking)
        {
                Play("UpSlash");
        }
        else if (cState.downAttacking)
        {
                Play("DownSlash");
        }
        else if (!cState.altAttack)
        {
                Play("Slash");
        }
        else
        {
                Play("SlashAlt");
        }
    }

using System;
using GlobalEnums;
using UnityEngine;public class HeroAnimationController : MonoBehaviour
{private HeroController heroCtrl;private HeroControllerStates cState;private tk2dSpriteAnimator animator;private PlayerData pd;private bool wasFacingRight;private bool playLanding;private bool playRunToIdle;//播放"Run To Idle"动画片段private bool playDashToIdle; //播放"Dash To Idle"动画片段private bool playBackDashToIdleEnd; //播放"Back Dash To Idle"动画片段(其实并不会播放)private bool changedClipFromLastFrame;public ActorStates actorStates { get; private set; }public ActorStates prevActorStates { get; private set; }private void Awake(){heroCtrl = HeroController.instance;cState = heroCtrl.cState;animator = GetComponent<tk2dSpriteAnimator>();}private void Start(){pd = PlayerData.instance;ResetAll();actorStates = heroCtrl.hero_state;if(heroCtrl.hero_state == ActorStates.airborne){animator.PlayFromFrame("Airborne", 7);return;}PlayIdle();}private void Update(){UpdateAnimation();if (cState.facingRight){wasFacingRight = true;return;}wasFacingRight = false;}private void UpdateAnimation(){changedClipFromLastFrame = false;if (playLanding){Play("Land");animator.AnimationCompleted = new Action<tk2dSpriteAnimator, tk2dSpriteAnimationClip>(AnimationCompleteDelegate);playLanding = false;}if (playRunToIdle){Play("Run To Idle");animator.AnimationCompleted = new Action<tk2dSpriteAnimator, tk2dSpriteAnimationClip>(AnimationCompleteDelegate);playRunToIdle = false;}if (playBackDashToIdleEnd){Play("Backdash Land 2");//处理animation播放完成后的事件(其实并不会播放)animator.AnimationCompleted = new Action<tk2dSpriteAnimator, tk2dSpriteAnimationClip>(AnimationCompleteDelegate);playDashToIdle = false;}if (playDashToIdle){Play("Dash To Idle");//处理animation播放完成后的事件animator.AnimationCompleted = new Action<tk2dSpriteAnimator, tk2dSpriteAnimationClip>(AnimationCompleteDelegate);playDashToIdle = false;}if (actorStates == ActorStates.no_input){//TODO:}else if (cState.dashing){if (heroCtrl.dashingDown){Play("Dash Down");}else{Play("Dash"); //通过cState.dashing判断是否播放Dash动画片段}}else if (cState.backDashing){Play("Back Dash");}else if(cState.attacking){if (cState.upAttacking){Play("UpSlash");}else if (cState.downAttacking){Play("DownSlash");}else if (!cState.altAttack){Play("Slash");}else{Play("SlashAlt");}}else if (actorStates == ActorStates.idle){//TODO:if (CanPlayIdle()){PlayIdle();}}else if (actorStates == ActorStates.running){if (!animator.IsPlaying("Turn")){if (cState.inWalkZone){if (!animator.IsPlaying("Walk")){Play("Walk");}}else{PlayRun();}}}else if (actorStates == ActorStates.airborne){if (cState.jumping){if (!animator.IsPlaying("Airborne")){animator.PlayFromFrame("Airborne", 0);}}else if (cState.falling){if (!animator.IsPlaying("Airborne")){animator.PlayFromFrame("Airborne", 7);}}else if (!animator.IsPlaying("Airborne")){animator.PlayFromFrame("Airborne", 3);}}//(其实并不会播放)else if (actorStates == ActorStates.dash_landing){animator.Play("Dash Down Land");}else if(actorStates == ActorStates.hard_landing){animator.Play("HardLand");}if (cState.facingRight){if(!wasFacingRight && cState.onGround && CanPlayTurn()){Play("Turn");}wasFacingRight = true;}else{if (wasFacingRight && cState.onGround && CanPlayTurn()){Play("Turn");}wasFacingRight = false;}ResetPlays();}private void AnimationCompleteDelegate(tk2dSpriteAnimator anim, tk2dSpriteAnimationClip clip){if(clip.name == "Land"){PlayIdle();}if(clip.name == "Run To Idle"){PlayIdle();}if(clip.name == "Backdash To Idle")//(其实并不会播放){PlayIdle();}if(clip.name == "Dash To Idle"){PlayIdle();}}private void Play(string clipName){if(clipName != animator.CurrentClip.name){changedClipFromLastFrame = true;}animator.Play(clipName);}private void PlayRun(){animator.Play("Run");}public void PlayIdle(){animator.Play("Idle");}public void StopAttack(){if(animator.IsPlaying("UpSlash") || animator.IsPlaying("DownSlash")){animator.Stop();}}public void FinishedDash(){playDashToIdle = true;}private void ResetAll(){playLanding = false;playRunToIdle = false;playDashToIdle = false;wasFacingRight = false;}private void ResetPlays(){playLanding = false;playRunToIdle = false;playDashToIdle = false;}public void UpdateState(ActorStates newState){if(newState != actorStates){if(actorStates == ActorStates.airborne && newState == ActorStates.idle && !playLanding){playLanding = true;}if(actorStates == ActorStates.running && newState == ActorStates.idle && !playRunToIdle && !cState.inWalkZone){playRunToIdle = true;}prevActorStates = actorStates;actorStates = newState;}}private bool CanPlayIdle(){return !animator.IsPlaying("Land") && !animator.IsPlaying("Run To Idle") && !animator.IsPlaying("Dash To Idle") && !animator.IsPlaying("Backdash Land") && !animator.IsPlaying("Backdash Land 2") && !animator.IsPlaying("LookUpEnd") && !animator.IsPlaying("LookDownEnd") && !animator.IsPlaying("Exit Door To Idle") && !animator.IsPlaying("Wake Up Ground") && !animator.IsPlaying("Hazard Respawn");}private bool CanPlayTurn(){return !animator.IsPlaying("Wake Up Ground") && !animator.IsPlaying("Hazard Respawn"); ;}}

回到HeroController.cs当中,我们来为攻击添加完整的行为状态控制机:

    [SerializeField] private NailSlash slashComponent; //决定使用哪种攻击的NailSlash
    [SerializeField] private PlayMakerFSM slashFsm;//决定使用哪种攻击的PlayMakerFSM

    public NailSlash normalSlash;
    public NailSlash altetnateSlash;
    public NailSlash upSlash;
    public NailSlash downSlash;

    public PlayMakerFSM normalSlashFsm; 
    public PlayMakerFSM altetnateSlashFsm;
    public PlayMakerFSM upSlashFsm;
    public PlayMakerFSM downSlashFsm;

    private bool attackQueuing; //是否开始攻击计数步骤
    private int attackQueueSteps; //攻击计数步骤

    private float attack_time;
    private float attackDuration; //攻击状态持续时间,根据有无护符来决定
    private float attack_cooldown;
    private float altAttackTime; //当时间超出可按二段攻击的时间后,cstate.altattack就会为false

    public float ATTACK_DURATION; //无护符时攻击状态持续时间
    public float ATTACK_COOLDOWN_TIME; //攻击后冷却时间
    public float ATTACK_RECOVERY_TIME; //攻击恢复时间,一旦超出这个时间就退出攻击状态
    public float ALT_ATTACK_RESET; //二段攻击重置时间

    private int ATTACK_QUEUE_STEPS = 5; //超过5步即可开始攻击

    private float NAIL_TERRAIN_CHECK_TIME = 0.12f;

在Update()中我们当攻击时间超过attackDuration后重置攻击,并开启冷却倒计时attack_cooldown :

  else if (hero_state != ActorStates.no_input)
        {
            LookForInput();

            if(cState.attacking && !cState.dashing)
           {
                attack_time += Time.deltaTime;
                if(attack_time >= attackDuration)
                    {
                    ResetAttacks();
                    animCtrl.StopAttack();
                   }
             }
        }

 if(attack_cooldown > 0f)
    {
            attack_cooldown -= Time.deltaTime;
    }

在方法LookForQueueInput()中我们判断是否按下攻击键:

 if(inputHandler.inputActions.attack.IsPressed && attackQueueSteps <= ATTACK_QUEUE_STEPS && CanAttack() && attackQueuing)
        {
                Debug.LogFormat("Start Do Attack");
                DoAttack();
        }

以及进入attackQueuing:

if(inputHandler.inputActions.attack.WasPressed)
        {
                if (CanAttack())
        {
                    DoAttack();
        }
        else
        {
                    attackQueueSteps = 0;
                    attackQueuing = true;
                }
        }

在Update()中我们直接++

    if(attackQueuing)
    {
            attackQueueSteps++;
    }

如果没按下攻击键就attackQueuing = false;

 if (!inputHandler.inputActions.attack.IsPressed)
        {
                attackQueuing = false;
        }

当然还有Attack(),DoAttack(),CanAttack(),CancelAttack()等等方法构成完整的攻击行为:

private bool CanAttack()
    {
        return hero_state != ActorStates.no_input && hero_state != ActorStates.hard_landing && hero_state != ActorStates.dash_landing && attack_cooldown <= 0f && !cState.attacking && !cState.dashing;
    }

private void DoAttack()
    {


        attack_cooldown = ATTACK_COOLDOWN_TIME;
        if(vertical_input > Mathf.Epsilon)
    {
            Attack(AttackDirection.upward);
            StartCoroutine(CheckForTerrainThunk(AttackDirection.upward));
            return;
    }
        if(vertical_input >= -Mathf.Epsilon)
    {
            Attack(AttackDirection.normal);
            StartCoroutine(CheckForTerrainThunk(AttackDirection.normal));
            return;
        }
        if(hero_state != ActorStates.idle && hero_state != ActorStates.running)
    {
            Attack(AttackDirection.downward);
            StartCoroutine(CheckForTerrainThunk(AttackDirection.downward));
            return;
        }
        Attack(AttackDirection.normal);
        StartCoroutine(CheckForTerrainThunk(AttackDirection.normal));
    }

private void Attack(AttackDirection attackDir)
    {
        if(Time.timeSinceLevelLoad - altAttackTime > ALT_ATTACK_RESET)
    {
            cState.altAttack = false;
    }
        cState.attacking = true;
        attackDuration = ATTACK_DURATION;

        if (attackDir == AttackDirection.normal)
        {
            if (!cState.altAttack)
            {
                slashComponent = normalSlash;
                slashFsm = normalSlashFsm;
                cState.altAttack = true;

            }
            else
            {
                slashComponent = altetnateSlash;
                slashFsm = altetnateSlashFsm;
                cState.altAttack = false;
            }
        }
        else if (attackDir == AttackDirection.upward) 
        {
            slashComponent = upSlash;
            slashFsm = upSlashFsm;
            cState.upAttacking = true;

        }
        else if (attackDir == AttackDirection.downward)
        {
            slashComponent = downSlash;
            slashFsm = downSlashFsm;
            cState.downAttacking = true;

        }

        if(attackDir == AttackDirection.normal && cState.facingRight)
    {
            slashFsm.FsmVariables.GetFsmFloat("direction").Value = 0f;
    }
        else if (attackDir == AttackDirection.normal && !cState.facingRight)
        {
            slashFsm.FsmVariables.GetFsmFloat("direction").Value = 180f;
        }
        else if (attackDir == AttackDirection.upward)
        {
            slashFsm.FsmVariables.GetFsmFloat("direction").Value = 90f;
        }
        else if (attackDir == AttackDirection.downward)
        {
            slashFsm.FsmVariables.GetFsmFloat("direction").Value = 270f;
        }
        altAttackTime = Time.timeSinceLevelLoad;
        slashComponent.StartSlash();

    }

 完整的代码如下:

using System;
using System.Collections;
using System.Collections.Generic;
using HutongGames.PlayMaker;
using GlobalEnums;
using UnityEngine;public class HeroController : MonoBehaviour
{public ActorStates hero_state;public ActorStates prev_hero_state;public bool acceptingInput = true;public float move_input;public float vertical_input;private Vector2 current_velocity;public float WALK_SPEED = 3.1f;//走路速度public float RUN_SPEED = 5f;//跑步速度public float JUMP_SPEED = 5f;//跳跃的食欲[SerializeField] private NailSlash slashComponent; //决定使用哪种攻击的NailSlash[SerializeField] private PlayMakerFSM slashFsm;//决定使用哪种攻击的PlayMakerFSMpublic NailSlash normalSlash;public NailSlash altetnateSlash;public NailSlash upSlash;public NailSlash downSlash;public PlayMakerFSM normalSlashFsm; public PlayMakerFSM altetnateSlashFsm;public PlayMakerFSM upSlashFsm;public PlayMakerFSM downSlashFsm;private bool attackQueuing; //是否开始攻击计数步骤private int attackQueueSteps; //攻击计数步骤private float attack_time;private float attackDuration; //攻击状态持续时间,根据有无护符来决定private float attack_cooldown;private float altAttackTime; //当时间超出可按二段攻击的时间后,cstate.altattack就会为falsepublic float ATTACK_DURATION; //无护符时攻击状态持续时间public float ATTACK_COOLDOWN_TIME; //攻击后冷却时间public float ATTACK_RECOVERY_TIME; //攻击恢复时间,一旦超出这个时间就退出攻击状态public float ALT_ATTACK_RESET; //二段攻击重置时间private int ATTACK_QUEUE_STEPS = 5; //超过5步即可开始攻击private float NAIL_TERRAIN_CHECK_TIME = 0.12f;private int jump_steps; //跳跃的步private int jumped_steps; //已经跳跃的步private int jumpQueueSteps; //跳跃队列的步private bool jumpQueuing; //是否进入跳跃队列中private int jumpReleaseQueueSteps; //释放跳跃后的步private bool jumpReleaseQueuing; //是否进入释放跳跃队列中private bool jumpReleaseQueueingEnabled; //是否允许进入释放跳跃队列中public float MAX_FALL_VELOCITY; //最大下落速度(防止速度太快了)public int JUMP_STEPS; //最大跳跃的步public int JUMP_STEPS_MIN; //最小跳跃的步private int JUMP_QUEUE_STEPS; //最大跳跃队列的步private int JUMP_RELEASE_QUEUE_STEPS;//最大跳跃释放队列的步private int dashQueueSteps;private bool dashQueuing;private float dashCooldownTimer; //冲刺冷却时间private float dash_timer; //正在冲刺计数器private float back_dash_timer; 正在后撤冲刺计数器 (标注:此行代码无用待后续开发)private float dashLandingTimer;private bool airDashed;//是否是在空中冲刺public bool dashingDown;//是否正在执行向下冲刺public PlayMakerFSM dashBurst;public GameObject dashParticlesPrefab;//冲刺粒子效果预制体public GameObject backDashPrefab; //后撤冲刺特效预制体 标注:此行代码无用待后续开发private GameObject backDash;//后撤冲刺 (标注:此行代码无用待后续开发)private GameObject dashEffect;//后撤冲刺特效生成 (标注:此行代码无用待后续开发)public float DASH_SPEED; //冲刺时的速度public float DASH_TIME; //冲刺时间public float DASH_COOLDOWN; //冲刺冷却时间public float BACK_DASH_SPEED;//后撤冲刺时的速度 (标注:此行代码无用待后续开发)public float BACK_DASH_TIME;//后撤冲刺时间 (标注:此行代码无用待后续开发)public float BACK_DASH_COOLDOWN; //后撤冲刺冷却时间 (标注:此行代码无用待后续开发)public float DASH_LANDING_TIME;public int DASH_QUEUE_STEPS; //最大冲刺队列的步public float fallTimer { get; private set; }private float hardLandingTimer; //正在hardLanding的计时器,大于就将状态改为grounded并BackOnGround()private float hardLandFailSafeTimer; //进入hardLand后玩家失去输入的一段时间private bool hardLanded; //是否已经hardLand了public float HARD_LANDING_TIME; //正在hardLanding花费的时间。public float BIG_FALL_TIME;  //判断是否是hardLanding所需要的事件,大于它就是public GameObject hardLandingEffectPrefab;private float prevGravityScale;private int landingBufferSteps;private int LANDING_BUFFER_STEPS = 5;private bool fallRumble; //是否开启掉落时相机抖动public GameObject softLandingEffectPrefab;public bool touchingWall; //是否接触到墙public bool touchingWallL; //是否接触到的墙左边public bool touchingWallR; //是否接触到的墙右边private Rigidbody2D rb2d;private BoxCollider2D col2d;private GameManager gm;public PlayerData playerData;private InputHandler inputHandler;public HeroControllerStates cState;private HeroAnimationController animCtrl;private HeroAudioController audioCtrl; private static HeroController _instance;public static HeroController instance{get{if (_instance == null)_instance = FindObjectOfType<HeroController>();if(_instance && Application.isPlaying){DontDestroyOnLoad(_instance.gameObject);}return _instance;}}public HeroController(){ATTACK_QUEUE_STEPS = 5;NAIL_TERRAIN_CHECK_TIME = 0.12f;JUMP_QUEUE_STEPS = 2;JUMP_RELEASE_QUEUE_STEPS = 2;LANDING_BUFFER_STEPS = 5;}private void Awake(){if(_instance == null){_instance = this;DontDestroyOnLoad(this);}else if(this != _instance){Destroy(gameObject);return;}SetupGameRefs();}private void SetupGameRefs(){if (cState == null)cState = new HeroControllerStates();rb2d = GetComponent<Rigidbody2D>();col2d = GetComponent<BoxCollider2D>();animCtrl = GetComponent<HeroAnimationController>();audioCtrl = GetComponent<HeroAudioController>();gm = GameManager.instance;playerData = PlayerData.instance;inputHandler = gm.GetComponent<InputHandler>();}private void Start(){playerData = PlayerData.instance;if (dashBurst == null){Debug.Log("DashBurst came up null, locating manually");dashBurst = FSMUtility.GetFSM(transform.Find("Effects").Find("Dash Burst").gameObject);}}private void Update(){current_velocity = rb2d.velocity;FallCheck();FailSafeCheck();if (hero_state == ActorStates.running && !cState.dashing && !cState.backDashing){if (cState.inWalkZone){audioCtrl.StopSound(HeroSounds.FOOTSETP_RUN);audioCtrl.PlaySound(HeroSounds.FOOTSTEP_WALK);}else{audioCtrl.StopSound(HeroSounds.FOOTSTEP_WALK);audioCtrl.PlaySound(HeroSounds.FOOTSETP_RUN);}}else{audioCtrl.StopSound(HeroSounds.FOOTSETP_RUN);audioCtrl.StopSound(HeroSounds.FOOTSTEP_WALK);}if(hero_state == ActorStates.dash_landing){dashLandingTimer += Time.deltaTime;if(dashLandingTimer > DASH_LANDING_TIME){BackOnGround();}}if (hero_state == ActorStates.hard_landing){hardLandingTimer += Time.deltaTime;if (hardLandingTimer > HARD_LANDING_TIME){SetState(ActorStates.grounded);BackOnGround();}}if (hero_state == ActorStates.no_input){}else if (hero_state != ActorStates.no_input){LookForInput();if(cState.attacking && !cState.dashing){attack_time += Time.deltaTime;if(attack_time >= attackDuration){ResetAttacks();animCtrl.StopAttack();}}}LookForQueueInput();if(attack_cooldown > 0f){attack_cooldown -= Time.deltaTime;}if (dashCooldownTimer > 0f) //计时器在Update中-= Time.deltaTime{dashCooldownTimer -= Time.deltaTime;}}private void FixedUpdate(){if(hero_state == ActorStates.hard_landing || hero_state == ActorStates.dash_landing){ResetMotion();}else if(hero_state == ActorStates.no_input){}else if (hero_state != ActorStates.no_input){if(hero_state == ActorStates.running){if(move_input > 0f){if (CheckForBump(CollisionSide.right)){//rb2d.velocity = new Vector2(rb2d.velocity.x, BUMP_VELOCITY);}}else if (CheckForBump(CollisionSide.left)){//rb2d.velocity = new Vector2(rb2d.velocity.x, -BUMP_VELOCITY);}}if (!cState.dashing && !cState.backDashing){Move(move_input);if (!cState.attacking || attack_time >= ATTACK_RECOVERY_TIME){if (move_input > 0f && !cState.facingRight){FlipSprite();}else if (move_input < 0f && cState.facingRight){FlipSprite();}}}}if (cState.jumping) //如果cState.jumping就Jump{Jump();}if (cState.dashing)//如果cState.dashing就Dash{Dash();}//限制速度if(rb2d.velocity.y < -MAX_FALL_VELOCITY){rb2d.velocity = new Vector2(rb2d.velocity.x, -MAX_FALL_VELOCITY);}if (jumpQueuing){jumpQueueSteps++;}if (dashQueuing) //跳跃队列开始{dashQueueSteps++;}if(attackQueuing){attackQueueSteps++;}if (landingBufferSteps > 0){landingBufferSteps--;}if (jumpReleaseQueueSteps > 0){jumpReleaseQueueSteps--;}cState.wasOnGround = cState.onGround;}/// <summary>/// 小骑士移动的函数/// </summary>/// <param name="move_direction"></param>private void Move(float move_direction){if (cState.onGround){SetState(ActorStates.grounded);}if(acceptingInput){if (cState.inWalkZone){rb2d.velocity = new Vector2(move_direction * WALK_SPEED, rb2d.velocity.y);return;}rb2d.velocity = new Vector2(move_direction * RUN_SPEED, rb2d.velocity.y);}}private void Attack(AttackDirection attackDir){if(Time.timeSinceLevelLoad - altAttackTime > ALT_ATTACK_RESET){cState.altAttack = false;}cState.attacking = true;attackDuration = ATTACK_DURATION;if (attackDir == AttackDirection.normal){if (!cState.altAttack){slashComponent = normalSlash;slashFsm = normalSlashFsm;cState.altAttack = true;}else{slashComponent = altetnateSlash;slashFsm = altetnateSlashFsm;cState.altAttack = false;}}else if (attackDir == AttackDirection.upward) {slashComponent = upSlash;slashFsm = upSlashFsm;cState.upAttacking = true;}else if (attackDir == AttackDirection.downward){slashComponent = downSlash;slashFsm = downSlashFsm;cState.downAttacking = true;}if(attackDir == AttackDirection.normal && cState.facingRight){slashFsm.FsmVariables.GetFsmFloat("direction").Value = 0f;}else if (attackDir == AttackDirection.normal && !cState.facingRight){slashFsm.FsmVariables.GetFsmFloat("direction").Value = 180f;}else if (attackDir == AttackDirection.upward){slashFsm.FsmVariables.GetFsmFloat("direction").Value = 90f;}else if (attackDir == AttackDirection.downward){slashFsm.FsmVariables.GetFsmFloat("direction").Value = 270f;}altAttackTime = Time.timeSinceLevelLoad;slashComponent.StartSlash();}private void DoAttack(){attack_cooldown = ATTACK_COOLDOWN_TIME;if(vertical_input > Mathf.Epsilon){Attack(AttackDirection.upward);StartCoroutine(CheckForTerrainThunk(AttackDirection.upward));return;}if(vertical_input >= -Mathf.Epsilon){Attack(AttackDirection.normal);StartCoroutine(CheckForTerrainThunk(AttackDirection.normal));return;}if(hero_state != ActorStates.idle && hero_state != ActorStates.running){Attack(AttackDirection.downward);StartCoroutine(CheckForTerrainThunk(AttackDirection.downward));return;}Attack(AttackDirection.normal);StartCoroutine(CheckForTerrainThunk(AttackDirection.normal));}private bool CanAttack(){return hero_state != ActorStates.no_input && hero_state != ActorStates.hard_landing && hero_state != ActorStates.dash_landing && attack_cooldown <= 0f && !cState.attacking && !cState.dashing;}//TODO:private void CancelAttack(){}private void ResetAttacks(){cState.attacking = false;cState.upAttacking = false;cState.downAttacking = false;attack_time = 0f;}/// <summary>/// 小骑士跳跃的函数/// </summary>private void Jump(){if (jump_steps <= JUMP_STEPS){rb2d.velocity = new Vector2(rb2d.velocity.x, JUMP_SPEED);jump_steps++;jumped_steps++;return;}CancelJump();}/// <summary>/// 取消跳跃,这个在释放跳跃键时有用/// </summary>private void CancelJump(){cState.jumping = false;jumpReleaseQueuing = false;jump_steps = 0;}/// <summary>/// 标注:此函数暂且不具备任何内容待后续开发/// </summary>private void BackDash(){}/// <summary>/// 冲刺时执行的函数/// </summary>private void Dash(){AffectedByGravity(false); //不受到重力影响ResetHardLandingTimer();if(dash_timer > DASH_TIME){FinishedDashing();//大于则结束冲刺return;}float num;num = DASH_SPEED;if (dashingDown){rb2d.velocity = new Vector2(0f, -num);}else if (cState.facingRight){if (CheckForBump(CollisionSide.right)){//rb2d.velocity = new Vector2(num, cState.onGround ? BUMP_VELOCITY : BUMP_VELOCITY_DASH);}else{rb2d.velocity = new Vector2(num, 0f); //为人物的velocity赋值DASH_SPEED}}else if (CheckForBump(CollisionSide.left)){//rb2d.velocity = new Vector2(-num, cState.onGround ? BUMP_VELOCITY : BUMP_VELOCITY_DASH);}else{rb2d.velocity = new Vector2(-num, 0f);}dash_timer += Time.deltaTime;}private void HeroDash(){if (!cState.onGround){airDashed = true;}audioCtrl.StopSound(HeroSounds.FOOTSETP_RUN);audioCtrl.StopSound(HeroSounds.FOOTSTEP_WALK);audioCtrl.PlaySound(HeroSounds.DASH);if (inputHandler.inputActions.right.IsPressed){FaceRight();}else if (inputHandler.inputActions.left.IsPressed){FaceLeft();}cState.dashing = true;dashQueueSteps = 0;HeroActions inputActions = inputHandler.inputActions;if(inputActions.down.IsPressed && !cState.onGround && playerData.equippedCharm_31 && !inputActions.left.IsPressed && !inputActions.right.IsPressed){dashBurst.transform.localPosition = new Vector3(-0.07f, 3.74f, 0.01f); //生成dashBurst后设置位置和旋转角dashBurst.transform.localEulerAngles = new Vector3(0f, 0f, 90f);dashingDown = true;}else{dashBurst.transform.localPosition = new Vector3(4.11f, -0.55f, 0.001f); //生成dashBurst后设置位置和旋转角dashBurst.transform.localEulerAngles = new Vector3(0f, 0f, 0f);dashingDown = false;}dashCooldownTimer = DASH_COOLDOWN;dashBurst.SendEvent("PLAY"); //发送dashBurst的FSM的事件PLAYdashParticlesPrefab.GetComponent<ParticleSystem>().enableEmission = true;if (cState.onGround){dashEffect = Instantiate(backDashPrefab, transform.position, Quaternion.identity);dashEffect.transform.localScale = new Vector3(transform.localScale.x * -1f, transform.localScale.y, transform.localScale.z);}}/// <summary>/// 判断是否可以后撤冲刺/// </summary>/// <returns></returns>public bool CanBackDash(){return !cState.dashing && hero_state != ActorStates.no_input && !cState.backDashing && !cState.preventBackDash && !cState.backDashCooldown && cState.onGround && playerData.canBackDash;} /// <summary>/// 判断是否可以冲刺/// </summary>/// <returns></returns>public bool CanDash(){return hero_state != ActorStates.no_input && hero_state != ActorStates.hard_landing && hero_state != ActorStates.dash_landing &&dashCooldownTimer <= 0f && !cState.dashing && !cState.backDashing && !cState.preventDash && (cState.onGround || !airDashed)  && playerData.canDash;}/// <summary>/// 结束冲刺/// </summary>private void FinishedDashing(){CancelDash();AffectedByGravity(true);//物体重新受到重力的影响animCtrl.FinishedDash(); //该播放Dash To Idle动画片段了if (cState.touchingWall && !cState.onGround){if (touchingWallL){}if (touchingWallR){}}}/// <summary>/// 取消冲刺,将cState.dashing设置为false后动画将不再播放/// </summary>public void CancelDash(){cState.dashing = false;dash_timer = 0f; //重置冲刺时的计时器AffectedByGravity(true); //物体重新受到重力的影响if (dashParticlesPrefab.GetComponent<ParticleSystem>().enableEmission){dashParticlesPrefab.GetComponent<ParticleSystem>().enableEmission = false;}}private void CancelBackDash(){cState.backDashing = false;back_dash_timer = 0f;}/// <summary>/// 物体是否受到重力的影响/// </summary>/// <param name="gravityApplies"></param>private void AffectedByGravity(bool gravityApplies){float gravityScale = rb2d.gravityScale;if(rb2d.gravityScale > Mathf.Epsilon && !gravityApplies){prevGravityScale = rb2d.gravityScale;rb2d.gravityScale = 0f;return;}if(rb2d.gravityScale <= Mathf.Epsilon && gravityApplies){rb2d.gravityScale = prevGravityScale;prevGravityScale = 0f;}}private void FailSafeCheck(){if(hero_state == ActorStates.hard_landing){hardLandFailSafeTimer += Time.deltaTime;if(hardLandFailSafeTimer > HARD_LANDING_TIME + 0.3f){SetState(ActorStates.grounded);BackOnGround();hardLandFailSafeTimer = 0f;}}else{hardLandFailSafeTimer = 0f;}}/// <summary>/// 进入降落状态的检查/// </summary>private void FallCheck(){//如果y轴上的速度小于-1E-06F判断是否到地面上了if (rb2d.velocity.y < -1E-06F){if (!CheckTouchingGround()){cState.falling = true;cState.onGround = false;if(hero_state != ActorStates.no_input){SetState(ActorStates.airborne);}fallTimer += Time.deltaTime;if(fallTimer > BIG_FALL_TIME){if (!cState.willHardLand){cState.willHardLand = true;}if (!fallRumble){StartFallRumble();}}}}else{cState.falling = false;fallTimer = 0f;if (fallRumble){CancelFallEffects();}}}private void DoHardLanding(){AffectedByGravity(true);ResetInput();SetState(ActorStates.hard_landing);hardLanded = true;audioCtrl.PlaySound(HeroSounds.HARD_LANDING);Instantiate(hardLandingEffectPrefab, transform.position,Quaternion.identity);}public void ResetHardLandingTimer(){cState.willHardLand = false;hardLandingTimer = 0f;fallTimer = 0f;hardLanded = false;}private bool ShouldHardLand(Collision2D collision){return !collision.gameObject.GetComponent<NoHardLanding>() && cState.willHardLand && hero_state != ActorStates.hard_landing;}private void ResetInput(){move_input = 0f;vertical_input = 0f;}private void ResetMotion(){CancelJump();CancelDash();CancelBackDash();rb2d.velocity = Vector2.zero;}/// <summary>/// 翻转小骑士的localScale.x/// </summary>public void FlipSprite(){cState.facingRight = !cState.facingRight;Vector3 localScale = transform.localScale;localScale.x *= -1f;transform.localScale = localScale;}public void FaceRight(){cState.facingRight = true;Vector3 localScale = transform.localScale;localScale.x = -1f;transform.localScale = localScale;}public void FaceLeft(){cState.facingRight = false;Vector3 localScale = transform.localScale;localScale.x = 1f;transform.localScale = localScale;}private void LookForInput(){if (acceptingInput){move_input = inputHandler.inputActions.moveVector.Vector.x; //获取X方向的键盘输入vertical_input = inputHandler.inputActions.moveVector.Vector.y;//获取Y方向的键盘输入FilterInput();//规整化if (inputHandler.inputActions.jump.WasReleased && jumpReleaseQueueingEnabled){jumpReleaseQueueSteps = JUMP_RELEASE_QUEUE_STEPS;jumpReleaseQueuing = true;}if (!inputHandler.inputActions.jump.IsPressed){JumpReleased();}if (!inputHandler.inputActions.dash.IsPressed){if(cState.preventDash && !cState.dashCooldown){cState.preventDash = false;}dashQueuing = false;}if (!inputHandler.inputActions.attack.IsPressed){attackQueuing = false;}}}private void LookForQueueInput(){if (acceptingInput){if (inputHandler.inputActions.jump.WasPressed){if (CanJump()){HeroJump();}else{jumpQueueSteps = 0;jumpQueuing = true;}}if (inputHandler.inputActions.dash.WasPressed){if (CanDash()){HeroDash();}else{dashQueueSteps = 0;dashQueuing = true;}}if(inputHandler.inputActions.attack.WasPressed){if (CanAttack()){DoAttack();}else{attackQueueSteps = 0;attackQueuing = true;}}if (inputHandler.inputActions.jump.IsPressed){if(jumpQueueSteps <= JUMP_QUEUE_STEPS && CanJump() && jumpQueuing){Debug.LogFormat("Execute Hero Jump");HeroJump();}}if(inputHandler.inputActions.dash.IsPressed && dashQueueSteps <= DASH_QUEUE_STEPS && CanDash() && dashQueuing){Debug.LogFormat("Start Hero Dash");HeroDash();}if(inputHandler.inputActions.attack.IsPressed && attackQueueSteps <= ATTACK_QUEUE_STEPS && CanAttack() && attackQueuing){Debug.LogFormat("Start Do Attack");DoAttack();}}}/// <summary>/// 可以跳跃吗/// </summary>/// <returns></returns>private bool CanJump(){if(hero_state == ActorStates.no_input || hero_state == ActorStates.hard_landing || hero_state == ActorStates.dash_landing || cState.dashing || cState.backDashing ||  cState.jumping){return false;}if (cState.onGround){return true; //如果在地面上就return true}return false;}/// <summary>/// 小骑士跳跃行为播放声音以及设置cstate.jumping/// </summary>private void HeroJump(){audioCtrl.PlaySound(HeroSounds.JUMP);cState.jumping = true;jumpQueueSteps = 0;jumped_steps = 0;}private void HeroJumpNoEffect(){audioCtrl.PlaySound(HeroSounds.JUMP);cState.jumping = true;jumpQueueSteps = 0;jumped_steps = 0;}/// <summary>/// 取消跳跃/// </summary>public void CancelHeroJump(){if (cState.jumping){CancelJump();if(rb2d.velocity.y > 0f){rb2d.velocity = new Vector2(rb2d.velocity.x, 0f);}}}private void JumpReleased(){if(rb2d.velocity.y > 0f &&jumped_steps >= JUMP_STEPS_MIN){if (jumpReleaseQueueingEnabled){if(jumpReleaseQueuing && jumpReleaseQueueSteps <= 0){rb2d.velocity = new Vector2(rb2d.velocity.x, 0f); //取消跳跃并且设置y轴速度为0CancelJump();}}else{rb2d.velocity = new Vector2(rb2d.velocity.x, 0f);CancelJump();}}jumpQueuing = false;}/// <summary>/// 设置玩家的ActorState的新类型/// </summary>/// <param name="newState"></param>private void SetState(ActorStates newState){if(newState == ActorStates.grounded){if(Mathf.Abs(move_input) > Mathf.Epsilon){newState  = ActorStates.running;}else{newState = ActorStates.idle;}}else if(newState == ActorStates.previous){newState = prev_hero_state;}if(newState != hero_state){prev_hero_state = hero_state;hero_state = newState;animCtrl.UpdateState(newState);}}/// <summary>/// 回到地面上时执行的函数/// </summary>public void BackOnGround(){if(landingBufferSteps <= 0){landingBufferSteps = LANDING_BUFFER_STEPS;if(!cState.onGround && !hardLanded){Instantiate(softLandingEffectPrefab, transform.position,Quaternion.identity); //TODO:}}cState.falling = false;fallTimer = 0f;dashLandingTimer = 0f;cState.willHardLand = false;hardLandingTimer = 0f;hardLanded = false;jump_steps = 0;SetState(ActorStates.grounded);cState.onGround = true;airDashed = false;}/// <summary>/// 开启在下落时晃动/// </summary>public void StartFallRumble(){fallRumble = true;audioCtrl.PlaySound(HeroSounds.FALLING);}public void CancelFallEffects(){fallRumble = false;audioCtrl.StopSound(HeroSounds.FALLING);}/// <summary>/// 规整化输入/// </summary>private void FilterInput(){if (move_input > 0.3f){move_input = 1f;}else if (move_input < -0.3f){move_input = -1f;}else{move_input = 0f;}if (vertical_input > 0.5f){vertical_input = 1f;return;}if (vertical_input < -0.5f){vertical_input = -1f;return;}vertical_input = 0f;}private void OnCollisionEnter2D(Collision2D collision){if(collision.gameObject.layer == LayerMask.NameToLayer("Terrain") && collision.gameObject.CompareTag("HeroWalkable") && CheckTouchingGround()){}if(hero_state != ActorStates.no_input){if(collision.gameObject.layer == LayerMask.NameToLayer("Terrain") || collision.gameObject.CompareTag("HeroWalkable")){CollisionSide collisionSide = FindCollisionSide(collision);//如果头顶顶到了if (collisionSide == CollisionSide.top){if (cState.jumping){CancelJump();}}//如果底下碰到了if (collisionSide == CollisionSide.bottom){if(ShouldHardLand(collision)){DoHardLanding();}else if(collision.gameObject.GetComponent<SteepSlope>() == null && hero_state != ActorStates.hard_landing){BackOnGround();}if(cState.dashing && dashingDown){AffectedByGravity(true);SetState(ActorStates.dash_landing);hardLanded = true;return;}}}}else if(hero_state == ActorStates.no_input){}}private void OnCollisionStay2D(Collision2D collision){if(hero_state != ActorStates.no_input && collision.gameObject.layer == LayerMask.NameToLayer("Terrain")){if (collision.gameObject.GetComponent<NonSlider>() == null){if (CheckStillTouchingWall(CollisionSide.left, false)){cState.touchingWall = true;touchingWallL = true;touchingWallR = false;}else if (CheckStillTouchingWall(CollisionSide.right, false)){cState.touchingWall = true;touchingWallL = false;touchingWallR = true;}else{cState.touchingWall = false;touchingWallL = false;touchingWallR = false;}if (CheckTouchingGround()){if (ShouldHardLand(collision)){DoHardLanding();}if(hero_state != ActorStates.hard_landing && hero_state != ActorStates.dash_landing && cState.falling){BackOnGround();return;}}else if(cState.jumping || cState.falling){cState.onGround = false;SetState(ActorStates.airborne);return;}}else{}}}private void OnCollisionExit2D(Collision2D collision){if(touchingWallL && !CheckStillTouchingWall(CollisionSide.left, false)){cState.touchingWall = false;touchingWallL = false;}if (touchingWallR && !CheckStillTouchingWall(CollisionSide.left, false)){cState.touchingWall = false;touchingWallR = false;}if(hero_state != ActorStates.no_input && collision.gameObject.layer == LayerMask.NameToLayer("Terrain") && !CheckTouchingGround()){cState.onGround = false;SetState(ActorStates.airborne);}}/// <summary>/// 检查是否接触到地面/// </summary>/// <returns></returns>public bool CheckTouchingGround(){Vector2 vector = new Vector2(col2d.bounds.min.x, col2d.bounds.center.y);Vector2 vector2 = col2d.bounds.center;Vector2 vector3 = new Vector2(col2d.bounds.max.x, col2d.bounds.center.y);float distance = col2d.bounds.extents.y + 0.16f;Debug.DrawRay(vector, Vector2.down, Color.yellow);Debug.DrawRay(vector2, Vector2.down, Color.yellow);Debug.DrawRay(vector3, Vector2.down, Color.yellow);RaycastHit2D raycastHit2D = Physics2D.Raycast(vector, Vector2.down, distance, LayerMask.GetMask("Terrain"));RaycastHit2D raycastHit2D2 = Physics2D.Raycast(vector2, Vector2.down, distance, LayerMask.GetMask("Terrain"));RaycastHit2D raycastHit2D3 = Physics2D.Raycast(vector3, Vector2.down, distance, LayerMask.GetMask("Terrain"));return raycastHit2D.collider != null || raycastHit2D2.collider != null || raycastHit2D3.collider != null;}/// <summary>/// 检查是否保持着接触着墙/// </summary>/// <param name="side"></param>/// <param name="checkTop"></param>/// <returns></returns>private bool CheckStillTouchingWall(CollisionSide side,bool checkTop = false){Vector2 origin = new Vector2(col2d.bounds.min.x, col2d.bounds.max.y);Vector2 origin2 = new Vector2(col2d.bounds.min.x, col2d.bounds.center.y);Vector2 origin3 = new Vector2(col2d.bounds.min.x, col2d.bounds.min.y);Vector2 origin4 = new Vector2(col2d.bounds.max.x, col2d.bounds.max.y);Vector2 origin5 = new Vector2(col2d.bounds.max.x, col2d.bounds.center.y);Vector2 origin6 = new Vector2(col2d.bounds.max.x, col2d.bounds.min.y);float distance = 0.1f;RaycastHit2D raycastHit2D = default(RaycastHit2D);RaycastHit2D raycastHit2D2 = default(RaycastHit2D);RaycastHit2D raycastHit2D3 = default(RaycastHit2D);if(side == CollisionSide.left){if (checkTop){raycastHit2D = Physics2D.Raycast(origin, Vector2.left, distance, LayerMask.GetMask("Terrain"));}raycastHit2D2 = Physics2D.Raycast(origin2, Vector2.left, distance, LayerMask.GetMask("Terrain"));raycastHit2D3 = Physics2D.Raycast(origin3, Vector2.left, distance, LayerMask.GetMask("Terrain"));}else{if(side != CollisionSide.right){Debug.LogError("Invalid CollisionSide specified.");return false;}if (checkTop){raycastHit2D = Physics2D.Raycast(origin4, Vector2.right, distance, LayerMask.GetMask("Terrain"));}raycastHit2D2 = Physics2D.Raycast(origin5, Vector2.right, distance, LayerMask.GetMask("Terrain"));raycastHit2D3 = Physics2D.Raycast(origin6, Vector2.right, distance, LayerMask.GetMask("Terrain"));}if(raycastHit2D2.collider != null){bool flag = true;if (raycastHit2D2.collider.isTrigger){flag = false;}if(raycastHit2D2.collider.GetComponent<SteepSlope>() != null){flag = false;}if (raycastHit2D2.collider.GetComponent<NonSlider>() != null){flag = false;}if (flag){return true;}}if (raycastHit2D3.collider != null){bool flag2 = true;if (raycastHit2D3.collider.isTrigger){flag2 = false;}if (raycastHit2D3.collider.GetComponent<SteepSlope>() != null){flag2 = false;}if (raycastHit2D3.collider.GetComponent<NonSlider>() != null){flag2 = false;}if (flag2){return true;}}if (checkTop && raycastHit2D.collider != null){bool flag3 = true;if (raycastHit2D.collider.isTrigger){flag3 = false;}if (raycastHit2D.collider.GetComponent<SteepSlope>() != null){flag3 = false;}if (raycastHit2D.collider.GetComponent<NonSlider>() != null){flag3 = false;}if (flag3){return true;}}return false;}public IEnumerator CheckForTerrainThunk(AttackDirection attackDir){bool terrainHit = false;float thunkTimer = NAIL_TERRAIN_CHECK_TIME;while (thunkTimer > 0.12f){if (!terrainHit){float num = 0.25f;float num2;if (attackDir == AttackDirection.normal){num2 = 2f;}else{num2 = 1.5f;}float num3 = 1f;//TODO:num2 *= num3;Vector2 size = new Vector2(0.45f, 0.45f);Vector2 origin = new Vector2(col2d.bounds.center.x, col2d.bounds.center.y + num);Vector2 origin2 = new Vector2(col2d.bounds.center.x, col2d.bounds.max.y);Vector2 origin3 = new Vector2(col2d.bounds.center.x, col2d.bounds.min.y);int layerMask = 33554432; //2的25次方,也就是Layer Soft Terrain;RaycastHit2D raycastHit2D = default(RaycastHit2D);if (attackDir == AttackDirection.normal){if ((cState.facingRight && !cState.wallSliding) || (!cState.facingRight && !cState.wallSliding)){raycastHit2D = Physics2D.BoxCast(origin, size, 0f, Vector2.right, num2, layerMask);}else{raycastHit2D = Physics2D.BoxCast(origin, size, 0f, Vector2.right, num3, layerMask);}}else if (attackDir == AttackDirection.upward){raycastHit2D = Physics2D.BoxCast(origin2, size, 0f, Vector2.up, num2, layerMask);}else if (attackDir == AttackDirection.downward){raycastHit2D = Physics2D.BoxCast(origin3, size, 0f, Vector2.down, num2, layerMask);}if (raycastHit2D.collider != null && !raycastHit2D.collider.isTrigger){NonThunker component = raycastHit2D.collider.GetComponent<NonThunker>();bool flag = !(component != null) || !component.active;if (flag){terrainHit = true;if (attackDir == AttackDirection.normal){if (cState.facingRight){}else{}}else if (attackDir == AttackDirection.upward){}}}thunkTimer -= Time.deltaTime;}yield return null;}}public bool CheckForBump(CollisionSide side){float num = 0.025f;float num2 = 0.2f;Vector2 vector = new Vector2(col2d.bounds.min.x + num2, col2d.bounds.min.y + 0.2f);Vector2 vector2 = new Vector2(col2d.bounds.min.x + num2, col2d.bounds.min.y - num);Vector2 vector3 = new Vector2(col2d.bounds.max.x - num2, col2d.bounds.min.y + 0.2f);Vector2 vector4 = new Vector2(col2d.bounds.max.x - num2, col2d.bounds.min.y - num);float num3 = 0.32f + num2;RaycastHit2D raycastHit2D = default(RaycastHit2D);RaycastHit2D raycastHit2D2 = default(RaycastHit2D);if(side == CollisionSide.left){Debug.DrawLine(vector2, vector2 + Vector2.left * num3, Color.cyan, 0.15f);Debug.DrawLine(vector, vector + Vector2.left * num3, Color.cyan, 0.15f);raycastHit2D = Physics2D.Raycast(vector2, Vector2.left, num3, LayerMask.GetMask("Terrain"));raycastHit2D2 = Physics2D.Raycast(vector, Vector2.left, num3, LayerMask.GetMask("Terrain"));}else if (side == CollisionSide.right){Debug.DrawLine(vector4, vector4 + Vector2.right * num3, Color.cyan, 0.15f);Debug.DrawLine(vector3, vector3 + Vector2.right * num3, Color.cyan, 0.15f);raycastHit2D = Physics2D.Raycast(vector4, Vector2.right, num3, LayerMask.GetMask("Terrain"));raycastHit2D2 = Physics2D.Raycast(vector3, Vector2.right, num3, LayerMask.GetMask("Terrain"));}else{Debug.LogError("Invalid CollisionSide specified.");}if(raycastHit2D2.collider != null && raycastHit2D.collider == null){Vector2 vector5 = raycastHit2D2.point + new Vector2((side == CollisionSide.right) ? 0.1f : -0.1f, 1f);RaycastHit2D raycastHit2D3 = Physics2D.Raycast(vector5, Vector2.down, 1.5f, LayerMask.GetMask("Terrain"));Vector2 vector6 = raycastHit2D2.point + new Vector2((side == CollisionSide.right) ? -0.1f : 0.1f, 1f);RaycastHit2D raycastHit2D4 = Physics2D.Raycast(vector6, Vector2.down, 1.5f, LayerMask.GetMask("Terrain"));if(raycastHit2D3.collider != null){Debug.DrawLine(vector5, raycastHit2D3.point, Color.cyan, 0.15f);if (!(raycastHit2D4.collider != null)){return true;}Debug.DrawLine(vector6, raycastHit2D4.point, Color.cyan, 0.15f);float num4 = raycastHit2D3.point.y - raycastHit2D4.point.y;if(num4 > 0f){Debug.Log("Bump Height: " + num4.ToString());return true;}}}return false;}/// <summary>/// 找到碰撞点的方向也就是上下左右/// </summary>/// <param name="collision"></param>/// <returns></returns>private CollisionSide FindCollisionSide(Collision2D collision){Vector2 normal = collision.GetSafeContact().Normal ;float x = normal.x;float y = normal.y;if(y >= 0.5f){return CollisionSide.bottom; }if (y <= -0.5f){return CollisionSide.top;}if (x < 0){return CollisionSide.right;}if (x > 0){return CollisionSide.left;}Debug.LogError(string.Concat(new string[]{"ERROR: unable to determine direction of collision - contact points at (",normal.x.ToString(),",",normal.y.ToString(),")"}));return CollisionSide.bottom;}}[Serializable]
public class HeroControllerStates
{public bool facingRight;public bool onGround;public bool wasOnGround;public bool attacking;public bool altAttack;public bool upAttacking;public bool downAttacking;public bool inWalkZone;public bool jumping;public bool falling;public bool dashing;public bool backDashing;public bool touchingWall;public bool wallSliding;public bool willHardLand;public bool preventDash;public bool preventBackDash;public bool dashCooldown;public bool backDashCooldown;public bool isPaused;public HeroControllerStates(){facingRight = false;onGround = false;wasOnGround = false;attacking = false;altAttack = false;upAttacking = false;downAttacking = false;inWalkZone = false;jumping = false;falling = false;dashing = false;backDashing = false;touchingWall = false;wallSliding = false;willHardLand = false;preventDash = false;preventBackDash = false;dashCooldown = false;backDashCooldown = false;isPaused = false;}
}

有一些地形检测函数式后续要用的,所以我先创建的,内容也很简单,其实就是一个判断类来用的,等后续某些地形是要用到的。 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class NonThunker : MonoBehaviour
{public bool active = true;public void SetActive(bool active){this.active = active;}
}
using System;
using UnityEngine;public class NonBouncer : MonoBehaviour
{public bool active = true;public void SetActive(bool active){this.active = active;}
}

然后就到了介绍NailSlash.cs的时候了,其实这就是一个控制slash effect的动画和音效之类的,还有就是开关PolygonCollider2D和MeshRenderer,我直接上代码吧你们看看也就懂了:

using System;
using GlobalEnums;
using UnityEngine;public class NailSlash : MonoBehaviour
{public string animName;public Vector3 scale;private HeroController heroCtrl;private PlayMakerFSM slashFsm;private tk2dSpriteAnimator anim;private MeshRenderer mesh;private AudioSource audioSource;private PolygonCollider2D poly;private PolygonCollider2D clashTinkpoly;private float slashAngle;private bool slashing;private bool animCompleted;private int stepCounter;private int polyCounter;private void Awake(){try{heroCtrl = transform.root.GetComponent<HeroController>();}catch(NullReferenceException ex){string str = "NailSlash: could not find HeroController on parent: ";string name = transform.root.name;string str2 = " ";NullReferenceException ex2 = ex;Debug.LogError(str + name + str2 + ((ex2 != null) ? ex2.ToString() : null));}slashFsm = GetComponent<PlayMakerFSM>();audioSource = GetComponent<AudioSource>();anim = GetComponent<tk2dSpriteAnimator>();mesh = GetComponent<MeshRenderer>();poly = GetComponent<PolygonCollider2D>();clashTinkpoly = transform.Find("Clash Tink").GetComponent<PolygonCollider2D>();poly.enabled = false;mesh.enabled = false;}private void FixedUpdate(){if (slashing){if(stepCounter == 1){poly.enabled = true;clashTinkpoly.enabled = true;}if(stepCounter >= 5 && polyCounter > 0f){poly.enabled = false;clashTinkpoly.enabled = false;}if(animCompleted && polyCounter > 1){CancelAttack();}if (poly.enabled){polyCounter++;}stepCounter++;}}public void StartSlash(){audioSource.Play();slashAngle = slashFsm.FsmVariables.GetFsmFloat("direction").Value;transform.localScale = scale;anim.Play(animName);anim.PlayFromFrame(0);stepCounter = 0;polyCounter = 0;poly.enabled = false;clashTinkpoly.enabled = false;animCompleted = false;anim.AnimationCompleted = new Action<tk2dSpriteAnimator, tk2dSpriteAnimationClip>(Disable);slashing = true;mesh.enabled = true;}private void Disable(tk2dSpriteAnimator sprite, tk2dSpriteAnimationClip clip){animCompleted = true;}private void OnTriggerEnter2D(Collider2D otherCollider){if(otherCollider != null){if(slashAngle == 0f){int layer = otherCollider.gameObject.layer;if(layer == 11 && (otherCollider.gameObject.GetComponent<NonBouncer>() == null || !otherCollider.gameObject.GetComponent<NonBouncer>().active)){}}else if(slashAngle == 180f){int layer2 = otherCollider.gameObject.layer;if (layer2 == 11 && (otherCollider.gameObject.GetComponent<NonBouncer>() == null || !otherCollider.gameObject.GetComponent<NonBouncer>().active)){}}else if (slashAngle == 90f){int layer3 = otherCollider.gameObject.layer;if (layer3 == 11 && (otherCollider.gameObject.GetComponent<NonBouncer>() == null || !otherCollider.gameObject.GetComponent<NonBouncer>().active)){}}else if(slashAngle == 270f){PhysLayers layer4 = (PhysLayers)otherCollider.gameObject.layer;if((layer4 == PhysLayers.ENEMIES || layer4 == PhysLayers.INTERACTIVE_OBJECT || layer4 == PhysLayers.HERO_ATTACK) && (otherCollider.gameObject.GetComponent<NonBouncer>() == null || !otherCollider.gameObject.GetComponent<NonBouncer>().active)){}}}}private void OnTriggerStay2D(Collider2D otherCollision){OnTriggerEnter2D(otherCollision);}public void CancelAttack(){slashing = false;poly.enabled = false;clashTinkpoly.enabled = false;mesh.enabled = false;}}

3.使用状态机实现击中敌人造成伤害机制

        然后就到了制作PlaymakerFSM之slashFSM,我们先给Attacks添加一个FSM设置nailDamage也就是骨钉伤害

首先自定义一个Action脚本叫GetNailDamage:

using System;
using HutongGames.PlayMaker;
using UnityEngine;[ActionCategory("Hollow Knight")]
public class GetNailDamage : FsmStateAction
{[UIHint(UIHint.Variable)]public FsmInt storeValue;public override void Reset(){storeValue = null;}public override void OnEnter(){//TODO:if(!storeValue.IsNone){storeValue.Value = GameManager.instance.playerData.nailDamage;}base.Finish();}}

在PlayerData.cs中我们添加上     public int nailDamage;

using System;
using System.Collections.Generic;
using System.Reflection;
using GlobalEnums;
using UnityEngine;[Serializable]
public class PlayerData
{private static PlayerData _instance;public static PlayerData instance{get{if(_instance == null){_instance = new PlayerData();}return _instance;}set{_instance = value;}}public int nailDamage;public bool hasDash;public bool canDash;public bool hasBackDash;public bool canBackDash;public bool gotCharm_31;public bool equippedCharm_31;protected PlayerData(){SetupNewPlayerData();}public void Reset(){SetupNewPlayerData();}private void SetupNewPlayerData(){nailDamage = 5;hasDash = true; //测试阶段先设置为true方便测试canDash = true;hasBackDash = false;canBackDash = false;gotCharm_31 = true;equippedCharm_31 = true;}public int GetInt(string intName){if (string.IsNullOrEmpty(intName)){Debug.LogError("PlayerData: Int with an EMPTY name requested.");return -9999;}FieldInfo fieldInfo = GetType().GetField(intName);if(fieldInfo != null){return (int)fieldInfo.GetValue(instance);}Debug.LogError("PlayerData: Could not find int named " + intName + " in PlayerData");return -9999;}}

获取PlayerData中int类型的变量的自定义行为: 

using UnityEngine;namespace HutongGames.PlayMaker.Actions
{[ActionCategory("PlayerData")][Tooltip("Sends a Message to PlayerData to send and receive data.")]public class GetPlayerDataInt : FsmStateAction{[RequiredField][Tooltip("GameManager reference, set this to the global variable GameManager.")]public FsmOwnerDefault gameObject;[RequiredField]public FsmString intName;[RequiredField][UIHint(UIHint.Variable)]public FsmInt storeValue;public override void Reset(){gameObject = null;intName = null;storeValue = null;}public override void OnEnter(){GameObject ownerDefaultTarget = Fsm.GetOwnerDefaultTarget(gameObject);if(ownerDefaultTarget == null){return;}GameManager gameManager = ownerDefaultTarget.GetComponent<GameManager>();if(gameManager == null){Debug.Log("GetPlayerDataInt: could not find a GameManager on this object, please refere to the GameManager global variable");return;}storeValue.Value = gameManager.GetPlayerDataInt(intName.Value);Finish();}}}

 对比String类型名字的自定义行为CompareNames .cs

using HutongGames.PlayMaker;
using UnityEngine;[ActionCategory("Hollow Knight")]
public class CompareNames : FsmStateAction
{public FsmString name;[ArrayEditor(VariableType.String, "", 0, 0, 65536)]public FsmArray strings;public FsmEventTarget target;public FsmEvent trueEvent;public FsmEvent falseEvent;public override void Reset(){name = new FsmString();target = new FsmEventTarget();strings = new FsmArray();trueEvent = null;falseEvent = null;}public override void OnEnter(){if(!name.IsNone && name.Value != ""){foreach (string value in strings.stringValues){if(name.Value.Contains(value)){Fsm.Event(target, trueEvent);base.Finish();return;}}Fsm.Event(target, falseEvent);}base.Finish();}}

回到Playermaker面板中: 

 

然后就给每一个Slash先添加一个简单的nail_cancel_attack的FSM,这个是下一期要用到的,是取消攻击的状态:

 

 

这个CancelAttack()方法下一期再来做。

然后到了重点再添加一个新的playmakerFSM:damages_enemy给每一个Slash:

我们先添加事件和变量:

 

然后就到了我们最喜欢的自定义playmaker Action脚本 :

2d碰撞检测trigger2d事件:

using System;
using UnityEngine;namespace HutongGames.PlayMaker.Actions
{[ActionCategory("Physics 2d")][Tooltip("Detect 2D trigger collisions between the Owner of this FSM and other Game Objects that have RigidBody2D components.\nNOTE: The system events, TRIGGER ENTER 2D, TRIGGER STAY 2D, and TRIGGER EXIT 2D are sent automatically on collisions triggers with any object. Use this action to filter collision triggers by Tag.")]public class Trigger2dEvent : FsmStateAction{[Tooltip("The type of trigger to detect.")]public PlayMakerUnity2d.Trigger2DType trigger;[UIHint(UIHint.Tag)][Tooltip("Filter by Tag.")]public FsmString collideTag;[UIHint(UIHint.Layer)][Tooltip("Filter by Layer.")]public FsmString collideLayer;[RequiredField][Tooltip("Event to send if a collision is detected.")]public FsmEvent sendEvent;[UIHint(UIHint.Variable)][Tooltip("Store the GameObject that collided with the Owner of this FSM.")]public FsmGameObject storeCollider;private PlayMakerUnity2DProxy _proxy;public override void Reset(){trigger =  PlayMakerUnity2d.Trigger2DType.OnTriggerEnter2D;collideTag = new FsmString(){UseVariable=true};sendEvent = null;storeCollider = null;}public override void OnEnter(){_proxy = (PlayMakerUnity2DProxy) this.Owner.GetComponent<PlayMakerUnity2DProxy>();if (_proxy == null){_proxy = this.Owner.AddComponent<PlayMakerUnity2DProxy>();}switch (trigger){case PlayMakerUnity2d.Trigger2DType.OnTriggerEnter2D:_proxy.AddOnTriggerEnter2dDelegate(this.DoTriggerEnter2D);break;case PlayMakerUnity2d.Trigger2DType.OnTriggerStay2D:_proxy.AddOnTriggerStay2dDelegate(this.DoTriggerStay2D);break;case PlayMakerUnity2d.Trigger2DType.OnTriggerExit2D:_proxy.AddOnTriggerExit2dDelegate(this.DoTriggerExit2D);break;}}public override void OnExit(){if (_proxy==null){return;}switch (trigger){case PlayMakerUnity2d.Trigger2DType.OnTriggerEnter2D:_proxy.RemoveOnTriggerEnter2dDelegate(this.DoTriggerEnter2D);break;case PlayMakerUnity2d.Trigger2DType.OnTriggerStay2D:_proxy.RemoveOnTriggerStay2dDelegate(this.DoTriggerStay2D);break;case PlayMakerUnity2d.Trigger2DType.OnTriggerExit2D:_proxy.RemoveOnTriggerExit2dDelegate(this.DoTriggerExit2D);break;}}void StoreCollisionInfo(Collider2D collisionInfo){storeCollider.Value = collisionInfo.gameObject;}public void DoTriggerEnter2D(Collider2D collisionInfo){if (trigger == PlayMakerUnity2d.Trigger2DType.OnTriggerEnter2D){if (collisionInfo.gameObject.tag == collideTag.Value || collideTag.IsNone || string.IsNullOrEmpty(collideTag.Value) ){StoreCollisionInfo(collisionInfo);Fsm.Event(sendEvent);}}}public void DoTriggerStay2D(Collider2D collisionInfo){if (trigger == PlayMakerUnity2d.Trigger2DType.OnTriggerStay2D){if (collisionInfo.gameObject.tag == collideTag.Value || collideTag.IsNone || string.IsNullOrEmpty(collideTag.Value) ){StoreCollisionInfo(collisionInfo);Fsm.Event(sendEvent);}}}public void DoTriggerExit2D(Collider2D collisionInfo){if (trigger == PlayMakerUnity2d.Trigger2DType.OnTriggerExit2D){if (collisionInfo.gameObject.tag == collideTag.Value || collideTag.IsNone || string.IsNullOrEmpty(collideTag.Value)){StoreCollisionInfo(collisionInfo);Fsm.Event(sendEvent);}}}public override string ErrorCheck(){string text = string.Empty;if (Owner != null && Owner.GetComponent<Collider2D>() == null && Owner.GetComponent<Rigidbody2D>() == null){text += "Owner requires a RigidBody2D or Collider2D!\n";}return text;}}
}

 检测发送事件限制CheckSendEventLimit:

using HutongGames.PlayMaker;
using UnityEngine;[ActionCategory("Hollow Knight")]
public class CheckSendEventLimit : FsmStateAction
{public FsmGameObject gameObject;public FsmEventTarget target;public FsmEvent trueEvent;public FsmEvent falseEvent;public override void Reset(){gameObject = null;target = null;trueEvent = null;falseEvent = null;}public override void OnEnter(){if (gameObject.Value){LimitSendEvents component = Owner.gameObject.GetComponent<LimitSendEvents>(); if(component && !component.Add(gameObject.Value)){Fsm.Event(target, falseEvent);}else{Fsm.Event(target, trueEvent);}}base.Finish();}}

我们为每一种Slash再添加一个新的脚本叫LimitSendEvents.cs:

 这个就是限制发送事件的,

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class LimitSendEvents : MonoBehaviour
{public Collider2D monitorCollider;private List<GameObject> sentList = new List<GameObject>();private bool? previousColliderState;private void OnEnable(){sentList.Clear();}private void Update(){if (monitorCollider){bool enabled = monitorCollider.enabled;bool? flag = previousColliderState;if(enabled == flag.GetValueOrDefault() && flag != null){return;}previousColliderState = new bool?(monitorCollider.enabled);}if(sentList.Count > 0){sentList.Clear();}}public bool Add(GameObject obj){if (!sentList.Contains(obj)){sentList.Add(obj);return true;}return false;}}

回到编辑器中,把每一种Slash的PolygonCollider2D拖进来

最后一个自定义行为Action脚本叫TakeDamage:

using HutongGames.PlayMaker;
using UnityEngine;[ActionCategory("Hollow Knight")]
public class TakeDamage : FsmStateAction
{public FsmGameObject Target; //FSM的目标,一般是自己public FsmInt AttackType; //攻击类型public FsmBool CircleDirection; //是否是圆形方向public FsmInt DamageDealt; //伤害值public FsmFloat Direction; //受伤方向public FsmBool IgnoreInvulnerable; //是否忽略无敌public FsmFloat MagnitudeMultiplier;//伤害倍值public FsmFloat MoveAngle; //移动的角度public FsmBool MoveDirection; //移动的方向public FsmFloat Multiplier;//伤害倍值public FsmInt SpecialType; //特殊类型public override void Reset(){base.Reset();Target = new FsmGameObject{UseVariable = true};AttackType = new FsmInt{UseVariable = true};CircleDirection = new FsmBool{UseVariable = true};DamageDealt = new FsmInt{UseVariable = true};Direction = new FsmFloat{UseVariable = true};IgnoreInvulnerable = new FsmBool{UseVariable = true};MagnitudeMultiplier = new FsmFloat{UseVariable = true};MoveAngle = new FsmFloat{UseVariable = true};MoveDirection = new FsmBool{UseVariable = true};Multiplier = new FsmFloat{UseVariable = true};SpecialType = new FsmInt{UseVariable = true};}public override void OnEnter(){base.OnEnter();HitTaker.Hit(Target.Value, new HitInstance{Source = Owner,AttackType = (AttackTypes)AttackType.Value,CircleDirection = CircleDirection.Value,DamageDealt = DamageDealt.Value,IgnoreInvulnerable = IgnoreInvulnerable.Value,MagnitudeMultiplier = MagnitudeMultiplier.Value,MoveAngle = MoveAngle .Value,MoveDirection = MoveDirection.Value,Multiplier = Multiplier.IsNone ? 1f:Multiplier.Value,SpecialType = (SpecialTypes)SpecialType.Value,IsExtraDamage = false},3);base.Finish();}}

 创建一个新的静态类HitTaker,正如注释描述的,这个类是为了检测targetGameObject自己this,父对象parent,爷对象grandparent有咩有IHitResponder,有的话执行Hit()函数,

using System;
using UnityEngine;public static class HitTaker
{private const int DefaultRecursionDepth = 3;public static void Hit(GameObject targetGameObject,HitInstance damageInstance,int recursionDepth = DefaultRecursionDepth){if (targetGameObject != null){Transform transform = targetGameObject.transform;//说白了就是检测targetGameObject自己this,父对象parent,爷对象grandparent有咩有IHitResponder,有的话执行Hitfor (int i = 0; i < recursionDepth; i++) {IHitResponder component = transform.GetComponent<IHitResponder>();if(component != null){component.Hit(damageInstance);}transform = transform.parent;if(transform == null){break;}}}}
}

 其实这个IHitResponder 只是一个接口,只有一个要实现的方法就是Hit();下一章我们将创建类来继承这个接口

using System;public interface IHitResponder 
{void Hit(HitInstance damageInstance);
}

还是就是受击实例化结构体类型HitTaker:我们还在这个下面添加了AttackType攻击类型和Special Type特殊类型两个数组。

using System;
using UnityEngine;[Serializable]
public struct HitInstance
{public GameObject Source;public AttackTypes AttackType;public bool CircleDirection;public int DamageDealt;public float Direction;public bool IgnoreInvulnerable;public float MagnitudeMultiplier;public float MoveAngle;public bool MoveDirection;public float Multiplier;public SpecialTypes SpecialType;public bool IsExtraDamage;public float GetActualDirection(Transform target){if(Source != null && target != null && CircleDirection){Vector2 vector = target.position - Source.transform.position;return Mathf.Atan2(vector.y, vector.x) * 57.29578f;}return Direction;}}public enum AttackTypes
{Nail,Generic
}public enum SpecialTypes
{None,Acid
}

然后我们就可以创建State了:

 如果层级是9和20就执行CANCEL事件回到Idle状态,如果骨钉伤害=0也执行CANCEL事件,如果CheckSentEventLimit为false就执行FALSE事件

下两个状态就是Parent和GrandParent,我们只需要把上一个状态填Collider 的改成Parent和GrandParent游戏对象,但别忘了改第一个行为GetParent!

 

至此我们完成了基本的攻击行为Attack!播放一下发现没有问题!

二、为敌人制作生命系统

1.使用代码制作生命系统

创建新的脚本HealthManager.cs添加到每一个敌人的游戏对象上。

还记得我上面说的接口IHitResponder吗?我们要继承这个接口并实现里面的方法:

using System;
using System.Collections;
using HutongGames.PlayMaker;
using UnityEngine;
using UnityEngine.Audio;public class HealthManager : MonoBehaviour, IHitResponder
{public void Hit(HitInstance hitInstance){}
}

除了基本的组件 

    private BoxCollider2D boxCollider;
    private tk2dSpriteAnimator animator;
    private tk2dSprite sprite;

以外我们想想生命系统需要什么?首先要有hp, enemyType; //敌人类型,Vector3 effectOrigin; //生效偏移量,isDead死了没有?还要一开始判断它死了没有,有了这些想法后我们做个简易版本的生命系统:

using System;
using System.Collections;
using HutongGames.PlayMaker;
using UnityEngine;
using UnityEngine.Audio;public class HealthManager : MonoBehaviour, IHitResponder
{private BoxCollider2D boxCollider;private tk2dSpriteAnimator animator;private tk2dSprite sprite;[Header("Asset")][SerializeField] private AudioSource audioPlayerPrefab; //声音播放器预制体[Header("Body")][SerializeField] public int hp; //血量[SerializeField] public int enemyType; //敌人类型[SerializeField] private Vector3 effectOrigin; //生效偏移量public bool isDead;private int directionOfLastAttack; //最后一次受到攻击的方向private float evasionByHitRemaining; //剩余攻击下的逃避时间private const string CheckPersistenceKey = "CheckPersistence";public delegate void DeathEvent();public event DeathEvent OnDeath;protected void Awake(){boxCollider = GetComponent<BoxCollider2D>();animator = GetComponent<tk2dSpriteAnimator>();sprite = GetComponent<tk2dSprite>();}protected void OnEnable(){StartCoroutine(CheckPersistenceKey);}protected void Start(){evasionByHitRemaining = -1f;}protected void Update(){evasionByHitRemaining -= Time.deltaTime;}public void Hit(HitInstance hitInstance){if (isDead){return;}if(hitInstance.DamageDealt < 0f){return;}FSMUtility.SendEventToGameObject(hitInstance.Source, "DEALT DAMAGE", false);int cardinalDirection = DirectionUtils.GetCardinalDirection(hitInstance.GetActualDirection(transform));TakeDamage(hitInstance);}private void TakeDamage(HitInstance hitInstance){}public void SendDeathEvent(){if (OnDeath != null){OnDeath();}}protected IEnumerator CheckPersistence(){yield return null;if (isDead){gameObject.SetActive(false);}yield break;}}

 三、为敌人制作受伤系统 

1.使用代码制作受伤系统

        其实说白了就是完善这个HealthManager.cs脚本,在此之前我们现在静态类Extensions.cs添加一个新的静态方法:

    public static float GetPositionY(this Transform t)
    {
            return t.position.y;
    }

        然后回到HealthManager.cs,我们添加几个新的方法,无敌时间evasionByHitRemaining,是否在某个方向上阻挡所有攻击的IsBlockingByDirection(int cardinalDirection,AttackTypes attackType),无敌Invincible(HitInstance hitInstance),受到伤害TakeDamage(HitInstance hitInstance),以及完善Hit()函数:

using System;
using System.Collections;
using HutongGames.PlayMaker;
using UnityEngine;
using UnityEngine.Audio;public class HealthManager : MonoBehaviour, IHitResponder
{private BoxCollider2D boxCollider;private tk2dSpriteAnimator animator;private tk2dSprite sprite;[Header("Asset")][SerializeField] private AudioSource audioPlayerPrefab; //声音播放器预制体[Header("Body")][SerializeField] public int hp; //血量[SerializeField] public int enemyType; //敌人类型[SerializeField] private Vector3 effectOrigin; //生效偏移量public bool isDead;private int directionOfLastAttack; //最后一次受到攻击的方向private float evasionByHitRemaining; //剩余攻击下的逃避时间private const string CheckPersistenceKey = "CheckPersistence";public delegate void DeathEvent();public event DeathEvent OnDeath;protected void Awake(){boxCollider = GetComponent<BoxCollider2D>();animator = GetComponent<tk2dSpriteAnimator>();sprite = GetComponent<tk2dSprite>();}protected void OnEnable(){StartCoroutine(CheckPersistenceKey);}protected void Start(){evasionByHitRemaining = -1f;}protected void Update(){evasionByHitRemaining -= Time.deltaTime;}public void Hit(HitInstance hitInstance){if (isDead){return;}if(evasionByHitRemaining > 0f) { return;}if(hitInstance.DamageDealt < 0f){return;}FSMUtility.SendEventToGameObject(hitInstance.Source, "DEALT DAMAGE", false);int cardinalDirection = DirectionUtils.GetCardinalDirection(hitInstance.GetActualDirection(transform));if (IsBlockingByDirection(cardinalDirection, hitInstance.AttackType)){Invincible(hitInstance);return;}TakeDamage(hitInstance);}private void Invincible(HitInstance hitInstance){int cardinalDirection = DirectionUtils.GetCardinalDirection(hitInstance.GetActualDirection(transform));directionOfLastAttack = cardinalDirection;FSMUtility.SendEventToGameObject(gameObject, "BLOCKED HIT", false);FSMUtility.SendEventToGameObject(hitInstance.Source, "HIT LANDED", false);if (!(GetComponent<DontClinkGates>() != null)){FSMUtility.SendEventToGameObject(gameObject, "HIT", false);if(hitInstance.AttackType == AttackTypes.Nail){if(cardinalDirection == 0){}else if(cardinalDirection == 2){}}Vector2 v;Vector3 eulerAngles;if (boxCollider != null){switch (cardinalDirection){case 0:v = new Vector2(transform.GetPositionX() + boxCollider.offset.x - boxCollider.size.x * 0.5f, hitInstance.Source.transform.GetPositionY());eulerAngles = new Vector3(0f, 0f, 0f);break;case 1:v = new Vector2(hitInstance.Source.transform.GetPositionX(), Mathf.Max(hitInstance.Source.transform.GetPositionY(), transform.GetPositionY() + boxCollider.offset.y - boxCollider.size.y * 0.5f));eulerAngles = new Vector3(0f, 0f, 90f);break;case 2:v = new Vector2(transform.GetPositionX() + boxCollider.offset.x + boxCollider.size.x * 0.5f, hitInstance.Source.transform.GetPositionY());eulerAngles = new Vector3(0f, 0f, 180f);break;case 3:v = new Vector2(hitInstance.Source.transform.GetPositionX(), Mathf.Min(hitInstance.Source.transform.GetPositionY(), transform.GetPositionY() + boxCollider.offset.y + boxCollider.size.y * 0.5f));eulerAngles = new Vector3(0f, 0f, 270f);break;default:break;}}else{v = transform.position;eulerAngles = new Vector3(0f, 0f, 0f);}}evasionByHitRemaining = 0.15f;}private void TakeDamage(HitInstance hitInstance){Debug.LogFormat("Enemy Take Damage");int cardinalDirection = DirectionUtils.GetCardinalDirection(hitInstance.GetActualDirection(transform));directionOfLastAttack = cardinalDirection;FSMUtility.SendEventToGameObject(gameObject, "HIT", false);FSMUtility.SendEventToGameObject(hitInstance.Source, "HIT LANDED", false);FSMUtility.SendEventToGameObject(gameObject, "TOOK DAMAGE", false);switch (hitInstance.AttackType){case AttackTypes.Nail:if(hitInstance.AttackType == AttackTypes.Nail && enemyType !=3 && enemyType != 6){}Vector3 position = (hitInstance.Source.transform.position + transform.position) * 0.5f + effectOrigin;break;case AttackTypes.Generic:break;default:break;}int num = Mathf.RoundToInt((float)hitInstance.DamageDealt * hitInstance.Multiplier);hp = Mathf.Max(hp - num, -50);if(hp > 0){}else{Die(new float?(hitInstance.GetActualDirection(transform)), hitInstance.AttackType, hitInstance.IgnoreInvulnerable);}}public void Die(float? v, AttackTypes attackType, bool ignoreInvulnerable){if (isDead){return;}if (sprite){sprite.color = Color.white;}FSMUtility.SendEventToGameObject(gameObject, "ZERO HP", false);isDead = true;SendDeathEvent();Destroy(gameObject); //TODO:}public void SendDeathEvent(){if (OnDeath != null){OnDeath();}}public bool IsBlockingByDirection(int cardinalDirection,AttackTypes attackType){switch (cardinalDirection){default:return false;}}protected IEnumerator CheckPersistence(){yield return null;if (isDead){gameObject.SetActive(false);}yield break;}}

核心代码一句话hp = Mathf.Max(hp - num,-50) 

还有一个就是空内容的类,这个暂时先不管它:

using UnityEngine;public class DontClinkGates : MonoBehaviour
{}

回到编辑器中,我们设置好HealthManager.cs的内容:

2.制作受伤特效

现在我们还差受伤特效没有实现,我们想想受伤特效也是有很多共性的,而且还有一个接收受伤特效的方法要实现,于是再创建一个接口:

using System;
using UnityEngine;public interface IHitEffectReciever
{void ReceiverHitEffect(float attackDirection);
}

为每一个敌人创建一个新的脚本EnemyHitEffectsBlackKnight.cs ,使其继承这个接口并实现里面的方法:

using System;
using UnityEngine;public class EnemyHitEffectsBlackKnight : MonoBehaviour,IHitEffectReciever
{public Vector3 effectOrigin;[Space]public AudioSource audioPlayerPrefab;public AudioEvent enemyDamage;[Space]public GameObject hitFlashOrange;public GameObject hitPuffLarge;private SpriteFlash spriteFlash;private bool didFireThisFrame;private void Awake(){spriteFlash = GetComponent<SpriteFlash>();}protected void Update(){didFireThisFrame = false;}public void ReceiverHitEffect(float attackDirection){if (didFireThisFrame)return;FSMUtility.SendEventToGameObject(this.gameObject, "DAMAGE FLASH", false);enemyDamage.SpawnAndPlayOneShot(audioPlayerPrefab, transform.position);if (spriteFlash){spriteFlash.flashInfected();}GameObject gameObject = Instantiate(hitFlashOrange, transform.position + effectOrigin, Quaternion.identity);switch (DirectionUtils.GetCardinalDirection(attackDirection)){case 0:gameObject.transform.eulerAngles = new Vector3(0f, 90f, 270f);break;case 1:gameObject.transform.eulerAngles = new Vector3(270f, 90f, 270f);break;case 2:gameObject.transform.eulerAngles = new Vector3(180f, 90f, 270f);break;case 3:gameObject.transform.eulerAngles = new Vector3(-72.5f, -180f, -180f);break;}didFireThisFrame = true;}}

 再为每一个敌人创建一个新的脚本SpriteFlash.cs ,里面是负责实现Sprite精灵图闪烁效果的,通过控制material中的"_FlashAmount"(线性插值的方法),更改flashColour等等

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class SpriteFlash : MonoBehaviour
{private Renderer rend;private Color flashColour;private float amount;private float amountCurrent;private float timeUp;private float stayTime;private float timeDown;private int flashingState;private float flashTimer;private float t;private bool repeatFlash;private bool cancelFlash;private MaterialPropertyBlock block;private bool sendToChildren = true;private void Start(){if(rend == null){rend = GetComponent<Renderer>();}if (block == null){block = new MaterialPropertyBlock();}}private void OnDisable(){if (rend == null){rend = GetComponent<Renderer>();}if (block == null){block = new MaterialPropertyBlock();}block.SetFloat("_FlashAmount", 0f);rend.SetPropertyBlock(block);flashTimer = 0f;flashingState = 0;repeatFlash = false;cancelFlash = false;}private void Update(){if (cancelFlash){block.SetFloat("_FlashAmount", 0f);rend.SetPropertyBlock(block);flashingState = 0;cancelFlash = false;}if(flashingState == 1){if (flashTimer < timeUp){flashTimer += Time.deltaTime;t = flashTimer / timeUp;amountCurrent = Mathf.Lerp(0f, amount, t);block.SetFloat("_FlashAmount", amountCurrent);rend.SetPropertyBlock(block);}else{block.SetFloat("_FlashAmount", amount);rend.SetPropertyBlock(block);flashTimer = 0f;flashingState = 2;}}if(flashingState == 2){if(flashTimer < stayTime){flashTimer += Time.deltaTime;}else{flashTimer = 0f;flashingState = 3;}}if(flashingState == 3){if (flashTimer < timeDown){flashTimer += Time.deltaTime;t = flashTimer / timeDown;amountCurrent = Mathf.Lerp(amount, 0f, t);block.SetFloat("_FlashAmount", amountCurrent);rend.SetPropertyBlock(block);}else{block.SetFloat("_FlashAmount", 0f);rend.SetPropertyBlock(block);flashTimer = 0f;if (repeatFlash){flashingState = 1;}else{flashingState = 0;}}}}public void flashInfected(){if (block == null){block = new MaterialPropertyBlock();}flashColour = new Color(1f, 0.31f, 0f);amount = 0.9f;timeUp = 0.01f;timeDown = 0.25f;block.Clear();block.SetColor("_FlashColor", flashColour);flashingState = 1;flashTimer = 0f;repeatFlash = false;SendToChildren(new Action(flashInfected));}private void SendToChildren(Action function){if (!sendToChildren)return;foreach (SpriteFlash spriteFlash  in GetComponentsInChildren<SpriteFlash>()){if(!(spriteFlash == null)){spriteFlash.sendToChildren = false;spriteFlash.GetType().GetMethod(function.Method.Name).Invoke(spriteFlash, null);}}}
}

再创建一个结构体AudioEvent: 

using System;
using UnityEngine;[Serializable]
public struct AudioEvent
{public AudioClip Clip;public float PitchMin;public float PitchMax;public float Volume;public void Reset(){PitchMin = 0.75f;PitchMax = 1.25f;Volume = 1f;}public float SelectPitch(){if (Mathf.Approximately(PitchMin, PitchMax)){return PitchMax;}return UnityEngine.Random.Range(PitchMin, PitchMax);}public void SpawnAndPlayOneShot(AudioSource prefab, Vector3 position){if (Clip == null)return;if(Volume < Mathf.Epsilon)return;if (prefab == null)return;AudioSource audioSource = GameObject.Instantiate(prefab, position,Quaternion.identity);audioSource.volume = Volume;audioSource.pitch = SelectPitch();audioSource.PlayOneShot(Clip);}}

回到HealthManager.cs当中,添加    private IHitEffectReciever hitEffectReceiver;并且

hitEffectReceiver = GetComponent<IHitEffectReciever>();

 现阶段完整的HealthManager.cs如下所示:

using System;
using System.Collections;
using HutongGames.PlayMaker;
using UnityEngine;
using UnityEngine.Audio;public class HealthManager : MonoBehaviour, IHitResponder
{private BoxCollider2D boxCollider;private IHitEffectReciever hitEffectReceiver;private tk2dSpriteAnimator animator;private tk2dSprite sprite;[Header("Asset")][SerializeField] private AudioSource audioPlayerPrefab; //声音播放器预制体[Header("Body")][SerializeField] public int hp; //血量[SerializeField] public int enemyType; //敌人类型[SerializeField] private Vector3 effectOrigin; //生效偏移量public bool isDead;private int directionOfLastAttack; //最后一次受到攻击的方向private float evasionByHitRemaining; //剩余攻击下的逃避时间private const string CheckPersistenceKey = "CheckPersistence";public delegate void DeathEvent();public event DeathEvent OnDeath;protected void Awake(){boxCollider = GetComponent<BoxCollider2D>();hitEffectReceiver = GetComponent<IHitEffectReciever>();animator = GetComponent<tk2dSpriteAnimator>();sprite = GetComponent<tk2dSprite>();}protected void OnEnable(){StartCoroutine(CheckPersistenceKey);}protected void Start(){evasionByHitRemaining = -1f;}protected void Update(){evasionByHitRemaining -= Time.deltaTime;}public void Hit(HitInstance hitInstance){if (isDead){return;}if(evasionByHitRemaining > 0f) { return;}if(hitInstance.DamageDealt < 0f){return;}FSMUtility.SendEventToGameObject(hitInstance.Source, "DEALT DAMAGE", false);int cardinalDirection = DirectionUtils.GetCardinalDirection(hitInstance.GetActualDirection(transform));if (IsBlockingByDirection(cardinalDirection, hitInstance.AttackType)){Invincible(hitInstance);return;}TakeDamage(hitInstance);}private void Invincible(HitInstance hitInstance){int cardinalDirection = DirectionUtils.GetCardinalDirection(hitInstance.GetActualDirection(transform));directionOfLastAttack = cardinalDirection;FSMUtility.SendEventToGameObject(gameObject, "BLOCKED HIT", false);FSMUtility.SendEventToGameObject(hitInstance.Source, "HIT LANDED", false);if (!(GetComponent<DontClinkGates>() != null)){FSMUtility.SendEventToGameObject(gameObject, "HIT", false);if(hitInstance.AttackType == AttackTypes.Nail){if(cardinalDirection == 0){}else if(cardinalDirection == 2){}}Vector2 v;Vector3 eulerAngles;if (boxCollider != null){switch (cardinalDirection){case 0:v = new Vector2(transform.GetPositionX() + boxCollider.offset.x - boxCollider.size.x * 0.5f, hitInstance.Source.transform.GetPositionY());eulerAngles = new Vector3(0f, 0f, 0f);break;case 1:v = new Vector2(hitInstance.Source.transform.GetPositionX(), Mathf.Max(hitInstance.Source.transform.GetPositionY(), transform.GetPositionY() + boxCollider.offset.y - boxCollider.size.y * 0.5f));eulerAngles = new Vector3(0f, 0f, 90f);break;case 2:v = new Vector2(transform.GetPositionX() + boxCollider.offset.x + boxCollider.size.x * 0.5f, hitInstance.Source.transform.GetPositionY());eulerAngles = new Vector3(0f, 0f, 180f);break;case 3:v = new Vector2(hitInstance.Source.transform.GetPositionX(), Mathf.Min(hitInstance.Source.transform.GetPositionY(), transform.GetPositionY() + boxCollider.offset.y + boxCollider.size.y * 0.5f));eulerAngles = new Vector3(0f, 0f, 270f);break;default:break;}}else{v = transform.position;eulerAngles = new Vector3(0f, 0f, 0f);}}evasionByHitRemaining = 0.15f;}private void TakeDamage(HitInstance hitInstance){Debug.LogFormat("Enemy Take Damage");int cardinalDirection = DirectionUtils.GetCardinalDirection(hitInstance.GetActualDirection(transform));directionOfLastAttack = cardinalDirection;FSMUtility.SendEventToGameObject(gameObject, "HIT", false);FSMUtility.SendEventToGameObject(hitInstance.Source, "HIT LANDED", false);FSMUtility.SendEventToGameObject(gameObject, "TOOK DAMAGE", false);switch (hitInstance.AttackType){case AttackTypes.Nail:if(hitInstance.AttackType == AttackTypes.Nail && enemyType !=3 && enemyType != 6){}Vector3 position = (hitInstance.Source.transform.position + transform.position) * 0.5f + effectOrigin;break;case AttackTypes.Generic:break;default:break;}if(hitEffectReceiver != null){hitEffectReceiver.ReceiverHitEffect(hitInstance.GetActualDirection(transform));}int num = Mathf.RoundToInt((float)hitInstance.DamageDealt * hitInstance.Multiplier);hp = Mathf.Max(hp - num, -50);if(hp > 0){}else{Die(new float?(hitInstance.GetActualDirection(transform)), hitInstance.AttackType, hitInstance.IgnoreInvulnerable);}}public void Die(float? v, AttackTypes attackType, bool ignoreInvulnerable){if (isDead){return;}if (sprite){sprite.color = Color.white;}FSMUtility.SendEventToGameObject(gameObject, "ZERO HP", false);isDead = true;SendDeathEvent();Destroy(gameObject); //TODO:}public void SendDeathEvent(){if (OnDeath != null){OnDeath();}}public bool IsBlockingByDirection(int cardinalDirection,AttackTypes attackType){switch (cardinalDirection){default:return false;}}protected IEnumerator CheckPersistence(){yield return null;if (isDead){gameObject.SetActive(false);}yield break;}}

 回到Unity编辑器中,我是这样设置的:

两个自己创建的预制体:

 

这里我为它们创建了一个临时的脚本就是一段时间回收这个物体,但我还没有做对象池所以只能粗暴的Destory(gameObject)了

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class AutoRecycle : MonoBehaviour
{//这个脚本是暂时性的,用在暂时处理自动回收一次性生成的物体和粒子系统,等后续开发就可以删除掉了public float recycleTimer = 1f;private void Update(){recycleTimer -= Time.deltaTime;if(recycleTimer <= 0f){Destroy(gameObject); // TODO:}}
}


总结

最后看看我填的参数,需要注意到是你的ATTACK_DURATION和你的攻击动画播放时间Clip Time要保持一致

 

下一期我们来做后坐力系统Recoil和玩家的生命系统和受伤系统。本文一共七万多字,看完记得做下眼保健操

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

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

相关文章

前端vue-3种生命周期,只能在各自的领域使用

上面的表格可以简化为下面的两句话&#xff1a; setup是语法糖&#xff0c;下面的两个import导入是vue3和vue2的区别&#xff0c;现在的vue3直接导入&#xff0c;比之前vue2简单 还可以是导入两个生命周期函数

kafka负载均衡迁移(通过kafka eagle)

在grafana监控中发现kafka的各个节点磁盘不均匀 出现这样的情况是因为kafka默认是以文件数作为平衡的条件的。换句话说&#xff0c;kafka不会管一个副本有多大&#xff0c;只会看磁盘中有多少个副本文件。 解决方式&#xff1a; 1、修改策略&#xff0c;改为按照磁盘大小平衡…

闯关leetcode——69. Sqrt(x)

大纲 题目地址内容 解题代码地址 题目 地址 https://leetcode.com/problems/sqrtx/description/ 内容 Given a non-negative integer x, return the square root of x rounded down to the nearest integer. The returned integer should be non-negative as well. You mu…

《动手学深度学习》笔记1.10——激活函数←模型初始化←数值稳定性

目录 1. 数值稳定性 1.1 神经网络的梯度 1.2 数值稳定性的常见两个问题 1.3 梯度爆炸 1.3.1 MLP的例子 1.3.2 使用ReLU激活函数 1.3.3 产生的问题 1.4 梯度消失 1.4.1 使用sigmoid激活函数 1.4.2 梯度消失的问题 1.5 总结 2. 让训练更稳定 2.1 目标 (ResNet, LSTM…

深入探究PR:那些被忽视却超实用的视频剪辑工具

如果想要了解视频剪辑的工具&#xff0c;那一定听说过pr视频剪辑吧。如果你是新手其实我更推荐你从简单的视频剪辑工具入手&#xff0c;这次我就介绍一些简单好操作的视频剪辑工具来入门吧。 1.福晰视频剪辑 连接直达>>https://www.pdf365.cn/foxit-clip/ 这款工具操…

论文阅读 | 一种基于潜在向量优化的可证明安全的图像隐写方法(TMM 2023)

TMM 2023 中国科学技术大学 针对现有的可证明安全的图像隐写不能抵抗有损图像操作&#xff0c;而现有的生成图像隐写不能证明安全问题&#xff0c;提出一种基于潜在向量优化的可证明安全的图像隐写方法&#xff08;名为PARIS&#xff09;&#xff0c;该方法受到逆采样器和噪声…

Unity 热更新(HybridCLR+Addressable)-创建Addressable资源

三、创建Addressable资源 创建三个文件夹&#xff0c;放Addressable资源&#xff0c;里面对应放程序集&#xff0c;预制体以及场景 拖拽到Addressable Groups对应组中 其中文件名太长&#xff0c;带着路径&#xff0c;可以简化名字 创建一个脚本&#xff0c;对于这个脚本进行一…

C#常用数据结构栈的介绍

定义 在C#中&#xff0c;Stack<T> 是一个后进先出&#xff08;LIFO&#xff0c;Last-In-First-Out&#xff09;集合类&#xff0c;位于System.Collections.Generic 命名空间中。Stack<T> 允许你将元素压入栈顶&#xff0c;并从栈顶弹出元素。 不难看出&#xff0c;…

量子计算Quantum Computing

引子&#xff1a;朋友闲谈&#xff0c;问及工作&#xff0c;一个朋友说&#xff0c;他在一家做量子通信的公司上班&#xff0c;具体岗位是做结构设计&#xff0c;他抱怨说&#xff0c;直到现在他都搞不懂量子计算是什么&#xff1f; 一、量子计算是什么&#xff1f; 什么是量子…

LCD屏JD9853各个接口最大支持速率

概述 电子产品开发时常会遇到有带LCD屏的产品&#xff0c;是怎么计算出来的呢&#xff1f;接下来以JD9853这颗驱动IC举例说明&#xff0c;改驱动IC分别支持&#xff1a;8080、(3-line SPI&#xff09;、 (4-line SPI)、QSPI、RGB 1、8080 通过“时钟周期为传输速率的倒数”&a…

k8s上安装prometheus

一、下载对应的kube-prometheus源码 github地址&#xff1a;GitHub - prometheus-operator/kube-prometheus: Use Prometheus to monitor Kubernetes and applications running on Kubernetes 1&#xff09;进入目录 [rootk8s-master ~]# cd kube-prometheus [rootk8s-master…

Spring Boot 学习之路 -- 配置项目

前言 最近因为业务需要&#xff0c;被拉去研究后端的项目&#xff0c;代码基于 Spring Boot&#xff0c;对我来说完全小白&#xff0c;需要重新学习研究…出于个人习惯&#xff0c;会以 Blog 文章的方式做一些记录&#xff0c;文章内容基本来源于「 Spring Boot 从入门到精通&…

周家庄智慧旅游小程序

项目概述 周家庄智慧旅游小程序将通过数字化手段提升游客的旅游体验&#xff0c;依托周家庄的自然与文化资源&#xff0c;打造智慧旅游新模式。该小程序将结合虚拟现实&#xff08;VR&#xff09;、增强现实&#xff08;AR&#xff09;和人工智能等技术&#xff0c;提供丰富的…

国际化适配对照

中文 zh 葡萄牙语 pt 荷兰语 nl 泰文 th 匈牙利语 hu 波兰 pl 土耳其 tr 乌克兰 uk 希腊 el 印度尼西亚 in 越南语 vi 阿拉伯语 ar 希波来语 iw 英语 en 日语 ja 德语 de 法语 fr 意大利语 it 西班牙语 es 俄罗斯语 ru

1.5 计算机网络的性能指标

参考&#xff1a;&#x1f4d5;深入浅出计算机网络 目录 速率 带宽 吞吐量 时延 时延带宽积 往返时间 利用率 丢包率 速率 速率是指数据的传送速率&#xff08;即每秒传送多少个比特&#xff09;&#xff0c;也称为数据率&#xff08;Data Rate&#xff09;或比特率&am…

Python | Leetcode Python题解之第432题全O(1)的数据结构

题目&#xff1a; 题解&#xff1a; class Node:def __init__(self, key"", count0):self.prev Noneself.next Noneself.keys {key}self.count countdef insert(self, node: Node) -> Node: # 在 self 后插入 nodenode.prev selfnode.next self.nextnode.…

Axure9破解

1.下载安装包 通过百度网盘分享的文件&#xff1a;Axure RP 9.zip 链接&#xff1a;https://pan.baidu.com/s/1Lcu-gg4qF8tTkOlt7bC2ww?pwdwmqq 提取码&#xff1a;wmqq 2.设置登录以及破解码 位置&#xff1a;帮助-管理授权-添加key Licensee&#xff1a;123456 Key&#…

健身房管理系统设计与实现

摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装健身房管理系统软件来发挥其高效地信息处理的作用&#xff…

2024年信息安全企业CRM选型与应用研究报告

数字化的生活给人们带来便利的同时也带来一定的信息安全隐患&#xff0c;如网络侵权、泄露用户隐私、黑客攻击等。在互联网高度发展的今天&#xff0c;信息安全与我们每个人、每个组织甚至每个国家都息息相关。 信息安全行业蓬勃发展。根据智研咨询数据&#xff0c;2021年&…

【LLM学习之路】9月22日 第九天 自然语言处理

【LLM学习之路】9月22日 第九天 直接看Transformer 第一章 自然语言处理 自然语言处理发展史 只要看的足够多&#xff0c;未必需要理解语言 统计语言模型发展史 统计语言模型&#xff1a; 判断一个句子是否合理&#xff0c;就计算这个句子会出现的概率 缺点是句子越长越…