[Unity Demo]从零开始制作空洞骑士Hollow Knight第十一集:制作法术系统的回血机制和火球机制

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

文章目录

  • 前言
  • 一、制作法术系统的回血机制
    • 1.制作动画以及使用UNITY编辑器编辑
    • 2.使用代码和PlaymakerFSM制作回血机制
  • 二、制作法术系统的火球机制
    • 1.制作动画以及使用UNITY编辑器编辑
    • 2.使用代码和PlaymakerFSM制作火球机制
  • 总结


前言

Hello大家好久不见,隔了一天没发文章是因为这期的工程也挺复杂的,警告:此篇文章难度偏高,非常不适合刚刚入门的或者没看过我前几期的读者,之所以做了这么久是因为一堆奇奇怪怪的bug真的差点给我整崩溃了然后有些素材我又找不到只能对着Texture2D去PS一张张扣下来都给我整笑了,请读者如果在阅读后感到身体不适请立刻退出这篇文章。

这期我们的主题是:制作法术系统的回血机制和法球机制,友情提示我盲猜会有一堆图片,所以用流量的请检查自己流量是否够用。


一、制作法术系统的回血机制

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

还是老规矩,我们来为小骑士添加几个新的tk2dSprite和tk2dSpriteAnimation:

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

 然后就是创建动画了:

 

 然后还有特效效果SpellEffect的tk2dSprite和tk2dSpriteAnimation:这些都是我在PS

 

 

 

 为我们的小骑士生成一个新的子对象叫Focus Effect,为里面添加几个新的子对象:

Lines Anim: 

Dust R:

Dust L只需要改变Transform的值即可:

其中的PlayerMaker就是上一期我们讲的延迟游戏对象SetActive = false

 

2.使用代码和PlayMakerFSM制作法术系统

        接下来就是用代码来制作法术系统了,首先到PlayerData.cs中我们来添加几个新的变量都是和法术有关的,以及一些方法主要是获得和设置Int和Bool类型的变量:

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 bool disablePause;public int health;public int maxHealth;public int maxHealthBase;public int prevHealth;public int nailDamage;public int maxMP; //最大MP容量public int MPCharge; //当前MP存储量public int MPReverse; //花费的MPpublic int MPReverseMax; //花费的最大MPpublic bool soulLimited; //是否灵魂容量被限制了public int focusMP_amount; //聚集法术所需要的MPpublic bool hasDash;public bool canDash;public bool hasBackDash;public bool canBackDash;public bool hasSpell; //是否有法术public int fireballLevel;//火球法术等级,0表示没有public int quakeLevel;//地震法术等级,0表示没有public int screamLevel;//狂啸法术等级,0表示没有public bool overcharmed;public bool gotCharm_7; //快速聚集public bool equippedCharm_7;public bool gotCharm_10; //英勇者勋章public bool equippedCharm_10;public bool gotCharm_11; //吸虫之巢public bool equippedCharm_11;public bool gotCharm_17; public bool equippedCharm_17;public bool gotCharm_19; //萨满之石public bool equippedCharm_19;public bool gotCharm_28; //乌恩符咒public bool equippedCharm_28;public bool gotCharm_31; //冲刺大师public bool equippedCharm_31;public bool gotCharm_34; //深度聚集public bool equippedCharm_34;public int CurrentMaxHealth{get{return maxHealth;}}protected PlayerData(){SetupNewPlayerData();}public void Reset(){SetupNewPlayerData();}private void SetupNewPlayerData(){disablePause = false;health = 5;maxHealth = 5;maxHealthBase = 5;prevHealth = health;nailDamage = 5;maxMP = 99;MPCharge = 0;MPReverse = 0;MPReverseMax = 0;soulLimited = false;focusMP_amount = 33;hasDash = true; //测试阶段先设置为true方便测试canDash = true;hasBackDash = false;canBackDash = false;hasSpell = false;fireballLevel = 0;quakeLevel = 0;screamLevel = 0;overcharmed = false;gotCharm_7 = false;equippedCharm_7 = false;gotCharm_10 = false;equippedCharm_10 = false;gotCharm_11 = false;equippedCharm_11 = false;gotCharm_17 = false;equippedCharm_17 = false;gotCharm_19 = false;equippedCharm_19 = false;gotCharm_28 = false;equippedCharm_28 = false;gotCharm_31 = true;equippedCharm_31 = true;gotCharm_34 = false;equippedCharm_34 = false;}public void AddHealth(int amount){if (health + amount >= maxHealth){health = maxHealth;}else{health += amount;}if (health >= CurrentMaxHealth){health = maxHealth;}}public void TakeHealth(int amount){if(amount > 0 && health == maxHealth && health != CurrentMaxHealth){health = CurrentMaxHealth;}if(health - amount < 0){health = 0;return;}health -= amount;}public void MaxHealth(){health = CurrentMaxHealth;}public bool AddMPCharge(int amount){bool result = false;if(soulLimited && maxMP != 66){maxMP = 66;}if (!soulLimited && maxMP != 99){maxMP = 99;}if(MPCharge + amount > maxMP){if(MPReverse < MPReverseMax){MPReverse += amount - (maxMP - MPCharge);result = true;if(MPReverse > MPReverseMax){MPReverse = MPReverseMax;}}MPCharge = maxMP;}else{MPCharge += amount;result = true;}return result;}public void TakeMP(int amount){if(amount < MPCharge){MPCharge -= amount;if(MPCharge < 0){MPCharge = 0;return;}}else{MPCharge = 0;}}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;}public bool GetBool(string boolName){if (string.IsNullOrEmpty(boolName)){return false;}FieldInfo field = GetType().GetField(boolName);if (field != null){return (bool)field.GetValue(instance);}Debug.Log("PlayerData: Could not find bool named " + boolName + " in PlayerData");return false;}}

回到我们的HeroActions.cs中,我们来给法术系统添加几个新行为,一个是回血机制行为的focus,一个是快速法术的quickcast:

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 PlayerAction cast;public PlayerAction focus;public PlayerAction quickCast;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");cast = CreatePlayerAction("Cast");focus = CreatePlayerAction("Focus");quickCast = CreatePlayerAction("QuickCast");}
}

来到InputHandler.cs中把刚刚创建的变量添加到方法中:

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");AddKeyBinding(inputActions.cast, "F");AddKeyBinding(inputActions.quickCast, "Q");}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}));}}

 接下来就是到HeroAnimationController.cs我们添加几个新的方法,因为在空洞骑士这个游戏中,回血的时候不能移动和播放其它动画,所以我们得创建一个属性来记录是否可以播放动画还有一个ActorStates记录失去控制之前的动画。

        public ActorStates stateBeforeControl { get; private set; }
    public bool controlEnabled { get; private set; }

 public void StartControl()
    {
            actorStates = heroCtrl.hero_state;
            controlEnabled = true;
            PlayIdle();
    }

    public void StopControl()
    {
            if(controlEnabled)
            {
                controlEnabled = false;
                stateBeforeControl = actorStates;
            }
    }

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; }public ActorStates stateBeforeControl { get; private set; }public bool controlEnabled { 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 (!controlEnabled){animator.Stop();}if(heroCtrl.hero_state == ActorStates.airborne){animator.PlayFromFrame("Airborne", 7);return;}PlayIdle();}private void Update(){if(controlEnabled){UpdateAnimation();}else if (cState.facingRight){wasFacingRight = true;}else{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:if (cState.recoilFrozen){Play("Stun");}else if (cState.recoiling){Play("Recoil");}}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;controlEnabled = true;}private void ResetPlays(){playLanding = false;playRunToIdle = false;playDashToIdle = false;}public void UpdateState(ActorStates newState){if(controlEnabled &&  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;}}public void PlayClip(string clipName){if (controlEnabled){Play(clipName);}}public void StartControl(){actorStates = heroCtrl.hero_state;controlEnabled = true;PlayIdle();}public void StopControl(){if(controlEnabled){controlEnabled = false;stateBeforeControl = actorStates;}}public void StartControlWithoutSettingState(){controlEnabled = true;if (stateBeforeControl == ActorStates.running && actorStates == ActorStates.running){actorStates = ActorStates.idle;}}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"); ;}}

来到HeroControllerStates.cs,除了我们要创建几个和法术有关的变量外,还需要获得某个状态的方法和设置某个状态的方法:

[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 recoilFrozen;public bool recoiling;public bool recoilingLeft;public bool recoilingRight;public bool freezeCharge;public bool focusing;public bool dead;public bool hazardDeath;public bool invulnerable;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;recoilFrozen = false;recoiling = false;recoilingLeft = false;recoilingRight = false;freezeCharge = false;focusing = false;dead = false;hazardDeath = false;invulnerable = false;preventDash = false;preventBackDash = false;dashCooldown = false;backDashCooldown = false;isPaused = false;}/// <summary>/// 设置一个新的状态(通常用在playmakerFSM上)/// </summary>/// <param name="stateName"></param>/// <param name="value"></param>public void SetState(string stateName, bool value){FieldInfo field = GetType().GetField(stateName);if (field != null){try{field.SetValue(HeroController.instance.cState, value);return;}catch (Exception ex){string str = "Failed to set cState: ";Exception ex2 = ex;Debug.LogError(str + ((ex2 != null) ? ex2.ToString() : null));return;}}Debug.LogError("HeroControllerStates: Could not find bool named" + stateName + "in cState");}/// <summary>/// 获取一个新的状态(通常用在playmakerFSM上)/// </summary>/// <param name="stateName"></param>/// <returns></returns>public bool GetState(string stateName){FieldInfo field = GetType().GetField(stateName);if (field != null){return (bool)field.GetValue(HeroController.instance.cState);}Debug.LogError("HeroControllerStates: Could not find bool named" + stateName + "in cState");return false;}
}

终于来到HeroController.cs中,我们来设置回血机制所需要的变量: 

    private bool drainMP; //是否正在流走MP
    private float drainMP_timer; //流走MP的计时器
    private float drainMP_time; //流走MP花费的时间
    private float MP_drained; //已经流走的MP数量
    private float focusMP_amount; //使用focus回血所需要的MP数量
    private float preventCastByDialogueEndTimer;
    public PlayMakerFSM spellControl; 

在Update函数中:

