How to structure different types of 2D jump logic?
Good morning!
In early development of a 2D platformer I've met a few complications which I'm afraid only will get worse by time. I successfully implemented a jump mechanic where he jumps higher by holding the jump button in comparison to a simple tap.
Throughout the game the character should be able to get new abilities to both double jump and hover in the air for a short time.
New jump abilities might be implemented in the future.
.
Currently I got these relevant scripts on my player:
- PlayerCollisionCheck (ground check, etc)
- PlayerInput (handles all input and forwards it to the relevant script, I'd like to keep all of my user input code here)
- PlayerMovement (handles left and right movements)
- PlayerJump (jump logic)
.
I see the reason of my difficulty is my lack of knowledge of how to properly structure the different jump logic. The script is currently full of states and checks (even redundant ones) to restrict the player from unwanted movement, yet allowing flexibility.
.
I'm starting to lose track of the logic and I'd like to know how to structure it in an orderly matter.
Thanks in advance!
Here's my current code for the PlayerJump script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(Rigidbody2D))]
public class PlayerJump : MonoBehaviour
{
// Jump states
enum JumpState { jumpReady, jumping, jumpDone, hover }
JumpState jumpState;
// Jump abilities
enum JumpAbility { none, doubleJump, hover }
[SerializeField]
JumpAbility jumpAbility = JumpAbility.none;
// Jump
[SerializeField]
float jumpForce;
[SerializeField]
float jumpTimeMax;
[SerializeField]
float jumpTimeMin;
float jumpTimeCounter;
// Double Jump
bool hasDoubleJump = false;
// Hover
[SerializeField]
float hoverForce = 0.3f;
[SerializeField]
float hoverTime = 1f;
// Input
bool jumpButtonDown = false;
bool jumpButtonUp = false;
// Components
Rigidbody2D rgbd2d;
// Scripts
PlayerCollisionCheck playerCollisionCheck;
// Awake
void Awake()
{
rgbd2d = GetComponent<Rigidbody2D>();
playerCollisionCheck = GetComponent<PlayerCollisionCheck>();
}
// Start
void Start()
{
jumpTimeCounter = jumpTimeMax;
jumpState = JumpState.jumpReady;
}
// Update
void FixedUpdate()
{
// Check if the player is grounded
if (playerCollisionCheck.GroundCheck())
{
jumpState = JumpState.jumpReady;
hasDoubleJump = false;
}
// if jump button is pressed and the player is grounded
if (jumpButtonDown && jumpState == JumpState.jumpReady)
{
// Double Jump
if (hasDoubleJump)
jumpTimeCounter = jumpTimeMin;
// Normal Jump
else
jumpTimeCounter = jumpTimeMax;
// Jump
rgbd2d.velocity = Vector2.up * jumpForce;
jumpButtonDown = false;
jumpState = JumpState.jumping;
}
// if jump button is held down and the timer hasn't run out yet OR minimum jump height has not yet been achieved
if (!jumpButtonUp && (jumpState == JumpState.jumping || jumpState == JumpState.jumpDone) && jumpTimeCounter > 0)
{
// Keep jumping
rgbd2d.velocity = Vector2.up * jumpForce;
jumpTimeCounter -= Time.deltaTime;
}
// if jump button is held down AND the timer has run out AND the player has the ability to hover
else if (!jumpButtonUp && jumpState == JumpState.hover && jumpTimeCounter > 0)
{
// Hover
float yVelocity = 0f;
float newVelocity = Mathf.SmoothDamp(rgbd2d.velocity.y, hoverForce, ref yVelocity, 1);
rgbd2d.velocity = Vector2.up * Mathf.Clamp(newVelocity, 1, int.MaxValue);
jumpTimeCounter -= Time.deltaTime;
}
// if jump has finished AND button is still pressed AND the player has the ability to hover
if (jumpTimeCounter < 0 && !jumpButtonUp && jumpAbility == JumpAbility.hover && jumpState == JumpState.jumping)
{
// Initiate Hover
jumpState = JumpState.hover;
jumpTimeCounter = hoverTime;
}
// if jump button is released
if (jumpButtonUp)
{
// If stopping hover
if (jumpAbility == JumpAbility.hover && jumpState == JumpState.hover)
{
jumpTimeCounter = 0;
jumpButtonUp = false;
jumpState = JumpState.jumpDone;
}
// If minimum jump has been achieved
else if (jumpTimeMax - jumpTimeCounter > jumpTimeMin)
{
jumpTimeCounter = 0;
jumpButtonUp = false;
jumpState = JumpState.jumpDone;
}
// If minimum jump has not been achieved
else
{
jumpTimeCounter = jumpTimeMin - (jumpTimeMax - jumpTimeCounter);
jumpButtonUp = false;
jumpState = JumpState.jumpDone;
}
}
}
// When jump button is pressed
public void OnJumpInputDown()
{
// If the player is ready to jump
if (jumpState == JumpState.jumpReady && !hasDoubleJump)
{
jumpButtonDown = true;
}
// If the player CAN and is ready to double jump
else if (jumpAbility == JumpAbility.doubleJump && !hasDoubleJump && (jumpState == JumpState.jumpDone || jumpState == JumpState.jumping))
{
jumpButtonDown = true;
jumpState = JumpState.jumpReady;
hasDoubleJump = true;
}
// If the player CAN and is ready to hover
else if (jumpAbility == JumpAbility.hover && jumpState != JumpState.hover && (jumpState == JumpState.jumpDone || jumpState == JumpState.jumping))
{
jumpState = JumpState.hover;
jumpTimeCounter = hoverTime;
jumpButtonDown = false;
}
}
// When jump button is released
public void OnJumpInputUp()
{
// If the player is jumping
if (jumpState == JumpState.jumping || jumpState == JumpState.hover)
{
jumpButtonUp = true;
}
}
}
Answer by SmackDan · Feb 16, 2018 at 08:23 PM
I generally don't like bumping threads, I'll give it a one time go to see if some new eyes has an answer to point me in the right direction. :)