4
\$\begingroup\$

I'm making a pool game, and would like the circle (imaginary) to be in contact with the table and balls, and to fit perfectly as a ball would.

Now I thought I would use Rigidbody and lerp the position of the circle to the raycast hit, and its collider it would force it into a fitting position.

The problem I encountered is that since it has a collider, it also pushes the balls, which I do not want.

So my question is, how do I make an object act like it's touching & blocked by other GameObjects with Colliders and Rigidbodies, without pushing them?

enter image description here

\$\endgroup\$
6
  • \$\begingroup\$ Could we rephrase your question as "How can I position a circle so it sits in contact with a group of Rigidbodies, but without pushing them"? I don't know of a way to make a Rigidbody that reacts in only a one-sided way, but I could show you the math for finding the closest non-overlapping position to place the circle. \$\endgroup\$
    – DMGregory
    Commented Jan 29, 2017 at 20:41
  • \$\begingroup\$ That would be perfect \$\endgroup\$
    – John Smith
    Commented Jan 29, 2017 at 20:42
  • \$\begingroup\$ Can you clarify whether you're using 3D physics (with SphereColliders) or 2D physics (with CircleCollider2Ds)? \$\endgroup\$
    – DMGregory
    Commented Jan 29, 2017 at 20:53
  • \$\begingroup\$ I'm using 3D physics, there is currently a sphere collider on the circle (transparent) \$\endgroup\$
    – John Smith
    Commented Jan 29, 2017 at 20:57
  • \$\begingroup\$ You could make all the other objects Kinematic for the period in which you want the balls to remain static. \$\endgroup\$
    – jgallant
    Commented Jan 30, 2017 at 11:33

2 Answers 2

7
\$\begingroup\$

Here's a way to do this using Physics.SphereCast to test against the bodies' colliders without actually exerting any forces on them.

enter image description here

It assumes that your cursor object is placed on exactly the same y plane as the centers of the balls, so that all the collisions are in-plane against their widest circumference, and the camera is looking straight down at the playfield. (This can be adjusted for a camera at an oblique angle, I just haven't done that bit)

It also currently only works for colliders at rest - it tests that the cursor doesn't move into a ball, but not if a ball moves into the cursor. That could be added if needed.

public class ContactCursor : MonoBehaviour {

    // Limits our maximum travel to keep the complexity down.
    public float maxSpeed = 15f;

    public float radius = 0.5f;

    // Remember to set this to include the layer your balls are on.
    public LayerMask collisionLayers; 

    Camera _camera;
    float _cameraPlane;

    // Cache camera and our distance from it, for positioning relative to cursor.
    void Start () {
        _camera = Camera.main;
        _cameraPlane = _camera.WorldToScreenPoint(transform.position).z;
    }

    void LateUpdate () {
        // Transform the mouse into a position on our travel plane.
        Vector3 mousePosition = Input.mousePosition;
        mousePosition.z = _cameraPlane;
        mousePosition = _camera.ScreenToWorldPoint(mousePosition);

        Vector3 offset = mousePosition - transform.position;
        Vector3 direction = offset.normalized;
        float maxDistance = Mathf.Min(offset.magnitude, maxSpeed * Time.deltaTime);

        Vector3 targetPosition = transform.position + direction * maxDistance;

        // Currently set to do 2 passes:
        // 1: Beeline toward the mouse until you hit something.
        // 2: Use any remaining movement to move perpendicular to the obstacle.
        // In my tests this was enough, but you can increase this
        // if you have more complex arrangements or a faster cursor.
        int limit = 2;
        for (int i = 0; i < limit; i++) {

            // Check for a collision in the direction we're trying to move.
            RaycastHit hit;    
            if (Physics.SphereCast(transform.position, radius, direction, out hit, maxDistance, collisionLayers)) {

                // Back up to closest non-intersecting point.
                // (Plus a small fudge factor for stability).
                offset = hit.point + radius * hit.normal - transform.position;                
                float distance = Vector3.Dot(offset, direction) - 0.001f;
                targetPosition = transform.position + direction * distance;

                transform.position = targetPosition;

                if (i + 1 == limit)
                    return;

                // Determine a new move to approach the cursor without
                // penetrating deeper into whatever we hit.
                maxDistance = Mathf.Max(maxDistance - distance, 0f);

                offset = mousePosition - transform.position;    

                // Make sure we don't overshoot the closest point on this line
                // to the mouse (prevents vibrating when close-but-mot-quite).
                distance = offset.sqrMagnitude;
                float forbidden = Vector3.Dot(offset, hit.normal);
                distance -= forbidden * forbidden;
                // Shouldn't happen with infinite-precision real numbers, 
                // but rounding errors happen in practice.
                if (distance < 0f) 
                    return;    
                maxDistance = Mathf.Min(maxDistance, Mathf.Sqrt(distance));                            

                // Calculate new movement direction.
                offset -= forbidden * hit.normal;
                offset.y = 0f;
                direction = offset.normalized;

                targetPosition = transform.position + direction * maxDistance;
            }
            else {
                // No collision! Complete the move.
                transform.position = targetPosition;
                return;
            }
        }
    }
}
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Could you explain what's happening after the comment "Make sure we don't overshoot the closest point on this line..." I don't understand what the dot products are doing and what the forbidden variable is. \$\endgroup\$
    – mr-matt
    Commented Feb 1, 2019 at 21:20
2
\$\begingroup\$

If your problem here is that when player is placing the ball, it pushes other balls back, then why not just turn isKinematic on and off?

Turn off physics for all the other balls on the playing field, let the player make ball placement and when the player presses "OK" button, enable physics and let the player make the shot.

\$\endgroup\$
1
  • \$\begingroup\$ This is a good idea and was my second alternative as I'm sure it would work, but I decided to give soherecast a try so that it looks neater. Thanks anyway \$\endgroup\$
    – John Smith
    Commented Feb 11, 2017 at 10:06

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .