[Unity Demo]从零开始制作空洞骑士Hollow Knight第六集:制作小骑士完整的跳跃落地行为

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

文章目录

  • 前言
  • 一、制作一个完整的小骑士跳跃落地行为
    • 1.制作动画以及UNITY编辑器编辑
    • 2.使用代码实现完整的跳跃落地行为控制
    • 3.更多要考虑到的点
  • 总结


前言

大家好久不见(虽然就过了半天),经过慎重考虑后我决定这期继续完善小骑士的行为,这其中涉及到素材的导入,创建tk2dSprite和tk2dSpriteAnimation,以及代码控制行为等等,在本篇最后还会考虑到更多的代码设计。


一、制作一个完整的小骑士跳跃落地行为

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

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

我们用一个动画片段Airborne来实现跳跃到降落的全部动画,所以要根据实际情况设置好Clip time。 

 然后我们再做一个turn动画,这样转身不会只是更改一个transform.localScale.x而已还会播放一个很短暂的动画

2.使用代码实现完整的跳跃落地行为控制

首先我们先处理玩家音效管理问题,你问我为什么先做这个?肯定是因为它简单啊,给小骑士创建一个脚本,在这个脚本中我们这么做:

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;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.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();footStepsRun.Stop();footStepsWalk.Stop();}public void PauseAllSounds(){softLanding.Pause();jump.Pause();falling.Pause();footStepsRun.Pause();footStepsWalk.Pause();}public void UnPauseAllSounds(){softLanding.UnPause();jump.UnPause();falling.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;}}

然后回到Unity编辑器中,像我一样给小骑士子对象sounds,然后拖曳音频文件上去

 下面几个都同理,最后都拖到这里

然后到了跳跃部分,首先我们要到GlobalEnums中创建HeroSounds和Collsionside的数组

using System;namespace GlobalEnums
{public enum ActorStates{grounded,idle,running,airborne,wall_sliding,hard_landing,dash_landing,no_input,previous}public enum CollisionSide{top,left,right,bottom,other}public enum HeroSounds{FOOTSETP_RUN,FOOTSTEP_WALK,SOFT_LANDING,JUMP,FALLING}public enum PhysLayers{DEFAULT,IGNORE_RAYCAST = 2,WATER = 4,UI,TERRAIN = 8,PLAYER,TRANSITION_GATES,ENEMIES,PROJECTILES,HERO_DETECTOR,TERRAIN_DETECTOR,ENEMY_DETECTOR,BOUNCER = 24,SOFT_TERRAIN = 25}
}

然后我们要到HeroActions创建一个新的PlayerAction叫jump:

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

回到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, "W");AddKeyBinding(inputActions.down, "S");AddKeyBinding(inputActions.left, "A");AddKeyBinding(inputActions.right, "D");AddKeyBinding(inputActions.jump, "X");}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}));}}

 

通过代码来实现完整的跳跃落地行为控制:

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; //是否进入跳跃队列中public float MAX_FALL_VELOCITY; //最大下落速度(防止速度太快了)public int JUMP_STEPS; //最大跳跃的步public int JUMP_STEPS_MIN; //最小跳跃的步public int JUMP_QUEUE_STEPS; //最大跳跃队列的步public bool touchingWall; //是否接触到墙public bool touchingWallL; //是否接触到的墙左边public bool touchingWallR; //是否接触到的墙右边private Rigidbody2D rb2d;private BoxCollider2D col2d;private GameManager gm;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;inputHandler = gm.GetComponent<InputHandler>();}void Start(){}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();}private void FixedUpdate(){if (hero_state != ActorStates.no_input){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(rb2d.velocity.y < -MAX_FALL_VELOCITY){rb2d.velocity = new Vector2(rb2d.velocity.x, -MAX_FALL_VELOCITY);}if (jumpQueuing){jumpQueueSteps++;}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;jump_steps = 0;}/// <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;}private void LookForInput(){if (acceptingInput){move_input = inputHandler.inputActions.moveVector.Vector.x; //获取X方向的键盘输入vertical_input = inputHandler.inputActions.moveVector.Vector.y;//获取Y方向的键盘输入FilterInput();//规整化}}private void LookForQueueInput(){if (acceptingInput){if (inputHandler.inputActions.jump.WasPressed){if (CanJump()){HeroJump();}else{jumpQueueSteps = 0;jumpQueuing = true;}}if (inputHandler.inputActions.jump.IsPressed){if(jumpQueueSteps <= JUMP_QUEUE_STEPS && CanJump() && jumpQueuing){Debug.LogFormat("Execute Hero Jump");HeroJump();}}}}/// <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);}}}/// <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;}/// <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;}/// <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 touchingWall;public bool isPaused;public HeroControllerStates(){facingRight = false;onGround = false;wasOnGround = false;inWalkZone = false;jumping = false;falling = false;touchingWall = false;isPaused = false;}
}