if (drainMP)
    {
            drainMP_timer += Time.deltaTime;
            while (drainMP_timer >= drainMP_time) //drainMP_time在无护符下等于0.027
            {
                MP_drained += 1f;
                drainMP_timer -= drainMP_time;
                TakeMp(1);

                if(MP_drained == focusMP_amount)
        {
                    MP_drained -= drainMP_time;
                    proxyFSM.SendEvent("HeroCtrl-FocusCompleted");

        }
        }
    }

preventCastByDialogueEndTimer -= Time.deltaTime;

设置获取MP和消耗MP的方法,增加血量和消耗血量的方法,是否能聚集,是否能释放法术,能否输入和忽略输入,失去控制和重新获得控制:

    public void AddMPCharge(int amount){int mpreverse = playerData.MPReverse;playerData.AddMPCharge(amount);if(playerData.MPReverse != mpreverse && gm){}}public void SetMPCharge(int amount){playerData.MPCharge = amount;//TODO:}public void SoulGain(){int num;if(playerData.MPCharge < playerData.maxMP){num = 11;}else{num = 6;}int mpreverse = playerData.MPReverse;playerData.AddMPCharge(num);if(playerData.MPReverse != mpreverse){}}public void TakeMp(int amount){if(playerData.MPCharge > 0){playerData.TakeMP(amount);if(amount > 1){}}}public void AddHealth(int amount){playerData.AddHealth(amount);proxyFSM.SendEvent("HeroCtrl-Healed");}public void TakeHealth(int amount){playerData.TakeHealth(amount);proxyFSM.SendEvent("HeroCtrl-HeroDamaged");}public void MaxHealth(){proxyFSM.SendEvent("HeroCtrl-MaxHealth");playerData.MaxHealth();}public void StartMPDrain(float time){Debug.LogFormat("Start MP Drain");drainMP = true;drainMP_timer = 0f;MP_drained = 0f;drainMP_time = time; //focusMP_amount = (float)playerData.GetInt("focusMP_amount");}public void StopMPDrain(){drainMP = false;}public bool CanFocus(){return !gm.isPaused && hero_state != ActorStates.no_input && !cState.dashing && !cState.backDashing && (!cState.attacking || attack_time > ATTACK_RECOVERY_TIME) && !cState.recoiling && cState.onGround && !cState.recoilFrozen && !cState.hazardDeath && CanInput();}public bool CanCast(){return !gm.isPaused && !cState.dashing && hero_state != ActorStates.no_input && !cState.backDashing && (!cState.attacking || attack_time >= ATTACK_RECOVERY_TIME) && !cState.recoiling && !cState.recoilFrozen && CanInput() && preventCastByDialogueEndTimer <= 0f;}public bool CanInput(){return acceptingInput;}public void IgnoreInput(){if (acceptingInput){acceptingInput = false;ResetInput();}}public void AcceptInput(){acceptingInput = true;}/// <summary>/// 放弃控制/// </summary>public void RelinquishControl(){if(!controlReqlinquished && !cState.dead){    ResetInput();ResetMotion();IgnoreInput();controlReqlinquished = true;ResetAttacks();touchingWallL = false;touchingWallR = false;}}/// <summary>/// 重新获得控制/// </summary>public void RegainControl(){AcceptInput();hero_state = ActorStates.idle;if(controlReqlinquished && !cState.dead){AffectedByGravity(true);SetStartingMotionState();controlReqlinquished = false;if (startWithWallslide){cState.willHardLand = false;cState.touchingWall = true;if(transform.localScale.x< 0f){touchingWallR = true;return;}touchingWallL = true;}else{if (startWithJump){HeroJumpNoEffect();startWithJump = false;return;}if (startWithFullJump){HeroJump();startWithFullJump = false;return;}if (startWithDash){HeroDash();startWithDash = false;return;}if (startWithAttack){DoAttack();startWithAttack = false;return;}cState.touchingWall = false;touchingWallL = false;touchingWallR = false;}}}

 这个SoulGain()我们要在HealthManager.cs中来调用:

public 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);if(recoil != null){recoil.RecoilByDirection(cardinalDirection,hitInstance.MagnitudeMultiplier);}switch (hitInstance.AttackType){case AttackTypes.Nail:if(hitInstance.AttackType == AttackTypes.Nail && enemyType !=3 && enemyType != 6){HeroController.instance.SoulGain();}Vector3 position = (hitInstance.Source.transform.position + transform.position) * 0.5f + effectOrigin;break;case AttackTypes.Generic:break;case AttackTypes.Spell: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){NonFatalHit(hitInstance.IgnoreInvulnerable);}else{Die(new float?(hitInstance.GetActualDirection(transform)), hitInstance.AttackType, hitInstance.IgnoreInvulnerable);}}

这里我们添加了一个新的AttackType类型就叫Spell:

public enum AttackTypes
{Nail,Generic,Spell
}

完整的HeroController.cs如下所示:

