Can you return a nodepath by only knowing its node name?

I’m taking a class on Panda at school and we are do a space shooter. The way the class is taught, every bullet fired is added to several dictionaries to track aspects of its existence (collision nodes and what not).

I suggested that it might be easier to do this in an OOP fashion and the professor said that I should explore that possibility. I am having limited success mostly due to the fact that I am just learning Panda and some things seem a lot more difficult than they should be.

I created a missile object and it instantiates just fine and does everything I need it to (for the most part). However, I am at a point where I need to know its nodepath and I can’t figure out how to determine that. I can find out its name. I can find out its “PandaNode”. But I can’t figure out its nodepath which is what I need to add specific collider to it…

I realize more information might be needed to resolve this, but I don’t want to bother posting the entire program when it is most likely a smaller portion that is needed (if any)…

You can create a NodePath pointing to a PandaNode by just doing NodePath(node).

However, you don’t need the NodePath to attach anything to it. You can just create the CollisionNode of choice, and then call node.addChild(collider).

You say that you’re taking an Object-Oriented approach to your missiles; do I take it then that each missile is represented by an instance of some class of yours, which contains the various pieces of information related to that missile?

If so, then as long as you have the object’s NodePath at some point (such as at the point at which you create it), note that you should be able to store a reference to that NodePath as a class-variable for later use, much like the missile’s velocity or position. Quite how this is achieved might depend on how you’re setting up your missiles; for example, if the NodePath is being created directly before the missile object is instantiated, then you might simply pass it into the constructor of the missile class–or even move creation of the NodePath into the constructor.

Thanks for your replies. They helped me figure it out. The collision node is created in the missile constructor.

After copying and pasting code into a response, I actually figured out how to do what I needed to do. But now I have another issue related to it. Chalk this up to me not fully understanding the scene graph and/or not understanding how to traverse it…

Here is the code I fixed where the missile is instantiated:

self.missile = Missile(shipVector, hudVector, taskname) 
# shipVector = player position 
# hudVector = hidden object in front of and parented to the player' ship
# taskname  = random number I use internally to debug and learn how everything works

            self.shooting = 0 #this is a variable that increments later to control how often a player can fire
            self.name = self.missile.getnodename() # debug/learning
            self.nodename = self.missile.getnodepath() # debug/learning
            print self.nodename debug/learning
            self.traverser.addCollider(self.missile.collisionnode, self.handler) # this line fixed the code 
#'collisionnode' is the name of the collision node created in the missile's constructor - I didn't realize it was a property of each instance

Here is the missile class:

class Missile(object):

    maxRange = 50
       
    def __init__(self, shipVector, hudVector, taskname): #explained in instancing code
        self.directionVector = hudVector - shipVector    
        self.directionVector.normalize()
        self.fireVector = (self.directionVector * self.maxRange) + hudVector
        
        self.missileModel = loader.loadModel('models/jbickelmissile.egg')
        self.missile = render.attachNewNode("missile" + taskname)

        self.missileModel.instanceTo(self.missile)
       
        self.collisionnode = self.missile.attachNewNode(CollisionNode('collisionnode'))
        self.collisionnode.node().addSolid(CollisionSphere(0,0,0,.03))
        
        self.missileModel.setScale(.02, .02, .02)
        self.missile.reparentTo(render)
        self.ttl = 200
        taskMgr.add(self.rotateMissile, 'rotate' + taskname)
        taskMgr.add(self.moveMissile, 'move' + taskname)
        self.taskname = taskname
        self.interval = LerpPosInterval(self.missile, 3, self.fireVector, hudVector, fluid = 1)
        self.interval.start()     
        
    def rotateMissile(self, task):  # the missile is more like a star Trek photon torpedo and looks better like this
        self.h = random.randint(0, 359)
        self.p = random.randint(0, 359)
        self.r = random.randint(0, 359)
        self.missileModel.setHpr(self.h, self.p, self.r)
        return Task.cont

    def moveMissile(self, task):  #ttl = time to live - makes missile self destruct after so long a time
        self.ttl -= 1
             
        if self.ttl < 1:
            taskMgr.remove('rotate' + self.taskname)
            taskMgr.remove('move' + self.taskname)
            self.explode(self.missile)
      
        return Task.cont

    def explode(self, missile):
        missile.removeNode()

    def getnodename(self):  #debug/learning
        return self.missile.getName()

    def getnodepath(self):  #debug/learning
        return self.missile.node()

The new issue has to do with how the professor is having us deal with collisions. Here is how we have them set up:

