My main character in a 2D platformer game that I am making in Unity is constantly falling through the floor whenever I move after a specific duration of time. The code seemed to work fine before and now it is not. My code for the player controller is a modified version of Tarodev’s OG player controller, and I will leave the code below. I changed the ground detection from raycast to tag so it could work with tilemaps and added knockback code for when the player is hit by an enemy. For reference, the Ground Tilemap is the basic ground tiles that you can walk on and collide with, walls and ceilings can be collided with but not jumpable on, and hazards for now are basically just ground.
using System;
using UnityEngine;
[RequireComponent(typeof(Rigidbody2D), typeof(Collider2D))]
public class PlayerController : MonoBehaviour, IPlayerController
{
[SerializeField] private ScriptableStats _stats;
private Rigidbody2D _rb;
private BoxCollider2D _col;
private FrameInput _frameInput;
private Vector2 _frameVelocity;
private bool _cachedQueryStartInColliders;
public float KBForce;
public float KBCounter;
public float KBTotalTime;
public bool KnockFromRight;
#region Interface
public Vector2 FrameInput => _frameInput.Move;
public event Action<bool, float> GroundedChanged;
public event Action Jumped;
#endregion
private float _time;
private void Awake()
{
_rb = GetComponent<Rigidbody2D>();
_col = GetComponent<BoxCollider2D>();
_cachedQueryStartInColliders = Physics2D.queriesStartInColliders;
}
private void Update()
{
_time += Time.deltaTime;
GatherInput();
}
private void GatherInput()
{
_frameInput = new FrameInput
{
JumpDown = Input.GetButtonDown("Jump") || Input.GetKeyDown(KeyCode.C),
JumpHeld = Input.GetButton("Jump") || Input.GetKey(KeyCode.C),
Move = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"))
};
if (_stats.SnapInput)
{
_frameInput.Move.x = Mathf.Abs(_frameInput.Move.x) < _stats.HorizontalDeadZoneThreshold ? 0 : Mathf.Sign(_frameInput.Move.x);
_frameInput.Move.y = Mathf.Abs(_frameInput.Move.y) < _stats.VerticalDeadZoneThreshold ? 0 : Mathf.Sign(_frameInput.Move.y);
}
if (_frameInput.JumpDown)
{
_jumpToConsume = true;
_timeJumpWasPressed = _time;
}
}
private void FixedUpdate()
{
// CheckCollisions();
if (KBCounter <= 0)
{
HandleJump();
HandleDirection();
HandleGravity();
ApplyMovement();
}
else
{
if (KnockFromRight == true)
{
_rb.velocity = new Vector2(-KBForce, KBForce);
}
if (KnockFromRight == false)
{
_rb.velocity = new Vector2(KBForce, KBForce);
}
KBCounter -= Time.deltaTime;
}
}
#region Collisions
private float _frameLeftGrounded = float.MinValue;
private bool _grounded;
private void OnCollisionEnter2D(Collision2D other)
{
if (other.gameObject.CompareTag("Ground") || other.gameObject.CompareTag("Hazard"))
{
_grounded = true;
_coyoteUsable = true;
_bufferedJumpUsable = true;
_endedJumpEarly = false;
GroundedChanged?.Invoke(true, Mathf.Abs(_frameVelocity.y));
}
}
private void OnCollisionExit2D(Collision2D other)
{
if (other.gameObject.CompareTag("Ground") || other.gameObject.CompareTag("Hazard"))
{
_grounded = false;
_frameLeftGrounded = _time;
GroundedChanged?.Invoke(false, 0);
}
}
#endregion
#region Jumping
private bool _jumpToConsume;
private bool _bufferedJumpUsable;
private bool _endedJumpEarly;
private bool _coyoteUsable;
private float _timeJumpWasPressed;
private bool HasBufferedJump => _bufferedJumpUsable && _time < _timeJumpWasPressed + _stats.JumpBuffer;
private bool CanUseCoyote => _coyoteUsable && !_grounded && _time < _frameLeftGrounded + _stats.CoyoteTime;
private void HandleJump()
{
if (!_endedJumpEarly && !_grounded && !_frameInput.JumpHeld && _rb.velocity.y > 0) _endedJumpEarly = true;
if (!_jumpToConsume && !HasBufferedJump) return;
if (_grounded || CanUseCoyote) ExecuteJump();
_jumpToConsume = false;
}
private void ExecuteJump()
{
_endedJumpEarly = false;
_timeJumpWasPressed = 0;
_bufferedJumpUsable = false;
_coyoteUsable = false;
_frameVelocity.y = _stats.JumpPower;
Jumped?.Invoke();
}
#endregion
#region Horizontal
private void HandleDirection()
{
if (_frameInput.Move.x == 0)
{
var deceleration = _grounded ? _stats.GroundDeceleration : _stats.AirDeceleration;
_frameVelocity.x = Mathf.MoveTowards(_frameVelocity.x, 0, deceleration * Time.fixedDeltaTime);
}
else
{
_frameVelocity.x = Mathf.MoveTowards(_frameVelocity.x, _frameInput.Move.x * _stats.MaxSpeed, _stats.Acceleration * Time.fixedDeltaTime);
transform.localScale = new Vector3(Input.GetAxisRaw("Horizontal"),1,1);
}
}
#endregion
#region Gravity
private void HandleGravity()
{
if (_grounded && _frameVelocity.y <= 0f)
{
_frameVelocity.y = _stats.GroundingForce;
}
else
{
var inAirGravity = _stats.FallAcceleration;
if (_endedJumpEarly && _frameVelocity.y > 0) inAirGravity *= _stats.JumpEndEarlyGravityModifier;
_frameVelocity.y = Mathf.MoveTowards(_frameVelocity.y, -_stats.MaxFallSpeed, inAirGravity * Time.fixedDeltaTime);
}
}
#endregion
private void ApplyMovement() => _rb.velocity = _frameVelocity;
#if UNITY_EDITOR
private void OnValidate()
{
if (_stats == null) Debug.LogWarning("Please assign a ScriptableStats asset to the Player Controller's Stats slot", this);
}
#endif
}
public struct FrameInput
{
public bool JumpDown;
public bool JumpHeld;
public Vector2 Move;
}
public interface IPlayerController
{
public event Action<bool, float> GroundedChanged;
public event Action Jumped;
public Vector2 FrameInput { get; }
}