Edit: This answer might also be helpful to those who come across this question.
I've been messing around with raycast collisions and slopes, but am having an issue when trying to get my collider to stay grounded and rotate to the angle of a slope. Everything works when I cast a ray from the exact center of my collider, but things mess up when I try to cast rays from another point along the horizontal center of the collider.
For example, let's say my character's position is (0,0), and the "center" ray has an origin at (0,0). This gives me the behavior I want, but if I try to cast a ray from (-0.5, 0) my character is no longer flush with the ground even though the center.y point is the same. Oddly enough, this discrepancy only manifests when the character is on a slope; if it's on a flat surface the behavior of both origin points is the same.
Here's the correct behavior using the center ray:
Here's a similar ray which is supposedly being cast from the center.y (the origin is Vector2(bounds.max.x, bounds.center.y)
of my collider), but with incorrect behavior:
...but that same ray works fine on a flat surface:
Even though Unity's print()
function tells me the origin point.y of both rays are the same, I suspect that this must not be true based on how the ray is being drawn by Debug.DrawRay()
, and from simply testing things in the game view. My script is fairly simple:
using UnityEngine;
public class RayRotationTest : MonoBehaviour
{
//Movement values to tweak in the inspector
public float gravity = -20;
public float speed = 5, jump = 5;
public LayerMask collisionMask;
private BoxCollider2D boxCollider;
private Vector2 velocity;
private bool below;
//The different origin points of the ray
private Vector2 center, centerRight, centerLeft;
private void Awake()
{
boxCollider = GetComponent<BoxCollider2D>();
}
private void LateUpdate()
{
velocity.y += gravity * Time.deltaTime;
MoveCharacter(velocity * Time.deltaTime);
}
//Get the bounds of the box collider, and set the ray origin points accordingly
private void UpdateRaycastOrigins()
{
Bounds bounds = boxCollider.bounds;
center = new Vector2(bounds.center.x, bounds.center.y);
centerLeft = new Vector2(bounds.min.x, bounds.center.y);
centerRight = new Vector2(bounds.max.x, bounds.center.y);
}
private void MoveCharacter(Vector2 velocity)
{
UpdateRaycastOrigins();
bool wasGrounded = below;
below = false; //Reset state
CollideVertically(ref velocity, wasGrounded);
transform.Translate(velocity);
}
private void CollideVertically(ref Vector2 deltaMovement, bool wasGrounded)
{
float directionY = Mathf.Sign(deltaMovement.y);
float rayLength = (wasGrounded) ? 5 : Mathf.Abs(deltaMovement.y) + boxCollider.size.y * .5f;
Vector2 rayOrigin = (Mathf.Sign(deltaMovement.x) == -1) ? centerLeft : centerRight;
rayOrigin.x += deltaMovement.x;
//Unity says the y coordinates of both of these points are the same
print(center);
print(centerRight);
RaycastHit2D hit = Physics2D.Raycast(rayOrigin, transform.up * directionY, rayLength, collisionMask);
Debug.DrawRay(rayOrigin, transform.up * directionY * rayLength, Color.red);
if (hit)
{
float collisionAngle = Vector2.Angle(hit.normal, Vector2.up);
float currentRotation = transform.eulerAngles.z;
transform.rotation = Quaternion.Euler(new Vector3(0, 0, collisionAngle));
deltaMovement.y = (hit.distance - boxCollider.size.y * .5f) * directionY;
velocity.y = 0; //This stops the character from accumulating gravity when grounded
below = true;
}
}
}
Essentially, I'm casting a ray from the center of the collider down to the ground, making sure its direction matches the direction of the y axis when the collider is rotated; this is done by setting the ray direction to transform.up
. If it hits something I set my change in movement this frame, deltaMovement.y
, to be the distance away from the origin point of the ray, subtracting half of my box collider's size to make sure I'm flat on the ground. I don't understand why the formula deltaMovement.y = (hit.distance - boxCollider.size.y * .5f) * directionY
works in all cases except for the case shown in the last image.
Surely I must be missing something having to do with the ray's origin point. I think I really just need to know how to change deltaMovement.y = (hit.distance - boxCollider.size.y *.5f)
to fix this problem, but I'm not sure what to do.