当前位置:   article > 正文

【Unity从零开始制作空洞骑士】①制作人物的移动跳跃转向以及初始的动画制作_空洞骑士源码

空洞骑士源码

事情的起因:

  首先我之前在b站的时候突然发现有个大佬说复刻了空洞骑士,点进去一看发现很多场景都福源道非常详细,当时我除了觉得大佬很强的同时也想自己试一下,而且当时对玩家血条设计等都很模糊,就想着问up主,结果因为制作的时间过了很久了,大佬也有些答不上来,于是我就先下来,然后一直跟着其它视频继续学,这几天闲着就试着通过大佬的代码能不能逐步做一个空洞骑士的mod出来,所幸前面的步骤都比较顺利,通过大佬的代码还是能慢慢做出来

Steam截图镇个楼)

学习目标:

大佬的视频以及Github源码:

【Unity3D】空洞骑士の复刻_哔哩哔哩_bilibili

项目开源:https://github.com/dreamCirno/Hollow-Knight-Imitation


学习内容:

初始工作就先创建一个2D项目,然后本项目需要准备的插件有点多,把没必要的插件删除后就这些了,ProCamera2D,Input system,Post Poccessing,PlayerMaker(这个我没买)

打开开源项目,先别一次性把Assets的项目全部导入,不然肯定一堆报错的,我们先把角色的精灵图导入,然后再拖入几个地板,然后场景就暂时这样了。 

接着我们要为玩家创建动作了。

创建Input Actions命名为InputControl,然后这些都是老操作了。

 

然后就生成一个C#脚本名字就叫InputControl,然后创建一个名字叫InputManger的空对象以及一个同名脚本、

我们暂时只用到GamePlayer的动作表所以就先这样写了。 

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class InputManager : MonoBehaviour
  5. {
  6. private static InputControl inputControl;
  7. public static InputControl InputControl
  8. {
  9. get
  10. {
  11. if(inputControl == null)
  12. {
  13. inputControl = new InputControl();
  14. }
  15. return inputControl;
  16. }
  17. }
  18. private void OnEnable()
  19. {
  20. InputControl.GamePlayer.Movement.Enable();
  21. InputControl.GamePlayer.Jump.Enable();
  22. InputControl.GamePlayer.Attack.Enable();
  23. }
  24. private void OnDisable()
  25. {
  26. InputControl.GamePlayer.Movement.Disable();
  27. InputControl.GamePlayer.Jump.Disable();
  28. InputControl.GamePlayer.Attack.Disable();
  29. }
  30. }

 

玩家类脚本:

  我们为我们的Player创建一个名字叫CharacterController2D的脚本。

然后为我们的Player对象添加上组件

2D物理材质如下

首先我们先实现玩家的移动和转向

对于移动我们采用InputSystem对于行为动作的订阅事件和退订事件,用vectorInput读入键盘的输入,