其中我们需要创建两个空脚本,日后要用:

using UnityEngine;public class NonSlider : MonoBehaviour
{}
using UnityEngine;public class SteepSlope : MonoBehaviour
{}

 还有碰撞检测中自己创建的GetSafeContact():

using System;
using UnityEngine;public static class Collision2DUtils
{private static ContactPoint2D[] contactsBuffer;static Collision2DUtils(){contactsBuffer = new ContactPoint2D[1];}public static Collision2DSafeContact GetSafeContact(this Collision2D collision){if(collision.GetContacts(contactsBuffer) >= 1){ContactPoint2D contactPoint2D = contactsBuffer[0];return new Collision2DSafeContact{Point = contactPoint2D.point,Normal = contactPoint2D.normal,IsLegitimate = true};}Vector2 b = collision.collider.transform.TransformPoint(collision.collider.offset);Vector2 a = collision.otherCollider.transform.TransformPoint(collision.otherCollider.offset);return new Collision2DSafeContact{Point = (a + b) * 0.5f,Normal = (a - b).normalized,IsLegitimate = false};}public struct Collision2DSafeContact{public Vector2 Point;public Vector2 Normal;public bool IsLegitimate;}
}

 回到Unity编辑器中设置好参数,我们就会发现小骑士能正常跳跃了。

3.更多要考虑到的点

        更多要考虑的点指的是无论你按跳跃键按的再用力或再小力,jumpSpeed决定了小骑士的跳跃高度,这往往是我们不想看到的,我们想根据按下的力度来决定玩家跳跃的高度,因此我们要制作一个jumpRelease的完整行为来控制玩家跳跃的高度。

所以完整的HeroController.cs如下所示:

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; //最小跳跃的步[SerializeField] private int JUMP_QUEUE_STEPS; //最大跳跃队列的步[SerializeField] private int JUMP_RELEASE_QUEUE_STEPS; public bool touchingWall; //是否接触到墙public bool touchingWallL; //是否接触到的墙左边public bool touchingWallR; //是否接触到的墙右边private Rigidbody2D rb2d;private BoxCollider2D col2d;private GameManager gm;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;inputHandler = gm.GetComponent<InputHandler>();}void Start(){}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();}private void FixedUpdate(){if (hero_state != ActorStates.no_input){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(rb2d.velocity.y < -MAX_FALL_VELOCITY){rb2d.velocity = new Vector2(rb2d.velocity.x, -MAX_FALL_VELOCITY);}if (jumpQueuing){jumpQueueSteps++;}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 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;}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();}}}private void LookForQueueInput(){if (acceptingInput){if (inputHandler.inputActions.jump.WasPressed){if (CanJump()){HeroJump();}else{jumpQueueSteps = 0;jumpQueuing = true;}}if (inputHandler.inputActions.jump.IsPressed){if(jumpQueueSteps <= JUMP_QUEUE_STEPS && CanJump() && jumpQueuing){Debug.LogFormat("Execute Hero Jump");HeroJump();}}}}/// <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;}/// <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;}/// <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 touchingWall;public bool isPaused;public HeroControllerStates(){facingRight = false;onGround = false;wasOnGround = false;inWalkZone = false;jumping = false;falling = false;touchingWall = false;isPaused = false;}
}

