Hello! I am currently working on selecting 3d items with my mouse and have successfully done so using CollisionHandlerQueue and kind of using CollisionHandlerEvent.
My issue lies with CollisionHandlerEvent. I want to create a tile based layout where you can edit 3d terrain in a 2d manner. So essentially like placing roads on the ground. I would like to have a hover graphic so the tile changes colour when the mouse is over top of it, and then goes back to its original colour when the mouse leaves it.
I was able to get the first part with CollisionHandlerQueue but the second part seemed like the perfect job for CollisionHandlerEvent. So I tried it. I was able to get the first part with a function for mouseRay-into-tile but once I implemented the colour change back, it stopped working.
Thankfully I found what was wrong quite quickly, but I do not know how to fix it. When I simplified the functions for the mouseIn and mouseOut to just print statements (with mouseRay-agian-tile just using the mouseIn function) I got the following output:
In!
In!
Out!
So from my interpretation, it registers that I’ve collided going in, and that I’m still colliding once, but it does not register the continuation of the collision and thinks I’ve stopped colliding on the third call. All relevant code is below.
Upon testing the code below to make sure it still ran and had the same error (I hade some code to move the camera around so I could make sure models were loaded properly) I can also see that the collision will continue through objects. While for my top down view this is unimportant (and might actually be useful in some cases) would just accepting the message once for every time the hoverMouse task is called be the solution to that?
As an aside my tags for each tile seem to not be working as the tag is just Tile.obj rather than the tag I gave it, so if you see an easy fix for that I’d appreciate it greatly.
class MyApp(base):
def __init__(self):
base.__init__(self)
base.disableMouse(self)
tileMap = NodePath("map")
tileMap.reparentTo(self.render)
for x in range(2):
for y in range(2):
tile = loader.loadModel("./Models/Tile.obj")
tile.setTag('tile', f"{x}{y}")
tile.setScale(0.5, 0.5, 0.5)
tile.setColor(0.6,1.0,0.2,1)
tile.setPos(10 + 10*x,-20 + 10*y,0)
tile.copyTo(tileMap)
tileMap.flattenStrong()
self.myTraverser = CollisionTraverser('baseTraverser')
base.cTrav = self.myTraverser
self.myHandler = CollisionHandlerEvent()
self.myHandler.addInPattern('%fn-into-%in')
self.myHandler.addInPattern('%fn-again-%in')
self.myHandler.addInPattern('%fn-out-%in')
pickerNode = CollisionNode('mouseRay')
pickerNP = self.cam.attachNewNode(pickerNode)
pickerNode.setFromCollideMask(GeomNode.getDefaultCollideMask())
self.pickerRay = CollisionRay()
pickerNode.addSolid(self.pickerRay)
self.myTraverser.addCollider(pickerNP, self.myHandler)
self.accept("mouseRay-into-Tile.obj", self.mouseRayIn)
self.accept("mouseRay-again-Tile.obj", self.mouseRayIn)
self.accept("mouseRay-out-Tile.obj", self.mouseRayOut)
self.taskMgr.add(self.hoverMouse)
def hoverMouse(self, task):
if self.mouseWatcherNode.hasMouse():
mpos = self.mouseWatcherNode.getMouse()
self.pickerRay.setFromLens(self.camNode, mpos.getX(), mpos.getY())
self.myTraverser.traverse(self.render)
return Task.cont
def mouseRayIn(self, entry):
print("In!")
def mouseRayOut(self, entry):
print("Out!")
app = MyApp()
app.run()
Edit: I have figured out logically that the ray is just going through the tile and so it has an intermediate detection for when it’s inside and then as it leaves the program says it is out. So I have solved my own problem by using CollisionHandlerQueue and only grabbing the closest element. Then using a variable to track the last touched tile and when that tile stops being touched (either I collide with another tile, or no tile at all. I detect the latter by clearing the queue once I use the first element) I reset the colour.