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

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


前言

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; //花费的MP
    public int MPReverseMax; //花费的最大MP
    public bool soulLimited; //是否灵魂容量被限制了
    public int focusMP_amount; //聚集法术所需要的MP

    public 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; //决定使用哪种攻击的NailSlash
    private PlayMakerFSM slashFsm;//决定使用哪种攻击的PlayMakerFSM

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

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

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

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

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

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

    private float NAIL_TERRAIN_CHECK_TIME = 0.12f;

    private bool drainMP; //是否正在流走MP
    private float drainMP_timer; //流走MP的计时器
    private float drainMP_time; //流走MP花费的时间
    private float MP_drained; //已经流走的MP数量
    private float focusMP_amount; //使用focus回
【基于AT89C51单片机的交通灯系统】是电子工程领域中的一个经典实践项目,尤其适合初学者进行单片机编程硬件控制的学习。AT89C51是一款广泛应用的8位微处理器,由美国Atmel公司生产,具有4KB的可编程Flash存储器,可以执行各种控制任务,包括交通灯系统的控制。 交通灯控制系统是城市交通管理的重要组成部分,通过红绿黄三色灯的变化来指示行人车辆何时通行。在本项目中,交通灯系统采用AT89C51单片机作为核心控制器,通过编程实现红绿黄灯的定时切换,确保交通流畅且安全。 DSN(Design Suite Notation)文件,如`C51交通灯.DSN`,通常是在电路设计软件,如Keil uVision或Proteus中创建的工程文件。这种文件包含了整个项目的配置信息,包括源代码、元器件库、仿真设置等,使得开发者可以在虚拟环境中对交通灯系统进行仿真测试。Proteus是一款强大的电子电路仿真软件,可以直观地模拟硬件电路的行为,无需物理硬件即可验证设计的正确性。 数码管(7段显示器)是显示倒计时的关键部件。在这个项目中,数码管用于显示每个灯组的剩余时间,增强用户交互体验,使驾驶员行人能够清晰了解何时转换灯色。AT89C51通过串行或并行接口与数码管连接,并通过特定的驱动程序代码控制数码管的显示内容。 编程方面,AT89C51使用C51语言编写,这是一种为8051系列单片机定制的C语言变体。代码中包含的详细注释对于初学者理解程序逻辑至关重要,通过注释可以学习如何设置定时器、中断服务子程序以及I/O端口操作,这些都是单片机编程的基础知识。 交通灯的控制通常基于定时器中断,例如,可以设置一个定时器在特定周期后触发中断,然后在中断服务程序中改变灯的状态。此外,为了实现数码管显示,可能需要用到移位寄存器译码器等外围设备,这些都需要在代码中进行编程控制。 这个项目涵
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值