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
boundsof my box collider, and fire a raycast with an origin point.y ofskinWidthinside the collider (this ensures that collisions can still be detected when the character is flat on the ground).skinWidthis 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.skinWidthis added back to this value to compensate for the ray beginning inside the box. - If the raycast hits the ground,
deltaMovementis updated to be equal to the(hit.distance - skinWidth) * velocityDir. This means our character will move anotherhit.distance - skinWidthmore before colliding with an object.skinWidthis subtracted because I added it to the ray's length, and I multiply byvelocityDirto maintain my direction becausehit.distanceis 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?
