自学内容网 自学内容网

[Unity Demo]从零开始制作空洞骑士Hollow Knight第七集:制作小骑士完整的冲刺Dash行为

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

文章目录


前言

大家又好久不见(虽然就过了半天)这期我决定继续完善小骑士的行为,上一期我们实现了小骑士完整的跳跃降落行为,这一期我们就来做小骑士的冲刺行为,这其中涉及到素材的导入,创建tk2dSprite和tk2dSpriteAnimation,以及代码控制行为等等,难度适中,希望你能够耐心阅读。


一、制作一个完整的小骑士冲刺Dash行为

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

        我们先把素材导入后,开始回到tk2dspriteEditor中,由于我们第二期就已经制作了小骑士的spritecollection和spriteanimation,所以我们直接把图片拖进去即可。

        

 创建两个动画Dash和Dash To Idle:

2.使用代码实现完整的冲刺行为控制

如果你玩过空洞骑士一段时间,你可能会知道玩家的冲刺时要到苍绿之境中击败大黄蜂才能获得的,所以我们需要一个脚本来记录玩家数据,脚本包含玩家是否拥有某些能力,是否能使用这个能力,就比如这个dash,因此我们要创建PlayerData.cs:

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 hasDash;
    public bool canDash;

    protected PlayerData()
    {
SetupNewPlayerData();
    }

    public void Reset()
    {
SetupNewPlayerData();
    }

    private void SetupNewPlayerData()
    {

hasDash = true; //测试阶段先设置为true方便测试
canDash = true;
    }
}

还是先从最简单的音频管理开始吧,只需要创建一个AudioSource并把dash的audioclip拖上去即可

 

using System.Collections;
using System.Collections.Generic;
using GlobalEnums;
using UnityEngine;

public class HeroAudioController : MonoBehaviour
{
    private HeroController heroCtrl;

    private void Awake()
    {
heroCtrl = GetComponent<HeroController>();
    }

    [Header("Sound Effects")]
    public AudioSource softLanding;
    public AudioSource jump;
    public AudioSource footStepsRun;
    public AudioSource footStepsWalk;
    public AudioSource falling;
    public AudioSource dash;

    private Coroutine fallingCo;

    public void PlaySound(HeroSounds soundEffect)
    {
if(!heroCtrl.cState.isPaused)
{
    switch (soundEffect)
    {
case HeroSounds.FOOTSETP_RUN:
    if(!footStepsRun.isPlaying && !softLanding.isPlaying)
    {
footStepsRun.Play();
return;
    }
    break;
case HeroSounds.FOOTSTEP_WALK:
    if (!footStepsWalk.isPlaying && !softLanding.isPlaying)
    {
footStepsWalk.Play();
return;
    }
    break;
case HeroSounds.SOFT_LANDING:
    RandomizePitch(softLanding, 0.9f, 1.1f);
    softLanding.Play();
    break;
case HeroSounds.JUMP:
    RandomizePitch(jump, 0.9f, 1.1f);
    jump.Play();
    break;
case HeroSounds.DASH:
    dash.Play();
    break;
case HeroSounds.FALLING:
    fallingCo = StartCoroutine(FadeInVolume(falling, 0.7f));
    falling.Play();
    break;
default:
    break;
    }
}
    }

    public void StopSound(HeroSounds soundEffect)
    {
if(soundEffect == HeroSounds.FOOTSETP_RUN)
{
    footStepsRun.Stop();
    return;
}
if (soundEffect == HeroSounds.FOOTSTEP_WALK)
{
    footStepsWalk.Stop();
    return;
}
switch (soundEffect)
{
    case HeroSounds.FALLING:
falling.Stop();
if(fallingCo != null)
{
    StopCoroutine(fallingCo);
}
return;
    default:
return;
}
    }

    public void StopAllSounds()
    {
softLanding.Stop();
jump.Stop();
falling.Stop();
dash.Stop();
footStepsRun.Stop();
footStepsWalk.Stop();
    }