对于转向则根据任务面部朝向,当向右移动的时候transform.localScale为(-1,1,1),向左则为(1,1,1);

  1. using Com.LuisPedroFonseca.ProCamera2D;
  2. using System;
  3. using System.Collections;
  4. using System.Collections.Generic;
  5. using UnityEngine;
  6. using UnityEngine.InputSystem;
  7. public class CharacterController2D : MonoBehaviour
  8. {
  9. #region Propertries
  10. readonly Vector3 flippedScale = new Vector3(-1, 1, 1);
  11. private Rigidbody2D controllerRigibody;
  12. [Header("依赖脚本")] Animator animator;
  13. [Header("移动参数")]
  14. [SerializeField] float maxSpeed = 0.0f;
  15. [SerializeField] float maxGravityVelocity = 10.0f;
  16. [SerializeField] float jumpForce = 0.0f;
  17. [SerializeField] float groundedGravityScale = 0.0f;
  18. [SerializeField] float jumpGravityScale = 0.0f;
  19. [SerializeField] float fallGravityScale = 0.0f;
  20. private Vector2 vectorInput;
  21. private int jumpCount;
  22. private bool JumpInput;
  23. private float counter;
  24. private bool enableGravity;
  25. private bool canMove;
  26. private bool isOnGround;
  27. private bool isFacingLeft;
  28. private bool isJumping;
  29. private bool isFalling;
  30. private int animatorFirstLandingBool;
  31. private int animatorGroundedBool;
  32. private int animatorMovementSpeed;
  33. private int animatorVelocitySpeed;
  34. private int animatorJumpTrigger;
  35. private int animatorDoubleJumpTrigger;
  36. [Header("其它参数")]
  37. [SerializeField] private bool firstLanding;
  38. #endregion
  39. #region CallBackFunctions
  40. private void Awake()
  41. {
  42. controllerRigibody = GetComponent<Rigidbody2D>();
  43. animator = GetComponent<Animator>();
  44. }
  45. private void OnEnable()
  46. {
  47. InputManager.InputControl.GamePlayer.Movement.performed += ctx => vectorInput = ctx.ReadValue<Vector2>();
  48. InputManager.InputControl.GamePlayer.Jump.started += Jump_Started;
  49. InputManager.InputControl.GamePlayer.Jump.performed += Jump_Performed;
  50. InputManager.InputControl.GamePlayer.Jump.canceled += Jump_Canceled;
  51. }
  52. private void OnDisable()
  53. {
  54. InputManager.InputControl.GamePlayer.Movement.performed -= ctx => vectorInput = ctx.ReadValue<Vector2>();
  55. InputManager.InputControl.GamePlayer.Jump.started -= Jump_Started;
  56. InputManager.InputControl.GamePlayer.Jump.performed -= Jump_Performed;
  57. InputManager.InputControl.GamePlayer.Jump.canceled -= Jump_Canceled;
  58. }
  59. private void Start()
  60. {
  61. animatorFirstLandingBool = Animator.StringToHash("FirstLanding");
  62. animatorGroundedBool = Animator.StringToHash("Grounded");
  63. animatorVelocitySpeed = Animator.StringToHash("Velocity");
  64. animatorMovementSpeed = Animator.StringToHash("Movement");
  65. animatorJumpTrigger = Animator.StringToHash("Jump");
  66. animatorDoubleJumpTrigger = Animator.StringToHash("DoubleJump");
  67. animator.SetBool(animatorFirstLandingBool, firstLanding);
  68. enableGravity = true;
  69. canMove = true;
  70. }
  71. private void FixedUpdate()
  72. {
  73. UpdateVelocity();
  74. UpdateDirection();
  75. }
  76. #endregion
  77. #region Movement
  78. private void UpdateVelocity()
  79. {
  80. Vector2 velocity = controllerRigibody.velocity;
  81. if (vectorInput.x != 0)
  82. {
  83. velocity.y = Mathf.Clamp(velocity.y, -maxGravityVelocity / 2, maxGravityVelocity / 2);
  84. }
  85. else
  86. {
  87. velocity.y = Mathf.Clamp(velocity.y, -maxGravityVelocity, maxGravityVelocity);
  88. }
  89. animator.SetFloat(animatorVelocitySpeed, controllerRigibody.velocity.y);
  90. if (canMove)
  91. {
  92. controllerRigibody.velocity = new Vector2(vectorInput.x * maxSpeed, velocity.y);
  93. animator.SetInteger(animatorMovementSpeed, (int)vectorInput.x);
  94. }
  95. }
  96. private void UpdateDirection()
  97. {
  98. //控制玩家的旋转
  99. if (controllerRigibody.velocity.x > 1f && isFacingLeft)
  100. {
  101. isFacingLeft = false;
  102. transform.localScale = flippedScale;
  103. }
  104. else if (controllerRigibody.velocity.x < -1f && !isFacingLeft)
  105. {
  106. isFacingLeft = true;
  107. transform.localScale = Vector3.one;
  108. }
  109. }
  110. private void UpdateGrounding(Collision2D collision,bool exitState)
  111. {
  112. if (exitState)
  113. {
  114. if (collision.gameObject.layer == LayerMask.NameToLayer("Terrian") || collision.gameObject.layer == LayerMask.NameToLayer("Soft Terrian"))
  115. {
  116. isOnGround = false;
  117. }
  118. }
  119. else
  120. {
  121. //判断为落地状态
  122. if (collision.gameObject.layer == LayerMask.NameToLayer("Terrian")
  123. || collision.gameObject.layer == LayerMask.NameToLayer("Soft Terrian")
  124. && collision.contacts[0].normal == Vector2.up
  125. && !isOnGround)
  126. {
  127. isOnGround = true;
  128. isJumping = false;
  129. isFalling = false;
  130. }
  131. //判断为头顶碰到物体状态
  132. else if (collision.gameObject.layer == LayerMask.NameToLayer("Terrian") || collision.gameObject.layer == LayerMask.NameToLayer("Soft Terrian")
  133. && collision.contacts[0].normal == Vector2.down && isJumping)
  134. {
  135. }
  136. }
  137. animator.SetBool(animatorGroundedBool, isOnGround);
  138. }
  139. public void StopHorizontalMovement()
  140. {
  141. Vector2 velocity = controllerRigibody.velocity;
  142. velocity.x = 0;
  143. controllerRigibody.velocity = velocity;
  144. animator.SetInteger(animatorMovementSpeed, 0);
  145. }
  146. public void SetIsOnGrounded(bool state)
  147. {
  148. isOnGround = state;
  149. animator.SetBool(animatorGroundedBool, isOnGround);
  150. }
  151. #endregion
  152. #region Combat
  153. private void Jump_Canceled(InputAction.CallbackContext context)
  154. {
  155. }
  156. private void Jump_Performed(InputAction.CallbackContext context)
  157. {
  158. }
  159. private void Jump_Started(InputAction.CallbackContext context)
  160. {
  161. }
  162. private void OnCollisionEnter2D(Collision2D collision)
  163. {
  164. UpdateGrounding(collision, false);
  165. }
  166. private void OnCollisionStay2D(Collision2D collision)
  167. {
  168. UpdateGrounding(collision, false);
  169. }
  170. private void OnCollisionExit2D(Collision2D collision)
  171. {
  172. UpdateGrounding(collision, true);
  173. }
  174. #endregion
  175. #region Others
  176. public void FirstLanding()
  177. {
  178. }
  179. #endregion
  180. }

