Forked from NickDiMucci/RaycastCollisionDetection.cs
Created
September 27, 2015 16:11
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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