How to drag 3d/2d objects with mouse

what I basically gonna do it:
pressing a button spawns (clone) an object to the screen.
I wanna drag it with the mouse into place.
the scene is in 2d view (top view camera) but the objects are 3d.
they can be 2d if it makes things easier.

so far, I have only come by solutions like raycasting from both object and the camera and calculating intersection point.
is there a simpler way?

if not
how should I modify the “movePlayerTo” so the object will simply set position to target pos rather than moving towards it (chance, basically being placed on the same position as the cursor)

https://developer.playcanvas.com/en/tutorials/point-and-click-movement/

That sounds about right. Can’t think of a simpler way TBH.

A VERY quick hack is to increase the speed to something VERY high. The proper way is remove the code in the update function and change the content of the movePlayerTo function to set the position of player entity.

Well, I think dragging will be much easier, since you basically just position the object under the mouse cursor in the game world.

Something like this will do the trick, updated the point and click script (only mouse input):

var PointAndClick = pc.createScript('pointAndClick');

PointAndClick.attributes.add('cameraEntity', {type: 'entity', title: 'Camera Entity'});
PointAndClick.attributes.add('playerEntity', {type: 'entity', title: 'Player Entity'});
PointAndClick.attributes.add('playerSpeed', {type: 'number', default: 0, title: 'Player Speed'});

// initialize code called once per entity
PointAndClick.prototype.initialize = function() {
    this.groundShape = new pc.BoundingBox(new pc.Vec3(0, 0, 0), new pc.Vec3(4, 0.001, 4));
    this.direction = new pc.Vec3();
    this.distanceToTravel = 0;
    this.targetPosition = new pc.Vec3();
    
    // Register the mouse down and touch start event so we know when the user has clicked
    this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
    this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
    this.app.mouse.on(pc.EVENT_MOUSEUP, this.onMouseUp, this);
};


PointAndClick.newPosition = new pc.Vec3();

// update code called every frame
PointAndClick.prototype.update = function(dt) {
    this.playerEntity.setPosition(this.targetPosition);
};


PointAndClick.prototype.movePlayerTo = function (worldPosition) {
    this.targetPosition.copy(worldPosition);

    this.targetPosition.y = this.playerEntity.getPosition().y;
};


PointAndClick.prototype.onMouseDown = function(event) {
    if (event.button == pc.MOUSEBUTTON_LEFT) {
        this.dragging = true;
    }
};

PointAndClick.prototype.onMouseMove = function(event) {
    if (this.dragging === true) {
        this.doRayCast(event);
    }
};

PointAndClick.prototype.onMouseUp = function(event) {
    if (event.button == pc.MOUSEBUTTON_LEFT) {
        this.dragging = false;
    }
};

PointAndClick.ray = new pc.Ray();
PointAndClick.hitPosition = new pc.Vec3();

PointAndClick.prototype.doRayCast = function (screenPosition) {
    // Initialise the ray and work out the direction of the ray from the a screen position
    var ray = PointAndClick.ray;
    var hitPosition = PointAndClick.hitPosition;

    this.cameraEntity.camera.screenToWorld(screenPosition.x, screenPosition.y, this.cameraEntity.camera.farClip, ray.direction); 
    ray.origin.copy(this.cameraEntity.getPosition());
    ray.direction.sub(ray.origin).normalize();
    
    // Test the ray against the ground
    var result = this.groundShape.intersectsRay(ray, hitPosition);  
    if (result) {
        this.movePlayerTo(hitPosition);
    }  
};

interesting

now, if I have more than 1 object i want that if the ray hits one of them they become the ‘player entity’ until released.
I see that in order to detect the intersection of the floor, you saved it as a variable.
can I be acquired via investigation of any kind of “out hit”? is there an entity return result to raycasting?
and how do I raycast without intersectsRay when I don’t have a predefined object?

If you would like to select an entity when clicking in the game world take a look on the entity picking tutorials here:

https://developer.playcanvas.com/en/tutorials/?tags=raycast

oh neat
now i have something interesting here
i did use the tutorial and got this

var PickerRaycast = pc.createScript('pickerRaycast');
PointAndClick.attributes.add('pickedEntity', {type: 'entity', title: 'picked Entity'});

// initialize code called once per entity
PickerRaycast.prototype.initialize = function() {
    this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onSelect, this);
};

PickerRaycast.prototype.onSelect = function (e) {
    var from = this.entity.camera.screenToWorld(e.x, e.y, this.entity.camera.nearClip);
    var to = this.entity.camera.screenToWorld(e.x, e.y, this.entity.camera.farClip);

    this.pickedEntity = this.app.systems.rigidbody.raycastFirst(from, to);
    //  pickedEntity.script.pulse.pulse();

};

simply instead of invoking pulse, I aimed to save it ina variable which I wanna access from another script.