回到Unity编辑器中设置好参数,就可以看到跳跃高度随着按跳跃键的强度而改变了


总结

跳跃高度随着按跳跃键的强度而改变:

下一期我们来做冲刺动画吧。

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

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

相关文章

基于MTL的多任务视频推荐系统

多任务学习&#xff0c;也就是MTL(Multi-task Learning)&#xff0c;现在已经被用在很多领域了&#xff0c;比如处理自然语言、搞计算机视觉&#xff0c;还有语音识别这些领域。MTL在大规模的推荐系统里也玩得挺溜&#xff0c;尤其是那些做视频推荐的大家伙。 MTL的玩法就是&a…

NLP 主要语言模型分类

文章目录 ngram自回归语言模型TransformerGPTBERT&#xff08;2018年提出&#xff09;基于 Transformer 架构的预训练模型特点应用基于 transformer&#xff08;2017年提出&#xff0c;attention is all you need&#xff09;堆叠层数与原transformer 的差异bert transformer 层…

浅谈穷举法

穷举法 穷举法是一种通过逐一列举所有可能情况来寻找解决方案的方法。就像找到一把钥匙打开一把锁&#xff0c;我们会尝试每一把钥匙直到找到正确的那一把。比如&#xff0c;如果你忘记了自己的密码&#xff0c;可以尝试每一种可能的组合直到找到正确的密码为止 穷举法的结构 …

【Python】快速判断两个commit 是否存在cherry-pick 关系

判断两个提交是否有 cherry-pick 关系的 Python 脚本&#xff0c;可以基于以下三种常见情况进行优化&#xff1a; Commit Hash 一致&#xff1a;如果两个提交的 hash 完全相同&#xff0c;那么它们是相同的提交。 Commit Title 存在关联&#xff1a;如果两个提交的 commit mes…

如何下载ComfyUI开发版

看B站视频&#xff0c;见用绘世可以下载ComfyUI开发版&#xff0c;而我又不想在电脑里放太多东西&#xff0c;于是研究了一下&#xff0c;如何直接从GitHub网站下载。具体步骤看图示。 看压缩包内容&#xff0c;应该直接解压覆盖就可以了&#xff0c;暂未有时间测试。

科研绘图系列:R语言散点图和小提琴图(scatter plot violin plot)

