Search Unity

Raycasting For A Fast-Moving Ball

Discussion in 'Scripting' started by Kripto, Aug 25, 2007.

  1. Kripto

    Kripto

    Joined:
    Feb 12, 2007
    Posts:
    119
    Greetings All,:D

    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?

    Any help would be greatly appreciated.

    Thanks!
     
  2. drJones

    drJones

    Joined:
    Oct 19, 2005
    Posts:
    1,351
  3. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    Also here.

    --Eric
     
  4. Daniel_Brauer

    Daniel_Brauer

    Unity Technologies

    Joined:
    Aug 11, 2006
    Posts:
    3,355
    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.
    Code (csharp):
    1. #pragma strict
    2.  
    3. var layerMask : LayerMask; //make sure we aren't in this layer
    4. var skinWidth : float = 0.1; //probably doesn't need to be changed
    5. private var minimumExtent : float;
    6. private var partialExtent : float;
    7. private var sqrMinimumExtent : float;
    8. private var previousPosition : Vector3;
    9. private var myRigidbody : Rigidbody;
    10. //initialize values
    11. function Awake() {
    12.     myRigidbody = rigidbody;
    13.     previousPosition = myRigidbody.position;
    14.     minimumExtent = Mathf.Min(Mathf.Min(collider.bounds.extents.x, collider.bounds.extents.y), collider.bounds.extents.z);
    15.     partialExtent = minimumExtent*(1.0 - skinWidth);
    16.     sqrMinimumExtent = minimumExtent*minimumExtent;
    17. }
    18.  
    19. function FixedUpdate() {
    20.     //have we moved more than our minimum extent?
    21.     if ((previousPosition - myRigidbody.position).sqrMagnitude > sqrMinimumExtent) {
    22.         var movementThisStep : Vector3 = myRigidbody.position - previousPosition;
    23.         var movementMagnitude : float = movementThisStep.magnitude;
    24.         var hitInfo : RaycastHit;
    25.         //check for obstructions we might have missed
    26.         if (Physics.Raycast(previousPosition, movementThisStep, hitInfo, movementMagnitude, layerMask.value))
    27.             myRigidbody.position = hitInfo.point - (movementThisStep/movementMagnitude)*partialExtent;
    28.     }
    29.     previousPosition = myRigidbody.position;
    30. }
     
  5. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    Cool; that's a good improvement!

    --Eric
     
  6. Daniel_Brauer

    Daniel_Brauer

    Unity Technologies

    Joined:
    Aug 11, 2006
    Posts:
    3,355
    If you find your object is still missing a lot of collisions, make skinWidth closer to 1.0.
     
  7. Kripto

    Kripto

    Joined:
    Feb 12, 2007
    Posts:
    119
    Thanks so much for the help everybody. I'm trying to get the last script working on my object but keep getting the error-

    'bounds' is not a member of 'System.Object'.

    While the debugger points to the line-

    minimumExtent = Mathf.Min(Mathf.Min(collider.bounds.extents.x, collider.bounds.extents.y), collider.bounds.extents.z);


    Any idea what is going on? My ball has a sphere collider and rigidbody attached.

    I'm sure it's something simple I'm missing. Thanks again for all your help.
     
  8. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    Simplest thing is just to take "#pragma strict" off, and then it'll compile.

    --Eric
     
  9. Kripto

    Kripto

    Joined:
    Feb 12, 2007
    Posts:
    119
    That did the trick! I'm still getting some weirdness, but this collision method may well save a game I had previously abandoned.

    Thanks again to everyone who helped. :D
     
  10. MitchStan

    MitchStan

    Joined:
    Feb 26, 2007
    Posts:
    568
    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?

    Thanks for any help.
     
  11. Daniel_Brauer

    Daniel_Brauer

    Unity Technologies

    Joined:
    Aug 11, 2006
    Posts:
    3,355
    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.
     
  12. MitchStan

    MitchStan

    Joined:
    Feb 26, 2007
    Posts:
    568
    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!)

    Code (csharp):
    1.  
    2. var puckImage : Transform; // puckImage is a child GO of the puck - contains the image mesh only - attach via the inspector
    3.  
    4. var layerMask : LayerMask; //make sure we aren't in this layer
    5. var skinWidth : float = 0.0001; //probably doesn't need to be changed
    6.  
    7. private var minimumExtent : float;
    8. private var partialExtent : float;
    9. private var sqrMinimumExtent : float;
    10. private var previousPosition : Vector3;
    11. private var myRigidbody : Rigidbody;
    12.  
    13. //initialize values
    14. function Awake()
    15. {
    16.     myRigidbody = rigidbody;
    17.     previousPosition = myRigidbody.position;
    18. }
    19.  
    20. function FixedUpdate()
    21. {
    22.     minimumExtent = Mathf.Min(Mathf.Min(collider.bounds.extents.x, collider.bounds.extents.y), collider.bounds.extents.z);
    23.     partialExtent = minimumExtent*(1.0 - skinWidth);
    24.     sqrMinimumExtent = minimumExtent*minimumExtent;
    25.     puckImage.localPosition = Vector3.zero; // reset child GO
    26.  
    27.     // WILL we move more than our minimum extent in the next time step?
    28.     if (myRigidbody.velocity.sqrMagnitude*Time.deltaTime > sqrMinimumExtent)
    29.     {
    30.         var movementNextStep : Vector3 = myRigidbody.velocity*Time.deltaTime;
    31.         var nextStepHitInfo : RaycastHit;
    32.         // check for obstructions we might hit
    33.         if (Physics.Raycast(myRigidbody.position, movementNextStep, nextStepHitInfo, movementNextStep.magnitude, layerMask.value))
    34.         {
    35.             puckImage.position = nextStepHitInfo.point - myRigidbody.velocity*Time.deltaTime; // need to subtract the velocity since it is a child of a rigidbody!
    36.            
    37.             // let's adjust the puckImage position by the extents in the direction of travel!!!
    38.             if (myRigidbody.position.x < nextStepHitInfo.point.x)
    39.                 puckImage.position.x -= collider.bounds.extents.x;
    40.             else
    41.                 puckImage.position.x += collider.bounds.extents.x;
    42.  
    43.             if (myRigidbody.position.y < nextStepHitInfo.point.y)
    44.                 puckImage.position.y -= collider.bounds.extents.y;
    45.             else
    46.                 puckImage.position.y += collider.bounds.extents.y;
    47.                
    48.             if (myRigidbody.position.z < nextStepHitInfo.point.z)
    49.                 puckImage.position.z -= collider.bounds.extents.z;
    50.             else
    51.                 puckImage.position.z += collider.bounds.extents.z; 
    52.         }                      
    53.     }
    54.    
    55.     //have we moved more than our minimum extent?
    56.     if ((previousPosition - myRigidbody.position).sqrMagnitude > sqrMinimumExtent)
    57.     {
    58.         var movementThisStep : Vector3 = myRigidbody.position - previousPosition;
    59.         var thisStepHitInfo : RaycastHit;
    60.         //check for obstructions we might have missed
    61.         if (Physics.Raycast(previousPosition, movementThisStep, thisStepHitInfo, movementThisStep.magnitude, layerMask.value))
    62.         {
    63.             myRigidbody.position = thisStepHitInfo.point - (movementThisStep/movementThisStep.magnitude)*partialExtent;
    64.             puckImage.localPosition = Vector3.zero; // this is redundant, but for complicated collisions, may be needed
    65.         }
    66.    }
    67.    previousPosition = myRigidbody.position;
    68. }
    69.  
     
  13. benblo

    benblo

    Joined:
    Aug 14, 2007
    Posts:
    476
    Just a tip: if you want to keep your #pragma strict, you can probably do something like that:
    Code (csharp):
    1. var sphereCollider : SphereCollider = collider;
    2. minimumExtent = Mathf.Min(Mathf.Min(sphereCollider.bounds.extents.x, sphereCollider.bounds.extents.y), sphereCollider.bounds.extents.z);
    This way the compiler should find bounds without problem.