How to Drag Actor Around Using Mouse

This is my current version of the code:

from direct.showbase.ShowBase import ShowBase
from direct.actor.Actor import Actor
from direct.showbase import DirectObject
from direct.task import Task
from panda3d.core import Fog

class MyApp(ShowBase):

    def __init__(self):
        ShowBase.__init__(self)

        # Load the environment model.
        self.environ = self.loader.loadModel("environment")
        self.environ.reparentTo(self.render)
        tex = self.loader.loadTexture('/Users/38167/PycharmProjects/viren/venv/wood.jpg')
        self.environ.setTexture(tex, 1)
        # We do not have a skybox, so we will just use a sky blue background color
        self.setBackgroundColor(0.53, 0.80, 0.92, 1)

        myFog = Fog("Fog Name")
        myFog.setColor(0, 0 , 0)
        myFog.setExpDensity(0.01)
        self.render.setFog(myFog)

        self.pandaActor = Actor("models/panda-model",
                                {"walk": "models/panda-walk4"})
        self.pandaActor.setScale(0.005, 0.005, 0.005)
        self.pandaActor.reparentTo(self.render)


        self.accept("arrow_up", self.Move)
        self.accept("arrow_up-repeat", self.Move)
        self.accept("arrow_up-up", self.stopMove)
        self.accept("arrow_right", self.Move2)

        self.taskMgr.add(self.camera_task, "camera")

    def Move(self):
        self.pandaActor.setY(self.pandaActor, -200)
        self.pandaActor.setPlayRate(2, "walk")
        walk = self.pandaActor.getAnimControl("walk")
        if not walk.isPlaying():
            self.pandaActor.loop("walk")

    def stopMove(self):
        self.pandaActor.stop("walk")

    def Move2(self):
        self.pandaActor.setH(self.pandaActor, -90)

    def camera_task(self, task):
        self.trackball.node().setPos(self.pandaActor.getX(), self.pandaActor.getY() + 15, -4)
        self.camera.lookAt(self.pandaActor)
        return Task.cont

app = MyApp()
app.run()

I would like to implement a feature where you can drag the actor around the environment, but I’m not really sure if this is possible and if so how to go about doing it. It would be great if anyone could provide some insight into this topic.

Thanks!

The exactitudes might depend on just how you want the feature to behave, but a simple, broad-strokes approach might be something like this:

  • Give your actor collision geometry
    • If it already has collision geometry, then you may be able to simply use that geometry.
  • Similarly, give your environment collision geometry
    • And again, if it already has collision geometry, then you may well be able to use that geometry.
  • Use a ray to check for collisions with the actor’s collision geometry.
    • If a collision has been found, and the mouse-button is down, start dragging.
  • If dragging, use a ray to check for collisions with the environment’s collision geometry
    • If a collision has been found, place the actor at the location of the collision, perhaps with an offset to account for the location of the actor’s origin.

Sorry, but how would I actually make the code for the dragging function? What would I use for that?

Could you be more specific, please? What are you having trouble with? Positioning the actor? Collision detection? Something else?

I want to make it so that if I press on the panda with my mouse/trackpad it will drag/move it. Sorry if this is asking too much but, could you maybe provide some sample code or an example? I’m not trying to do any collision detection.

I’m not willing to code the entire feature for you, but I am willing to help with specific issues.

I think that most of the parts for such a feature can be found in the manual, or derived from my “beginner’s tutorial”. (For example, both teach the use of collision-rays, as well as moving objects.)

That said, if you want sample code, I suspect that someone has posted some such on the forum somewhere. If you haven’t done so already, you might find the code that you’re looking for with a search!

As to collision detection, I mention is as one way that you might detect which object has been clicked on. There are others–you could find the object’s bounds in screen-space and check whether the mouse-cursor is within those bounds, for example–but collision detection with a ray is a fairly simple approach.

Yeah I know, I was just hoping for some sources you could lead me to or something. Thanks though I’ll keep you posted!

If you want to see working drag and drop code, look at the chessboard example in the panda repository, I guess it present everything you need.

1 Like

I’ll look into it, thanks!

Ah, I’d forgotten the chess example. A good call, @eldee! :slight_smile:

Note that it doesn’t include clicking on a specific piece; you select pieces to drag by selecting their square. Otherwise, however, it looks like a pretty good basis for what you want to do. :slight_smile:

Sorry if I was overly harsh earlier! ^^;

I’ll create my code first, what imports btw? it says unresolved reference collision node

1 Like

Fair enough! Feel free to ask for help. :slight_smile:

I recommend taking a look at the manual’s pages on collision detection, by the way. There’s a lot of information there on how to do this sort of thing.

(Although I don’t recommend taking their suggestion of clicking on visible geometry unless you’re very confident that your program and geometry are simple enough that you can get away with it.)

That suggests that you should import CollisionNode.

In general, I imagine that you’ll want to import CollisionRay, CollisionHandlerQueue, and whatever solids you’re using (CollisionRay at the least, I imagine).

   colliderNode = CollisionNode("player")
   colliderNode.addSolid(CollisionSphere(0, 0, 0, 0.3))
   collider = self.pandaActor.attachNewNode(colliderNode)
   collider.show()

for some reason it doesn’t show the collision object, do you mind helping? Right now I’m just getting a feel for the collision detection, I still want to make it wok though to visualize it.

If I recall correctly, you have a scale applied to your panda actor, is that correct? If so, then the problem might be that the scale in question is resulting in your collision-object being too small to be seen.

