Size and Position of BoxCollider

I’m trying to code a basic collision system that detects when an actor hits a platform and stops the actor on the platform—basic 2D platformer stuff. I’m using Orthello2D. The way it should work is as follows:

2 RayCasts are fired out from the left and right sides of the actor’s collider, both from the bottom of the actor’s collider (this is very important). The length of the RayCast is equal to the absolute value of the actor’s current y velocity (since they’re moving down, it would be negative). If a collision is detected, the actor is moved to the point of impact plus half of the collider’s size (taking into account that the collider’s size is relative to the actor) and sets various variables (like airborne and velocity) to cease falling.

The problem I’ve been seeing during debugging is that the RayCasts are not firing from the bottom of the collider, but rather from slightly above said bottom, and I think that’s messing up all the hit detection/translation that needs to happen. This code works perfectly if I use hard numbers instead of calculating based on size (but I’m using size so I can use the same code for all actors regardless of sprite size—less error prone), so I think I’m missing something about how size and position of colliders is determined. Could someone take a look at my code and tell me what I’m not getting?

int platformMask = 1 << 8;
RaycastHit hit;

// colliderCenter should be the collider's position in the scene
// myCollider is the collider attached to the actor using GetComponent<BoxCollider>();
Vector3 colliderCenter = thisTransform.position + myCollider.center;
// colliderSize should be the collider's size, factoring in scale of the actor
Vector3 colliderSize = Vector3.Scale(thisTransform.localScale, myCollider.size);

// colliderBottom here is the problem; it's too high!
// How do I get the actual bottom of the collider (y value)?
float colliderBottom = colliderCenter.y - (colliderSize.y / 2);
float colliderLeft = colliderCenter.x - (colliderSize.x / 2);
float colliderRight = colliderCenter.x + (colliderSize.x / 2);
float rayLength = Mathf.Abs(velocity.y * Time.deltaTime) > 0 ? Mathf.Abs(velocity.y * Time.deltaTime) : 0.01f;

if (Physics.Raycast(new Vector3(colliderLeft, colliderBottom, colliderCenter.z), Vector3.down, out hit, rayLength, platformMask)
    || Physics.Raycast(new Vector3(colliderRight, colliderBottom, colliderCenter.z), Vector3.down, out hit, rayLength, platformMask))
{
    // Momentum is 1 if velocity is positive, -1 if negative, and 0 if 0
    // In this case, we're checking the the actor is falling
    if (Momentum.y < 0) {
        // Move the actor to its box collider height above the hit point
        thisTransform.position = new Vector3(thisTransform.position.x, hit.point.y + (colliderSize.y / 2) - myCollider.center.y, 0f);

        // Ground the actor (for acceleration and animation)
        airborne = isJumping = false;
        
    }
} else {
    // Set the actor as airborne (for acceleration and animation)
    airborne = true;
}

I believe I solved the problem, but feel free to weigh in, this is by no means the best solution. Here’s the adjusted code and what I discovered.

int platformMask = 1 << 8;
RaycastHit hit;

Vector3 colliderCenter = Vector3.Scale(thisTransform.localScale, myCollider.center);
Vector3 colliderPosition = thisTransform.position + colliderCenter;
Vector3 colliderSize = Vector3.Scale(thisTransform.localScale, myCollider.size);

float colliderBottom = colliderPosition.y - (colliderSize.y / 2);
float colliderLeft = colliderPosition.x - (colliderSize.x / 2);
float colliderRight = colliderPosition.x + (colliderSize.x / 2);
// rayLength needs to be > 0;
float rayLength = Mathf.Abs(velocity.y * Time.deltaTime) > 0 ? Mathf.Abs(velocity.y * Time.deltaTime) : 0.1f;

// SPECIAL NOTE
if (Physics.Raycast(new Vector3(colliderLeft, colliderBottom + 0.1f, colliderCenter.z), Vector3.down, out hit, rayLength + 0.1f, platformMask)
    || Physics.Raycast(new Vector3(colliderRight, colliderBottom + 0.1f, colliderCenter.z), Vector3.down, out hit, rayLength + 0.1f, platformMask))
{
    if (Momentum.y < 0) {
        thisTransform.position = new Vector3(thisTransform.position.x, hit.point.y + (colliderSize.y / 2) - (colliderCenter.y), 0f);

        airborne = isJumping = false;
    }
} else {
    airborne = true;
}

Problem one is that the center of the collider needs to be scaled to the actor’s size as well (hence the addition of a colliderPosition variable as well as a modified colliderCenter. Problem two is that a collision hit occurs at the edge of a box, not inside, and so when the collider is literally smack against the side, a RayCast of any length will not produce a hit. The if clause under SPECIAL NOTE in the code has been adjusted to cast the ray from slightly above the bottom of the collider, for a length equally longer than the y velocity.

Hope this helps others!

Lines 4 & 5 don’t take rotation of the parent object into account, which will give an incorrect world position if it’s rotated. I did it like this:

Vector3 colliderPos = gameObject.transform.TransformPoint(collider.center);
Vector3 screenPos = camera.WorldToScreenPoint (colliderPos);