I have a game in progress with a ball under physics control that can get to moving so fast that it will sometimes fly through objects. I have increased the the number of times per second physics is calculated and it resolves this issue, but SIGNIFICANTLY increases CPU Load.
Someone had mentioned in passing that it could be possible to implement a system where the fast moving ball would use raycasting so as to avoid these âpass throughsâ. Any ideas as to how this could be done?
Hereâs the solution Iâm using. Itâs more general and more efficient than what the other threads discuss. Improvements include:
⢠works for any type of Collider
⢠tests where weâve been rather than where we might be going
⢠LayerMask for ignoring the object itself, or any other layer
⢠compares squared magnitudes which is faster, and more appropriate for FixedUpdate()Thereâs a variable called skinWidth, which allows the definition of a skin width, which is expressed as a fraction of the minimum extent. This defines how far into the obstruction the script will place the object, and should be a value between 0 and 1. A value of 1 means the object will have its origin exactly at the ray hit. A value of 0.2 or so will result in the following:
A value of 0 means the object will be placed with its origin a distance of minimumExtent away from the hit point. There is the possibility of missing the collision if you use a value of zero.
#pragma strict
var layerMask : LayerMask; //make sure we aren't in this layer
var skinWidth : float = 0.1; //probably doesn't need to be changed
private var minimumExtent : float;
private var partialExtent : float;
private var sqrMinimumExtent : float;
private var previousPosition : Vector3;
private var myRigidbody : Rigidbody;
//initialize values
function Awake() {
myRigidbody = rigidbody;
previousPosition = myRigidbody.position;
minimumExtent = Mathf.Min(Mathf.Min(collider.bounds.extents.x, collider.bounds.extents.y), collider.bounds.extents.z);
partialExtent = minimumExtent*(1.0 - skinWidth);
sqrMinimumExtent = minimumExtent*minimumExtent;
}
function FixedUpdate() {
//have we moved more than our minimum extent?
if ((previousPosition - myRigidbody.position).sqrMagnitude > sqrMinimumExtent) {
var movementThisStep : Vector3 = myRigidbody.position - previousPosition;
var movementMagnitude : float = movementThisStep.magnitude;
var hitInfo : RaycastHit;
//check for obstructions we might have missed
if (Physics.Raycast(previousPosition, movementThisStep, hitInfo, movementMagnitude, layerMask.value))
myRigidbody.position = hitInfo.point - (movementThisStep/movementMagnitude)*partialExtent;
}
previousPosition = myRigidbody.position;
}
This is a great piece of code. Iâm using it now in my program to keep a hockey puck from going through the boards at high speed.
There is just one issue - the way it works is that we check to see if we already passed through any colliders. Itâs checking the previous position in the last frame of action. And then backs the projectile up so that the physics engine can do the work in the next frame. (I think)
Itâs brilliant, but we do go through a mesh for one frame. Anyway to fix that?
I was thinking of making a GO of the puck that is just of the image and make that GO a child of the puck GO. And then dynamically correct the position of the image GO in a script so that it doesnât âlookâ like it went through the mesh for even one frame (although it would freeze for a frame).
Iâm having no success. Any thoughts how to make this work? Is there a better way to approach this problem?
Thereâs no LateFixedUpdate(), so youâll have to predict where the object will be next physics frame in order to catch it before it goes through anything. You can pretty easily change the code to cast a ray in the direction of rigidbody.velocity.
OK - thatâs what I am doing. I made another GO as a child of the projectile GO and assigned it the visual mesh. I also exposed the child GO in the script and attached the child GO in the inspector.
I cast a new raycast using velocity to see if we will hit anything in the next time step and if we do move the child GO that contains the mesh so that it âlooksâ like it does not go through the object we hit for one frame.
I also factored in the collider.bounds.extents so that the projectile (hockey puck) doesnât go halfway into the mesh that it collides with.
So two raycasts are used - one that cast back to see if we passed through anything so we can manually back up and let the physics engine do the work, and another ray that casts forward to fix the visual image.
Hereâs the additional code I added to the original code. Iâm not the most experienced programmer so forgive my slopiness. (Iâm working on an ice hockey game so thatâs why my code makes references to a puck!)
var puckImage : Transform; // puckImage is a child GO of the puck - contains the image mesh only - attach via the inspector
var layerMask : LayerMask; //make sure we aren't in this layer
var skinWidth : float = 0.0001; //probably doesn't need to be changed
private var minimumExtent : float;
private var partialExtent : float;
private var sqrMinimumExtent : float;
private var previousPosition : Vector3;
private var myRigidbody : Rigidbody;
//initialize values
function Awake()
{
myRigidbody = rigidbody;
previousPosition = myRigidbody.position;
}
function FixedUpdate()
{
minimumExtent = Mathf.Min(Mathf.Min(collider.bounds.extents.x, collider.bounds.extents.y), collider.bounds.extents.z);
partialExtent = minimumExtent*(1.0 - skinWidth);
sqrMinimumExtent = minimumExtent*minimumExtent;
puckImage.localPosition = Vector3.zero; // reset child GO
// WILL we move more than our minimum extent in the next time step?
if (myRigidbody.velocity.sqrMagnitude*Time.deltaTime > sqrMinimumExtent)
{
var movementNextStep : Vector3 = myRigidbody.velocity*Time.deltaTime;
var nextStepHitInfo : RaycastHit;
// check for obstructions we might hit
if (Physics.Raycast(myRigidbody.position, movementNextStep, nextStepHitInfo, movementNextStep.magnitude, layerMask.value))
{
puckImage.position = nextStepHitInfo.point - myRigidbody.velocity*Time.deltaTime; // need to subtract the velocity since it is a child of a rigidbody!
// let's adjust the puckImage position by the extents in the direction of travel!!!
if (myRigidbody.position.x < nextStepHitInfo.point.x)
puckImage.position.x -= collider.bounds.extents.x;
else
puckImage.position.x += collider.bounds.extents.x;
if (myRigidbody.position.y < nextStepHitInfo.point.y)
puckImage.position.y -= collider.bounds.extents.y;
else
puckImage.position.y += collider.bounds.extents.y;
if (myRigidbody.position.z < nextStepHitInfo.point.z)
puckImage.position.z -= collider.bounds.extents.z;
else
puckImage.position.z += collider.bounds.extents.z;
}
}
//have we moved more than our minimum extent?
if ((previousPosition - myRigidbody.position).sqrMagnitude > sqrMinimumExtent)
{
var movementThisStep : Vector3 = myRigidbody.position - previousPosition;
var thisStepHitInfo : RaycastHit;
//check for obstructions we might have missed
if (Physics.Raycast(previousPosition, movementThisStep, thisStepHitInfo, movementThisStep.magnitude, layerMask.value))
{
myRigidbody.position = thisStepHitInfo.point - (movementThisStep/movementThisStep.magnitude)*partialExtent;
puckImage.localPosition = Vector3.zero; // this is redundant, but for complicated collisions, may be needed
}
}
previousPosition = myRigidbody.position;
}