Chipmunk2D Unity Documentation | Main Page | Forum

Chipmunk2D For Unity- Clicking and Dragging Objects:

[Video coming soon]

Clicking or Tapping on a Chipmunk Shape:

You can download this tutorial and all the project files by getting Chipmunk2D for Unity on our store and download page.

Tapping and Dragging

Figuring out what a player has clicked or tapped on is a vital interaction in many games. Chipmunk offer some important improvements that are especially relevant on mobile devices. By using Chipmunk's efficient nearest point queries, the closest object to a touch can be identified, bringing very high touch accuracy even with fat fingers and small objects. This eliminates the struggle of tapping exactly on a small object, but guarantees accuracy.

This example also shows how to drag a physics body across the screen. To make this behavior as stable as possible, the motion is driven via a constraint in the solver. This technique has been recommended before! Maximum forces are limited to prevent horrific exploding physics objects. Rotational forces can be modeled accurately. And finally, this technique has been designed to seamlessly work with a variety of camera settings, orthographic, perspective, and even a perspective camera that is not facing the physics plane head-on.

Let's get started!

First, a ChipmunkBody without an attached shape is used to track the position of the mouse / touches. This makes a lot of sense conceptually - cursor will basically become a physics enabled object that drives the motion of other bodies. Then, the mouseBody needs to be moved to mirror the position of the cursor.

    // Handle input by moving a body around to match the cursor (or touch)
    public ChipmunkBody mouseBody;
    // Then connect the mouse to whatever you clicked on.
    private ChipmunkPivotJoint mouseConstraint;

    protected void FixedUpdate(){
        mouseBody.position = GetPosition(Input.mousePosition);
    }

GetPosition is our method for converting a screen-space touch or mouse position to world-space coordinates. In this situation, the world space coordinates of the click must be in the plane that in which Chipmunk2D is simulating. The z-coordinate must be zero.

    public Vector2 GetPosition(Vector2 mouseOrTouchPosition){   
        // We want to transform this screen position "mouseOrTouchPosition" to world coordinates.
        // However, you might be using orthographic or perspective cameras
        // or even a perspective camera not perpendicular to the plane of action.
            
        // So manually cast a ray to the plane of action.
        Ray ray = Camera.mainCamera.ScreenPointToRay(mouseOrTouchPosition);
        Vector3 worldPos = CastRayToPlane(ray);

        return worldPos;
    }
    
    public Vector3 CastRayToPlane(Ray ray){
        float distance = ray.origin.z/ray.direction.z;
        Vector3 zeroCross = ray.origin - ray.direction*distance;
        return zeroCross;
    }

To find the world-space position of the input, a Ray is cast out of the main camera at the touch position. We calculate the raycast's collision with the plane manually in CastRayToPlane. There's no need for a complicated raycast, since finding the zero crossing point of a ray to a plane is very simple (and requires no iteration!). This simple and flexible approach works with orthographic, perspective, and perspective cameras that aren't facing the physics plane. It's a very flexible approach that allows a lot of freedom with the game's camera, so feel free to use this picking approach in games with 3D art.

Now that the mouse (or touch) position has been reliably determined, only the easy part remains: interfacing with Chipmunk!

    protected void Update(){
        // Works for the mouse or a single touch. If doing multitouch, replace with with a loop over Input.touches
        // and adapt accordingly
        if(Input.GetMouseButtonDown(0)){
            
            ChipmunkNearestPointQueryInfo info;
            Chipmunk.NearestPointQueryNearest(mouseBody.position, fingerThickness, out info);
            Debug.Log ("Grabbed shape: " + info.shape);
        }   
    }

NearestPointQueryNearest is the core method in this instructional guide. NearestPointQuery finds the nearest points on any shapes within a certain radius - it's used for finding multiple matching objects. The NearestPointQueryNearest finds the nearest point on the single nearest shape to a point; since only one object should be selected per tap, this method is used. The radius parameter is used to see how far out from the touch position the engine looks for objects. A good rule of thumb is to make the radius about the size of the user's finger. If you want to force the user to be extremely precise with their fingers, a radius of zero can be used.

If a shape is found within the radius of the touch position, info.shape will contain whatever was touched.

            if(info.shape != null){
                
                if(info.shape.body == null){
                    // We clicked on a static shape. You can't drag those!
                    return;
                }
                
                grabbedShape = info.shape;
                
                mouseConstraint = mouseBody.gameObject.AddComponent(typeof(ChipmunkPivotJoint)) as ChipmunkPivotJoint;
                mouseConstraint.bodyB = grabbedShape.body;
                mouseConstraint.pivot = Vector2.zero;
                
                // high but not infinite. You can push heavy things, but not force yourself through things.
                mouseConstraint.maxForce = 1e3f;
                
                // 60f = the approximate intended frame rate
                mouseConstraint.errorBias = Mathf.Pow(1.0f - 0.15f, 60f);
            }

Adding to the above method, exit early if the user clicked on a static shape, such as a piece of terrain. After clicking on an object, it will be attached to the mouseBody with a pivot joint. The pivot joint allows the grabbed shape to rotate as the mouse moves around the screen. By limiting the maxForce to a fairly high number, a dragged object has enough force to plow through big piles of shapes, but not enough force to do odd non-physical things. Unstoppable forces paired with immovable objects tend to result in nonsensical and uncontrolled physical explosions.

Error bias limits the amount of joint correction that happens per frame and provides some smoothing to the motion.

Stopping the dragging effect is as simple as removing the joint on mouseUp.

        
        if(Input.GetMouseButtonUp(0)){
            // remove mouse constraint.
            Destroy (mouseConstraint);
        }

Expand on what you learned: feel free to adapt this example for multitouch by looping over each touch and tracking a list of grabbed shapes. The example code can also be modified just for mouse picking by removing the dragging parts. This example accurately rotates objects based on where they were grabbed, but the rotation could also be impeded by also attaching a motor joint with a high maxForce.