This question has been updated to better reflect my current issue, and some of the progress made while solving it.
I've been working on a 2D character controller that uses raycasting to detect collisions with objects. Since Physics2D.Raycast
and Physics2D.BoxCast
both return a RaycastHit2D
object, I was hoping to convert my raycast detection to boxcast detection, but I've been having trouble understanding how to do it. Unity's scripting API is a little vague on how BoxCast
actually works; specifically, I don't understand the difference in use between the size
and distance
parameters.
My current method works like this:
- I get the
bounds
of my box collider, and fire a raycast with an origin point.y ofskinWidth
inside the collider (this ensures that collisions can still be detected when the character is flat on the ground).skinWidth
is a very small number (like.015f
) used to achieve this result. - If I'm moving down, the raycast is fired from the bottom center (center.x, min.y) of my collider, otherwise it's fired from the top center.
- The length of the raycast this frame is determined by
deltaMovement.y + skinWidth
, which is a reference to my character'svelocity
, but represents the change in movement this frame; this is also a small number.skinWidth
is added back to this value to compensate for the ray beginning inside the box. - If the raycast hits the ground,
deltaMovement
is updated to be equal to the(hit.distance - skinWidth) * velocityDir
. This means our character will move anotherhit.distance - skinWidth
more before colliding with an object.skinWidth
is subtracted because I added it to the ray's length, and I multiply byvelocityDir
to maintain my direction becausehit.distance
is always a positive value, even when moving down.
Here's what this looks like in code (this works exactly as I'd like):
private void CollideVertically(ref Vector2 deltaMovement)
{
float velocityDir = Mathf.Sign(deltaMovement.y);
float raycastLength = Mathf.Abs(deltaMovement.y) + skinWidth;
Vector2 origin = (velocityDir == -1) ? bottomCenter : topCenter;
//this makes the raycast move with the character on the x axis
origin.x += deltaMovement.x
RaycastHit2D hit = Physics2D.Raycast(origin, Vector2.up * velocityDir, raycastLength, verticalCollisionMask);
if (hit)
{
deltaMovement.y = (hit.distance - skinWidth) * velocityDir;
State.IsCollidingAbove = velocityDir == 1;
State.IsCollidingBelow = velocityDir == -1;
}
}
I thought adapting this to work with boxcasts would be easy; after all, Physics2D.BoxCast
has many of the same parameters. I ran into some issues, though, but here's my code:
private void CollideVertically(ref Vector2 deltaMovement)
{
float velocityDir = Mathf.Sign(deltaMovement.y);
float raycastLength = Mathf.Abs(deltaMovement.y) + skinWidth;
Vector2 origin = (velocityDir == -1) ? bottomCenter : topCenter;
origin.x += deltaMovement.x;
Vector2 size = new Vector2(boxCollider.size.x, .02f);
RaycastHit2D hit = Physics2D.BoxCast(origin, size, 0, Vector2.up * directionY, raycastLength, verticalCollisionMask);
if (hit)
{
deltaMovement.y = hit.distance * velocityDir;
State.IsCollidingBelow = velocityDir == -1;
State.IsCollidingAbove = velocityDir == 1;
}
}
One of the issues with this is simply how my character "feels" when using this method vs my raycast method. The movement doesn't feel quite as tight. Similarly, I have to use the arbitrary value of .02f
as the box's size.y in order to make my character be flat on the ground. I've tried other values like skinWidth
, or something like .03f
, but these put my collider slightly in the ground or above it. I had initially used raycastLength
as both the box's size.y and the distance
parameter of the boxcast method, but this was inconsistent, and I think it produced "too much" of a box when casting (I was frequently colliding when my character was still airborne).
I also don't understand why I don't have to take away skinWidth
in the line deltaMovement.y = hit.distance * velocityDir
. I had been subtracting it, but this was producing a lot of bouncing; the movement wasn't right at all.
Ultimately, this related question helps explain why I want to use boxcasting. Instead of using many rays, each of which has a gap between it and the next ray, I'd like to use a boxcast to simulate a solid "sheet" of rays that can determine my collisions. Imagine a scenario in which the character is on the peak of a mountain; raycasts (with the aforementioned gaps) might miss this sharp point, but a boxcast will hit it, and that hit point will determine the character's movement.
Here's a little visualization I made about the two approaches: https://i.sstatic.net/GYZS3.jpg Am I thinking about this correctly? Should I be using Physics2D.OverlapBox
instead?