    public void PauseAllSounds()
    {
softLanding.Pause();
jump.Pause();
falling.Pause();
dash.Pause();
footStepsRun.Pause();
footStepsWalk.Pause();
    }

    public void UnPauseAllSounds()
    {
softLanding.UnPause();
jump.UnPause();
falling.UnPause();
dash.UnPause();
footStepsRun.UnPause();
footStepsWalk.UnPause();
    }

    /// <summary>
    /// 音量淡入线性插值的从0到1
    /// </summary>
    /// <param name="src"></param>
    /// <param name="duration"></param>
    /// <returns></returns>
    private IEnumerator FadeInVolume(AudioSource src, float duration)
    {
float elapsedTime = 0f;
src.volume = 0f;
while (elapsedTime < duration)
{
    elapsedTime += Time.deltaTime;
    float t = elapsedTime / duration;
    src.volume = Mathf.Lerp(0f, 1f, t);
    yield return null;
}
    }

    /// <summary>
    /// 随机旋转一个在和之间的pitch的值返回给audiosource
    /// </summary>
    /// <param name="src"></param>
    /// <param name="minPitch"></param>
    /// <param name="maxPitch"></param>
    private void RandomizePitch(AudioSource src, float minPitch, float maxPitch)
    {
float pitch = Random.Range(minPitch, maxPitch);
src.pitch = pitch;
    }

    /// <summary>
    /// 重置audiosource的pitch
    /// </summary>
    /// <param name="src"></param>
    private void ResetPitch(AudioSource src)
    {
src.pitch = 1f;
    }

}

 

回到脚本HeroAnimationController.cs中,我们要创建Dash相关动画的代码 管理Dash的动画行为:

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 playRunToIdle;//播放"Run To Idle"动画片段
    private bool playDashToIdle; //播放"Dash To Idle"动画片段

    private bool changedClipFromLastFrame;

    public ActorStates actorStates { get; private set; }
    public ActorStates prevActorStates { get; private set; }

    private void Awake()
    {
heroCtrl = HeroController.instance;
cState = heroCtrl.cState;
animator = GetComponent<tk2dSpriteAnimator>();
    }

    private void Start()
    {
pd = PlayerData.instance;
ResetAll();
actorStates = heroCtrl.hero_state;

if(heroCtrl.hero_state == ActorStates.airborne)
{
    animator.PlayFromFrame("Airborne", 7);
    return;
}
PlayIdle();
    }

    private void Update()
    {
UpdateAnimation();
if (cState.facingRight)
{
    wasFacingRight = true;
    return;
}
wasFacingRight = false;
    }

    private void UpdateAnimation()
    {
changedClipFromLastFrame = false;
if (playRunToIdle)
{
    Play("Run To Idle");
    animator.AnimationCompleted = new Action<tk2dSpriteAnimator, tk2dSpriteAnimationClip>(AnimationCompleteDelegate);
    playRunToIdle = 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.dashing)
    {

    }
}
else if (cState.dashing)
{
    Play("Dash"); //通过cState.dashing判断是否播放Dash动画片段
}
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);
    }
}
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 == "Run 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 FinishedDash()
    {
playDashToIdle = true;
    }

    private void ResetAll()
    {
playRunToIdle = false;
playDashToIdle = false;
    }

    private void ResetPlays()
    {
playRunToIdle = false;
playDashToIdle = false;
    }

    public void UpdateState(ActorStates newState)
    {
if(newState != actorStates)
{
    if(actorStates == ActorStates.running && newState == ActorStates.idle && !playRunToIdle && !cState.inWalkZone)
    {
playRunToIdle = true;
    }
    prevActorStates = actorStates;
    actorStates = newState;
}
    }

    private bool CanPlayIdle()
    {
return !animator.IsPlaying("Land") && !animator.IsPlaying("Run To Idle") && !animator.IsPlaying("Dash To Idle") && !animator.IsPlaying("Backdash Land") && !animator.IsPlaying("Backdash Land 2") && !animator.IsPlaying("LookUpEnd") && !animator.IsPlaying("LookDownEnd") && !animator.IsPlaying("Exit Door To Idle") && !animator.IsPlaying("Wake Up Ground") && !animator.IsPlaying("Hazard Respawn");
    }
    private bool CanPlayTurn()
    {
return !animator.IsPlaying("Wake Up Ground") && !animator.IsPlaying("Hazard Respawn"); ;
    }

}