using System;
using System.Collections;
using System.Collections.Generic;
using HutongGames.PlayMaker;
using GlobalEnums;
using UnityEngine;
using System.Reflection;public class HeroController : MonoBehaviour
{public ActorStates hero_state;public ActorStates prev_hero_state;public bool acceptingInput = true;public bool controlReqlinquished; //控制是否被放弃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;//跳跃的食欲private NailSlash slashComponent; //决定使用哪种攻击的NailSlashprivate 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 bool drainMP; //是否正在流走MPprivate float drainMP_timer; //流走MP的计时器private float drainMP_time; //流走MP花费的时间private float MP_drained; //已经流走的MP数量private float focusMP_amount; //使用focus回血所需要的MP数量private float preventCastByDialogueEndTimer;public PlayMakerFSM spellControl;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 delegate void TakeDamageEvent();public event TakeDamageEvent OnTakenDamage;public delegate void OnDeathEvent();public event OnDeathEvent OnDeath;public bool takeNoDamage; //不受到伤害public PlayMakerFSM damageEffectFSM; //负责的受伤效果playmakerFSMpublic DamageMode damageMode; //受伤类型private Coroutine takeDamageCoroutine; //受伤协程private float parryInvulnTimer;  //无敌时间public float INVUL_TIME;//无敌时间public float DAMAGE_FREEZE_DOWN;  //受伤冻结的上半程时间public float DAMAGE_FREEZE_WAIT; //受伤冻结切换的时间public float DAMAGE_FREEZE_UP;//受伤冻结的下半程时间private int recoilSteps; private float recoilTimer; //后坐力计时器private bool recoilLarge; //是否是更大的后坐力private Vector2 recoilVector; //后坐力二维上的速度public float RECOIL_HOR_VELOCITY; //后坐力X轴上的速度public float RECOIL_HOR_VELOCITY_LONG; //后坐力X轴上更大的速度public float RECOIL_DOWN_VELOCITY; //后坐力Y轴上的速度public float RECOIL_HOR_STEPS; //后坐力X轴的步public float RECOIL_DURATION; //后坐力持续时间public float RECOIL_VELOCITY; //后坐力时的速度(是两个轴上都适用的)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; //是否开启掉落时相机抖动private float floatingBufferTimer;private float FLOATING_CHECK_TIME = 0.18f;private bool startWithWallslide;private bool startWithJump;private bool startWithFullJump;private bool startWithDash;private bool startWithAttack;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 new MeshRenderer renderer;private InvulnerablePulse invPulse;private SpriteFlash spriteFlash;public PlayMakerFSM proxyFSM { get; private set; }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;FLOATING_CHECK_TIME = 0.18f;}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>();renderer = GetComponent<MeshRenderer>();invPulse = GetComponent<InvulnerablePulse>();spriteFlash = GetComponent<SpriteFlash>();proxyFSM = FSMUtility.LocateFSM(gameObject, "ProxyFSM");}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);}if (spellControl == null){Debug.Log("SpellControl came up null, locating manually");spellControl = FSMUtility.LocateFSM(gameObject, "Spell Control");}}private void Update(){current_velocity = rb2d.velocity;FallCheck();FailSafeCheck();if (hero_state == ActorStates.running && !cState.dashing && !cState.backDashing && !controlReqlinquished){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){if (cState.recoiling){if (recoilTimer < RECOIL_DURATION){recoilTimer += Time.deltaTime;}else{CancelDamageRecoil();if ((prev_hero_state == ActorStates.idle || prev_hero_state == ActorStates.running) && !CheckTouchingGround()){cState.onGround = false;SetState(ActorStates.airborne);}else{SetState(ActorStates.previous);}}}}else if (hero_state != ActorStates.no_input){LookForInput();if (cState.recoiling){cState.recoiling = false;AffectedByGravity(true);}if (cState.attacking && !cState.dashing){attack_time += Time.deltaTime;if (attack_time >= attackDuration){ResetAttacks();animCtrl.StopAttack();}}if(hero_state == ActorStates.idle){if(!controlReqlinquished && !gm.isPaused){//TODO:}}}LookForQueueInput();if (drainMP){drainMP_timer += Time.deltaTime;while (drainMP_timer >= drainMP_time) //drainMP_time在无护符下等于0.027{MP_drained += 1f;drainMP_timer -= drainMP_time;TakeMp(1);if(MP_drained == focusMP_amount){MP_drained -= drainMP_time;proxyFSM.SendEvent("HeroCtrl-FocusCompleted");}}}if (attack_cooldown > 0f){attack_cooldown -= Time.deltaTime;}if (dashCooldownTimer > 0f) //计时器在Update中-= Time.deltaTime{dashCooldownTimer -= Time.deltaTime;}preventCastByDialogueEndTimer -= Time.deltaTime;if (parryInvulnTimer > 0f){parryInvulnTimer -= Time.deltaTime;}}private void FixedUpdate(){if(cState.recoilingLeft || cState.recoilingRight){if(recoilSteps <= RECOIL_HOR_STEPS){recoilSteps++;}else{CancelRecoilHorizonal();}}if(hero_state == ActorStates.hard_landing || hero_state == ActorStates.dash_landing){ResetMotion();}else if(hero_state == ActorStates.no_input){if (cState.recoiling){AffectedByGravity(false);rb2d.velocity = recoilVector;}}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();CancelAttack();}else if (move_input < 0f && cState.facingRight){FlipSprite();CancelAttack();}}if(cState.recoilingLeft){float num;if (recoilLarge){num = RECOIL_HOR_VELOCITY_LONG;}else{num = RECOIL_HOR_VELOCITY;}if(rb2d.velocity.x > -num){rb2d.velocity = new Vector2(-num, rb2d.velocity.y);}else{rb2d.velocity = new Vector2(rb2d.velocity.x - num, rb2d.velocity.y);}}if (cState.recoilingRight){float num2;if(recoilLarge){num2 = RECOIL_HOR_VELOCITY_LONG;}else{num2 = RECOIL_HOR_VELOCITY;}if (rb2d.velocity.x < num2){rb2d.velocity = new Vector2(num2, rb2d.velocity.y);}else{rb2d.velocity = new Vector2(rb2d.velocity.x + num2, rb2d.velocity.y);}}}}if (cState.jumping) //如果cState.jumping就Jump{Jump();}if (cState.dashing)//如果cState.dashing就Dash{Dash();}//限制速度if(rb2d.velocity.y < -MAX_FALL_VELOCITY && !controlReqlinquished ){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(){cState.recoiling = false;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 && !cState.dead && !cState.hazardDeath && !controlReqlinquished;}private void CancelAttack(){if (cState.attacking){slashComponent.CancelAttack();ResetAttacks();}}private void ResetAttacks(){cState.attacking = false;cState.upAttacking = false;cState.downAttacking = false;attack_time = 0f;}public void AddMPCharge(int amount){int mpreverse = playerData.MPReverse;playerData.AddMPCharge(amount);if(playerData.MPReverse != mpreverse && gm){}}public void SetMPCharge(int amount){playerData.MPCharge = amount;//TODO:}public void SoulGain(){int num;if(playerData.MPCharge < playerData.maxMP){num = 11;}else{num = 6;}int mpreverse = playerData.MPReverse;playerData.AddMPCharge(num);if(playerData.MPReverse != mpreverse){}}public void TakeMp(int amount){if(playerData.MPCharge > 0){playerData.TakeMP(amount);if(amount > 1){}}}public void AddHealth(int amount){playerData.AddHealth(amount);proxyFSM.SendEvent("HeroCtrl-Healed");}public void TakeHealth(int amount){playerData.TakeHealth(amount);proxyFSM.SendEvent("HeroCtrl-HeroDamaged");}public void MaxHealth(){proxyFSM.SendEvent("HeroCtrl-MaxHealth");playerData.MaxHealth();}public void StartMPDrain(float time){Debug.LogFormat("Start MP Drain");drainMP = true;drainMP_timer = 0f;MP_drained = 0f;drainMP_time = time; //focusMP_amount = (float)playerData.GetInt("focusMP_amount");}public void StopMPDrain(){drainMP = false;}public bool CanFocus(){return !gm.isPaused && hero_state != ActorStates.no_input && !cState.dashing && !cState.backDashing && (!cState.attacking || attack_time > ATTACK_RECOVERY_TIME) && !cState.recoiling && cState.onGround && !cState.recoilFrozen && !cState.hazardDeath && CanInput();}public bool CanCast(){return !gm.isPaused && !cState.dashing && hero_state != ActorStates.no_input && !cState.backDashing && (!cState.attacking || attack_time >= ATTACK_RECOVERY_TIME) && !cState.recoiling && !cState.recoilFrozen && CanInput() && preventCastByDialogueEndTimer <= 0f;}public bool CanInput(){return acceptingInput;}public void IgnoreInput(){if (acceptingInput){acceptingInput = false;ResetInput();}}public void AcceptInput(){acceptingInput = true;}/// <summary>/// 放弃控制/// </summary>public void RelinquishControl(){if(!controlReqlinquished && !cState.dead){ResetInput();ResetMotion();IgnoreInput();controlReqlinquished = true;ResetAttacks();touchingWallL = false;touchingWallR = false;}}/// <summary>/// 重新获得控制/// </summary>public void RegainControl(){AcceptInput();hero_state = ActorStates.idle;if(controlReqlinquished && !cState.dead){AffectedByGravity(true);SetStartingMotionState();controlReqlinquished = false;if (startWithWallslide){cState.willHardLand = false;cState.touchingWall = true;if(transform.localScale.x< 0f){touchingWallR = true;return;}touchingWallL = true;}else{if (startWithJump){HeroJumpNoEffect();startWithJump = false;return;}if (startWithFullJump){HeroJump();startWithFullJump = false;return;}if (startWithDash){HeroDash();startWithDash = false;return;}if (startWithAttack){DoAttack();startWithAttack = false;return;}cState.touchingWall = false;touchingWallL = false;touchingWallR = false;}}}private void SetStartingMotionState(){SetStartingMotionState(false);}private void SetStartingMotionState(bool preventRunDip){move_input = (acceptingInput || preventRunDip) ? inputHandler.inputActions.moveVector.X : 0f;cState.touchingWall = false;if (CheckTouchingGround()){cState.onGround = true;SetState(ActorStates.grounded);}else{cState.onGround = false;SetState(ActorStates.airborne);}animCtrl.UpdateState(hero_state);}/// <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);cState.recoiling = false;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.attacking || attack_time >= ATTACK_RECOVERY_TIME)  &&!cState.preventBackDash && !cState.backDashCooldown && !controlReqlinquished && !cState.recoilFrozen && !cState.recoiling && 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;}if(rb2d.velocity.y == 0f && !cState.onGround && !cState.falling && !cState.jumping && !cState.dashing && hero_state != ActorStates.hard_landing && hero_state != ActorStates.no_input){if (CheckTouchingGround()){floatingBufferTimer += Time.deltaTime;if(floatingBufferTimer > FLOATING_CHECK_TIME){if (cState.recoiling){CancelDamageRecoil();}BackOnGround();floatingBufferTimer = 0f;return;}}else{floatingBufferTimer = 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();CancelRecoilHorizonal();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;}public void RecoilLeft(){if(!cState.recoilingLeft && !cState.recoilingRight && !controlReqlinquished){CancelDash();recoilSteps = 0;cState.recoilingLeft = true;cState.recoilingRight = false;recoilLarge = false;rb2d.velocity = new Vector2(-RECOIL_HOR_VELOCITY, rb2d.velocity.y);}}public void RecoilRight(){if (!cState.recoilingLeft && !cState.recoilingRight && !controlReqlinquished){CancelDash();recoilSteps = 0;cState.recoilingLeft = false;cState.recoilingRight = true;recoilLarge = false;rb2d.velocity = new Vector2(RECOIL_HOR_VELOCITY, rb2d.velocity.y);}}public void RecoilLeftLong(){if (!cState.recoilingLeft && !cState.recoilingRight && !controlReqlinquished){CancelDash();ResetAttacks();recoilSteps = 0;cState.recoilingLeft = true;cState.recoilingRight = false;recoilLarge = true;rb2d.velocity = new Vector2(-RECOIL_HOR_VELOCITY_LONG, rb2d.velocity.y);}}public void RecoilRightLong(){if (!cState.recoilingLeft && !cState.recoilingRight && !controlReqlinquished){CancelDash();ResetAttacks();recoilSteps = 0;cState.recoilingLeft = false;cState.recoilingRight = true;recoilLarge = true;rb2d.velocity = new Vector2(RECOIL_HOR_VELOCITY_LONG, rb2d.velocity.y);}}public void RecoilDown(){CancelJump();if(rb2d.velocity.y > RECOIL_DOWN_VELOCITY && !controlReqlinquished){rb2d.velocity = new Vector2(rb2d.velocity.x, RECOIL_DOWN_VELOCITY);}}public void CancelRecoilHorizonal(){cState.recoilingLeft = false;cState.recoilingRight = false;recoilSteps = 0;}private bool CanTakeDamage(){return damageMode != DamageMode.NO_DAMAGE && !cState.invulnerable && !cState.recoiling && !cState.dead;}public void TakeDamage(GameObject go,CollisionSide damageSide,int damageAmount,int hazardType){bool spawnDamageEffect = true;if (damageAmount > 0){if (CanTakeDamage()){if (damageMode == DamageMode.HAZARD_ONLY && hazardType == 1){return;}if (parryInvulnTimer > 0f && hazardType == 1){return;}proxyFSM.SendEvent("HeroCtrl-HeroDamaged");CancelAttack();if (cState.touchingWall){cState.touchingWall = false;}if (cState.recoilingLeft || cState.recoilingRight){CancelRecoilHorizonal();}audioCtrl.PlaySound(HeroSounds.TAKE_HIT);if (!takeNoDamage){playerData.TakeHealth(damageAmount);}if (damageAmount > 0 && OnTakenDamage != null){OnTakenDamage();}if (playerData.health == 0){StartCoroutine(Die());return;}if (hazardType == 2){Debug.LogFormat("Die From Spikes");return;}if (hazardType == 3){Debug.LogFormat("Die From Acid");return;}if (hazardType == 4){Debug.LogFormat("Die From Lava");return;}if (hazardType == 5){Debug.LogFormat("Die From Pit");return;}StartCoroutine(StartRecoil(damageSide, spawnDamageEffect, damageAmount));return;}else if (cState.invulnerable && !cState.hazardDeath){if(hazardType == 2){if (!takeNoDamage){playerData.TakeHealth(damageAmount);}proxyFSM.SendEvent("HeroCtrl-HeroDamaged");if(playerData.health == 0){StartCoroutine(Die());return;}audioCtrl.PlaySound(HeroSounds.TAKE_HIT);StartCoroutine(DieFromHazard(HazardTypes.SPIKES, (go != null) ? go.transform.rotation.z : 0f));return;}else if (hazardType == 3){             playerData.TakeHealth(damageAmount);proxyFSM.SendEvent("HeroCtrl-HeroDamaged");if (playerData.health == 0){StartCoroutine(Die());return;}audioCtrl.PlaySound(HeroSounds.TAKE_HIT);StartCoroutine(DieFromHazard(HazardTypes.ACID, 0f));return;}else if(hazardType == 4){Debug.LogFormat("Die From Lava");}}}}private IEnumerator Die(){if (OnDeath != null){OnDeath();}if (!cState.dead){playerData.disablePause = true;rb2d.velocity = Vector2.zero;CancelRecoilHorizonal();AffectedByGravity(false);HeroBox.inactive = true;rb2d.isKinematic = true;SetState(ActorStates.no_input);cState.dead = true;ResetMotion();ResetHardLandingTimer();renderer.enabled = false;gameObject.layer = 2;yield return null;}}private IEnumerator DieFromHazard(HazardTypes hazardType,float angle){if (!cState.hazardDeath){playerData.disablePause = true;SetHeroParent(null);SetState(ActorStates.no_input);cState.hazardDeath = true;ResetMotion();ResetHardLandingTimer();AffectedByGravity(false);renderer.enabled = false;gameObject.layer = 2;if(hazardType == HazardTypes.SPIKES){}else if(hazardType == HazardTypes.ACID){}yield return null;}}private IEnumerator StartRecoil(CollisionSide impactSide, bool spawnDamageEffect, int damageAmount){if (!cState.recoiling){playerData.disablePause = true;ResetMotion();AffectedByGravity(false);if(impactSide == CollisionSide.left){recoilVector = new Vector2(RECOIL_VELOCITY, RECOIL_VELOCITY * 0.5f);if (cState.facingRight){FlipSprite();}}else if (impactSide == CollisionSide.right){recoilVector = new Vector2(-RECOIL_VELOCITY, RECOIL_VELOCITY * 0.5f);if (!cState.facingRight){FlipSprite();}}else{recoilVector = Vector2.zero;}SetState(ActorStates.no_input);cState.recoilFrozen = true;if (spawnDamageEffect){damageEffectFSM.SendEvent("DAMAGE");if(damageAmount > 1){}}StartCoroutine(Invulnerable(INVUL_TIME));yield return takeDamageCoroutine = StartCoroutine(gm.FreezeMoment(DAMAGE_FREEZE_DOWN, DAMAGE_FREEZE_WAIT, DAMAGE_FREEZE_UP, 0.0001f));cState.recoilFrozen = false;cState.recoiling = true;playerData.disablePause = false;}}private IEnumerator Invulnerable(float duration){cState.invulnerable = true;yield return new WaitForSeconds(DAMAGE_FREEZE_DOWN);invPulse.StartInvulnerablePulse();yield return new WaitForSeconds(duration);invPulse.StopInvulnerablePulse();cState.invulnerable = false;cState.recoiling = false;}public void CancelDamageRecoil(){cState.recoiling = false;recoilTimer = 0f;ResetMotion();AffectedByGravity(true);}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.recoiling = false;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){RecoilLeft();}else{RecoilRight();}}else if (attackDir == AttackDirection.upward){RecoilDown();}}}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){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;}public void SetHeroParent(Transform newParent){transform.parent = newParent;if (newParent == null){DontDestroyOnLoad(gameObject);}}/// <summary>/// 设置一个新的HeroControllerStates状态/// </summary>/// <param name="stateName"></param>/// <param name="value"></param>public void SetCState(string stateName, bool value){cState.SetState(stateName, value);}/// <summary>/// 获取当前HeroControllerStates状态/// </summary>/// <param name="stateName"></param>/// <returns></returns>public bool GetCState(string stateName){return cState.GetState(stateName);}public void StartAnimationControl(){animCtrl.StartControl();}public void StopAnimationControl(){animCtrl.StopControl();}}[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 recoilFrozen;public bool recoiling;public bool recoilingLeft;public bool recoilingRight;public bool freezeCharge;public bool focusing;public bool dead;public bool hazardDeath;public bool invulnerable;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;recoilFrozen = false;recoiling = false;recoilingLeft = false;recoilingRight = false;freezeCharge = false;focusing = false;dead = false;hazardDeath = false;invulnerable = false;preventDash = false;preventBackDash = false;dashCooldown = false;backDashCooldown = false;isPaused = false;}/// <summary>/// 设置一个新的状态(通常用在playmakerFSM上)/// </summary>/// <param name="stateName"></param>/// <param name="value"></param>public void SetState(string stateName, bool value){FieldInfo field = GetType().GetField(stateName);if (field != null){try{field.SetValue(HeroController.instance.cState, value);return;}catch (Exception ex){string str = "Failed to set cState: ";Exception ex2 = ex;Debug.LogError(str + ((ex2 != null) ? ex2.ToString() : null));return;}}Debug.LogError("HeroControllerStates: Could not find bool named" + stateName + "in cState");}/// <summary>/// 获取一个新的状态(通常用在playmakerFSM上)/// </summary>/// <param name="stateName"></param>/// <returns></returns>public bool GetState(string stateName){FieldInfo field = GetType().GetField(stateName);if (field != null){return (bool)field.GetValue(HeroController.instance.cState);}Debug.LogError("HeroControllerStates: Could not find bool named" + stateName + "in cState");return false;}}

 你可能注意到代码中关于法术有关的代码少之又少,这是因为我打算用playmakerFSM制作整个法术系统,给小骑士创建一个新的playMakerFSM就叫Spell Control:

底下是一个基本的法术系统playmakerFSM,所以这就是我说的图片很多小心流量

我们先来把变量和事件都添加上去:

需要特别设置的变量如下所示:

然后我将逐个介绍每种状态和每种状态要用某些行为的含义:

首先一开始先顿个一帧切换到FINISHED

初始化Init,主要是获取需要的子对象:

初始化结束后进入不活跃Inactive状态:

这两个是我新建的自定义脚本,用于监听Cast和QuickCast的按键输入:

using UnityEngine;namespace HutongGames.PlayMaker.Actions
{[ActionCategory("Controls")][Tooltip("Listens for an action button press (using HeroActions InControl mappings).")]public class ListenForCast : FsmStateAction{[Tooltip("Where to send the event.")]public FsmEventTarget eventTarget;public FsmEvent wasPressed;public FsmEvent wasReleased;public FsmEvent isPressed;public FsmEvent isNotPressed;public FsmBool activeBool;public bool stateEntryOnly;private GameManager gm;private InputHandler inputHandler;public override void Reset(){eventTarget = null;activeBool = new FsmBool{UseVariable = true};}public override void OnEnter(){gm = GameManager.instance;inputHandler = gm.GetComponent<InputHandler>();CheckForInput();if (stateEntryOnly){Finish();}}public override void OnUpdate(){CheckForInput();}private void CheckForInput(){if (!gm.isPaused && (activeBool.IsNone || activeBool.Value)){if (inputHandler.inputActions.cast.WasPressed){Fsm.Event(wasPressed);}if (inputHandler.inputActions.cast.WasReleased){Fsm.Event(wasReleased);}if (inputHandler.inputActions.cast.IsPressed){Fsm.Event(isPressed);}if (!inputHandler.inputActions.cast.IsPressed){Fsm.Event(isNotPressed);}}}}}
using UnityEngine;namespace HutongGames.PlayMaker.Actions
{[ActionCategory("Controls")][Tooltip("Listens for an action button press (using HeroActions InControl mappings).")]public class ListenForQuickCast : FsmStateAction{[Tooltip("Where to send the event.")]public FsmEventTarget eventTarget;public FsmEvent wasPressed;public FsmEvent wasReleased;public FsmEvent isPressed;public FsmEvent isNotPressed;private GameManager gm;private InputHandler inputHandler;public override void Reset(){eventTarget = null;}public override void OnEnter(){gm = GameManager.instance;inputHandler = gm.GetComponent<InputHandler>();}public override void OnUpdate(){if (!gm.isPaused){if (inputHandler.inputActions.quickCast.WasPressed){Fsm.Event(wasPressed);}if (inputHandler.inputActions.quickCast.WasReleased){Fsm.Event(wasReleased);}if (inputHandler.inputActions.quickCast.IsPressed){Fsm.Event(isPressed);}if (!inputHandler.inputActions.quickCast.IsPressed){Fsm.Event(isNotPressed);}}}}
}

如果按下按钮后就判断是否有上下的按键输入,有的话就进入判断地震和狂啸状态,没有的话就等时间到后进入回血状态:

这两个是我新建的自定义脚本,用于监听上Up和下Down的按键输入:

using UnityEngine;namespace HutongGames.PlayMaker.Actions
{[ActionCategory("Controls")][Tooltip("Listens for an action button press (using HeroActions InControl mappings).")]public class ListenForDown : FsmStateAction{[Tooltip("Where to send the event.")]public FsmEventTarget eventTarget;public FsmEvent wasPressed;public FsmEvent wasReleased;public FsmEvent isPressed;public FsmEvent isNotPressed;[UIHint(UIHint.Variable)]public FsmBool isPressedBool;public bool stateEntryOnly;private GameManager gm;private InputHandler inputHandler;public override void Reset(){eventTarget = null;}public override void OnEnter(){gm = GameManager.instance;inputHandler = gm.GetComponent<InputHandler>();CheckForInput();if (stateEntryOnly){Finish();}}public override void OnUpdate(){CheckForInput();}private void CheckForInput(){if (!gm.isPaused){if (inputHandler.inputActions.down.WasPressed){Fsm.Event(wasPressed);}if (inputHandler.inputActions.down.WasReleased){Fsm.Event(wasReleased);}if (inputHandler.inputActions.down.IsPressed){Fsm.Event(isPressed);if (!isPressedBool.IsNone){isPressedBool.Value = true;}}if (!inputHandler.inputActions.down.IsPressed){Fsm.Event(isNotPressed);if (!isPressedBool.IsNone){isPressedBool.Value = false;}}}}}
}
using UnityEngine;namespace HutongGames.PlayMaker.Actions
{[ActionCategory("Controls")][Tooltip("Listens for an action button press (using HeroActions InControl mappings).")]public class ListenForUp : FsmStateAction{[Tooltip("Where to send the event.")]public FsmEventTarget eventTarget;public FsmEvent wasPressed;public FsmEvent wasReleased;public FsmEvent isPressed;public FsmEvent isNotPressed;[UIHint(UIHint.Variable)]public FsmBool isPressedBool;public bool stateEntryOnly;private GameManager gm;private InputHandler inputHandler;public override void Reset(){eventTarget = null;}public override void OnEnter(){gm = GameManager.instance;inputHandler = gm.GetComponent<InputHandler>();CheckForInput();if (stateEntryOnly){Finish();}}public override void OnUpdate(){CheckForInput();}private void CheckForInput(){if (!gm.isPaused){if (inputHandler.inputActions.up.WasPressed){Fsm.Event(wasPressed);}if (inputHandler.inputActions.up.WasReleased){Fsm.Event(wasReleased);}if (inputHandler.inputActions.up.IsPressed){Fsm.Event(isPressed);if (!isPressedBool.IsNone){isPressedBool.Value = true;}}if (!inputHandler.inputActions.up.IsPressed){Fsm.Event(isNotPressed);if (!isPressedBool.IsNone){isPressedBool.Value = false;}}}}}
}

 如果进入Can Focus?状态通过HeroController.cs的CanFocus()方法和GameManager里面的PlayerData的Int值类型够不够来判断:

这里有一个自定义行为脚本CallMethodProper .cs:

using System;
using System.Reflection;
using UnityEngine;namespace HutongGames.PlayMaker.Actions
{[ActionCategory(ActionCategory.ScriptControl)]public class CallMethodProper : FsmStateAction{[RequiredField][Tooltip("The game object that owns the Behaviour.")]public FsmOwnerDefault gameObject;[RequiredField][UIHint(UIHint.Behaviour)][Tooltip("The Behaviour that contains the method to start as a coroutine.")]public FsmString behaviour;[UIHint(UIHint.Method)][Tooltip("Name of the method to call on the component")]public FsmString methodName;[Tooltip("Method paramters. NOTE: these must match the method's signature!")]public FsmVar[] parameters;[ActionSection("Store Result")][UIHint(UIHint.Variable)][Tooltip("Store the result of the method call.")]public FsmVar storeResult;private UnityEngine.Object cachedBehaviour;private Type cachedType;private MethodInfo cachedMethodInfo;private ParameterInfo[] cachedParameterInfo;private object[] parametersArray;private string errorString;private MonoBehaviour component;public override void OnEnter(){parametersArray = new object[parameters.Length];DoMethodCall();Finish();}private void DoMethodCall(){if (behaviour.Value == null){Finish();return;}GameObject ownerDefaultTarget = base.Fsm.GetOwnerDefaultTarget(gameObject);if (ownerDefaultTarget == null){return;}component = (ownerDefaultTarget.GetComponent(behaviour.Value) as MonoBehaviour);if (component == null){LogWarning("CallMethodProper: " + ownerDefaultTarget.name + " missing behaviour: " + behaviour.Value);return;}if (cachedMethodInfo == null){errorString = string.Empty;if (!DoCache()){Debug.LogError(errorString);Finish();return;}}object value = null;if (cachedParameterInfo.Length == 0){value = cachedMethodInfo.Invoke(cachedBehaviour, null);}else{for (int i = 0; i < parameters.Length; i++){FsmVar fsmVar = parameters[i];fsmVar.UpdateValue();parametersArray[i] = fsmVar.GetValue();}try{value = cachedMethodInfo.Invoke(cachedBehaviour, parametersArray);}catch (Exception ex){string str = "CallMethodProper error on ";string ownerName = Fsm.OwnerName;string str2 = " -> ";Exception ex2 = ex;Debug.LogError(str + ownerName + str2 + ((ex2 != null) ? ex2.ToString() : null));}}if (storeResult.Type != VariableType.Unknown){storeResult.SetValue(value);}}private bool DoCache(){cachedBehaviour = component;cachedType = component.GetType();cachedMethodInfo = cachedType.GetMethod(methodName.Value);if (cachedMethodInfo == null){errorString = errorString + "Method Name is invalid: " + methodName.Value + "\n";Finish();return false;}cachedParameterInfo = cachedMethodInfo.GetParameters();return true;}}}

然后就到了设置动画播放Set Slug Anim的时候了,这里我们通过有无护符乌恩之形(equippedCharm_28)来判断播放哪种回血动画:

然后就到了开始回血的Focus Start状态了:设置HeroControllerState.freezeCharge状态为true,HeroControllerState.focusing为true,并开始播放音效了,玩家失去控制并停止动画控制的方法,播放新的动画等待FocusStartTimer结束后进入下一个状态:

 

到了设置回血速度Set Focus Speed的状态:通过判断是否装备快速聚集(equippedCharm_7)护符 来设置不同的回血速度:

 到了设置深度回血速度Deep Focus Speed的状态同样也是根据护符来判断的:

进入判断是否是Slug?状态:我们还没做到这个部分所以就不管了,让它进slug speed状态(不过肯定不会进的我们不会触发SLUG事件)

 然后进入Focus状态:设置子对象Line Anim的MeshRenderere为true,然后播放里面的动画,并设置Dust  L和Dust R的emission为true,执行函数StartMPDrain(TimePerMPDrain),

 

自定义脚本SetParticleEmissionRate.cs:

using UnityEngine;namespace HutongGames.PlayMaker.Actions
{[ActionCategory("Particle System")][Tooltip("Set particle emission on or off on an object with a particle emitter")]public class SetParticleEmissionRate : FsmStateAction{[RequiredField][Tooltip("The particle emitting GameObject")]public FsmOwnerDefault gameObject;public FsmFloat emissionRate;public bool everyFrame;private ParticleSystem emitter;public override void Reset(){gameObject = null;emissionRate = null;everyFrame = false;}public override void OnEnter(){if(gameObject != null){GameObject ownerDefaultTarget = Fsm.GetOwnerDefaultTarget(gameObject);if (ownerDefaultTarget){emitter = ownerDefaultTarget.GetComponent<ParticleSystem>();}DoSetEmitRate();if (!everyFrame){Finish();}}}public override void OnUpdate(){DoSetEmitRate();}private void DoSetEmitRate(){if (emitter){emitter.emissionRate = emissionRate.Value;}}}}

如果Focus完成后,我们的HeroController.cs就会向小骑士的另一个playmaker叫proxyFSM发送事件:HeroCtrl-FocusCompleted,然后proxyFSM就会向自身所有的playmaker状态机(包括我们上面创建的spell control)发送FOCUS COMPLETED事件,然后就可以进入Spore Cloud状态了:

if (drainMP){drainMP_timer += Time.deltaTime;while (drainMP_timer >= drainMP_time) //drainMP_time在无护符下等于0.027{MP_drained += 1f;drainMP_timer -= drainMP_time;TakeMp(1);if(MP_drained == focusMP_amount){MP_drained -= drainMP_time;proxyFSM.SendEvent("HeroCtrl-FocusCompleted");}}}

 我们来到proxyFSM创建新的事件:

Sproe状态中判断冷却时间以及是否有两个护符,如果有equippedCharm_10英勇者勋章就执行DUNG事件进入Dung Cloud状态:

进入Dung Cloud状态:其实我还没做,所以就不用介绍了,进入FINISHED进入Set HP Amount状态

 状态Set HP Amount:通过护符判断该回多少滴血:

来到focusHealth状态:首先执行SpriteFlash.cs脚本中的flashFocusHeal(这个后面会讲,要留意)方法,播放一次性的AudioSource,执行HeroController.cs中的StopMPDrain()方法停止MP流失,小骑士播放动画Focus Get,执行HeroController.cs中的AddHealth()方法增加一滴血,然后记录满血的值maxHealth,等0.2s进入下一个状态。

这里有几个自定义的行为脚本我先贴出来吧:

using System;
using UnityEngine;namespace HutongGames.PlayMaker.Actions
{[ActionCategory(ActionCategory.Audio)][Tooltip("Instantiate an Audio Player object and play a oneshot sound via its Audio Source.")]public class AudioPlayerOneShotSingle : FsmStateAction{[RequiredField][Tooltip("The object to spawn. Select Audio Player prefab.")]public FsmGameObject audioPlayer;[RequiredField][Tooltip("Object to use as the spawn point of Audio Player")]public FsmGameObject spawnPoint;[ObjectType(typeof(AudioClip))]public FsmObject audioClip;public FsmFloat pitchMin;public FsmFloat pitchMax;public FsmFloat volume = 1f;public FsmFloat delay;public FsmGameObject storePlayer;private AudioSource audio;private float timer;public override void Reset(){spawnPoint = null;pitchMin = 1f;pitchMax = 1f;volume = 1f;}public override void OnEnter(){timer = 0f;if(delay.Value == 0f){DoPlayRandomClip();Finish();}}public override void OnUpdate(){if (delay.Value > 0f){timer += Time.deltaTime;return;}DoPlayRandomClip();Finish();}private void DoPlayRandomClip(){if(!audioPlayer.IsNone && !spawnPoint.IsNone && spawnPoint.Value != null){GameObject value = audioPlayer.Value;Vector3 position = spawnPoint.Value.transform.position;Vector3 up = Vector3.up;if (audioPlayer.Value != null){GameObject gameObject = GameObject.Instantiate(audioPlayer.Value, position, Quaternion.Euler(up));audio = gameObject.GetComponent<AudioSource>();storePlayer.Value = gameObject;AudioClip audioClip = this.audioClip.Value as AudioClip;float pitch = UnityEngine.Random.Range(pitchMin.Value, pitchMax.Value);audio.pitch = pitch;audio.volume = volume.Value;if (audioClip != null){audio.PlayOneShot(audioClip);return;}}else{Debug.LogError("AudioPlayer object not set!");}}}}}

 判断是否满血Full HP?的状态:如果MP小于回血所需要的MP就执行CANCEL事件回到Focus状态,FINISHED事件进入Set Full状态:

 

Set Full状态:

Focus Get Finish状态:完成回血后的状态,淡出音量,HeroControllerState.focusing等于false,Lines Anim播放Focus Effect End动画,小骑士自身播放Focus Get Once动画,停止Dust L,Dust R的粒子系统的Emission为0,执行HeroController的StopMPDrain,设置指为0.02,等0.3秒后进入下一个状态:

这里有一个自定义行为脚本FadeAudio.cs:

using UnityEngine;namespace HutongGames.PlayMaker.Actions
{[ActionCategory(ActionCategory.Audio)][Tooltip("Sets the Volume of the Audio Clip played by the AudioSource component on a Game Object.")]public class FadeAudio : ComponentAction<AudioSource>{[RequiredField][CheckForComponent(typeof(AudioSource))]public FsmOwnerDefault gameObject;public FsmFloat startVolume;public FsmFloat endVolume;public FsmFloat time;private float timeElapsed;private float timePercentage;private bool fadingDown;public override void Reset(){gameObject = null;startVolume = 1f;endVolume = 0f;time = 1f;}public override void OnEnter(){if(startVolume.Value > endVolume.Value){fadingDown = true;}else{fadingDown = false;}GameObject ownerDefaultTarget = Fsm.GetOwnerDefaultTarget(gameObject);if (UpdateCache(ownerDefaultTarget)){audio.volume = startVolume.Value;}}public override void OnUpdate(){DoSetAudioVolume();}public override void OnExit(){GameObject ownerDefaultTarget = Fsm.GetOwnerDefaultTarget(gameObject);if (UpdateCache(ownerDefaultTarget)){audio.volume = endVolume.Value;}}private void DoSetAudioVolume(){GameObject ownerDefaultTarget = Fsm.GetOwnerDefaultTarget(gameObject);if (UpdateCache(ownerDefaultTarget)){timeElapsed += Time.deltaTime;timePercentage = timeElapsed / time.Value * 100f;float num = (endVolume.Value - startVolume.Value) * (timePercentage / 100f);audio.volume = audio.volume + num;if (fadingDown && audio.volume <= endVolume.Value){audio.volume = endVolume.Value;Finish();}else if (!fadingDown && audio.volume >= endVolume.Value){audio.volume = endVolume.Value;Finish();}timeElapsed = 0f;}}}}

 到了Regain Control重新获得控制的状态:HeroControllerState里面的状态freezeCharge设置为false停止播放Audio,将Lines Anim的meshrenderer设置为false,执行HeroController的RegainControl()和StartAnimationControl()两个方法

检测是否要回到不活跃状态的Back in?:如果是就回,不是的话进入Can Focus状态:

Grace Check:宽限期检测,如果是第一次则进入First Grace Check状态,如果不是就进入Focus Cancel状态:

First Grace Check状态:执行SetMPCharge()函数直接设置成初始MP值即可:

至此我们完成了完整的法术系统的回血机制,这些大多都是通过playmakerFSM状态机来实现的。

二、制作法术系统的法球机制

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

首先先把图片和动画导进来吧:

OK接下来我们就来制作法球了,继续到Spell Control的playmakerFSM中,当BUTTON DOWN选择BUTTON UP事件后,先判断Can Cast?:

选择法术类型Spell Chocie:

由于我只做了法球,所以法球和地震的状态我们就直接CANCEL到Has Fireball?状态即可:

 直接进入Cast状态的Quick Cast,我们还是一样先判断能否Cast:

 同样也是选择:

 有火球法术吗?Has Fireball?:获取playerdata的fireballLevel。

是否是在墙边:如果是的话就执行FlipSprite()翻转人物:

 准备执行fireball阶段:小骑士播放动画Fireball Antic,执行函数RelinquishControl()和StopAnimationControl(),AffectedByGravity()设置为false,小骑士速度设置为0,监听Cast的输入,我们可以在InputManager中设置好。

 

检测火球等级的Level Check: 

 火球为1级的Fireball 1:

 播放动画Fireball1 Cast,执行函数TakeMp()消耗MP,生成一个预制体,这个是法球生成位置的预制体,等会再说,发送事件FINISHED到 Fireball Recoil       

发送火球的后坐力Fireball Recoil状态:

 Spell End状态:播放结束后执行函数RegainControl()和StartAnimationControl()回到inactive状态

2.制作法术系统的法球机制

        接下来就是要制作预制体Fireball Top

这个particles我找不到素材,你们就自由发挥吧:

这个Fireball Blast的playmakerFSM内容如下:就是等动画播放完成后回收它:

 

using UnityEngine;namespace HutongGames.PlayMaker.Actions
{[ActionCategory("Object Pool")][Tooltip("Recycles the Owner of the Fsm. Useful for Object Pool spawned Prefabs that need to kill themselves, e.g., a projectile that explodes on impact.")]public class RecycleSelf : FsmStateAction{// TODO:后续有了ObjectPool的时候就替换掉public override void OnEnter(){if (Owner != null)GameObject.Destroy(Owner);Finish();}}
}

 再给Fireball Top一个新的FSM:Fireball Cast

每一个状态如下,我感觉不用我介绍了,就直接贴图自己看看吧:

这里的自定义脚本SpawnObjectFromGlobalPool.cs如下:

using UnityEngine;namespace HutongGames.PlayMaker.Actions
{[ActionCategory(ActionCategory.GameObject)][Tooltip("Spawns a prefab Game Object from the Global Object Pool on the Game Manager.")]public class SpawnObjectFromGlobalPool : FsmStateAction{[RequiredField][Tooltip("GameObject to create. Usually a Prefab.")]public FsmGameObject gameObject;[Tooltip("Optional Spawn Point.")]public FsmGameObject spawnPoint;[Tooltip("Position. If a Spawn Point is defined, this is used as a local offset from the Spawn Point position.")]public FsmVector3 position;[Tooltip("Rotation. NOTE: Overrides the rotation of the Spawn Point.")]public FsmVector3 rotation;[UIHint(UIHint.Variable)][Tooltip("Optionally store the created object.")]public FsmGameObject storeObject;public override void Reset(){gameObject = null;spawnPoint = null;position = new FsmVector3{UseVariable = true};rotation = new FsmVector3{UseVariable = true};storeObject = null;}public override void OnEnter(){if (gameObject.Value != null){Vector3 a = Vector3.zero;Vector3 euler = Vector3.up;if (spawnPoint.Value != null){a = spawnPoint.Value.transform.position;if (!position.IsNone){a += position.Value;}euler = ((!rotation.IsNone) ? rotation.Value : spawnPoint.Value.transform.eulerAngles);}else{if (!position.IsNone){a = position.Value;}if (!rotation.IsNone){euler = rotation.Value;}}if (gameObject != null){//TODO:以后创造完对象池后记得替换掉GameObject value = GameObject.Instantiate(gameObject.Value, a, Quaternion.Euler(euler));storeObject.Value = value;}}Finish();}}}

自定义行为的脚本如下:

using UnityEngine;namespace HutongGames.PlayMaker.Actions
{[ActionCategory(ActionCategory.GameObject)][Tooltip("Spawns a random amount of chosen GameObject from global pool and fires them off in random directions.")]public class FlingObjectsFromGlobalPool : RigidBody2dActionBase{[RequiredField][Tooltip("GameObject to spawn.")]public FsmGameObject gameObject;[Tooltip("GameObject to spawn at (optional).")]public FsmGameObject spawnPoint;[Tooltip("Position. If a Spawn Point is defined, this is used as a local offset from the Spawn Point position.")]public FsmVector3 position;[Tooltip("Minimum amount of objects to be spawned.")]public FsmInt spawnMin;[Tooltip("Maximum amount of objects to be spawned.")]public FsmInt spawnMax;[Tooltip("Minimum speed objects are fired at.")]public FsmFloat speedMin;[Tooltip("Maximum speed objects are fired at.")]public FsmFloat speedMax;[Tooltip("Minimum angle objects are fired at.")]public FsmFloat angleMin;[Tooltip("Maximum angle objects are fired at.")]public FsmFloat angleMax;[Tooltip("Randomises spawn points of objects within this range. Leave as 0 and all objects will spawn at same point.")]public FsmFloat originVariationX;public FsmFloat originVariationY;[Tooltip("Optional: Name of FSM on object you want to send an event to after spawn")]public FsmString FSM;[Tooltip("Optional: Event you want to send to object after spawn")]public FsmString FSMEvent;private float vectorX;private float vectorY;private bool originAdjusted;public override void Reset(){gameObject = null;spawnPoint = null;position = new FsmVector3{UseVariable = true};spawnMin = null;spawnMax = null;speedMin = null;speedMax = null;angleMin = null;angleMax = null;originVariationX = null;originVariationY = null;FSM = new FsmString{UseVariable = true};FSMEvent = new FsmString{UseVariable = true};}public override void OnEnter(){if (gameObject.Value != null){Vector3 a = Vector3.zero;Vector3 zero = Vector3.zero;if (spawnPoint.Value != null){a = spawnPoint.Value.transform.position;if (!position.IsNone){a += position.Value;}}else if (!position.IsNone){a = position.Value;}int num = Random.Range(spawnMin.Value, spawnMax.Value + 1);for (int i = 1; i <= num; i++){//TODO:以后创造完对象池后记得替换掉GameObject gameObject = GameObject.Instantiate(this.gameObject.Value, a, Quaternion.Euler(zero));float x = gameObject.transform.position.x;float y = gameObject.transform.position.y;float z = gameObject.transform.position.z;if (originVariationX != null){x = gameObject.transform.position.x + Random.Range(-originVariationX.Value, originVariationX.Value);originAdjusted = true;}if (originVariationY != null){y = gameObject.transform.position.y + Random.Range(-originVariationY.Value, originVariationY.Value);originAdjusted = true;}if (originAdjusted){gameObject.transform.position = new Vector3(x, y, z);}base.CacheRigidBody2d(gameObject);float num2 = Random.Range(speedMin.Value, speedMax.Value);float num3 = Random.Range(angleMin.Value, angleMax.Value);vectorX = num2 * Mathf.Cos(num3 * 0.017453292f);vectorY = num2 * Mathf.Sin(num3 * 0.017453292f);Vector2 velocity;velocity.x = vectorX;velocity.y = vectorY;rb2d.velocity = velocity;if (!FSM.IsNone){FSMUtility.LocateFSM(gameObject, FSM.Value).SendEvent(FSMEvent.Value);}}}Finish();}}}
using UnityEngine;namespace HutongGames.PlayMaker.Actions
{[ActionCategory(ActionCategory.GameObject)][Tooltip("Fling")]public class FlingObject : RigidBody2dActionBase{[RequiredField]public FsmOwnerDefault flungObject;public FsmFloat speedMin;public FsmFloat speedMax;public FsmFloat AngleMin;public FsmFloat AngleMax;private float vectorX;private float vectorY;private bool originAdjusted;public override void Reset(){flungObject = null;speedMin = null;speedMax = null;AngleMin = null;AngleMax = null;}public override void OnEnter(){if(flungObject != null){GameObject owenrDefaultTarget = Fsm.GetOwnerDefaultTarget(flungObject);if(owenrDefaultTarget != null){float num = Random.Range(speedMin.Value, speedMax.Value);float num2 = Random.Range(AngleMin.Value, AngleMax.Value);vectorX = num * Mathf.Cos(num2 * 0.017453292f);vectorY = num * Mathf.Sin(num2 * 0.017453292f);Vector2 velocity;velocity.x = vectorX;velocity.y = vectorY;CacheRigidBody2d(owenrDefaultTarget);rb2d.velocity = velocity;}}Finish();}}}
using System;
using UnityEngine;namespace HutongGames.PlayMaker.Actions
{[ActionCategory(ActionCategory.Physics2D)][Tooltip("Sets the Angular Velocity of a Game Object. NOTE: Game object must have a rigidbody 2D.")]public class SetAngularVelocity2d : RigidBody2dActionBase{[RequiredField][CheckForComponent(typeof(Rigidbody2D))]public FsmOwnerDefault gameObject;public FsmFloat angularVelocity;public override void Reset(){angularVelocity = null;everyFrame = false;}public bool everyFrame;public override void OnEnter(){CacheRigidBody2d(Fsm.GetOwnerDefaultTarget(gameObject));DoSetVelocity();if (!everyFrame){Finish();}}public override void Awake(){Fsm.HandleFixedUpdate = true;}public override void OnPreprocess(){Fsm.HandleFixedUpdate = true;}public override void OnFixedUpdate(){DoSetVelocity();}private void DoSetVelocity(){if (rb2d == null){return;}if (!angularVelocity.IsNone){rb2d.angularVelocity = angularVelocity.Value;}}}}

这个Fireball就是生成的有实际伤害和碰撞体积的火球,我们一会来创建

 

这个Spell Fluke就是装备吸虫之巢护符后释放法术放出来的虫子,我们先给它一个空的游戏对象日后再来做。 

最后我们来创建火球预制体:Fireball它有Rigibody2d,collider2d,tk2dsprite和animation,以及两个playmakerFSM。
 

它拥有一个检测地形的子对象:

Dribble R和Dribble L都是粒子系统,我也找不到素材所以你们自由发挥吧:

particle system同理也自由发挥吧:

先从我们熟悉的playmakerFSMdamages_enemy开始,这一个我们在制作玩家攻击系统就介绍过了,其实都差不多只需要改改伤害和后坐力倍数即可:

剩下两个状态的行为就是把collider改成parent和gparent即可:

然后创建一个新的playmakerFSM叫Fireball Control:

第一个状态是设置造成的伤害:

Pause状态:开启meshrenderer和设置collider激活状态

自定义行为脚本如下:

using UnityEngine;namespace HutongGames.PlayMaker.Actions
{[ActionCategory(ActionCategory.Physics2D)][Tooltip("Set BoxCollider2D to active or inactive. Can only be one collider on object.")]public class SetCollider : FsmStateAction{[RequiredField][Tooltip("The particle emitting GameObject")]public FsmOwnerDefault gameObject;public FsmBool active;public override void Reset(){gameObject = null;active = false;}public override void OnEnter(){if (gameObject != null){BoxCollider2D component = Fsm.GetOwnerDefaultTarget(gameObject).GetComponent<BoxCollider2D>();if (component != null){component.enabled = active.Value;}}Finish();}}}

初始化判断左和右:

碰撞检测Idle状态: 

自定义行为脚本:

using System;
using UnityEngine;namespace HutongGames.PlayMaker.Actions
{[ActionCategory(ActionCategory.Physics2D)][Tooltip("Detect 2D collisions between the Owner of this FSM and other Game Objects that have RigidBody2D components.\nNOTE: The system events, COLLISION ENTER 2D, COLLISION STAY 2D, and COLLISION EXIT 2D are sent automatically on collisions with any object. Use this action to filter collisions by Tag.")]public class Collision2dEventLayer : FsmStateAction{[Tooltip("The type of collision to detect.")]public PlayMakerUnity2d.Collision2DType collision;[UIHint(UIHint.Tag)][Tooltip("Filter by Tag.")]public FsmString collideTag;[UIHint(UIHint.Layer)][Tooltip("Filter by Layer.")]public FsmInt 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;[UIHint(UIHint.Variable)][Tooltip("Store the force of the collision. NOTE: Use Get Collision Info to get more info about the collision.")]public FsmFloat storeForce;private PlayMakerUnity2DProxy _proxy;public override void Reset(){collision = PlayMakerUnity2d.Collision2DType.OnCollisionEnter2D;collideTag = new FsmString{UseVariable = true};sendEvent = null;storeCollider = null;storeForce = null;}public override void OnEnter(){_proxy = Owner.GetComponent<PlayMakerUnity2DProxy>();if(_proxy == null){_proxy = Owner.AddComponent<PlayMakerUnity2DProxy>();}switch (collision){case PlayMakerUnity2d.Collision2DType.OnCollisionEnter2D:_proxy.AddOnCollisionEnter2dDelegate(new PlayMakerUnity2DProxy.OnCollisionEnter2dDelegate(DoCollisionEnter2D));break;case PlayMakerUnity2d.Collision2DType.OnCollisionStay2D:_proxy.AddOnCollisionStay2dDelegate(new PlayMakerUnity2DProxy.OnCollisionStay2dDelegate(DoCollisionStay2D));break;case PlayMakerUnity2d.Collision2DType.OnCollisionExit2D:_proxy.AddOnCollisionExit2dDelegate(new PlayMakerUnity2DProxy.OnCollisionExit2dDelegate(DoCollisionExit2D));break;default:break;}}public override void OnExit(){if(_proxy == null){return;}switch (collision){case PlayMakerUnity2d.Collision2DType.OnCollisionEnter2D:_proxy.RemoveOnCollisionEnter2dDelegate(new PlayMakerUnity2DProxy.OnCollisionEnter2dDelegate(DoCollisionEnter2D));break;case PlayMakerUnity2d.Collision2DType.OnCollisionStay2D:_proxy.RemoveOnCollisionStay2dDelegate(new PlayMakerUnity2DProxy.OnCollisionStay2dDelegate(DoCollisionStay2D));break;case PlayMakerUnity2d.Collision2DType.OnCollisionExit2D:_proxy.RemoveOnCollisionExit2dDelegate(new PlayMakerUnity2DProxy.OnCollisionExit2dDelegate(DoCollisionExit2D));break;default:break;}}public new void DoCollisionEnter2D(Collision2D collisionInfo){if (collision == PlayMakerUnity2d.Collision2DType.OnCollisionEnter2D && (collisionInfo.collider.gameObject.tag == collideTag.Value || collideTag.IsNone || string.IsNullOrEmpty(collideTag.Value)) && (collisionInfo.gameObject.layer == collideLayer.Value || collideLayer.IsNone)){StoreCollisionInfo(collisionInfo);Fsm.Event(sendEvent);}}public new void DoCollisionStay2D(Collision2D collisionInfo){if (collision == PlayMakerUnity2d.Collision2DType.OnCollisionStay2D && (collisionInfo.collider.gameObject.tag == collideTag.Value || collideTag.IsNone || string.IsNullOrEmpty(collideTag.Value)) && (collisionInfo.gameObject.layer == collideLayer.Value || collideLayer.IsNone)){StoreCollisionInfo(collisionInfo);Fsm.Event(sendEvent);}}public new void DoCollisionExit2D(Collision2D collisionInfo){if (collision == PlayMakerUnity2d.Collision2DType.OnCollisionExit2D && (collisionInfo.collider.gameObject.tag == collideTag.Value || collideTag.IsNone || string.IsNullOrEmpty(collideTag.Value)) && (collisionInfo.gameObject.layer == collideLayer.Value || collideLayer.IsNone)){StoreCollisionInfo(collisionInfo);Fsm.Event(sendEvent);}}private void StoreCollisionInfo(Collision2D collisionInfo){storeCollider.Value = collisionInfo.gameObject;storeForce.Value = collisionInfo.relativeVelocity.magnitude;}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;}}}

完成后效果开始淡去:

 

如果是碰到墙壁后的效果:

 

最后我们来讲讲spriteFlash,首先添加好方法flashFocusHeal():

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class SpriteFlash : MonoBehaviour
{private Renderer rend;private Color flashColour;private Color prevColor;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>();prevColor = rend.material.color;}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);block.SetColor("_FlashColor", prevColor);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 flashFocusHeal(){Start();flashColour = new Color(1f, 1f, 1f);amount = 0.85f;timeUp = 0.1f;stayTime = 0.01f;timeDown = 0.35f;block.Clear();block.SetColor("_FlashColor", flashColour);flashingState = 1;flashTimer = 0f;repeatFlash = false;//SendToChildren(new Action(flashFocusHeal));}private void flashFocusGet(){Start();flashColour = new Color(1f, 1f, 1f);amount = 0.5f;timeUp = 0.1f;stayTime = 0.01f;timeDown = 0.35f;block.Clear();block.SetColor("_FlashColor", flashColour);flashingState = 1;flashTimer = 0f;repeatFlash = false;SendToChildren(new Action(flashFocusGet));}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);}}}
}

然后我们要新建shader目的是由发光效果和颜色变化的效果:

Shader "Sprites/Sprites_Default-ColorFlash"
{Properties{_MainTex ("Sprite Texture", 2D) = "white" {}_FlashColor("Flash Color",Color) = (1,1,1,1)_FlashAmount("Flash Amount",Range(0.0,1.0)) = 0.0_Color("Color", Color) = (1,1,1,1)[MaterialToggle] PixelSnap("Pixel snap",Float) = 0.0}SubShader{Tags { "CanUseSpriteAtlas" = "true" "IGNOREPROJECTOR" = "true" "PreviewType" = "Plane" "QUEUE" = "Transparent" "RenderType" = "Transparent"}Cull OffLighting OffZWrite OffFog{ Mode Off }Blend SrcAlpha OneMinusSrcAlphaCGPROGRAM#pragma surface surf Lambert alpha vertex:vert#pragma multi_compile DUMMY PIXELSNAP_ONsampler2D _MainTex;fixed4 _Color;fixed4 _FlashColor;float _FlashAmount,_SelfIllum;struct Input{float2 uv_MainTex;fixed4 color;};void vert(inout appdata_full v, out Input o){#if defined(PIXELSNAP_ON) && !defined(SHADER_API_FLASH)v.vertex = UnityPixelSnap(v.vertex);#endifv.normal = float3(0,0,-1);UNITY_INITIALIZE_OUTPUT(Input, o);o.color = _FlashColor;}void surf(Input IN, inout SurfaceOutput o){fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * IN.color;o.Albedo = lerp(c.rgb,float3(1.0,1.0,1.0),_FlashAmount);o.Emission = lerp(c.rgb,float3(1.0,1.0,1.0),_FlashAmount) * _SelfIllum;o.Alpha = c.a;}ENDCG}Fallback "Transparent/VertexLit"
}
Shader "Sprites/Sprites_Cherry-Default"
{Properties{_MainTex("Sprite Texture", 2D) = "white" {}_FlashColor("Flash Color",Color) = (1,1,1,1)_FlashAmount("Flash Amount",Range(0.0,1.0)) = 0.0_Color("Color", Color) = (1,1,1,1)[MaterialToggle] PixelSnap("Pixel snap",Float) = 0.0}SubShader{Tags { "CanUseSpriteAtlas" = "true" "IGNOREPROJECTOR" = "true" "PreviewType" = "Plane" "QUEUE" = "Transparent" "RenderType" = "Transparent"}Cull OffLighting OffZWrite OffFog{ Mode Off }Blend SrcAlpha OneMinusSrcAlphaCGPROGRAM#pragma surface surf Lambert alpha vertex:vert#pragma multi_compile DUMMY PIXELSNAP_ONsampler2D _MainTex;fixed4 _Color;float _FlashAmount,_SelfIllum;struct Input{float2 uv_MainTex;fixed4 color;};void vert(inout appdata_full v, out Input o){#if defined(PIXELSNAP_ON) && !defined(SHADER_API_FLASH)v.vertex = UnityPixelSnap(v.vertex);#endifv.normal = float3(0,0,-1);UNITY_INITIALIZE_OUTPUT(Input, o);o.color = _Color;}void surf(Input IN, inout SurfaceOutput o){fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * IN.color;o.Albedo = lerp(c.rgb,float3(1.0,1.0,1.0),_FlashAmount);o.Emission = lerp(c.rgb,float3(1.0,1.0,1.0),_FlashAmount) * _SelfIllum;o.Alpha = c.a;}ENDCG}
Fallback "Transparent/VertexLit"
}

然后更改小骑士和敌人的Material 的shader为Sprites_Default-ColorFlash

他们就拥有了闪光的效果了

还有就是上一期我忘记给敌人的生命系统添加受击后的无敌时间了,这里我们加上去:

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 Recoil recoil;private tk2dSpriteAnimator animator;private tk2dSprite sprite;private DamageHero damageHero;[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>();recoil = GetComponent<Recoil>();animator = GetComponent<tk2dSpriteAnimator>();sprite = GetComponent<tk2dSprite>();damageHero = GetComponent<DamageHero>();}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);}public 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){HeroController.instance.RecoilLeft();}else if(cardinalDirection == 2){HeroController.instance.RecoilRight();}}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;}public 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);if(recoil != null){recoil.RecoilByDirection(cardinalDirection,hitInstance.MagnitudeMultiplier);}switch (hitInstance.AttackType){case AttackTypes.Nail:if(hitInstance.AttackType == AttackTypes.Nail && enemyType !=3 && enemyType != 6){HeroController.instance.SoulGain();}Vector3 position = (hitInstance.Source.transform.position + transform.position) * 0.5f + effectOrigin;break;case AttackTypes.Generic:break;case AttackTypes.Spell: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){NonFatalHit(hitInstance.IgnoreInvulnerable);}else{Die(new float?(hitInstance.GetActualDirection(transform)), hitInstance.AttackType, hitInstance.IgnoreInvulnerable);}}private void NonFatalHit(bool ignoreEvasion){if (!ignoreEvasion){evasionByHitRemaining = 0.2f;}}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;if(damageHero != null){damageHero.damageDealt = 0;}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;}}

总结

为了方便测试,我们来给GameManager游戏对象添加一个新的脚本就叫CheatManager.cs方便我们测试用:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class CheatManager : MonoBehaviour
{private GameManager gm;[SerializeField]public PlayerData playerData;private void Awake(){gm = GameManager.instance;playerData = gm.playerData;}private void Update(){if (Input.GetKeyDown(KeyCode.P)){playerData.hasDash = true;playerData.MPCharge = playerData.maxMP;playerData.fireballLevel = 1;}}}

进入游戏,我们按P键获得满蓝状态和拥有火球术的状态:

按下Q键和F键试一下效果:

掉了15滴血,完美

下一期我们来设置一下相机还有布置一下场景吧。

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

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

相关文章

鸿蒙开发知识点速记全解

入门 1、API涵盖应用框架、系统、媒体、图形、应用服务、AI六大领域。 应用框架相关Kit开放能力&#xff1a;Ability Kit&#xff08;程序框架服务&#xff09;、ArkUI&#xff08;方舟UI框架&#xff09;等。系统相关Kit开放能力&#xff1a;Universal Keystore Kit&#xf…

测试管理新增视图与高级搜索功能,测试计划支持一键生成缺陷详情,MeterSphere开源持续测试工具v3.3版本发布

2024年9月29日&#xff0c;MeterSphere开源持续测试工具正式发布v3.3版本。 在这一版本中&#xff0c;接口测试方面&#xff0c;接口导入功能支持导入Postman、JMX、HAR和MeterSphere格式的文件&#xff0c;接口场景的自定义请求步骤支持cURL快捷导入&#xff1b;测试管理方面…

HarmonyOS/OpenHarmony 如何将rawfile中文件复制到沙箱中

关键词&#xff1a;h5离线加载、HarmonyOS、OpenHarmony、文件操作、复制、解压 当下有一个场景&#xff0c;需要离线加载 h5离线资源zip包&#xff0c;并实现资源包的动态更新&#xff0c;那么仅靠 $rawfile并不能实现该功能&#xff0c;那么我们该如何实现&#xff1f; 我们…

在线代码编辑器

在线代码编辑器 文章说明前台核心代码后台核心代码效果展示源码下载 文章说明 采用Java结合vue3设计实现的在线代码编辑功能&#xff0c;支持在线编辑代码、运行代码&#xff0c;同时支持导入文件&#xff0c;支持图片识别&#xff0c;支持复制代码&#xff0c;可将代码导出为图…

【mod分享】侠盗猎魔2冬日mod,贴图高清化,增加下雪场景,支持光追,并且增加红色霓虹灯

今天小编为大家带来一个新的游戏mod&#xff0c;这次mod主要是修改了游戏《侠盗猎魔2》&#xff0c;我给游戏增加了下雪的场景&#xff0c;并且增加了红色的霓虹灯&#xff0c;整体让游戏沉浸在一种诡异的圣诞气氛中。并且我还提高了游戏材质的分辨率。更多细节需要玩家自己探索…

【STM32开发笔记】移植AI框架TensorFlow到STM32单片机【下篇】

【STM32开发笔记】移植AI框架TensorFlow到STM32单片机【下篇】 一、上篇回顾二、项目准备2.1 准备模板项目2.2 支持计时功能2.3 配置UART4引脚2.4 支持printf重定向到UART42.5 支持printf输出浮点数2.6 支持printf不带\r的换行2.7 支持ccache编译缓存 三、TFLM集成3.1 添加tfli…

设计模式-策略模式-200

优点&#xff1a;用来消除 if-else、switch 等多重判断的代码&#xff0c;消除 if-else、switch 多重判断 可以有效应对代码的复杂性。 缺点&#xff1a;会增加类的数量&#xff0c;有的时候没必要为了消除几个if-else而增加很多类&#xff0c;尤其是那些类型又长又臭的 原始代…

小鱼ROS2 g++编译报错

把humble换成jazzy &#xff0c;起初报错 /opt/ros/jazzy/include/rcl_interfaces/rcl_interfaces/srv/detail/list_parameters__struct.hpp:267:10: fatal error: service_msgs/msg/detail/service_event_info__struct.hpp: 没有那个文件或目录 267 | #include "servi…

基于单片机远程家电控制系统设计

本设计基于单片机的远程家电控制系统&#xff0c;以STC89C52单片机为核心&#xff0c;通过液晶LCD1602实时显示并控制&#xff0c;利用ESP8266WiFi模块实现本地与云平台的连接&#xff0c;最终实现远程对于灯光&#xff0c;热水器等家电的开关控制。同时&#xff0c;系统设有防…

HTB:Oopsie[WriteUP]

目录 连接至HTB服务器并开启靶机 1.With what kind of tool can intercept web traffic? 2.What is the path to the directory on the webserver that returns a login page? 3.What can be modified in Firefox to get access to the upload page? 4.What is the acc…

html+css+js实现step进度条效果

实现效果 代码实现 HTML部分 <div class"box"><ul class"step"><li class"circle actives ">1</li><li class"circle">2</li><li class"circle">3</li><li class&quo…

【设计模式-模板】

定义 模板方法模式是一种行为设计模式&#xff0c;它在一个方法中定义了一个算法的骨架&#xff0c;并将一些步骤延迟到子类中实现。通过这种方式&#xff0c;模板方法允许子类在不改变算法结构的情况下重新定义算法中的某些特定步骤。 UML图 组成角色 AbstractClass&#x…

python编程开发“人机猜拳”游戏

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

回归预测 | Matlab基于SABO-SVR减法平均算法优化支持向量机的数据多输入单输出回归预测

回归预测 | Matlab基于SABO-SVR减法平均算法优化支持向量机的数据多输入单输出回归预测 目录 回归预测 | Matlab基于SABO-SVR减法平均算法优化支持向量机的数据多输入单输出回归预测预测效果基本描述程序设计参考资料 预测效果 基本描述 1.Matlab基于SABO-SVR减法平均算法优化…

如何创建一个docker,给它命名,且下次重新打开它

1.创建一个新的docker并同时命名 docker run -it --name one ubuntu:18.04 /bin/bash 这时候我们已经创建了一个docker,并且命名为"one" 2.关闭当前docker exit 3.这时docker已经终止了&#xff0c;我们需要使用它要重新启动 docker start one 4.现在可以重新打…

哈希表(HashMap、HashSet)

文章目录 一、 什么是哈希表二、 哈希冲突2.1 为什么会出现冲突2.2 如何避免出现冲突2.3 出现冲突如何解决 三、模拟实现哈希桶/开散列&#xff08;整型数据&#xff09;3.1 结构3.2 插入元素3.3 获取元素 四、模拟实现哈希桶/开散列&#xff08;泛型&#xff09;4.1 结构4.2 插…

QSqlDatabase在多线程中的使用

Qt中多线程使用数据库_qt数据库管理类支持多数据库,多线程-CSDN博客 1. 代码&#xff1a; #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QPushButton> #include <QSqlDatabase> #include <QSqlQuery> #include <QSqlError>…

Python编码系列—Python备忘录模式:掌握对象状态保存与恢复技术

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

【JavaSE】抽象类

1. 抽象类概念 在面向对象的概念中&#xff0c;所有的对象都是通过类来描绘的&#xff0c;但是反过来&#xff0c;并不是所有的类都是用来描绘对象的&#xff0c;如果一个类中没有包含足够的信息来描绘一个具体的对象&#xff0c;这样的类就是抽象类。 在打印图形例子中, 我们…

婚恋交友小程序的设计思路与用户体验优化

在数字化时代&#xff0c;婚恋小程序作为一种新兴的婚恋交友平台&#xff0c;正逐渐成为单身人士寻找伴侣的重要工具。一个优秀的婚恋小程序不仅要有创新的设计思路&#xff0c;还要注重用户体验的优化。编辑h17711347205以下是婚恋小程序的设计思路与用户体验优化的详细阐述&a…