def setupCollision(self):
        
        self.traverser = CollisionTraverser()
        self.pusher = CollisionHandlerPusher()
        self.handler = CollisionHandlerEvent()
        self.handler.addInPattern('into')
        self.accept('into', self.handleInto)
        base.cTrav = self.traverser
        self.traverser.traverse(render)

        #pass collisionNodePath and ModelNodePath to the Handler
        self.pusher.addCollider(self.playershipCnode, self.playership)
        self.traverser.addCollider(self.playershipCnode, self.pusher)

def handleInto(self, entry):
        fromNode = entry.getFromNode().getName()  #debug/learning
        fromNodePath = entry.getIntoNodePath() #debug/learning
        intoNode = entry.getIntoNode().getName() #debug/learning

        print entry
 
        print intoNode
        print fromNode

        fromNodePath.explode()
        print str(fromNodePath)

While running the above I shoot a drone ship, the output is…

Known pipe types:
  wglGraphicsPipe
(all display modules loaded.)

CollisionEntry:
  from render/missile238712/collisionnode
  into render/jbickeldrone.egg/droneCnode []
  at -6.70587 -1.30329 0
  normal 2.99467 0.178824 0
  interior -6.73434 -1.30499 0 (depth 0.0285231)
  respect_prev_transform = 0

droneCnode
collisionnode
render/jbickeldrone.egg/droneCnode

Concerning the above, is “render/modelname/collision node name” the node path?

When the missile hits something, I want it to call a function that makes it explode and etc. But the collision info only deals with the collision nodes. How do I find the node that the collision node is attached to so I can call the “explode” function? Is that the right way to approach this? I checked into a couple of methods that sounded like they would get the parent of the collision node, but I have had no success. It seems like there has to be a way to go from node path to object and vice versa, but I haven’t figured it out yet…

You can just call getParent() to get the parent NodePath that the CollisionNode is attached to.

However, what you should do is call setTag(“objectid”, 123) on your model, or something, in order to mark which object it is. Then, in the collision handler, you call nodepath.getNetTag(“objectid”) (which returns a tag specified on this node or any node above it), which will tell you exactly which object this is.

Then, you store some sort of global mapping from object Ids to instances of your object class (ie. Missile) so you can look up which object this actually is.

Of course, don’t forget to remove your objects from this dictionary when they are destroyed.

(A variation on this that you’ll often see is the use of setPythonTag to store a Python object on a node, so that you can store a reference to your Missile object itself on its collision node. However, this is a bit trickier to deal with as it creates a circular reference that you have to break later on.)

I thought the point of OOP was to have my objects do all of that book keeping for me? If I code the missile class correctly, then when I create a missile, the following happens…

I am a missile. I move forward from the time of my creation. If I hit something, I explode and remove myself from memory (whatever I hit has it's own code to deal with being hit).

Micro managing just seems to be the difficult way about it if you can write it using objects.

getParent() works, but not how I expected it to. Apparently the node path isn’t the object itself and as such I can’t call any functions from it (like explode()). I looked through the manual and some of the reference, but couldn’t find what I needed.

Essentially, I need to know how to pull an object out of the the info in the scenegraph so I can call functions from it. Each instance of the missile has a random number appended to it, so it’s not that hard to see which one is which. Is there a “get object from node path” function somewhere that I just can’t find?

I have all this information, but can’t seem to use it…

CollisionEntry:
  from render/missile728180/collisionnode
  into render/jbickeltransport.egg/myTransportCnode []
  at -6.70591 -1.30266 0
  normal 2.99463 0.179397 0
  interior -6.72963 -1.30408 0 (depth 0.0237666)
  respect_prev_transform = 0

This got hit - myTransportCnode
This caused the hit - collisionnode
I am the parent of the missile's node path - render/missile728180
I am the actual node that hit - CollisionNode collisionnode (1 solids) (hidden)

Well, I gave you all the information needed to implement that. I can give you some code to show how it works.

The easiest method is to just put this in your Missile constructor:

self.collisionnode.setPythonTag("object", self)

And in your collision handler:

missile = entry.getFromNode().getPythonTag("object")

Et voilà, you now have the Missile instance on which you can call explode().

To fix the circular reference you have created by storing this tag, you should put this in your explode() method, otherwise you get a garbage leak:

self.collisionnode.clearPythonTag("object")

I appreciate the code sample. Only being 4 weeks into learning Panda, it helps to see something implemented. Obviously I had to make a fair amount of customizations, but it’s working as expected.

When you first mentioned the word “dictionary”, I thought you meant something along the line of…

everyObjectInTheGame = {}

That’s how the professor is doing it and it’s way too much book keeping and micro management for my taste. He is using several dictionaries for each object. It just doesn’t seem to make sense when there are better, less headache filled ways to do it.

Thanks for your help!

Yes, that is what I meant by ‘dictionary’ originally; it’s a different approach to doing the same thing. That approach involves a bit more bookkeeping indeed. Use whichever works best for you.