既然涉及到角色新的动作,我们就要新注册一个Dash的Action:

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 jump;
    public PlayerAction dash;

    public HeroActions()
    {
left = CreatePlayerAction("Left");
left.StateThreshold = 0.3f;
right = CreatePlayerAction("Right");
right.StateThreshold = 0.3f;
up = CreatePlayerAction("Up");
up.StateThreshold = 0.3f;
down = CreatePlayerAction("Down");
down.StateThreshold = 0.3f;
moveVector = CreateTwoAxisPlayerAction(left, right, up, down);
moveVector.LowerDeadZone = 0.15f;
moveVector.UpperDeadZone = 0.95f;
jump = CreatePlayerAction("Jump");
dash = CreatePlayerAction("Dash");
    }
}

 这里我们绑定键盘的D键作为冲刺的设备输入:

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.jump, "X");
AddKeyBinding(inputActions.dash, "D");
    }

    private static void AddKeyBinding(PlayerAction action, string savedBinding)
    {
Mouse mouse = Mouse.None;
Key key;
if (!Enum.TryParse(savedBinding, out key) && !Enum.TryParse(savedBinding, out mouse))
{
    return;
}
if (mouse != Mouse.None)
{
    action.AddBinding(new MouseBindingSource(mouse));
    return;
}
action.AddBinding(new KeyBindingSource(new Key[]
{
    key
}));
    }

}

还需要扩展FSMUtility脚本的方法:

using System;
using System.Collections.Generic;
using HutongGames.PlayMaker;
using UnityEngine;

public static class FSMUtility
{
    private static List<List<PlayMakerFSM>> fsmListPool;
    private const int FsmListPoolSizeMax = 20;

    static FSMUtility()
    {
fsmListPool = new List<List<PlayMakerFSM>>();
    }

    private static List<PlayMakerFSM> ObtainFsmList()
    {
if (fsmListPool.Count > 0)
{
    List<PlayMakerFSM> result = fsmListPool[fsmListPool.Count - 1];
    fsmListPool.RemoveAt(fsmListPool.Count - 1);
    return result;
}
return new List<PlayMakerFSM>();
    }

    private static void ReleaseFsmList(List<PlayMakerFSM> fsmList)
    {
fsmList.Clear();
if (fsmListPool.Count < FsmListPoolSizeMax)
{
    fsmListPool.Add(fsmList);
}
    }

    public static PlayMakerFSM GetFSM(GameObject go)
    {
return go.GetComponent<PlayMakerFSM>();
    }

    public static GameObject GetSafe(this FsmOwnerDefault ownerDefault, FsmStateAction stateAction)
    {
if (ownerDefault.OwnerOption == OwnerDefaultOption.UseOwner)
{
    return stateAction.Owner;
}
return ownerDefault.GameObject.Value;
    }

    public static void SendEventToGameObject(GameObject go, string eventName, bool isRecursive = false)
    {
if (go != null)
{
    SendEventToGameObject(go, FsmEvent.FindEvent(eventName), isRecursive);
}
    }

    public static void SendEventToGameObject(GameObject go, FsmEvent ev, bool isRecursive = false)
    {
if (go != null)
{
    List<PlayMakerFSM> list = ObtainFsmList();
    go.GetComponents<PlayMakerFSM>(list);
    for (int i = 0; i < list.Count; i++)
    {
list[i].Fsm.Event(ev);
    }
    ReleaseFsmList(list);
    if (isRecursive)
    {
Transform transform = go.transform;
for (int j = 0; j < transform.childCount; j++)
{
    SendEventToGameObject(transform.GetChild(j).gameObject, ev, isRecursive);
}
    }
}
    }

}

