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

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

文章目录

  • 前言
  • 一、制作一个完整的小骑士冲刺Dash行为
    • 1.制作动画以及使用UNITY编辑器编辑
    • 2.使用代码实现完整的冲刺行为控制
  • 总结


前言

大家又好久不见(虽然就过了半天)这期我决定继续完善小骑士的行为,上一期我们实现了小骑士完整的跳跃降落行为,这一期我们就来做小骑士的冲刺行为,这其中涉及到素材的导入,创建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的事件PLAYdashParticlesPrefab.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轴速度为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(){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:

 

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

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

相关文章

论文速递!基于PINN的知识+数据融合方法!实现可再生能源电力系统中的TTC高效评估

本期推文将介绍一种结合知识驱动和数据驱动的混合算法在电力系统总传输能力&#xff08;TTC&#xff09;评估中的应用&#xff0c;这项研究发表于《IEEE Transactions on Power Systems》期刊&#xff0c;主要解决高比例可再生能源渗透下电力系统中的TTC快速评估问题。 荐读的论…

day21JS-npm中的部分插件使用方法

1. 静态资源目录 静态资源目录就是访问服务器的某些路劲时候&#xff0c;服务器可以吐出一个写好的指定页面。 实现思路&#xff1a; 1、先判断要找的路径是否是文件&#xff0c;如果是文件&#xff0c;就加载发给对方。 2、如果是文件夹&#xff0c;找到这个文件夹所在路径中…

linux----进程地址空间

前言 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、空间分布 二、栈和堆的特点 &#xff08;1&#xff09;栈堆相对而生&#xff0c;堆是向上增长的&#xff0c;栈是向下增长的。 验证&#xff1a;堆是向上增长的 这里我们看到申请的堆&#xff…

springMvc的初始配置

基础文件结构(toWeb插件) 1.导入对应依赖 <?xml version"1.0" encoding"UTF-8"?><project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation"ht…

【软考】循环冗余校验码

目录 1. 说明2. CRC 的代码格式3. 例题3.1 例题1 1. 说明 1.循环冗余校验码(Cyclic Redundancy Check&#xff0c;CRC)广泛应用于数据通信领域和磁介质存储系统中。2.它利用生成多项式为k个数据位产生&#xff0c;r个校验位来进行编码&#xff0c;其编码长度为 kr。3.循环几余…

如何成为信息安全等级测评师?具体有哪些要求?

给大家的福利&#xff0c;点击下方蓝色字 即可免费领取↓↓↓ &#x1f91f; 基于入门网络安全/黑客打造的&#xff1a;&#x1f449;黑客&网络安全入门&进阶学习资源包 文章目录 前言 信息安全等级测评师 定义与作用&#xff1a;证书颁发&#xff1a;能力要求&#x…

【React】(推荐项目)一个用 React 构建的 CRUD 应用程序

推荐项目&#xff1a;CRUD 应用示例 在本篇文章中&#xff0c;我想向大家推荐一个非常实用的项目&#xff1a;CRUD 应用示例。这个项目展示了如何使用现代技术栈创建一个基础的增删改查&#xff08;CRUD&#xff09;应用&#xff0c;非常适合用于学习和实践后端开发技能。 适…

【2024华为杯数学建模研赛赛题已出(A-F题)】

华为杯2024年中国研究生数学建模竞赛A-F题已公布 A题 B题 C题 D题 E题 F题

骨传导耳机怎么选?深扒2024五款热门骨传导耳机!

耳机在我们的日常生活中渐渐变得不可或缺&#xff0c;早晨出门、通勤、工作&#xff0c;甚至睡觉时&#xff0c;它们总是陪伴在侧。尽管我们都知道长期使用耳机会对听力造成一定影响&#xff0c;但骨传导耳机的出现为我们提供了更为安全和卫生的选择。这种耳机的设计使耳朵保持…

其他比较条件

使用BETWEEN条件 可以用BETWEEN范围条件显示基于一个值范围的行。指定的范围包含一个下限和一个上限。 示例&#xff1a;查询employees表&#xff0c;薪水在3000-8000之间的雇员ID、名字与薪水。 select employee_id,last_name,salary from employees where salary between 3…

泛微E9开发 创建自定义浏览框,关联物品管理表【1】

创建自定义浏览框&#xff0c;关联物品管理表【1】 1、自定义浏览框1.1 概念1.2 前端样式 2、创建物品管理表2.1 新建建模表单操作方法2.2 物品管理表 3、创建浏览按钮 1、自定义浏览框 1.1 概念 自定义浏览框可以理解为是建模引擎中的表与表关联的一个桥梁。比如利用建模引擎…

菜鸟也能轻松上手的Java环境配置方法

初学者学习Java这么编程语言&#xff0c;第一个难题往往是Java环境的配置&#xff0c;今天与大家详细地聊一聊&#xff0c;以便大家能独立完成配置方法和过程。 首先&#xff0c;找到“JDK”&#xff0c;点击“archive”&#xff1a; 向下滑&#xff0c;在“previous java rel…

小白src挖掘 | 记某证书站的虚拟仿真实验平台

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【edusrc漏洞挖掘】 【VulnHub靶场复现】【面试分析】 &#x1f389;欢迎关注…

017_FEA_CSG_in_Matlab新的统一有限元分析工作流之2D几何

Matlab新的统一有限元分析工作流 从2023a开始&#xff0c;Matlab提供了一个统一有限元分析工作流&#xff08;UFEAW&#xff0c;unified finite element analysis workflow&#xff09;。 这个新的工作留提供一个统一的接口来求解三类问题&#xff0c;并且可以用同一套数据随…

并查集(上)

并查集简要介绍&#xff1a; 我们先讲并查集的一般使用场景&#xff0c;之后再讲并查集的具体细节以及原理。 并查集的使用一般是如下的场景&#xff1a; 一开始每个元素都拥有自己的集合&#xff0c;在自己的集合里只有这个元素自己。 f i n d ( i ) find(i) find(i)&#…

数据结构之算法复杂度

目录 前言 一、复杂度的概念 二、时间复杂度 三、大O的渐进表示法 四、空间复杂度 五、常见复杂度对比 总结 前言 本文主要讲述数据结构中的算法复杂度 一、复杂度的概念 算法在编写成可执行程序后&#xff0c;运行时需要耗费时间资源和空间(内存)资源。因此衡量一个算法的好坏…

python源代码编译exe 防止反编译的问题

1&#xff09;使用pyinstaller 打包为exe, 记住是版本是5.*&#xff0c;我用的是5.13.2 &#xff0c;不能是6.* 这是第一步。 pyinstaller -F -i d:\whs.ico packer.py -w 2&#xff09;使用pyarmor 再次加密,我使用的版本是8.3.11&#xff0c;不是7.*&#xff0c;这是第二步…

摩托车骑行行为检测系统源码分享

摩托车骑行行为检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Comput…

Cursor Rules 让 Cursor AI 代码生成更智能、更高效,效率再次飞升!

最近,AI 代码生成工具越来越火,比如 Cursor AI 编辑器。很多开发者已经开始使用它来自动生成代码,以提高工作效率。不过你有没有发现,有时候 AI 自动生成的代码并不总是符合最佳实践?比如变量命名不够规范、代码风格不统一,或者生成的代码逻辑不够清晰。这些问题有时让人…

c# 线程等待变量的值符合条件

在C#中&#xff0c;如果你想让一个线程等待直到某个变量的值满足特定条件&#xff0c;你可以使用ManualResetEvent或者AutoResetEvent来实现线程间的同步。以下是使用AutoResetEvent实现的一个简单例子&#xff1a; 在这个例子中&#xff0c;同时实现了如何让static函数访问非…