Blocking movement on collision

Newbie question - this seems like it should be really easy but for some reason nothing is working right. I have a top-down Zelda style game and I need to detect when the player is attempting to move into a wall/obstacle so I can stop that direction of movement. It seems to me like the way to do this is with Physics2D.BoxCast - however any attempts to use it have proven very flaky. This is how I’m doing it now which sort of works:

Vector2 position = new Vector2(transform.position.x, transform.position.y);
Vector2 distance = new Vector2(movement.x * Time.deltaTime, movement.y * Time.deltaTime);
boxCollider.enabled = false;
RaycastHit2D hit = Physics2D.BoxCast(position, new Vector2(1, 1), 0, movement, distance.magnitude, blockingLayer);
if (hit.transform != null) {
  float xDiff = hit.transform.position.x - transform.position.x;
  float yDiff = hit.transform.position.y - transform.position.y;
  bool isHorizontal = Mathf.Abs(xDiff) > Mathf.Abs(yDiff);
  if (isHorizontal) {
    if (xDiff < 0) {
      movement.x = Mathf.Clamp(movement.x, 0, 1);
    } else {
      movement.x = Mathf.Clamp(movement.x, -1, 0);
    }
  } else {
    if (yDiff < 0) {
      movement.y = Mathf.Clamp(movement.x, 0, 1);
    } else {
      movement.y = Mathf.Clamp(movement.y, -1, 0);
    }
  }
}
boxCollider.enabled = true;

What I want is to project a Player-sized box into the future by one frame, and find out if that’s going to cause a collision. The problem is that half the time it works and half the time the player just phases right through the wall. If I replace “distance.magnitude” with a larger value, i.e. 0.2, it works more reliably but then the gap between the player and the wall is huge. BoxCast just doesn’t always trigger a hit when you expect it to - some collisions slip past it and then once you’re inside it just gives up.

Anyone have an idea of how I can fix this? Or is there a more natural way to handle this case?

Have you considered using tile maps to design your game world? If you have a map data structure it becomes very easy to check for collisions with the background. Simply calculate the new position based on the distance the object wants to move from its current position. Check that new position in your tile map. If it is a wall or other non-passable tile do not process the movement.

No, the walls are tiled but the player movement is fluid so I need partial collision detection.

Update: from this thread it sounds like BoxCast might still have some bugs: Physics2D.BoxCast bug or incorrect usage - Unity Engine - Unity Discussions

In any case I found some bugs in my own code and updated my logic to use two boxcasts, one straight vertical and one straight horizontal. This works a bit better and 0.1f seems to prevent phasing through walls, though it’s still not as close as I’d like:

private void HandleCollision(ref Vector2 movement) {
  boxCollider.enabled = false;
  Vector2 position = new Vector2(transform.position.x, transform.position.y);
  Vector2 distance = new Vector2(movement.x * Time.deltaTime, movement.y * Time.deltaTime);

  RaycastHit2D horizontalHit = Physics2D.BoxCast(
    position, new Vector2(1, 1), 0, new Vector2(movement.x, 0), 0.1f, blockingLayer);
  if (horizontalHit.transform != null) {
    float xDiff = horizontalHit.point.x - transform.position.x;
    if (xDiff < 0) {
      movement.x = Mathf.Clamp(movement.x, 0, 1);
    } else {
      movement.x = Mathf.Clamp(movement.x, -1, 0);
    }
  }

  RaycastHit2D verticalHit = Physics2D.BoxCast(
    position, new Vector2(1, 1), 0, new Vector2(0, movement.y), 0.1f, blockingLayer);
  if (verticalHit.transform != null) {
    float yDiff = verticalHit.point.y - transform.position.y;
    if (yDiff < 0) {
      movement.y = Mathf.Clamp(movement.y, 0, 1);
    } else {
      movement.y = Mathf.Clamp(movement.y, -1, 0);
    }
  }

  boxCollider.enabled = true;
}

With a player that moves “fluidly” i’m assuming you mean that its not moving tile by tile, but rather just smoothly on verticle/horizontal axis. If this is correct, why not just just 2D Colliders? Make your walls prefabs with a BoxCollider2D around the edge. Give them a rigidbody, make it Kinematic, and do the same to your player.

Is there some reason that you want/need to calculate collisions this way rather than just using the 2d collision system?

I can do collision detection i.e. OnTriggerEnter but I wasn’t sure if I could make that play ball with the movement calculations happening in FixedUpdate. Since each FixedUpdate iteration is directly modifying the velocity of the player, the only way to know what velocity to use is to know whether or not there’s going to be a collision (i.e. the y axis needs to be set to 0 against the top wall).