接下来到HeroController.cs中,我们仍然需要像jump行为一样为它创建steps和QueueSteps, 进入Dash()后执行的物理事件,HeroDash()处理声音逻辑以及生成Effect效果物体吗,CancelDash()取消冲刺,FinishedDash()完成冲刺,CanDash()能否冲刺,以及inputHandler.inputActions.dash.IsPressed作为输入检测是否按下按键。

using System;
using System.Collections;
using System.Collections.Generic;
using HutongGames.PlayMaker;
using GlobalEnums;
using UnityEngine;

public class HeroController : MonoBehaviour
{
    public ActorStates hero_state;
    public ActorStates prev_hero_state;

    public bool acceptingInput = true;

    public float move_input;
    public float vertical_input;

    private Vector2 current_velocity;

    public float WALK_SPEED = 3.1f;//走路速度
    public float RUN_SPEED = 5f;//跑步速度
    public float JUMP_SPEED = 5f;//跳跃的食欲

    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 bool airDashed;//是否是在空中冲刺
    public PlayMakerFSM dashBurst;
    public GameObject dashParticlesPrefab;

    public float DASH_SPEED; //冲刺时的速度
    public float DASH_TIME; //冲刺时间
    public float DASH_COOLDOWN; //冲刺冷却时间
    public int DASH_QUEUE_STEPS; //最大冲刺队列的步

    private float prevGravityScale;

    public bool touchingWall; //是否接触到墙
    public bool touchingWallL; //是否接触到的墙左边
    public bool touchingWallR; //是否接触到的墙右边

    private Rigidbody2D rb2d;
    private BoxCollider2D col2d;
    private GameManager gm;
    public PlayerData playerData;
    private InputHandler inputHandler;
    public HeroControllerStates cState;
    private HeroAnimationController animCtrl;
    private HeroAudioController audioCtrl; 

    private static HeroController _instance;

    public static HeroController instance
    {
get
{
            if (_instance == null)
                _instance = FindObjectOfType<HeroController>();
            if(_instance && Application.isPlaying)
    {
                DontDestroyOnLoad(_instance.gameObject);
    }
            return _instance;
}
    }