接着我们制作动画,制作好Idle,walk,Run的动画

 

由于我们还没为动画判断条件Grounded作代码判断条件,所以就先创建一个空对象用于地面检测

 再给他一个脚本

  1. using UnityEngine;
  2. public class GroundDetector : MonoBehaviour
  3. {
  4. private CharacterController2D character;
  5. private void Awake()
  6. {
  7. character = FindObjectOfType<CharacterController2D>();
  8. }
  9. private void OnTriggerEnter2D(Collider2D collision)
  10. {
  11. if (collision.gameObject.layer == LayerMask.NameToLayer("Terrian"))
  12. {
  13. character.SetIsOnGrounded(true);
  14. }
  15. }
  16. private void OnTriggerExit2D(Collider2D collision)
  17. {
  18. if (collision.gameObject.layer == LayerMask.NameToLayer("Terrian"))
  19. {
  20. character.SetIsOnGrounded(false);
  21. }
  22. }
  23. }

 移动的脚本做完了我们还需要做跳跃,跳跃分为一段跳和二段跳,首先打开CharacterController2D,我们将通过跳跃计数器决定播放一段跳或是二段跳的动画,并通过判断条件决定什么时候重置动画

  1. using Com.LuisPedroFonseca.ProCamera2D;
  2. using System;
  3. using System.Collections;
  4. using System.Collections.Generic;
  5. using UnityEngine;
  6. using UnityEngine.InputSystem;
  7. public class CharacterController2D : MonoBehaviour
  8. {
  9. #region Propertries
  10. readonly Vector3 flippedScale = new Vector3(-1, 1, 1);
  11. private Rigidbody2D controllerRigibody;
  12. [Header("依赖脚本")] Animator animator;
  13. [Header("移动参数")]
  14. [SerializeField] float maxSpeed = 0.0f;
  15. [SerializeField] float maxGravityVelocity = 10.0f;
  16. [SerializeField] float jumpForce = 0.0f;
  17. [SerializeField] float groundedGravityScale = 0.0f;
  18. [SerializeField] float jumpGravityScale = 0.0f;
  19. [SerializeField] float fallGravityScale = 0.0f;
  20. private Vector2 vectorInput;
  21. private int jumpCount;
  22. private bool JumpInput;
  23. private float counter;
  24. private bool enableGravity;
  25. private bool canMove;
  26. private bool isOnGround;
  27. private bool isFacingLeft;
  28. private bool isJumping;
  29. private bool isFalling;
  30. private int animatorFirstLandingBool;
  31. private int animatorGroundedBool;
  32. private int animatorMovementSpeed;
  33. private int animatorVelocitySpeed;
  34. private int animatorJumpTrigger;
  35. private int animatorDoubleJumpTrigger;
  36. [Header("其它参数")]
  37. [SerializeField] private bool firstLanding;
  38. #endregion
  39. #region CallBackFunctions
  40. private void Awake()
  41. {
  42. controllerRigibody = GetComponent<Rigidbody2D>();
  43. animator = GetComponent<Animator>();
  44. }
  45. private void OnEnable()
  46. {
  47. InputManager.InputControl.GamePlayer.Movement.performed += ctx => vectorInput = ctx.ReadValue<Vector2>();
  48. InputManager.InputControl.GamePlayer.Jump.started += Jump_Started;
  49. InputManager.InputControl.GamePlayer.Jump.performed += Jump_Performed;
  50. InputManager.InputControl.GamePlayer.Jump.canceled += Jump_Canceled;
  51. }
  52. private void OnDisable()
  53. {
  54. InputManager.InputControl.GamePlayer.Movement.performed -= ctx => vectorInput = ctx.ReadValue<Vector2>();
  55. InputManager.InputControl.GamePlayer.Jump.started -= Jump_Started;
  56. InputManager.InputControl.GamePlayer.Jump.performed -= Jump_Performed;
  57. InputManager.InputControl.GamePlayer.Jump.canceled -= Jump_Canceled;
  58. }
  59. private void Start()
  60. {
  61. animatorFirstLandingBool = Animator.StringToHash("FirstLanding");
  62. animatorGroundedBool = Animator.StringToHash("Grounded");
  63. animatorVelocitySpeed = Animator.StringToHash("Velocity");
  64. animatorMovementSpeed = Animator.StringToHash("Movement");
  65. animatorJumpTrigger = Animator.StringToHash("Jump");
  66. animatorDoubleJumpTrigger = Animator.StringToHash("DoubleJump");
  67. animator.SetBool(animatorFirstLandingBool, firstLanding);
  68. enableGravity = true;
  69. canMove = true;
  70. }
  71. private void FixedUpdate()
  72. {
  73. UpdateVelocity();
  74. UpdateJump();
  75. UpdateDirection();
  76. UpdateGravityScale();
  77. }
  78. #endregion
  79. #region Movement
  80. private void UpdateVelocity()
  81. {
  82. Vector2 velocity = controllerRigibody.velocity;
  83. if (vectorInput.x != 0)
  84. {
  85. velocity.y = Mathf.Clamp(velocity.y, -maxGravityVelocity / 2, maxGravityVelocity / 2);
  86. }
  87. else
  88. {
  89. velocity.y = Mathf.Clamp(velocity.y, -maxGravityVelocity, maxGravityVelocity);
  90. }
  91. animator.SetFloat(animatorVelocitySpeed, controllerRigibody.velocity.y);
  92. if (canMove)
  93. {
  94. controllerRigibody.velocity = new Vector2(vectorInput.x * maxSpeed, velocity.y);
  95. animator.SetInteger(animatorMovementSpeed, (int)vectorInput.x);
  96. }
  97. }
  98. private void UpdateDirection()
  99. {
  100. //控制玩家的旋转
  101. if (controllerRigibody.velocity.x > 1f && isFacingLeft)
  102. {
  103. isFacingLeft = false;
  104. transform.localScale = flippedScale;
  105. }
  106. else if (controllerRigibody.velocity.x < -1f && !isFacingLeft)
  107. {
  108. isFacingLeft = true;
  109. transform.localScale = Vector3.one;
  110. }
  111. }
  112. private void UpdateJump()
  113. {
  114. if(isJumping && controllerRigibody.velocity.y < 0)
  115. {
  116. isFalling = true;
  117. }
  118. if (JumpInput)
  119. {
  120. controllerRigibody.AddForce(new Vector2(0,jumpForce), ForceMode2D.Impulse);
  121. isJumping = true;
  122. }
  123. if(isOnGround && !isJumping && jumpCount != 0) //如果已经落地了,则重置跳跃计数器
  124. {
  125. jumpCount = 0;
  126. counter = Time.time - counter;
  127. }
  128. }
  129. private void UpdateGravityScale()
  130. {
  131. var gravityScale = groundedGravityScale;
  132. if (!isOnGround)
  133. {
  134. gravityScale = controllerRigibody.velocity.y > 0.0f ? jumpGravityScale : fallGravityScale;
  135. }
  136. if (!enableGravity)
  137. {
  138. gravityScale = 0;
  139. }
  140. controllerRigibody.gravityScale = gravityScale;
  141. }
  142. private void JumpCancel()
  143. {
  144. JumpInput = false;
  145. isJumping = false;
  146. if(jumpCount == 1)
  147. {
  148. animator.ResetTrigger(animatorJumpTrigger);
  149. }
  150. else if(jumpCount == 2)
  151. {
  152. animator.ResetTrigger(animatorDoubleJumpTrigger);
  153. }
  154. }
  155. private void UpdateGrounding(Collision2D collision,bool exitState)
  156. {
  157. if (exitState)
  158. {
  159. if (collision.gameObject.layer == LayerMask.NameToLayer("Terrian") || collision.gameObject.layer == LayerMask.NameToLayer("Soft Terrian"))
  160. {
  161. isOnGround = false;
  162. }
  163. }
  164. else
  165. {
  166. //判断为落地状态
  167. if (collision.gameObject.layer == LayerMask.NameToLayer("Terrian")
  168. || collision.gameObject.layer == LayerMask.NameToLayer("Soft Terrian")
  169. && collision.contacts[0].normal == Vector2.up
  170. && !isOnGround)
  171. {
  172. isOnGround = true;
  173. isJumping = false;
  174. isFalling = false;
  175. //effect
  176. }
  177. //判断为头顶碰到物体状态
  178. else if (collision.gameObject.layer == LayerMask.NameToLayer("Terrian") || collision.gameObject.layer == LayerMask.NameToLayer("Soft Terrian")
  179. && collision.contacts[0].normal == Vector2.down && isJumping)
  180. {
  181. JumpCancel();
  182. }
  183. }
  184. animator.SetBool(animatorGroundedBool, isOnGround);
  185. }
  186. public void StopHorizontalMovement()
  187. {
  188. Vector2 velocity = controllerRigibody.velocity;
  189. velocity.x = 0;
  190. controllerRigibody.velocity = velocity;
  191. animator.SetInteger(animatorMovementSpeed, 0);
  192. }
  193. public void SetIsOnGrounded(bool state)
  194. {
  195. isOnGround = state;
  196. animator.SetBool(animatorGroundedBool, isOnGround);
  197. }
  198. #endregion
  199. #region Combat
  200. private void Jump_Canceled(InputAction.CallbackContext context)
  201. {
  202. JumpCancel();
  203. }
  204. private void Jump_Performed(InputAction.CallbackContext context)
  205. {
  206. JumpCancel();
  207. }
  208. private void Jump_Started(InputAction.CallbackContext context)
  209. {
  210. counter = Time.time;
  211. if(jumpCount <= 1)
  212. {
  213. ++jumpCount;
  214. if(jumpCount == 1)
  215. {
  216. //Anim+Audio
  217. animator.SetTrigger(animatorJumpTrigger);
  218. }
  219. else if(jumpCount == 2)
  220. {
  221. //Anim+Audio+Effect
  222. animator.SetTrigger(animatorDoubleJumpTrigger);
  223. }
  224. }
  225. else
  226. {
  227. return;
  228. }
  229. JumpInput = true;
  230. }
  231. private void OnCollisionEnter2D(Collision2D collision)
  232. {
  233. UpdateGrounding(collision, false);
  234. }
  235. private void OnCollisionStay2D(Collision2D collision)
  236. {
  237. UpdateGrounding(collision, false);
  238. }
  239. private void OnCollisionExit2D(Collision2D collision)
  240. {
  241. UpdateGrounding(collision, true);
  242. }
  243. #endregion
  244. #region Others
  245. public void FirstLanding()
  246. {
  247. }
  248. #endregion
  249. }

 对于动画我们则要创建一个新的动画状态机名字就叫Jump StateMachine

