Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save kamend/9c01ba1a3338ab64f325 to your computer and use it in GitHub Desktop.
Save kamend/9c01ba1a3338ab64f325 to your computer and use it in GitHub Desktop.
Raycast collision detection in Unity from a box collider, with diagonal raycasts from the corners to guard against corner collisions.
using com.mindshaft.overtime.collision;
using UnityEngine;
namespace com.mindshaft.overtime.physics {
public class RaycastCollisionDetection : IEntityCollisionDetection {
private BoxCollider _collider;
private Rect _collisionRect;
private LayerMask _collisionMask;
private LayerMask _playerMask;
public GameObject ObjectCollidedWith { get; set; }
public bool OnGround { get; set; }
public bool SideCollision { get; set; }
public bool PlayerCollisionX { get; set; }
public bool PlayerCollisionY { get; set; }
public Vector3 HitNormal { get; set; }
public void Init(GameObject entityGo) {
_collisionMask = LayerMask.NameToLayer("Collisions");
_playerMask = LayerMask.NameToLayer("Player");
_collider = entityGo.GetComponent<BoxCollider>();
}
public Vector3 Move(Vector3 moveAmount, GameObject entityGo) {
float deltaX = moveAmount.x;
float deltaY = moveAmount.y;
Vector3 entityPosition = entityGo.transform.position;
// Resolve any possible collisions below and above the entity.
deltaY = YAxisCollisions(deltaY, Mathf.Sign(deltaX), entityPosition);
// Resolve any possible collisions left and right of the entity.
// Check if our deltaX value is 0 to avoid unnecessary collision detection.
if (deltaX != 0) {
deltaX = XAxisCollisions(deltaX, entityPosition);
}
if (deltaX != 0 && deltaY != 0 && !SideCollision && !OnGround) {
DiagonalCollisions(ref deltaX, ref deltaY, entityPosition);
}
Vector3 finalTransform = new Vector2(deltaX, deltaY);
return finalTransform;
}
private float XAxisCollisions(float deltaX, Vector3 entityPosition) {
SideCollision = false;
PlayerCollisionX = false;
ObjectCollidedWith = null;
// It's VERY important that the entity's collider doesn't change
// shape during the game. This will cause irregular raycast hits
// and most likely cause things to go through layers.
// Ensure sprites use a fixed collider size for all frames.
_collisionRect = GetNewCollisionRect();
// Increase this value if you want the rays to start and end
// outside of the entity's collider bounds.
float margin = 0.04f;
int numOfRays = 4;
Vector3 rayStartPoint = new Vector3(_collisionRect.center.x,
_collisionRect.yMin + margin, entityPosition.z);
Vector3 rayEndPoint = new Vector3(_collisionRect.center.x,
_collisionRect.yMax - margin, entityPosition.z);
float distance = (_collisionRect.width / 2) + Mathf.Abs(deltaX);
for (int i = 0; i < numOfRays; ++i) {
float lerpAmount = (float) i / ((float) numOfRays - 1);
Vector3 origin = Vector3.Lerp(rayStartPoint, rayEndPoint, lerpAmount);
Ray ray = new Ray(origin, new Vector2(Mathf.Sign(deltaX), 0));
Debug.DrawRay(ray.origin, ray.direction, Color.white);
RaycastHit hit;
// Bit shift the layers to tell Unity to NOT ignore them.
if (Physics.Raycast(ray, out hit, distance, 1 << _collisionMask) ||
Physics.Raycast(ray, out hit, distance, 1 << _playerMask)) {
HitNormal = hit.normal;
Debug.DrawRay(ray.origin, ray.direction, Color.yellow);
float x = Mathf.Sign(deltaX) == -1
? _collisionRect.xMin
: _collisionRect.xMax;
// Give a small amount of skin space to prevent snagging.
float skinSpace = 0.005f;
deltaX = (_collisionRect.center.x + hit.distance * ray.direction.x - x) + skinSpace;
if (hit.transform.gameObject.layer == _playerMask) {
PlayerCollisionX = true;
deltaX = 0;
ObjectCollidedWith = hit.transform.gameObject;
} else {
SideCollision = true;
}
break;
}
}
return deltaX;
}
private float YAxisCollisions(float deltaY, float dirX, Vector3 entityPosition) {
OnGround = false;
PlayerCollisionY = false;
ObjectCollidedWith = null;
// It's VERY important that the entity's collider doesn't change
// shape during the game. This will cause irregular raycast hits
// and most likely cause things to go through layers.
// Ensure sprites use a fixed collider size for all frames.
_collisionRect = GetNewCollisionRect();
// Increase this value if you want the rays to start and end
// outside of the entity's collider bounds.
float margin = 0.04f;
int numOfRays = 4;
Vector3 rayStartPoint = new Vector3(_collisionRect.xMin + margin,
_collisionRect.center.y, entityPosition.z);
Vector3 rayEndPoint = new Vector3(_collisionRect.xMax - margin,
_collisionRect.center.y, entityPosition.z);
float distance = (_collisionRect.height / 2) + Mathf.Abs(deltaY);
for (int i = 0; i < numOfRays; ++i) {
float lerpAmount = (float) i / ((float) numOfRays - 1);
// If we are facing left, start the rays on the left side,
// else start the ray rays on the right side.
// This will help ensure precise castings on the corners.
Vector3 origin = dirX == -1
? Vector3.Lerp(rayStartPoint, rayEndPoint, lerpAmount)
: Vector3.Lerp(rayEndPoint, rayStartPoint, lerpAmount);
Ray ray = new Ray(origin, new Vector2(0, Mathf.Sign(deltaY)));
//Debug.DrawRay(ray.origin, ray.direction, Color.white);
RaycastHit hit;
// Bit shift the layers to tell Unity to NOT ignore them.
if (Physics.Raycast(ray, out hit, distance, 1 << _collisionMask) ||
Physics.Raycast(ray, out hit, distance, 1 << _playerMask)) {
HitNormal = hit.normal;
//Debug.DrawRay(ray.origin, ray.direction, Color.yellow);
float y = Mathf.Sign(deltaY) == -1
? _collisionRect.yMin
: _collisionRect.yMax;
// Give a small amount of skin space to prevent snagging.
float skinSpace = 0.0005f;
deltaY = (_collisionRect.center.y + hit.distance * ray.direction.y - y) + skinSpace;
// Only flag player collision if we collide with them while traveling down.
if (hit.collider.gameObject.layer == _playerMask && Mathf.Sign(deltaY) == -1) {
PlayerCollisionY = true;
ObjectCollidedWith = hit.transform.gameObject;
}
OnGround = true;
break;
}
}
return deltaY;
}
private void DiagonalCollisions(ref float deltaX, ref float deltaY, Vector3 entityPosition) {
_collisionRect = GetNewCollisionRect();
float distance = (_collisionRect.height / 2) + Mathf.Abs(deltaX);
Ray ray = new Ray(_collisionRect.center, new Vector2(Mathf.Sign(deltaX), Mathf.Sign(deltaY)));
Debug.DrawRay(ray.origin, ray.direction, Color.white);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, distance, 1 << _collisionMask)) {
HitNormal = hit.normal;
Debug.DrawRay(ray.origin, ray.direction, Color.yellow);
// Stop deltaX and let entity drop by deltaY.
deltaX = 0;
SideCollision = true;
}
}
private Rect GetNewCollisionRect() {
return new Rect(
_collider.bounds.min.x,
_collider.bounds.min.y,
_collider.bounds.size.x,
_collider.bounds.size.y);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment