2D Drag & Drop in PlayCanvas
Tags: playcanvas, gamedev, beginners, basics
Published: 2020-06-12 14:39:44

This is a little tutorial I wish I would have found when I was asked to read more into PlayCanvas as a web developer with no game dev experience 😊

[PlayCanvas](https://www.playcanvas.com) is an open-source 3D game engine/interactive 3D application engine alongside a proprietary cloud-hosted creation platform that allows for simultaneous editing from multiple computers via a browser-based interface ([Source](https://en.wikipedia.org/wiki/PlayCanvas)). 

## Basics
First we need to define the basics for moving entities by dragging. We create a new script (in this case `picker.js`) which will then get attached to the element which we want to move with the mouse.

### Prerequisites
1. Create a new PlayCanvas project. 
2. Import `ammo.js`. Go to the settings in the bottom-left corner and then to "Physics". Click "IMPORT AMMO"
3. Create a new script down at the asset section. I called mine `picker.js`.

For beginners: I also made the following changes to my project setup before coding.
1. Create a new material (choose a color under "Diffuse") and drag it onto the plane for better visibility.
2. Remove the box entity and add a sprite entity instead.
3. Give the sprite entity a sprite and a script component (add `picker.js`).
4. Move and rotate all entities according to the following picture.

![PlayCanvas Project Setup](https://dev-to-uploads.s3.amazonaws.com/i/dx6qxkzaakgqndu26vce.png)

## Register mouse events
First we need to register the mouse events for dragging and dropping. This means pressing the mouse button, moving the mouse and also releasing the mouse button. Within `picker.js` we add the following code.

```javascript
var Picker = pc.createScript("picker");

Picker.prototype.initialize = function() {
    // Used for selecting an entity
    this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
    // Moving an entity
    this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
    // Dropping an entity
    this.app.mouse.on(pc.EVENT_MOUSEUP, this.onMouseUp, this);
};
```

### Find scene camera
Next we need to find the camera of the scene for the ray-casting in the next step. Add the following function to the script.

```javascript
Picker.prototype.findCamera = function() {
    var cameraEntity = pc.app.root.findByName('Camera');
    
    if (cameraEntity) {
        return cameraEntity;
    }
};
```

Call the new function from within `Picker.prototype.initialize`.

```javascript
Picker.prototype.initialize = function() {
    ...
    this.cameraEntity = this.findCamera();
    // console.log(this.cameraEntity);
};
```

Uncomment the `console.log` to check if everything is working so far. 

![Console log for camera](https://dev-to-uploads.s3.amazonaws.com/i/uat3w8pl0wy26k0bg08o.png)

### Pick an entity
For this example we use collision picking to determine which entity was clicked. Therefore we firstly need to add a collision component to our sprite. Select the sprite and click on "ADD COMPONENT" and then "Collision". Set "Half extends" to 0.5, 0.5 and 0.1.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/q23gqq5hqt64wba9j2bd.png)

Then we initialize the following two variables in our `initialize` function:

```javascript
Picker.prototype.initialize = function() {
    ...
    this.pickedEntity = null;
};
```

Next we define the first function from our mouse events - `onMouseDown()`. Add the following code within `picker.js`.

```javascript
Picker.prototype.onMouseDown = function(event) {
    // Raycast startpoint
    var from = this.cameraEntity.camera.screenToWorld(event.x, event.y, this.cameraEntity.camera.nearClip);
    // Raycast endpoint
    var to = this.cameraEntity.camera.screenToWorld(event.x, event.y, this.cameraEntity.camera.farClip);
    // The actual raycasting
    var result = pc.app.systems.rigidbody.raycastFirst(from, to);

    if (result && result.entity) {
        this.pickedEntity = result;
        // console.log(this.pickedEntity);

        this.initialPosition = this.pickedEntity.entity.getPosition().clone();
        // Map the position of the entity in scene to the position on screen
        var worldToScreen = this.cameraEntity.camera.worldToScreen(this.initialPosition);

        // The offset from the clicked position of the entity to the center of the entity
        this.offset = new pc.Vec3().sub2(worldToScreen, event);
    }
};
```

The offset is later needed to prevent the dragged entity's center to 'snap' to the cursor. 
Adding the offset later on back to the position allows to drag the entity by the point of the raycast hitting rather than the entities center.

Uncomment the `console.log` to check if the raycast was successful.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/g22hh3kscyonubobek98.png)

## Dragging
After picking our entity we now need to move it as soon as we move the mouse. Add the following code to your `picker.js`.

```javascript
Picker.prototype.onMouseMove = function(event) {
    if (this.pickedEntity) {
        // The distance between the camera and the dragged entity stays always the same
        var zDistance = this.cameraEntity.getPosition().z - this.initialPosition.z;
        // Get the new position adding the offset calculated before
        var pos = this.cameraEntity.camera.screenToWorld(event.x + this.offset.x, event.y + this.offset.y, zDistance);
        // Set the sprite to the new position
        this.pickedEntity.entity.setPosition(pos);
    }
};
```

Note how we check for `this.pickedEntity` since we want the code to fire only if an entity is selected and not on EVERY mouse move.

## Dropping
Finally add the following function to drop the picked entity on releasing the mouse button. Make sure you set `this.pickedEntity` to null.
```javascript
Picker.prototype.onMouseUp = function(event) {
    this.pickedEntity = null;
};
```

## Result
Aaaand finished. Now you can drag and drop your sprite over the screen.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/q7tho9mssuyo0m2vjbej.gif)

### Links
- See the full project here: https://playcanvas.com/project/689317/overview/2d-drag--drop-example
- I used a sprite by KENNEY. Get the full pack here: https://kenney.nl/assets/toon-characters-1

### Feedback
Spotted a mistake or got some feedback on the code? Leave a comment or reach out to me on [Twitter](https://twitter.com/mrnmnzl). I would love to here from you 😊