    public HeroController()
    {
        JUMP_QUEUE_STEPS = 2;
        JUMP_RELEASE_QUEUE_STEPS = 2;
    }

    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>();
    }

    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);
}
    }

    void Update()
    {
        orig_Update();
    }

    private void orig_Update()
    {

        current_velocity = rb2d.velocity;
        FallCheck();

        if(hero_state == ActorStates.running)
{
    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.no_input)
{

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

if (dashCooldownTimer > 0f) //计时器在Update中-= Time.deltaTime
        {
            dashCooldownTimer -= Time.deltaTime;
        }
    }

    private void FixedUpdate()
    {
        if (hero_state != ActorStates.no_input && !cState.dashing)
{
            Move(move_input);
            if(move_input > 0f && !cState.facingRight )
    {
                FlipSprite();
    }
            else if(move_input < 0f && cState.facingRight)
    {
                FlipSprite();
    }
}

if (cState.jumping) //如果cState.jumping就Jump
        {
            Jump();
}
if (cState.dashing)//如果cState.dashing就Dash
        {
            Dash();
}
        //限制速度
        if(rb2d.velocity.y < -MAX_FALL_VELOCITY)
{
            rb2d.velocity = new Vector2(rb2d.velocity.x, -MAX_FALL_VELOCITY);
}
if (jumpQueuing)
{
            jumpQueueSteps++;
}

if (dashQueuing) //跳跃队列开始
{
            dashQueueSteps++;
}
        if(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);
}
    }

    /// <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 Dash()
    {
        AffectedByGravity(false); //不受到重力影响
        if(dash_timer > DASH_TIME)
{
            FinishedDashing();//大于则结束冲刺
            return;
}
        float num;
    num = DASH_SPEED;

    if (cState.facingRight)
    {
    if (CheckForBump(CollisionSide.right))
    {
               
    }
    else
    {
                rb2d.velocity = new Vector2(num, 0f); //为人物的velocity赋值DASH_SPEED
    }
    }
        else if (CheckForBump(CollisionSide.left))
    {
           
        }
    else
    {
            rb2d.velocity = new Vector2(-num, 0f);
    }
        dash_timer += Time.deltaTime;
    }

    private void HeroDash()
    {
if (!cState.onGround)
{
            airDashed = true;
}

        audioCtrl.StopSound(HeroSounds.FOOTSETP_RUN);
        audioCtrl.StopSound(HeroSounds.FOOTSTEP_WALK);
        audioCtrl.PlaySound(HeroSounds.DASH);


if (inputHandler.inputActions.right.IsPressed)
{
            FaceRight();
}
        else if (inputHandler.inputActions.left.IsPressed)
        {
            FaceLeft();
        }
        cState.dashing = true;
        dashQueueSteps = 0;
        HeroActions heroActions = inputHandler.inputActions;

        dashBurst.transform.localPosition = new Vector3(4.11f, -0.55f, 0.001f); //生成dashBurst后设置位置和旋转角
        dashBurst.transform.localEulerAngles = new Vector3(0f, 0f, 0f);


        dashCooldownTimer = DASH_COOLDOWN;

        dashBurst.SendEvent("PLAY"); //发送dashBurst的FSM的事件PLAY
        dashParticlesPrefab.GetComponent<ParticleSystem>().enableEmission = true;

if (cState.onGround)
{

}
    }

    /// <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.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;
        }
    }

    /// <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;
}
    }

    /// <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);
}
    }
}
else
{
            cState.falling = false;

}
    }

    /// <summary>
    /// 翻转小骑士的localScale.x
    /// </summary>
    public void FlipSprite()
    {
        cState.facingRight = !cState.facingRight;
        Vector3 localScale = transform.localScale;
        localScale.x *= -1f;
        transform.localScale = localScale;
    }

    public void FaceRight()
    {
        cState.facingRight = true;
        Vector3 localScale = transform.localScale;
        localScale.x = -1f;
        transform.localScale = localScale;
    }

    public void FaceLeft()
    {
        cState.facingRight = false;
        Vector3 localScale = transform.localScale;
        localScale.x = 1f;
        transform.localScale = localScale;
    }

    private void LookForInput()
    {
        if (acceptingInput)
        {
            move_input = inputHandler.inputActions.moveVector.Vector.x; //获取X方向的键盘输入
            vertical_input = inputHandler.inputActions.moveVector.Vector.y;//获取Y方向的键盘输入
            FilterInput();//规整化


            if (inputHandler.inputActions.jump.WasReleased && jumpReleaseQueueingEnabled)
            {
                jumpReleaseQueueSteps = JUMP_RELEASE_QUEUE_STEPS;
                jumpReleaseQueuing = true;
            }
            if (!inputHandler.inputActions.jump.IsPressed)
            {
                JumpReleased();
            }
    if (!inputHandler.inputActions.dash.IsPressed)
    {
                if(cState.preventDash && !cState.dashCooldown)
{
                    cState.preventDash = false;
}
                dashQueuing = false;
    }
        }
    }

    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.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();
    }
}
    }

    /// <summary>
    /// 可以跳跃吗
    /// </summary>
    /// <returns></returns>
    private bool CanJump()
    {
if(hero_state == ActorStates.no_input || hero_state == ActorStates.hard_landing || hero_state == ActorStates.dash_landing || cState.jumping)
{
            return false;
}
if (cState.onGround)
{
            return true; //如果在地面上就return true
}
        return false;
    }

    /// <summary>
    /// 小骑士跳跃行为播放声音以及设置cstate.jumping
    /// </summary>
    private void HeroJump()
    {

        audioCtrl.PlaySound(HeroSounds.JUMP);

        cState.jumping = true;
        jumpQueueSteps = 0;
        jumped_steps = 0;
    }

    private void HeroJumpNoEffect()
    {

        audioCtrl.PlaySound(HeroSounds.JUMP);

        cState.jumping = true;
        jumpQueueSteps = 0;
        jumped_steps = 0;
    }

    /// <summary>
    /// 取消跳跃
    /// </summary>
    public void CancelHeroJump()
    {
if (cState.jumping)
{
            CancelJump();
            
            if(rb2d.velocity.y > 0f)
    {
                rb2d.velocity = new Vector2(rb2d.velocity.x, 0f);
    }
}
    }

    private void JumpReleased()
    {
        if(rb2d.velocity.y > 0f &&jumped_steps >= JUMP_STEPS_MIN)
{
    if (jumpReleaseQueueingEnabled)
    {
                if(jumpReleaseQueuing && jumpReleaseQueueSteps <= 0)
{
                    rb2d.velocity = new Vector2(rb2d.velocity.x, 0f); //取消跳跃并且设置y轴速度为0
                    CancelJump();
}
    }
    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()
    {
        cState.falling = false;


        jump_steps = 0;
        SetState(ActorStates.grounded);
cState.onGround = true;
        airDashed = false;
    }

    /// <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(collision.gameObject.GetComponent<SteepSlope>() == null && hero_state != ActorStates.hard_landing)
    {
                        BackOnGround();
    }

}
    }
}
        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(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 bool CheckForBump(CollisionSide side)
    {
        float num = 0.025f;
        float num2 = 0.2f;
        Vector2 vector = new Vector2(col2d.bounds.min.x + num2, col2d.bounds.min.y + 0.2f);
        Vector2 vector2 = new Vector2(col2d.bounds.min.x + num2, col2d.bounds.min.y - num);
        Vector2 vector3 = new Vector2(col2d.bounds.max.x - num2, col2d.bounds.min.y + 0.2f);
        Vector2 vector4 = new Vector2(col2d.bounds.max.x - num2, col2d.bounds.min.y - num);
        float num3 = 0.32f + num2;
        RaycastHit2D raycastHit2D = default(RaycastHit2D);
        RaycastHit2D raycastHit2D2 = default(RaycastHit2D);
        if(side == CollisionSide.left)
{
            Debug.DrawLine(vector2, vector2 + Vector2.left * num3, Color.cyan, 0.15f);
            Debug.DrawLine(vector, vector + Vector2.left * num3, Color.cyan, 0.15f);
            raycastHit2D = Physics2D.Raycast(vector2, Vector2.left, num3, LayerMask.GetMask("Terrain"));
            raycastHit2D2 = Physics2D.Raycast(vector, Vector2.left, num3, LayerMask.GetMask("Terrain"));
        }
        else if (side == CollisionSide.right)
        {
            Debug.DrawLine(vector4, vector4 + Vector2.right * num3, Color.cyan, 0.15f);
            Debug.DrawLine(vector3, vector3 + Vector2.right * num3, Color.cyan, 0.15f);
            raycastHit2D = Physics2D.Raycast(vector4, Vector2.right, num3, LayerMask.GetMask("Terrain"));
            raycastHit2D2 = Physics2D.Raycast(vector3, Vector2.right, num3, LayerMask.GetMask("Terrain"));
}
else
{
            Debug.LogError("Invalid CollisionSide specified.");
        }
        if(raycastHit2D2.collider != null && raycastHit2D.collider == null)
{
            Vector2 vector5 = raycastHit2D2.point + new Vector2((side == CollisionSide.right) ? 0.1f : -0.1f, 1f);
            RaycastHit2D raycastHit2D3 = Physics2D.Raycast(vector5, Vector2.down, 1.5f, LayerMask.GetMask("Terrain"));
            Vector2 vector6 = raycastHit2D2.point + new Vector2((side == CollisionSide.right) ? -0.1f : 0.1f, 1f);
    RaycastHit2D raycastHit2D4 = Physics2D.Raycast(vector6, Vector2.down, 1.5f, LayerMask.GetMask("Terrain"));
            if(raycastHit2D3.collider != null)
    {
Debug.DrawLine(vector5, raycastHit2D3.point, Color.cyan, 0.15f);
                if (!(raycastHit2D4.collider != null))
                {
                    return true;
}
Debug.DrawLine(vector6, raycastHit2D4.point, Color.cyan, 0.15f);
                float num4 = raycastHit2D3.point.y - raycastHit2D4.point.y;
                if(num4 > 0f)
{
                    Debug.Log("Bump Height: " + num4.ToString());
                    return true;
                }
    }
}
        return false;
    }

    /// <summary>
    /// 找到碰撞点的方向也就是上下左右
    /// </summary>
    /// <param name="collision"></param>
    /// <returns></returns>
    private CollisionSide FindCollisionSide(Collision2D collision)
    {
        Vector2 normal = collision.GetSafeContact().Normal ;
        float x = normal.x;
        float y = normal.y;
        if(y >= 0.5f)
{
            return CollisionSide.bottom; 
}
        if (y <= -0.5f)
        {
            return CollisionSide.top;
        }
        if (x < 0)
        {
            return CollisionSide.right;
        }
        if (x > 0)
        {
            return CollisionSide.left;
        }
        Debug.LogError(string.Concat(new string[]
        {
            "ERROR: unable to determine direction of collision - contact points at (",
            normal.x.ToString(),
            ",",
            normal.y.ToString(),
            ")"
        }));
        return CollisionSide.bottom;
    }


}