on that other script (on init this time, not the camera
I did

var PointAndClick = pc.createScript('pointAndClick');
PointAndClick.attributes.add('currEntity', {type: 'entity', title: 'Current Entity'});
PointAndClick.attributes.add('cameraEntity', {type: 'entity', title: 'Camera Entity'});

// initialize code called once per entity
PointAndClick.prototype.initialize = function() {
    var picker = this.cameraEntity;
    console.log("now selected " +this.picker.name);

};

and aim to assign to currentEntity the saved var on cameraEntity.
however, although I assigned the camera to the variable in the inspector, I get that cameraEntity is undefined, as well as picker;

did i miss anything?

Can you post a link to the project please?

here it is
the problematic script is on root

Can you post a link to the Editor so we can look at the setup etc please? That’s a link to a build of the game.

I found the project on your profile. There are a number of issues with the scripts in that it was using variables declared in the functions and not on the this object.

I’ve fixed a few of the issues here to give you a head start but there are still a number of outstanding code errors: https://playcanvas.com/project/682731/overview/simple-test-game-from-forums

thanks. could you please mark with //whatever you want
the places you changed so i won’t need to do a long match catch?
cause i see no change and the same errors persist…

1 of the changes i see you’ve made was replacing the

   if(this.cameraEntity.script.pickerRaycast.pickedEntity !== undefined && this.currEntity !=this.cameraEntity.script.pickerRaycast.pickedEntity)
    {
        this.currEntity = this.entity.script.pickerRaycast.pickedEntity;
        console.log("now selected " +this.currEntity.name);
    }  

with

    if(this.cameraEntity.script.pickerRaycast.pickedEntity)
    {
        if (!this.currEntity || this.currEntity !=this.cameraEntity.script.pickerRaycast.pickedEntity) 
        {
            this.currEntity = this.entity.script.pickerRaycast.pickedEntity;
            console.log("now selected " +this.currEntity.name);
        }
    }    

why was this.cameraEntity.script.pickerRaycast.pickedEntity !== undefined an error?


This is still an error as the entity that has the PointAndClick script doesn’t have the picker_raycast script.

hi
but shouldn’t the this.cameraEntity not direct to the variable which is the camera with the script?


This should be this.cameraEntity. I’ve updated the project to reflect that change: https://playcanvas.com/project/682731/overview/simple-test-game-from-forums

facepalm
can’t believe i missed it.
thanks for the help

hi

i have 1 think i didn’t understand so far and its the

    this.groundShape = new pc.BoundingBox(new pc.Vec3(0, 0, 0), new pc.Vec3(4, 0.001, 4));

why do I have to create an imaginary bounding box to intersect with and cannot use a plane model in the scene?
is it possible to use a collision property or anything?

The example used bounding shapes so it didn’t need to use the Ammo physics runtime library which is a MB and therefore faster to download.

You can use collision if you want to, it just means you also need the Ammo library imported too.

thanks
i have 1 last issue though

in this proect

i use the following script

var PointAndClick = pc.createScript('pointAndClick');
PointAndClick.attributes.add('currEntity', {type: 'entity', title: 'Current Entity'});

PointAndClick.attributes.add('cameraEntity', {type: 'entity', title: 'Camera Entity'});

//PointAndClick.attributes.add('Speed', {type: 'number', default: 10, title: 'Track Speed'});

// ray variables
PointAndClick.ray = new pc.Ray();
PointAndClick.hitPosition = new pc.Vec3();


//dragiing variable
PointAndClick.dragging = false;


// initialize code called once per entity
PointAndClick.prototype.initialize = function() {
    this.picker = this.cameraEntity;
    console.log("now selected " +this.picker.name);
    
    this.groundShape = new pc.BoundingBox(new pc.Vec3(0, 0, 0), new pc.Vec3(4, 0.001, 4));
   // this.direction = new pc.Vec3();
   // this.distanceToTravel = 0;
    this.targetPosition = new pc.Vec3();

    
    // Register the mouse down and touch start event so we know when the user has clicked
    this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
    this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
    this.app.mouse.on(pc.EVENT_MOUSEUP, this.onMouseUp, this);

};

PointAndClick.newPosition = new pc.Vec3();


// update code called every frame
PointAndClick.prototype.update = function(dt) {
    if(this.cameraEntity.script.pickerRaycast.pickedEntity)
    {
        if (!this.currEntity || this.currEntity !=this.cameraEntity.script.pickerRaycast.pickedEntity)
        {
            this.currEntity = this.cameraEntity.script.pickerRaycast.pickedEntity;
            console.log("now selected " +this.currEntity.name);
        }
        if(this.dragging)
            this.currEntity.setPosition(this.targetPosition);
    }
};

PointAndClick.prototype.onMouseDown = function(event) {
    if (event.button == pc.MOUSEBUTTON_LEFT) {
        this.dragging = true;
    }
};

PointAndClick.prototype.onMouseMove = function(event) {
    if (this.dragging === true) {
        this.doRayCast(event);
    }
};

PointAndClick.prototype.onMouseUp = function(event) {
    if (event.button == pc.MOUSEBUTTON_LEFT) {
        this.dragging = false;
    }
};

PointAndClick.prototype.doRayCast = function (screenPosition) {
    // Initialise the ray and work out the direction of the ray from the a screen position
    var ray = PointAndClick.ray;
    var hitPosition = PointAndClick.hitPosition;

    this.cameraEntity.camera.screenToWorld(screenPosition.x, screenPosition.y, this.cameraEntity.camera.farClip, ray.direction); 
    ray.origin.copy(this.cameraEntity.getPosition());
    ray.direction.sub(ray.origin).normalize();
    
    // Test the ray against the ground
    var result = this.groundShape.intersectsRay(ray, hitPosition);  
    if (result) {
        this.moveEntityTo(hitPosition);
    }  
};

PointAndClick.prototype.moveEntityTo = function (worldPosition) {
    if(this.currEntity)
    {
        this.targetPosition.copy(worldPosition);
        this.targetPosition.y = this.currEntity.getPosition().y;    
    }
};

to drag around objects that were selected by another script (‘PickerRaycast’);
while it drags really well, if I don’t touch it at the center, rather than ‘jumping’ to the center of the cursor, it jumps way way further for a frame or 2 and only then repositions itself under my cursor.

why is this jump? and how can I prevent it? bug?
the issue is not consistent and happens without an identifiable pattern I could figure.

Hello! Looking for similar functionality. Did you figure this out? Can I create a fork of your project?