Hey all, this my first post here, so if I’m doing something wrong, please forgive me!
I’ve always been a sucker for tile-based platformers, so after taking the 2D gamedev course, I figured I’d try my hand at something a little more involved. Turns out (for me) it’s a lot more involved, and I haven’t even gotten to the good stuff!
There’s probably a better way to do this, but I don’t know what it is, so here goes:
When the user presses jump, I’m adding a jumpSpeed variable, and that all works; at the same time, I’m setting a Boolean _isActivelyJumping = true. Okay, so far, so good.
I also have a Boolean called _isActivelyFalling. This is set if the player walks off of something without jumping (a little arm-flailing animation).
[Sidenote: What I originally wanted to do was have a “graduated” jump: where if the player holds down, they’d get the “full jump,” but if they released after a light tap, for example, they’d only jump, say, half the maximum jump height. I don’t know how to do this yet.]
Anyway, I’m setting _isActivelyJumping true as soon as the user presses the jump button. In my Update method, I check if _isJumping == true as well as if the player is on the ground. What I want to happen is for the player to leave the ground before the first check, because my thought is that if the player was jumping (_isActivelyJumping == true) and he’s back on the ground, then he’s not jumping anymore and I can reset the Boolean. But because I’m setting that Boolean and checking its value on the same frame (I think that’s what’s happening), obviously it’s not working: I’m immediately seeing _isActivelyJumping reset to false.
I tried setting another Boolean via a Coroutine that was supposed to give the game a chance to advance a frame or two, but that didn’t work the way I expected, and it’s probably not the best approach anyway.
Although to be honest, I have no idea what the best approach might look like!
Here’s the code I’ve written so far just in case anyone sees anything glaring. Thank you so much for looking! Sorry for the wall of text!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerMovement : MonoBehaviour
{
const string IS_RUNNING = "isRunning";
const string IS_JUMPING = "isJumping";
const string IS_CLIMBING = "isClimbing";
const string IS_FALLING = "isFalling";
[SerializeField] float moveSpeed = 6f;
[SerializeField] float jumpSpeed = 8.8f;
[SerializeField] float climbSpeed = 4.8f;
Animator _animator;
PlayerControls playerControls;
Rigidbody2D _rb;
CapsuleCollider2D _body;
BoxCollider2D _foot;
Vector2 _moveDir;
Vector2 _jumpDir;
float _rbOriginalGravity;
bool _isMovingSideways = false;
bool _isMovingUpOrDown = false;
bool _isDying = false;
bool _isActivelyRunning = false;
bool _isActivelyJumping = false;
bool _isActivelyClimbing = false;
bool _isActivelyFalling = false;
void Awake()
{
_animator = GetComponentInChildren<Animator>();
playerControls = new PlayerControls();
playerControls.Enable();
}
void Start()
{
_rb = GetComponent<Rigidbody2D>();
_body = GetComponent<CapsuleCollider2D>();
_foot = GetComponent<BoxCollider2D>();
_rbOriginalGravity = _rb.gravityScale;
}
void Update()
{
if (!_isDying)
{
PlayerMove();
PlayerClimb();
UpdatePlayerAnimationState();
FaceProperDirection();
}
}
void PlayerMove()
{
Vector2 _playerVelocity = new Vector2(_moveDir.x * moveSpeed, _rb.velocity.y);
_rb.velocity = _playerVelocity;
_isMovingSideways = Mathf.Abs(_rb.velocity.x) > Mathf.Epsilon;
}
void PlayerClimb()
{
if (!_foot.IsTouchingLayers(LayerMask.GetMask("Climbables")))
{
_rb.gravityScale = _rbOriginalGravity;
return;
}
Vector2 _playerVelocity = new Vector2(_rb.velocity.x, _moveDir.y * climbSpeed);
_rb.velocity = _playerVelocity;
_rb.gravityScale = 0f;
_isMovingUpOrDown = Mathf.Abs(_rb.velocity.y) > Mathf.Epsilon;
}
void UpdatePlayerAnimationState()
{
// TODO: Figure out how to jump "through" ladders if you're not pressing Up/Down
if (_isActivelyJumping)
{
if (!_foot.IsTouchingLayers(LayerMask.GetMask("Platforms", "Spans", "Climbables")))
{
// We're in the air, so we're not climbing or running or falling:
_isActivelyRunning = false;
_isActivelyClimbing = false;
}
else
{
// We're on the ground, so we're not jumping anymore
// (and we're not falling, either, but are we running or climbing?):
_isActivelyJumping = false;
}
_isActivelyFalling = false;
}
else
{
if (!_foot.IsTouchingLayers(LayerMask.GetMask("Platforms", "Spans", "Climbables")))
{
// We're in the air and NOT jumping, so we're falling:
_isActivelyRunning = false;
_isActivelyClimbing = false;
_isActivelyFalling = true;
}
else
{
// We're on the ground or a ladder:
if (!_foot.IsTouchingLayers(LayerMask.GetMask("Climbables")))
{
// Not on a ladder:
_isActivelyClimbing = false;
}
else
{
// We ARE on a ladder:
_isActivelyClimbing = _isMovingUpOrDown;
}
_isActivelyRunning = _isMovingSideways;
_isActivelyJumping = false;
_isActivelyFalling = false;
}
}
_animator.SetBool(IS_RUNNING, _isActivelyRunning);
_animator.SetBool(IS_JUMPING, _isActivelyJumping);
_animator.SetBool(IS_CLIMBING, _isActivelyClimbing);
_animator.SetBool(IS_FALLING, _isActivelyFalling);
}
// *****
// Public methods attached to the Player Input component in the Editor:
public void Move(InputAction.CallbackContext ctx)
{
// Includes climbing (y coord):
_moveDir = ctx.ReadValue<Vector2>();
}
public void Jump(InputAction.CallbackContext ctx)
{
if (ctx.action.WasPressedThisFrame())
{
if (!_foot.IsTouchingLayers(LayerMask.GetMask("Platforms", "Spans", "Climbables"))) return;
_isActivelyJumping = true;
_rb.velocity += new Vector2(0f, jumpSpeed);
}
}
public void Fire(InputAction.CallbackContext ctx)
{
//
}
// End public methods
// *****
void FaceProperDirection()
{
if (_isMovingSideways) transform.localScale = new Vector2(Mathf.Sign(_rb.velocity.x), 1f);
}
}