【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的动作表所以就先这样写了。 

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

public class InputManager : MonoBehaviour
{
    private static InputControl inputControl;
    public static InputControl InputControl
    {
        get
        {
            if(inputControl == null)
            {
                inputControl = new InputControl();
            }
            return inputControl;
        }
    }

    private void OnEnable()
    {
        InputControl.GamePlayer.Movement.Enable();
        InputControl.GamePlayer.Jump.Enable();
        InputControl.GamePlayer.Attack.Enable();
    }

    private void OnDisable()
    {
        InputControl.GamePlayer.Movement.Disable();
        InputControl.GamePlayer.Jump.Disable();
        InputControl.GamePlayer.Attack.Disable();
    }



}

 

玩家类脚本:

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

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

2D物理材质如下

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

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

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

using Com.LuisPedroFonseca.ProCamera2D;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class CharacterController2D : MonoBehaviour
{
    #region Propertries
    readonly Vector3 flippedScale = new Vector3(-1, 1, 1);

    private Rigidbody2D controllerRigibody;

    [Header("依赖脚本")] Animator animator;

    [Header("移动参数")]
    [SerializeField] float maxSpeed = 0.0f;
    [SerializeField] float maxGravityVelocity = 10.0f;
    [SerializeField] float jumpForce = 0.0f;
    [SerializeField] float groundedGravityScale = 0.0f;

    [SerializeField] float jumpGravityScale = 0.0f;
    [SerializeField] float fallGravityScale = 0.0f;

    private Vector2 vectorInput;
    private int jumpCount;
    private bool JumpInput;
    private float counter;

    private bool enableGravity;
    private bool canMove;

    private bool isOnGround;
    private bool isFacingLeft;
    private bool isJumping;
    private bool isFalling;

    private int animatorFirstLandingBool;
    private int animatorGroundedBool;
    private int animatorMovementSpeed;
    private int animatorVelocitySpeed;
    private int animatorJumpTrigger;
    private int animatorDoubleJumpTrigger;

    [Header("其它参数")]
    [SerializeField] private bool firstLanding;

    #endregion

    #region CallBackFunctions
    private void Awake()
    {
        controllerRigibody = GetComponent<Rigidbody2D>();
        animator = GetComponent<Animator>();
    }

    private void OnEnable()
    {
        InputManager.InputControl.GamePlayer.Movement.performed += ctx => vectorInput = ctx.ReadValue<Vector2>();
        InputManager.InputControl.GamePlayer.Jump.started += Jump_Started;
        InputManager.InputControl.GamePlayer.Jump.performed += Jump_Performed;
        InputManager.InputControl.GamePlayer.Jump.canceled += Jump_Canceled;
    }

    private void OnDisable()
    {
        InputManager.InputControl.GamePlayer.Movement.performed -= ctx => vectorInput = ctx.ReadValue<Vector2>();
        InputManager.InputControl.GamePlayer.Jump.started -= Jump_Started;
        InputManager.InputControl.GamePlayer.Jump.performed -= Jump_Performed;
        InputManager.InputControl.GamePlayer.Jump.canceled -= Jump_Canceled;
    }

    private void Start()
    {
        animatorFirstLandingBool = Animator.StringToHash("FirstLanding");
        animatorGroundedBool = Animator.StringToHash("Grounded");
        animatorVelocitySpeed = Animator.StringToHash("Velocity");
        animatorMovementSpeed = Animator.StringToHash("Movement");
        animatorJumpTrigger = Animator.StringToHash("Jump");
        animatorDoubleJumpTrigger = Animator.StringToHash("DoubleJump");

        animator.SetBool(animatorFirstLandingBool, firstLanding);

        enableGravity = true;
        canMove = true;
    }

    private void FixedUpdate()
    {
        UpdateVelocity();
        UpdateDirection();

    }

    #endregion

    #region Movement

    private void UpdateVelocity()
    {
        Vector2 velocity = controllerRigibody.velocity;
        if (vectorInput.x != 0)
        {
            velocity.y = Mathf.Clamp(velocity.y, -maxGravityVelocity / 2, maxGravityVelocity / 2);
        }
        else
        {
            velocity.y = Mathf.Clamp(velocity.y, -maxGravityVelocity, maxGravityVelocity);
        }
        animator.SetFloat(animatorVelocitySpeed, controllerRigibody.velocity.y);
        if (canMove)
        {
            controllerRigibody.velocity = new Vector2(vectorInput.x * maxSpeed, velocity.y);
            animator.SetInteger(animatorMovementSpeed, (int)vectorInput.x);
        }
    }

    private void UpdateDirection()
    {
        //控制玩家的旋转
        if (controllerRigibody.velocity.x > 1f && isFacingLeft)
        {
            isFacingLeft = false;
            transform.localScale = flippedScale;
        }
        else if (controllerRigibody.velocity.x < -1f && !isFacingLeft)
        {
            isFacingLeft = true;
            transform.localScale = Vector3.one;
        }
    }

   
    

    private void UpdateGrounding(Collision2D collision,bool exitState)
    {
        if (exitState)
        {
            if (collision.gameObject.layer == LayerMask.NameToLayer("Terrian") || collision.gameObject.layer == LayerMask.NameToLayer("Soft Terrian"))
            {
                isOnGround = false;

            }
        }
        else
        {   
//判断为落地状态
            if (collision.gameObject.layer == LayerMask.NameToLayer("Terrian")
                || collision.gameObject.layer == LayerMask.NameToLayer("Soft Terrian")
                && collision.contacts[0].normal == Vector2.up
                && !isOnGround)
            {
                isOnGround = true;
                isJumping = false;
                isFalling = false;
            }
            //判断为头顶碰到物体状态
            else if (collision.gameObject.layer == LayerMask.NameToLayer("Terrian") || collision.gameObject.layer == LayerMask.NameToLayer("Soft Terrian")
                && collision.contacts[0].normal == Vector2.down && isJumping)
            {

            }
        }
        animator.SetBool(animatorGroundedBool, isOnGround);
    }

    public void StopHorizontalMovement()
    {
        Vector2 velocity = controllerRigibody.velocity;
        velocity.x = 0;
        controllerRigibody.velocity = velocity;
        animator.SetInteger(animatorMovementSpeed, 0);
    }

    public void SetIsOnGrounded(bool state)
    {
        isOnGround = state;
        animator.SetBool(animatorGroundedBool, isOnGround);
    }
    #endregion

    #region Combat
    private void Jump_Canceled(InputAction.CallbackContext context)
    {
        
    }

    private void Jump_Performed(InputAction.CallbackContext context)
    {
       
    }

    private void Jump_Started(InputAction.CallbackContext context)
    {
        
    }

    private void OnCollisionEnter2D(Collision2D collision)
    {
        UpdateGrounding(collision, false);
    }

    private void OnCollisionStay2D(Collision2D collision)
    {
        UpdateGrounding(collision, false);
    }

    private void OnCollisionExit2D(Collision2D collision)
    {
        UpdateGrounding(collision, true);
    }
    #endregion

    #region Others

    public void FirstLanding()
    {

    }

    #endregion

}

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

 

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

 再给他一个脚本