文章目录 介绍加载R包导入数据数据预处理函数画图系统信息介绍 提取模型的结果并对模型的结果进行可视化。 加载R包 library(ggplot2) library(ggridges) library(patchwork) library(party) library(caret) library(dplyr

堆的向下调整算法和TOPK问题

目录 1.什么是堆&#xff1f; 1.1 向下调整建堆的时间复杂度计算 1.2 堆的结构体设计 2.堆的功能实现&#xff1a; 2.1 堆的插入&#xff1a; 2.2 堆的删除&#xff1a; 2.3 堆排序&#xff1a; 2.4 向下调整建堆&#xff1a; 2.5 TOPK问题&#xff1a; 2.6 向上调整算…

【Unity踩坑】UI Image的fillAmount不起作用

在游戏场景中&#xff0c;我们经常在界面上展示进度条&#xff0c;当然有各种形状的&#xff0c;线性的&#xff0c;长方形的&#xff0c;圆形&#xff0c;环形等等。 Unity中实现这种效果的话&#xff0c;最基本的方法说是改变Image的fillAmout属性。 如果你是初次使用UI Ima…

ubuntu安装SFML库+QT使用SFML库播放声音

(1)ubuntu安装SFML库 sudo apt-get install libsfml-dev (2)QT使用SFML库播放声音 在.pro文件中添加头文件路径和库文件路径 INCLUDEPATH /usr/include/SFML LIBS /usr/lib/x86_64-linux-gnu/libsfml*.so UI界面中创建一个pushbutton按钮&#xff0c;并且创建槽函数 加载…

国外大带宽服务器怎么连接

随着互联网技术的发展&#xff0c;企业和个人用户越来越依赖于高速的数据传输服务。国外的大带宽服务器因其高速度、稳定性及较低延迟等优势&#xff0c;成为了许多跨国公司、网站托管商以及数据密集型应用的选择。以下是连接国外大带宽服务器的一些常见方法及其注意事项。 选择…

STL-常用算法 遍历/查找/排序/拷贝和替换/算数生成/集合算法

STL常用算法 常用的遍历算法 for_each #define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std; #include<vector> #include<algorithm>void myPrint(int v) {cout << v << " "; }class MyPrint { public:void op…

切换到WDDM模式,Tesla M4可以用于本地显示输出了!

正文共&#xff1a;1333 字 21 图&#xff0c;预估阅读时间&#xff1a;2 分钟 上次安装完Tesla M4显卡之后&#xff08;HPE服务器通过显卡直通安装Tesla M4&#xff0c;这算亮机成功了吗&#xff1f;&#xff09;&#xff0c;系统识别正常&#xff0c;但是不能用于显示&#x…

C语言的文件基础知识

一、文件存在的意义 ① 文件的定义是什么&#xff1f; 文件是以单个名称在计算机上存储的信息集合。文件可以是文本文档、图片、程序等等。文件通常具有三个字母的文件扩展名&#xff0c;用于指示文件类型&#xff08;例如&#xff0c;图片文件常常以 JPEG 格式保存并且文件扩…

Hive企业级调优[4]——HQL语法优化之分组聚合优化

HQL语法优化之分组聚合优化 优化说明 在 Hive 中&#xff0c;未经优化的分组聚合通常通过一个 MapReduce Job 实现。Map 端负责读取数据&#xff0c;并按分组字段进行分区&#xff0c;通过 Shuffle 将数据发送至 Reduce 端&#xff0c;在 Reduce 端完成最终的聚合运算。 Hiv…

网页交互模拟:模拟用户输入、点击、选择、滚动等交互操作

目录 一、理论基础 1.1 网页交互模拟的重要性 1.2 网页交互的基本原理 二、常用工具介绍 2.1 Selenium 2.2 Puppeteer 2.3 Cypress 2.4 TestCafe 三、实战案例 3.1 模拟用户输入 3.2 模拟用户点击 3.3 模拟用户选择 3.4 模拟滚动操作 四、最佳实践与优化 4.1 代…

用 Pygame 实现一个乒乓球游戏

用 Pygame 实现一个乒乓球游戏 伸手需要一瞬间&#xff0c;牵手却要很多年&#xff0c;无论你遇见谁&#xff0c;他都是你生命该出现的人&#xff0c;绝非偶然。若无相欠&#xff0c;怎会相见。 引言 在这篇文章中&#xff0c;我将带领大家使用 Pygame 库开发一个简单的乒乓球…

系统优化工具 | Windows Manager v2.0.5 便携版

Windows Manager 是一款专为Microsoft Windows 10/11设计的系统优化和管理软件。它集成了多种实用程序&#xff0c;旨在帮助用户更好地管理和优化Windows操作系统。该软件的功能包括系统清理、系统优化、系统修复、硬件信息查看和系统设置调整等。 系统清理&#xff1a;Window…

Qt Creator项目模板介绍

在Qt Creator中创建项目时&#xff0c;用户可以从多个模板类别中进行选择&#xff0c;以满足不同的开发需求。 Application(Qt) 在Application(Qt)类别下&#xff0c;Qt Creator提供了多种用于创建不同类型Qt应用程序的模板。这些模板主要包括&#xff1a; Qt Widgets Applic…

前缀和与差分(二维)

二维前缀和 下面是一个二维数组&#xff0c;我们要求&#xff08;1&#xff0c;1&#xff09;到&#xff08;2&#xff0c;2&#xff09;区间内的所有元素的和&#xff0c;最原始的方法就是遍历每个元素然后一个一个加起来&#xff0c;此时时间复杂度为O(n*m)。 我们之前学过…