为我们的Jump,Fall,Soft Land,Double Jump添加好动画

接着就是动画连线了。凡是到Jump和DoubleJump都只用Triiger来作为动画转化条件

 

回到Base状态机中,Walk,Run,Idle的动画到Jump状态机的动画暂时只有Jump和Fall,而且动画条件也都是一模一样的

 

 除此之外我们还要为动画添加行为脚本,

由此我们先对部分创建好行为脚本。 

 这些里面大多都是添加音乐和粒子效果所以先不用管,但FallingBehavior则要进行修改

  1. using UnityEngine;
  2. public class FallingBehavior : StateMachineBehaviour
  3. {
  4. float lastPositionY;
  5. float fallDistance;
  6. CharacterController2D character;
  7. private void Awake()
  8. {
  9. //audio
  10. character = FindObjectOfType<CharacterController2D>();
  11. }
  12. // OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
  13. override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
  14. {
  15. fallDistance = 0;
  16. animator.SetFloat("FallDistance", fallDistance);
  17. //auido
  18. }
  19. // OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks
  20. override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
  21. {
  22. if(lastPositionY > character.transform.position.y)
  23. {
  24. fallDistance += lastPositionY - character.transform.position.y;
  25. }
  26. lastPositionY = character.transform.position.y;
  27. animator.SetFloat("FallDistance", fallDistance);
  28. }
  29. // OnStateExit is called when a transition ends and the state machine finishes evaluating this state
  30. override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
  31. {
  32. //audio
  33. }
  34. public void ResetAllParams()
  35. {
  36. lastPositionY = character.transform.position.y;
  37. fallDistance = 0;
  38. }
  39. }


学习产出:

  参数先随便设计,设计好后效果如图。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/602382
推荐阅读
相关标签
  

闽ICP备14008679号