using UnityEngine;

public class GroundDetector : MonoBehaviour
{
    private CharacterController2D character;

    private void Awake()
    {
        character = FindObjectOfType<CharacterController2D>();
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.gameObject.layer == LayerMask.NameToLayer("Terrian"))
        {
            character.SetIsOnGrounded(true);
        }
    }

    private void OnTriggerExit2D(Collider2D collision)
    {
        if (collision.gameObject.layer == LayerMask.NameToLayer("Terrian"))
        {
            character.SetIsOnGrounded(false);
        }
    }
}

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

using Com.LuisPedroFonseca.ProCamera2D;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class CharacterController2D : MonoBehaviour
{
    #region Propertries
    readonly Vector3 flippedScale = new Vector3(-1, 1, 1);

    private Rigidbody2D controllerRigibody;

    [Header("依赖脚本")] Animator animator;

    [Header("移动参数")]
    [SerializeField] float maxSpeed = 0.0f;
    [SerializeField] float maxGravityVelocity = 10.0f;
    [SerializeField] float jumpForce = 0.0f;
    [SerializeField] float groundedGravityScale = 0.0f;

    [SerializeField] float jumpGravityScale = 0.0f;
    [SerializeField] float fallGravityScale = 0.0f;

    private Vector2 vectorInput;
    private int jumpCount;
    private bool JumpInput;
    private float counter;

    private bool enableGravity;
    private bool canMove;

    private bool isOnGround;
    private bool isFacingLeft;
    private bool isJumping;
    private bool isFalling;

    private int animatorFirstLandingBool;
    private int animatorGroundedBool;
    private int animatorMovementSpeed;
    private int animatorVelocitySpeed;
    private int animatorJumpTrigger;
    private int animatorDoubleJumpTrigger;

    [Header("其它参数")]
    [SerializeField] private bool firstLanding;

    #endregion

    #region CallBackFunctions
    private void Awake()
    {
        controllerRigibody = GetComponent<Rigidbody2D>();
        animator = GetComponent<Animator>();
    }

    private void OnEnable()
    {
        InputManager.InputControl.GamePlayer.Movement.performed += ctx => vectorInput = ctx.ReadValue<Vector2>();
        InputManager.InputControl.GamePlayer.Jump.started += Jump_Started;
        InputManager.InputControl.GamePlayer.Jump.performed += Jump_Performed;
        InputManager.InputControl.GamePlayer.Jump.canceled += Jump_Canceled;
    }

    private void OnDisable()
    {
        InputManager.InputControl.GamePlayer.Movement.performed -= ctx => vectorInput = ctx.ReadValue<Vector2>();
        InputManager.InputControl.GamePlayer.Jump.started -= Jump_Started;
        InputManager.InputControl.GamePlayer.Jump.performed -= Jump_Performed;
        InputManager.InputControl.GamePlayer.Jump.canceled -= Jump_Canceled;
    }

    private void Start()
    {
        animatorFirstLandingBool = Animator.StringToHash("FirstLanding");
        animatorGroundedBool = Animator.StringToHash("Grounded");
        animatorVelocitySpeed = Animator.StringToHash("Velocity");
        animatorMovementSpeed = Animator.StringToHash("Movement");
        animatorJumpTrigger = Animator.StringToHash("Jump");
        animatorDoubleJumpTrigger = Animator.StringToHash("DoubleJump");

        animator.SetBool(animatorFirstLandingBool, firstLanding);

        enableGravity = true;
        canMove = true;
    }

    private void FixedUpdate()
    {
        UpdateVelocity();
        UpdateJump();
        UpdateDirection();
        UpdateGravityScale();
    }

    #endregion

    #region Movement

    private void UpdateVelocity()
    {
        Vector2 velocity = controllerRigibody.velocity;
        if (vectorInput.x != 0)
        {
            velocity.y = Mathf.Clamp(velocity.y, -maxGravityVelocity / 2, maxGravityVelocity / 2);
        }
        else
        {
            velocity.y = Mathf.Clamp(velocity.y, -maxGravityVelocity, maxGravityVelocity);
        }
        animator.SetFloat(animatorVelocitySpeed, controllerRigibody.velocity.y);
        if (canMove)
        {
            controllerRigibody.velocity = new Vector2(vectorInput.x * maxSpeed, velocity.y);
            animator.SetInteger(animatorMovementSpeed, (int)vectorInput.x);
        }
    }

    private void UpdateDirection()
    {
        //控制玩家的旋转
        if (controllerRigibody.velocity.x > 1f && isFacingLeft)
        {
            isFacingLeft = false;
            transform.localScale = flippedScale;
        }
        else if (controllerRigibody.velocity.x < -1f && !isFacingLeft)
        {
            isFacingLeft = true;
            transform.localScale = Vector3.one;
        }
    }

    private void UpdateJump()
    {
        if(isJumping && controllerRigibody.velocity.y < 0)
        {
            isFalling = true;
        }

        if (JumpInput)
        {
            controllerRigibody.AddForce(new Vector2(0,jumpForce), ForceMode2D.Impulse);
            isJumping = true;
        }
        if(isOnGround && !isJumping && jumpCount != 0) //如果已经落地了,则重置跳跃计数器
        {
            jumpCount = 0;
            counter = Time.time - counter;
        }
    }

    private void UpdateGravityScale()
    {
        var gravityScale = groundedGravityScale;

        if (!isOnGround)
        {
            
            gravityScale = controllerRigibody.velocity.y > 0.0f ? jumpGravityScale : fallGravityScale;
        }

        if (!enableGravity)
        {
            gravityScale = 0;
        }

        controllerRigibody.gravityScale = gravityScale;
    }

    private void JumpCancel()
    {
        JumpInput = false;
        isJumping = false;
        if(jumpCount == 1)
        {
            animator.ResetTrigger(animatorJumpTrigger);
        }
        else if(jumpCount == 2)
        {
            animator.ResetTrigger(animatorDoubleJumpTrigger);
        }
    }

    private void UpdateGrounding(Collision2D collision,bool exitState)
    {
        if (exitState)
        {
            if (collision.gameObject.layer == LayerMask.NameToLayer("Terrian") || collision.gameObject.layer == LayerMask.NameToLayer("Soft Terrian"))
            {
                isOnGround = false;
            }
        }
        else
        {
            //判断为落地状态
            if (collision.gameObject.layer == LayerMask.NameToLayer("Terrian")
                || collision.gameObject.layer == LayerMask.NameToLayer("Soft Terrian")
                && collision.contacts[0].normal == Vector2.up
                && !isOnGround)
            {
                isOnGround = true;
                isJumping = false;
                isFalling = false;
                //effect
            }
            //判断为头顶碰到物体状态
            else if (collision.gameObject.layer == LayerMask.NameToLayer("Terrian") || collision.gameObject.layer == LayerMask.NameToLayer("Soft Terrian")
                && collision.contacts[0].normal == Vector2.down && isJumping)
            {
                JumpCancel();
            }
        }
        animator.SetBool(animatorGroundedBool, isOnGround);
    }

    public void StopHorizontalMovement()
    {
        Vector2 velocity = controllerRigibody.velocity;
        velocity.x = 0;
        controllerRigibody.velocity = velocity;
        animator.SetInteger(animatorMovementSpeed, 0);
    }

    public void SetIsOnGrounded(bool state)
    {
        isOnGround = state;
        animator.SetBool(animatorGroundedBool, isOnGround);
    }
    #endregion

    #region Combat
    private void Jump_Canceled(InputAction.CallbackContext context)
    {
        JumpCancel();
    }

    private void Jump_Performed(InputAction.CallbackContext context)
    {
        JumpCancel();
    }

    private void Jump_Started(InputAction.CallbackContext context)
    {

        counter = Time.time;
        if(jumpCount <= 1)
        {
            ++jumpCount;
            if(jumpCount == 1)
            {
                //Anim+Audio
                animator.SetTrigger(animatorJumpTrigger);
            }
            else if(jumpCount == 2)
            {
                //Anim+Audio+Effect
                animator.SetTrigger(animatorDoubleJumpTrigger);
            }
        }
        else
        {
            return;
        }
        JumpInput = true;
    }

    private void OnCollisionEnter2D(Collision2D collision)
    {
        UpdateGrounding(collision, false);
    }

    private void OnCollisionStay2D(Collision2D collision)
    {
        UpdateGrounding(collision, false);
    }

    private void OnCollisionExit2D(Collision2D collision)
    {
        UpdateGrounding(collision, true);
    }
    #endregion

    #region Others

    public void FirstLanding()
    {

    }

    #endregion

}

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

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

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

 

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

 

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

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

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

using UnityEngine;

public class FallingBehavior : StateMachineBehaviour
{
    float lastPositionY;
    float fallDistance;
    CharacterController2D character;

    private void Awake()
    {
        //audio
        character = FindObjectOfType<CharacterController2D>();
    }

    // OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
    override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        fallDistance = 0;
        animator.SetFloat("FallDistance", fallDistance);

        //auido
    }

    // OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks
    override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        if(lastPositionY > character.transform.position.y)
        {
            fallDistance += lastPositionY - character.transform.position.y;
        }
        lastPositionY = character.transform.position.y;
        animator.SetFloat("FallDistance", fallDistance);
    }

    // OnStateExit is called when a transition ends and the state machine finishes evaluating this state
    override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        //audio
    }

    public void ResetAllParams()
    {
        lastPositionY = character.transform.position.y;
        fallDistance = 0;
    }
}


学习产出:

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

  • 11
    点赞
  • 82
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值