Here’s some sample code that implements this.
I found I had to record the difference between the position of the mouse pointer and the position of the object being dragged at the point when the drag button is pressed down, and maintain this gap while the object is being dragged, so the object gets dragged from the point it was clicked on, rather than its center point jumping to the mouse pointer when the drag button is pressed down.
Also I found that when an object is being dragged and you want to detect and highlight which object it is being dragged over, it’s not whether the mouse pointer is over a target object that you care about, but whether the object being dragged is intersecting a target object. In my case this required a rectangle-rectangle intersection test, and it seemed easiest to write my own.
The only bug with this is that if you drag a card and drop it on top of another card, then drag another card over these two, the highlight doesn’t work. It is highlighting the card on the bottom of the pile but not the one on top. Some sort of z-order is needed so that the card on top gets highlighted, or else you need to record which cards currently have some other card on top of them and remove these covered cards from the collisions tests.
import direct.directbase.DirectStart
from pandac.PandaModules import *
from direct.task import Task
import sys
cm = CardMaker('cm')
left,right,bottom,top = 0,2,0,-2
width = right - left
height = top - bottom
cm.setFrame(left,right,bottom,top)
# Colours.
normal = (1,1,1,1)
highlight = (.7,.7,.7,1)
node = aspect2d.attachNewNode('')
node.setPos(-1.2,0,0.9)
cards = []
for i in range(3):
for j in range(3):
card = node.attachNewNode(cm.generate())
card.setScale(.2)
card.setPos(i/2.0,0,-j/2.0)
card.setColor(*normal)
card.setCollideMask(BitMask32.bit(3))
cards.append(card)
class DragNDrop:
def __init__(self):
base.accept("escape",sys.exit)
base.accept('mouse1',self.drag)
base.accept('mouse1-up',self.drop)
cn = CollisionNode('')
cn.addSolid(CollisionRay(0,-100,0, 0,1,0))
cn.setFromCollideMask(BitMask32.bit(3))
cn.setIntoCollideMask(BitMask32.allOff())
self.cnp = aspect2d.attachNewNode(cn)
self.ctrav=CollisionTraverser()
self.queue = CollisionHandlerQueue()
self.ctrav.addCollider(self.cnp,self.queue)
# self.ctrav.showCollisions(aspect2d)
taskMgr.add(self.rolloverTask,'rolloverTask')
self.rollover = None
self.draggee = None
self.difference = None
def rolloverTask(self,t):
"""Move the mouse CollisionRay to the position of the mouse
pointer, check for collisions, and update self.rollover.
"""
if not base.mouseWatcherNode.hasMouse():
return Task.cont
mpos = base.mouseWatcherNode.getMouse()
if self.rollover is not None:
self.rollover.setColor(*normal)
if self.draggee is None:
# We are not dragging anything, use the CollisionRay to find out if
# the mouse pointer is over any card.
position = (mpos[0],0,mpos[1])
self.cnp.setPos(render2d,position[0],position[1],position[2])
self.ctrav.traverse(aspect2d)
self.queue.sortEntries()
if self.queue.getNumEntries():
self.rollover = self.queue.getEntry(self.queue.getNumEntries()-1).getIntoNodePath()
self.rollover.setColor(*highlight)
else:
self.rollover = None
else:
# We are dragging a card. Do our own test to see if it collides with
# any other card.
x,z = mpos[0],mpos[1]
self.draggee.setPos(render2d,x+self.difference[0],0,z+self.difference[1])
bl,tr = self.draggee.getTightBounds()
b,l,t,r = bl.getZ(),bl.getX(),tr.getZ(),tr.getX()
self.rollover = None
for card in cards:
if card == self.draggee: continue
bl,tr = card.getTightBounds()
b2,l2,t2,r2 = bl.getZ(),bl.getX(),tr.getZ(),tr.getX()
if t2 > b and b2 < t and l2 < r and r2 > l:
# Collision
self.rollover = card
self.rollover.setColor(*highlight)
break
return Task.cont
def drag(self):
if self.rollover is not None:
self.draggee = self.rollover
self.draggee.setCollideMask(BitMask32.allOff())
mpos = base.mouseWatcherNode.getMouse()
self.difference = (self.draggee.getX(render2d)-mpos[0],self.draggee.getZ(render2d)-mpos[1])
def drop(self):
if self.draggee is not None:
if self.rollover is not None:
self.draggee.setPos(self.rollover.getPos())
self.draggee.setCollideMask(BitMask32.bit(3))
self.draggee = None
self.difference = None
d = DragNDrop()
run()