[Serializable]
public class HeroControllerStates
{
    public bool facingRight;
    public bool onGround;
    public bool wasOnGround;
    public bool inWalkZone;
    public bool jumping;
    public bool falling;
    public bool dashing;
    public bool touchingWall;
    public bool preventDash;
    public bool dashCooldown;
    public bool isPaused;

    public HeroControllerStates()
    {
        facingRight = false;
        onGround = false;
        wasOnGround = false;
        inWalkZone = false;
        jumping = false;
        falling = false;
        dashing = false;
touchingWall = false;
        preventDash = false;
        dashCooldown = false;
isPaused = false;
    }
}

回到Unity编辑器中,我们要给HeroController.cs上的序列化参数赋值了:

需要注意的是,你的DASH_TIME最好和tk2dSpriteAnimation的Dash动画片段的Clip Time的数值相同,不然可能会显得画面不同步,像我就直接两个都设置成0.5

 

 小骑士游戏对象下新开一个叫Effect的子对象,里面存储效果的游戏对象:

首先是第一个Dash Ash:

 

然后是第二个效果Dash Burst

可以看到我们要单独为它创造一个tk2dSpriteCollection和tk2dSpriteAnimation:

 

 然后它还需要一个PlayMaker FSM:

我们通过开关它上面的Mesh Renderer来控制显示,首先要自定义一个playmaker action就叫SetMeshRenderer:

using UnityEngine;

namespace HutongGames.PlayMaker.Actions
{
    [ActionCategory(ActionCategory.GameObject)]
    [Tooltip("Set Mesh Renderer to active or inactive. Can only be one Mesh Renderer on object. ")]
    public class SetMeshRenderer : FsmStateAction
    {
[RequiredField] public FsmOwnerDefault gameObject;
public FsmBool active;

public override void Reset()
{
    gameObject = null;
    active = false;
}

public override void OnEnter()
{
    if (gameObject != null)
    {
GameObject ownerDefaultTarget = Fsm.GetOwnerDefaultTarget(gameObject);
if(ownerDefaultTarget != null)
{
    MeshRenderer component = ownerDefaultTarget.GetComponent<MeshRenderer>();
    if(component != null)
    {
component.enabled = active.Value;
    }
}
    }
    Finish();
}
    }
}

 状态机的事件如下:它并不需要变量

而且只有两种状态,我们在代码中通过SendEvent发送事件来控制它们切换:

 


总结

至此,我们制作了一个完整的小骑士冲刺Dash,我们赶紧运行游戏看看效果如何吧:

平地上的Dash:

空中的Dash:

这些都是有冷却时间的,不能一直按一直冲。 

还有就是离开地面后小骑士只能再按一下Dash,此后cState.Dashing就一直是false,直到下一次碰到地面cState.Dashing为true后才能按下一次Dash:

 


原文地址:https://blog.csdn.net/dangoxiba/article/details/142409037

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!