Skip to main content

Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
TagsGame Engines

Making mechanics of Cult of the Lamb in Unity: Rolling

This is the second tutorial of the making of the Cult of the Lamb mechanics in Unity. If you want to follow along the series you need to first read Ground Zero and Part 1.

Let’s get started! First you need to add a button Action Type to your Input Actions asset. Whatever you name it it will follow the OnXxx signature on the PlayerInput class. In our case, I named it Roll and assigned the “H” key on my keyboard.

Unity_OGCLu8F8FB.png

After that, hit the “Save Asset” button on the top left of this window. Go to your PlayerInput class and add the RollEvent event and the OnRoll callback (or however you named it):

namespace Player.Input
{
    public class PlayerInput : MonoBehaviour, GameControls.IPlayerActions
    {
        ...
        public event UnityAction RollEvent = delegate { };
	...        

        public void OnRoll(InputAction.CallbackContext context)
        {
            if (context.performed)
            {
                RollEvent?.Invoke();
            }
        }
    }
}

Now head to the PlayerMove3DState class. First we need to fix an issue we have when moving diagonally, making it move faster than if we move on the axis. After that we need to add a condition for when we want to apply the roll to the player. In our case only when we are moving and the button is pressed:

namespace Player.States
{
    [CreateAssetMenu(menuName = "States/Player/Move 3D")]
    public class PlayerMove3DState : State<PlayerStateMachine>
    {
        ...

        public override void Tick(float deltaTime)
        {
	    // we need to normalize the player's input to prevent moving faster diagonally
            _playerInput = new Vector3(_runner.Movement.x, 0, _runner.Movement.y).normalized;
        }

        ...

        public override void ChangeState()
        {
            if (_runner.RollPressed)
            {
                _runner.SetState(typeof(PlayerRollState));
                return;
            }
            
            if (_playerInput == Vector3.zero)
            {
                _runner.SetState(typeof(PlayerIdleState));
            }
        }
    }
}

Alright! Now we need to modify our PlayerStateMachine class a little bit. We need to subscribe to the new RollEvent from the input and then propagate this change through our states:

namespace Player
{
    public class PlayerStateMachine : StateMachine<PlayerStateMachine>
    {
        ...
        public bool RollPressed;
        
        private void OnEnable()
        {
            _playerInput.MovementEvent += HandleMove;
            _playerInput.RollEvent += HandleRoll; // subsribe
        }

        private void OnDisable()
        {
            _playerInput.MovementEvent -= HandleMove;
            _playerInput.RollEvent -= HandleRoll; // unsubsribe
        }

        private void HandleRoll()
        {
            RollPressed = true;
        }
        
        ...
    }
}

The next step is to actually create the PlayerRollState. This one will calculate where we want to move to. Every tick we will try and move to that position for a fixed amount of time. After this time we will set our state to Idle:

using BerserkPixel.StateMachine;
using UnityEngine;

namespace Player.States
{
    [CreateAssetMenu(menuName = "States/Player/Roll")]
    public class PlayerRollState : State<PlayerStateMachine>
    {
        [SerializeField] private float _rollSpeed = 50f;
        [SerializeField] private float _rollTime = .5f;

        [Header("DEBUG")] 
        [SerializeField] private bool _debug = true;

        private Vector3 _movementDirection;
        private float _elapsedTime;

        public override void Enter(PlayerStateMachine parent)
        {
            base.Enter(parent);
            
            // grab the direction were the player is aiming in a 3D plane
            var playerInput = new Vector3(parent.Movement.normalized.x, 0, parent.Movement.normalized.y);
            
            // instantly set this to false so there's no double rolling
            parent.RollPressed = false;

            _elapsedTime = 0f;

            var startingPos = parent.transform.position;

            // calculate the desired end position
            _movementDirection = startingPos + playerInput * _rollSpeed;

            if (!_debug) return;
            
            Debug.DrawLine(
                startingPos,
                _movementDirection,
                Color.red,
                .2f
            );
        }

        public override void Tick(float deltaTime)
        {
            _elapsedTime += deltaTime;
        }

        public override void FixedTick(float fixedDeltaTime)
        {
            if (!(_elapsedTime < _rollTime)) return;

            // each fixed frame we move a fraction towards the end value
            _runner.Move(_movementDirection * (_elapsedTime / _rollTime));
        }

        public override void ChangeState()
        {
            // only change if the "cooldown" timer is reached
            if (_elapsedTime >= _rollTime)
            {
                _runner.SetState(typeof(PlayerIdleState));
            }
        }
    }
}

Now go back to the Unity Editor. Right click on your project and create a new Player Roll State and add it to the player’s state list. Finally we need to also adjust the Collision Detection on the Rigidbody to Continuous to prevent the player from going through our walls.

Unity_H4FDnraWla.png

Now press play and roll around!

Unity_wrR4Ph7T3E.gif

Support this post

Did you like this post? Tell us

Leave a comment

Log in with your itch.io account to leave a comment.