# Need help rewriting buggy 2D player movement and floor/slope detection script

Hello,

First of all, this thread is a bit long, sorry - I’ve trimmed out what detail I can, but still ended up with a lot, so please let me know if I need to shorten anything else. I’m trying to make a simple 2D platformer, and I’m currently trying to rework the player physics to work in a specific way. What I’m trying to do feels like it should be simple enough, but the only way that I’ve been able to figure it out is through an overcomplicated system of raycasts and arrays that only half-works, anyway. The code is slowly becoming a complete mess as I try to figure it out, so I’m just coming here to ask for help figuring out a better way to do this.

To sum it up, I’m making a movement system that shouldn’t have acceleration but should also take external forces like surface effectors into account, so I’m altering the velocity directly by updating the velocity with MathF.MoveTowards, instead of using Rigidbody2D.AddForce (because AddForce seems to inherently use acceleration), at least until I can figure out how to get external force values and combine them with the velocity from the movement script. I also want the player to stick to platform surfaces within a certain angle range, so that they can smoothly walk up and down slopes, and go over sharp hills in platforms without flying over the peaks. That part is unfinished because there’s currently no limit to the angle that the player can walk up, but I’ve been adapting this tutorial to my script to my needs for the latter need, which is probably part of my problem:

With my current code, this mostly works, except that (aside from how unwieldy the code is becoming) the player object seems to have a 95% chance to fly over sharp corners in the terrain instead of going down at the new slope angle (and a 5% chance that it works as intended), and, if there are any sectors overlapping, there’s currently no way for it to detect if the surface that it’s detecting is the one you’ll actually be walking on, so it tends to grab the angle of one of the sides of a platform and launch the player in the air. I’ve been trying to figure this out for the last couple of days, and I’m ready to just give in and ask for help, because I’m probably going way off-course and most of it should just be rewritten. I also need to figure out how to redo the velocity alteration to take external forces into account, but that can probably come after this is fixed.

Any help or suggestions will be hugely appreciated. I not too experienced in programming, yet, and this project is mostly so that I can have the experience of actually finishing a simple game.

Here are the relevant parts of the player object script (it’s a little long, sorry):

``````private void FixedUpdate()
{
SlopeCheck();
}

private void Update()
{
playerMovement.UpdateMovementValue(rawInputMovement, slopeNormalPerpendicular);
playerAnim.UpdateMovementAnimation(rawInputMovement.x / playerMovement.fMovementSpeed, playerRigidBody.velocity.y, BIsGrounded()); //Pass the horizontal movement vector to the animation controller.
}

/// <summary>
/// Stores the input vector as a Vector2 to move the player directly in FixedUpdate, and animates the player object accordingly.
/// </summary>
/// <param name="value">The generic InputValue of the input that called OnMovement</param>
public void OnMovement(InputValue value)
{
Vector2 inputMovement = value.Get<Vector2>();
rawInputMovement = new Vector2(inputMovement.x, 0); //Player can only move left/right, so Y axis is 0.

//rawInputMovement is passed to Update() and then to the animation and movement scripts
}

/// <summary>
/// Gets and stores a RaycastHit2D from a CircleCast, taking the groundLayer object directly below the player.
/// If there are overlapping objects, it also stores the new one, so that the player starts moving along its angle.
/// </summary>
/// <returns>A RaycastHit2D comprising the latest groundLayer object that the player has walked on.</returns>
RaycastHit2D PlatformBelowPlayer()
{
float scale = Mathf.Abs(transform.localScale.x) / 2;
Vector2 checkPosition = transform.position - new Vector3(0.0f, fColliderVerticalMidpoint - playerCollider.size.x * scale / 2);
float distance = 0.4f;

RaycastHit2D[] circleCasts = Physics2D.CircleCastAll(checkPosition, playerCollider.size.x * scale, Vector2.down, distance, groundLayer);

for (int i = 0; i < circleCasts.Length; i++)
{
if (circleCasts[i].collider != storedPlatformCast.collider)
{
storedPlatformCast = circleCasts[i];
return circleCasts[i];
}
}

return Physics2D.CircleCast(checkPosition, playerCollider.size.x * scale, Vector2.down, distance, groundLayer);
}

/// <summary>
/// Uses a circle cast to determine if there is ground directly below the player.
/// </summary>
/// <returns>True if there is a platform collider 0.5 units below the player. False otherwise.</returns>
private bool BIsGrounded()
{
RaycastHit2D hit = PlatformBelowPlayer();
if (PlatformBelowPlayer())
{
Debug.DrawRay(hit.point, hit.normal / 4, Color.magenta);
}
return hit.collider != null;
}

/// <summary>
/// Checks collider contact angles to determine if the player is touching a platform.
/// </summary>
/// <param name="contact">The collider contact to check. Updated when the player collider makes contact with a platform collider.</param>
/// <returns>True if the collider contact's angle is below maxIncline, false otherwise.</returns>
private bool BIsGrounded(ContactPoint2D contact)
{
if (contact.collider != null && contact.collider.gameObject.layer == Mathf.Log(groundLayer, 2))
{
return contact.normal.y >= Mathf.Sin(fMaxIncline * Mathf.Deg2Rad);
}
return false;
}

/// <summary>
/// Checks angles of colliders both to the sides of the player object and under the player object to determine if the player is walking on a slope.
/// </summary>
private void SlopeCheck()
{
Vector2 checkPosition = transform.position - new Vector3(0.0f, fColliderVerticalMidpoint);

SlopeCheckHorizontal(checkPosition);
SlopeCheckVertical(checkPosition);
}

/// <summary>
/// Sends raycasts to both sides of the player, activates sloped movement if they hit a collider with an appropriate angle, and deactivates it if they do not.
/// </summary>
/// <param name="PositionToCheck"></param>
private void SlopeCheckHorizontal(Vector2 PositionToCheck)
{
RaycastHit2D slopeHitFront = Physics2D.Raycast(PositionToCheck, transform.right, fSlopeCheckDistance, groundLayer);
RaycastHit2D slopeHitBack = Physics2D.Raycast(PositionToCheck, -transform.right, fSlopeCheckDistance, groundLayer);

if (slopeHitFront)
{
Debug.DrawRay(PositionToCheck, transform.right, Color.green);
playerMovement.bSlopedMovement = true;
fSlopeSideAngle = Vector2.Angle(slopeHitFront.normal, Vector2.up);
}

else if (slopeHitBack)
{
Debug.DrawRay(PositionToCheck, -transform.right, Color.green);
playerMovement.bSlopedMovement = true;
fSlopeSideAngle = Vector2.Angle(slopeHitBack.normal, Vector2.up);
}

else
{
playerMovement.bSlopedMovement = false;
fSlopeSideAngle = 0.0f;
}
}

/// <summary>
/// Sends a circle cast below the player to check the angle of the floor beneath the player, and activates sloped movement if there is one with an appropriate angle.
/// </summary>
/// <param name="PositionToCheck"></param>
private void SlopeCheckVertical(Vector2 PositionToCheck)
{
float scale = Mathf.Abs(transform.localScale.x);
RaycastHit2D hit = PlatformBelowPlayer();

if (hit)
{
slopeNormalPerpendicular = Vector2.Perpendicular(hit.normal).normalized;
fSlopeDownAngle = Vector2.Angle(hit.normal, Vector2.up);

Debug.DrawRay(hit.point, hit.normal, Color.green);
Debug.DrawRay(hit.point, Vector2.Perpendicular(hit.normal), Color.red);

if (fSlopeDownAngle != fSlopeDownAngleStored)
{
playerMovement.bSlopedMovement = true;
}
fSlopeDownAngleStored = fSlopeDownAngle;
}
}

/// <summary>
/// Records the contact point from the contacted collider and uses it with BIsGrounded to check if the player is making contact with a walkable platform.
/// </summary>
/// <param name="collision">The collider with which the player has made contact.</param>
private void OnCollisionEnter2D(Collision2D collision)
{
int contactCount = collision.GetContacts(contacts);
Debug.Log("Contact made. collision=" + collision.collider.name + ", layer=" + collision.gameObject.layer + ", grounded=" + BIsGrounded(contacts[0]));
if (collision.gameObject.layer == Mathf.Log(groundLayer, 2) && BIsGrounded(contacts[0]) && !bCanJump)
{
Debug.Log("Hitting ground");
playerMovement.fDeceleration = collision.gameObject.GetComponent<Collider2D>().friction;
HitGround();
}
}