I see two potential solutions if this is the case:

  1. Make the solid much bigger.
  2. Make an empty NodePath that the panda and collision object are both children of. Then change your code such that any movement or turning of the model is applied to the new NodePath, instead. (Don’t forget to remove any previous concessions to the model’s scale, such as when moving the model!)

(2) is a little more complicated, but it’s the option that I recommend: I’m not sure of how reliable collision is when a scaling factor is applied.

right now it’s 0, 0, 0, 0.3 what should I change it too? I changed it to 0, 0, 0, 1 but it did nothing. Oh nevermind I changed it to 200 and it worked, but for some reason it’s under the panda, how do I fix this or is it supposed to be like this?

It’s likely under the panda because the panda’s origin is at its feet, I imagine. In this case, simply change the position of your collision-object’s NodePath until the collision-object is in the right place.

I tried making some walls just to test the collision object and for some reason the panda just starts floating in the air and I don’t know what’s happening. Can you please help me out?

here is my current code

from direct.showbase.ShowBase import ShowBase
from direct.actor.Actor import Actor
from direct.showbase import DirectObject
from direct.task import Task
from panda3d.core import Fog
from panda3d.core import CollisionTraverser, CollisionHandlerPusher, CollisionSphere, CollisionNode, CollisionTube

class MyApp(ShowBase):

    def __init__(self):
        ShowBase.__init__(self)
        self.cTrav = CollisionTraverser()
        self.pusher = CollisionHandlerPusher()
        # Load the environment model.
        self.environ = self.loader.loadModel("environment")
        self.environ.reparentTo(self.render)
        tex = self.loader.loadTexture('/Users/38167/PycharmProjects/viren/venv/wood.jpg')
        self.environ.setTexture(tex, 1)
        # We do not have a skybox, so we will just use a sky blue background color
        self.setBackgroundColor(0.53, 0.80, 0.92, 1)

        myFog = Fog("Fog Name")
        myFog.setColor(0, 0 , 0)
        myFog.setExpDensity(0.01)
        self.render.setFog(myFog)

        self.pandaActor = Actor("models/panda-model",
                                {"walk": "models/panda-walk4"})
        self.pandaActor.setScale(0.005, 0.005, 0.005)
        self.pandaActor.reparentTo(self.render)

        colliderNode = CollisionNode("player")
        colliderNode.addSolid(CollisionSphere(0, 0, 345, 900))
        collider = self.pandaActor.attachNewNode(colliderNode)
        collider.show()
        self.pusher.addCollider(collider, self.pandaActor)
        self.cTrav.addCollider(collider, self.pusher)

        wallSolid = CollisionTube(-8.0, 0, 0, 8.0, 0, 0, 0.2)
        wallNode = CollisionNode("wall")
        wallNode.addSolid(wallSolid)
        wall = self.render.attachNewNode(wallNode)
        wall.setY(8.0)

        wallSolid = CollisionTube(-8.0, 0, 0, 8.0, 0, 0, 0.2)
        wallNode = CollisionNode("wall")
        wallNode.addSolid(wallSolid)
        wall = self.render.attachNewNode(wallNode)
        wall.setY(-8.0)

        wallSolid = CollisionTube(0, -8.0, 0, 0, 8.0, 0, 0.2)
        wallNode = CollisionNode("wall")
        wallNode.addSolid(wallSolid)
        wall = self.render.attachNewNode(wallNode)
        wall.setX(8.0)

        wallSolid = CollisionTube(0, -8.0, 0, 0, 8.0, 0, 0.2)
        wallNode = CollisionNode("wall")
        wallNode.addSolid(wallSolid)
        wall = self.render.attachNewNode(wallNode)
        wall.setX(-8.0)

        self.accept("arrow_up", self.Move)
        self.accept("arrow_up-repeat", self.Move)
        self.accept("arrow_up-up", self.stopMove)
        self.accept("arrow_right", self.Move2)

        self.taskMgr.add(self.camera_task, "camera")

    def Move(self):
        self.pandaActor.setY(self.pandaActor, -200)
        self.pandaActor.setPlayRate(2, "walk")
        walk = self.pandaActor.getAnimControl("walk")
        if not walk.isPlaying():
            self.pandaActor.loop("walk")

    def stopMove(self):
        self.pandaActor.stop("walk")

    def Move2(self):
        self.pandaActor.setH(self.pandaActor, -90)

    def camera_task(self, task):
        self.trackball.node().setPos(self.pandaActor.getX(), self.pandaActor.getY() + 15, -4)
        self.camera.lookAt(self.pandaActor)
        return Task.cont

app = MyApp()
app.run()

Thanks!

It’s not clear what you mean by “starts floating in the air”. Do you mean that it’s pushed upwards by the collisions, and ends up stationary, but higher than you intend? Or does it continue to move upwards, as though a velocity were applied? Or something else?

If its the first one, then that’s likely just a result of the curved collision geometry resulting in the object being pushed upwards a bit. If you want purely two-dimensional collisions, you can call “setHorizontal” on your “CollisionHandlerPusher” object–that should cause your collisions to only operate on the XY plane, and thus not move objects upwards (or downwards).

For more information, see the API entry for CollisionHandlerPusher here:
http://www.panda3d.org/reference/python/classpanda3d_1_1core_1_1CollisionHandlerPusher.html

Oh yeah forgot about that, thanks it did the trick!

1 Like