/// <summary>
/// Called by floor detection methods when the player hits a Platform-tagged collider.
/// </summary>
public void HitGround()
{
//Other stuff related to hitting the ground.
playerMovement.bGrounded = true;
bFallCheck = true;
}
``````

and here’s the player movement behaviour script:

``````/// <summary>
/// Update the movement and angle values, which are multiplied by fMovementSpeed to get the player x velocity. Called by Update in PlayerScript.
/// </summary>
/// <param name="NewMovementValue">The updated horizontal movement axis value.</param>
/// <param name="NewSlopeNormalPerpendicular">The updated angle of the platform that the player is standing on.</param>
public void UpdateMovementValue(Vector2 NewMovementValue, Vector2 NewSlopeNormalPerpendicular)
{
movementValue = NewMovementValue;
slopeNormalPerpendicular = NewSlopeNormalPerpendicular;
}

private void FixedUpdate()
{
MovePlayer();
}

/// <summary>
/// Called once every FixedUpdate step to update the player object's velocity.
/// </summary>
public void MovePlayer()
{
float moveX; //moveX will be used to store and update the player's x velocity on the current update.

//If the player is moving, move the velocity towards the
//player's top speed, raising it by the acceleration value
//on each update.
//Eventually, this should also take external forces into
//account.
if (movementValue.x != 0.0f)
{
moveX = Mathf.MoveTowards(playerRigidBody.velocity.x, movementValue.x * fMovementSpeed, fAcceleration);
}

//If the player is not moving, move the velocity towards 0,
//lowering it by the deceleration value on each update.
//Deceleration rate is set to the material friction of any
//platform when the player hits it.
else
{
moveX = Mathf.MoveTowards(playerRigidBody.velocity.x, 0, fDeceleration);
}

//If the player is on unsloped ground and moving, alter the
//player velocity by applying moveX to x alone.
//Only doing this if the player is moving ensures that
//low-friction surfaces don't distribute the velocity and
//slow the player down when they should be sliding.
if (bGrounded && !bSlopedMovement)
{
if (movementValue.x == 0.0f && fDeceleration < fAcceleration)
{
updatedVelocity.Set(playerRigidBody.velocity.x, playerRigidBody.velocity.y);
}
else
{
updatedVelocity.Set(playerRigidBody.velocity.x, 0.0f);
}
}

//If the player is on sloped ground and moving, alter the
//player velocity by distributing moveX between x and y,
//by multiplying the velocity by the slope's angle vector.
if (bGrounded && bSlopedMovement)
{
if (movementValue.x == 0.0f && fDeceleration < fAcceleration)
{
updatedVelocity.Set(playerRigidBody.velocity.x, playerRigidBody.velocity.y);
}
else
{
updatedVelocity.Set(moveX * -slopeNormalPerpendicular.x, moveX * -slopeNormalPerpendicular.y);
}
}

//For any other situation, alter their x velocity by moveX
//and let physics handle the y velocity.
else
{
playerRigidBody.velocity = new Vector2(moveX, playerRigidBody.velocity.y);
}
playerRigidBody.velocity = updatedVelocity;
}
``````

Sometimes most easy solution - move the varibles initialisation through inspector.

Thank you for responding, but I’m not entirely sure what you mean, sorry. As far as I know, initialising the variables works fine, and moving them to the inspector won’t change how they work. It seems that the way that I’m processing these variables is the problem, but I can’t figure out how to even begin fixing the mess that I made of that.

Edit: The more I look at this, the more I think that this tutorial is just overly specific and won’t work for what I need. The actual slope handling seems to rely heavily on the Rigidbody’s gravity scale, which isn’t mentioned in the tutorial but I discovered by slowly copying over parts of the tutorial’s system until it worked, and the point where it worked is the point where I copied over the gravity scale, which is far too heavy for my needs. I need to redo this completely.