Manipulating Nodepaths from other Functions/.py files

Sorry if this is a stupid question.

Seeing as I’m going to be using the same Collison and whatnot for client players and online players, I put the code for setting that up in a separate file I can import.

The collision is built around a “centerNode” that I can use as a handle. When I just run the code to set up the collision shapes and parent the camera to the centerNode, everything works fine.

The problem arises back in main.py when I try to move the centerNode.

centerNode.setPos(0, 0, 3) gives “NameError: ‘centerNode’ is not defined”.

How would I go about this properly?

Hmm… A bit more context might be called for here.

The core of the problem, I imagine, is that “centreNode” is defined in the separate file, and thus isn’t defined within the scope of the “main.py” file. Thus some means of referencing its location in the separate file is called for.

The exact means by which this might be done likely depends on the context: How are you importing that separate file? And is “centreNode” a member of some class or other?

Maybe you could make this work with python tags.
You would pack everthing you want external access to, in a single class, including the hitbox and then setup a tag for the hitbox.

class myClass():
    def __init__()
        self.object = Actor("MyModelPath")
        self.collisionNode = setupMyCollisionNode()
        self.collisionNode.setPythonTag("MyHitboxTag", self)

Then, when your other class registers the hit, you’ll have it look for the tag.

collisionQueue = detectMyCollision()
pickedObject = collisionQueue.getEntry(0)
pickedObject.getIntoNodePath().getPythonTag("MyHitboxTag").object.setPos(MyNewCoordinates)

EDIT:
On second thought, the hitbox probably is not required to be part of such a class. As long as the tag points towards an object of that class, it should give access to that object nonetheless.

1 Like

That is a good point: when dealing with collisions, Python-tags can be a useful means of accessing related data.

There is a caveat, however: doing so can result in circular references, which can interfere with garbage collection and thus potentially cause memory leaks.

That isn’t to say that it shouldn’t be done–merely that caution and cleanup might be wise!

Of course, if the relationship between collider and “centerNode” is static–if, for example, the “centerNode” is always the parent or the parent-of-the-parent of the collider–then one can simply use that relationship to access it. This can be done via the “getParent” method of the “NodePath” class.

2 Likes

Thanks for the replies.

Yes, centerNode is defined in a function in another file, which is imported and called in main.py.

The relationship is in fact static, centerNode only exists to sort the nodes belonging to different players, and to act as a handle to move them all at once.

Right now, I can use the getParent of the camera, as this is a FPS game.
But with online players or npcs, would I need to have some some other way of referencing their centerNodes?

The way the function is set up, whatever gets attached in the position to the camera is a parameter.
I could probably just define the aim collision in the game loop and use that as a reference.

Could you not just get the parent (or other appropriate ancestor) of the collision-node itself?

Depending on how you’ve structured your game, that might well work for both player and online players or NPCs, I would imagine.

Unfortunately, the collision is also defined in the same function.

Actually, if I were to create an empty nodepath to put all the centerNodes under, could I use something like nodepath.find(“centerNode2”)?

Hum. Perhaps a bit more information would be useful here, then–I was imagining that you were trying to access “centerNode” in a collision-response event, in which case you would have direct access to the collision-node.

So, in what context are you trying to access “centerNode”? What are you trying to do, and under what conditions (including, if applicable, in response to what)?

There’s likely a simple way of doing this, but without more information it’s hard to guess at what might work, I fear!

I’m really just trying to reposition it and it’s children upwards using setPos() during init, before I can test and implement things like gravity.

Ah! Well, in that case, why not just do so in its own script-file?

I am able to move it in the function where it’s defined, so that’s a good start.
However, I can’t move it outside of the function whatsoever, even in the same file.

Because I am going to use the centerNode down the line for movement, this could be a problem.

I might as well share my code here, if it helps.

main.py:

from direct.showbase.ShowBase import ShowBase
from gameCode.characters_and_players.pcSharedFunctions import *


class MyGame(ShowBase):

    def __init__(self):
        ShowBase.__init__(self)
        #this only disables the "model viewer" controls.
        base.disableMouse()
        #Later, this should lead to a menu.
        self.scene = self.loader.loadModel("models/environment")
        self.scene.reparentTo(self.render)
        self.scene.setPos(0, 42, 0)
        pcCollideSetup(base.camera)
        #centerNode = camera.getParent()
        #centerNode.setpos(0, 0, 4)
        #moveToSpawn(0, 0, 3)
        



teGame = MyGame()
teGame.run()

pcSharedFunctions.py:

from panda3d.core import CollisionNode
from panda3d.core import CollisionSegment


    
def pcCollideSetup(attachee):
    
    centerNode = render.attachNewNode("charaCenter")
    charaColl = centerNode.attachNewNode(CollisionNode("charaColHolder"))
    charaLine = CollisionSegment(0, 0, 0.25, 0, 0, -0.25)
    charaColl.node().addSolid(charaLine)
    
    attachee.setPos(centerNode, 0.1, 0, 0.25)
    attachee.reparentTo(centerNode)
    centerNode.setPos(0, 0, 4)

Ah, okay, that helps!

As you currently have it, “centerNode” is a local variable–while the node itself should persist, the variable that holds it, “centerNode”, will presumably be lost when “pcCollideSetup” is done.

Further, being a local variable, it is indeed only accessible from within “pcCollideSetup”.

Now, there are a few ways that you could address this:

  • You could give the node a unique name, and then search the scene-graph for it.
    • This might be a little cumbersome at times
  • You could create a global variable
    • This can become messy as more global variables are made
  • You could create a module-level variable. That should be available to be referenced via its module.
    • In short, a variable defined outside of the “pcCollideSetup” method.
  • You could store a reference to the node in something else that persists
    • Of course, you’d need such a something else first

Let me focus on that last possibility for a moment: If you were to create a class to hold your character-nodes, and any other character-specific data that you might have (e.g. health), then you could create instances of that class. These instances could then be instantiated and stored in the main script, and thus be accessible from it.

(If the design of your game becomes more complicated, you might end up with layers of this: your “Game” class instantiating a “Level” class, which in turn instantiates your “Character” class, for example.)

1 Like

Sorry for the late reply, I’ve unfortunately been busy. And thank you so much for the help! as you may be able to tell, I’m totally new to this sort of thing.

I’ve rewrote my code to have all nodes defined as a class that I can instance.
However, I get in pcSharedFunctions.py:

   centerNode = render.attachNewNode

NameError: name “render” is not defined

I assume this is because render doesn’t exist in that file.
Should I try and import showbase? or could something else be going wrong here?

Edit: I just tried importing showbase, which doesn’t seem to do anything. I’ll look into this tomorrow, as it is getting late.

Hmm… Do I presume correctly that your main “game” class still inherits from ShowBase?

If so, then where does the line above get executed in the overall flow of the program–is it before or after the initialisation of ShowBase in your main “game” class?

That is, consider the following program:

“SomeOtherClass.py”

from panda3d.core import PandaNode

class SomeOtherClass():
    def __init__(self):
        render.attachNewNode(PandaNode("cat"))

“main.py”

from direct.showbase.ShowBase import ShowBase

from SomeOtherClass import SomeOtherClass

class MyGame(ShowBase):
    def __init__(self):
        myOtherObj = SomeOtherClass()

        ShowBase.__init__(self)

game = MyGame()
game.run()

In the above, follow the order in which things are executed: First our “MyGame” class is instantiated. Then our “SomeOtherClass” class is instantiated. Then the code within “SomeOtherClass”'s “__init__” method is run. Then ShowBase is initialised.

But there’s a problem there: If I’m not much mistaken, ShowBase sets up things like “render” and “loader” when it’s initialised. Thus, since the use of “render” occurs before ShowBase is initialised, it doesn’t yet exist.

However, if we were simply to swap the order of “myOtherObj = SomeOtherClass()” and “ShowBase.__init__(self)”, ShowBase will be initialised before SomeOtherClass, and thus before the use of “render”, and so the code should work!

Now, I am rather guessing at this being the problem in your case–perhaps I’m wrong! If so, perhaps you could post your code–that way we might be able to see where the problem lies and so hopefully help you better.

No, I don’t think that this will help at all, I’m afraid. ^^;

Thank you, looking at your example it became apparent that the problem was that I simply do not understand how to write classes. Now things are working better, except for addSolid(), as used here:

from panda3d.core import CollisionNode
from panda3d.core import CollisionSegment

    
class pcColliders:
    def __init__(self):
        centerNode = render.attachNewNode("charaCenter")
        charaColl = centerNode.attachNewNode(CollisionNode("charaColHolder"))
        charaLine = CollisionSegment(0, 0, 0.25, 0, 0, -0.25)
        charaColl.addSolid(charaLine)

When I try and run the program, I get “‘panda3d.core.NodePath’ has no attribute addSolid”.

My advice to you is not to use attachNewNode this method breaks the logic.

from panda3d.core import CollisionNode
from panda3d.core import CollisionSegment
from panda3d.core import NodePath


class pcColliders:
    def __init__(self):

        centerNode = NodePath("charaCenter")
        centerNode.reparentTo(render)

        cn = CollisionNode("charaColHolder")
        charaLine = CollisionSegment(0, 0, 0.25, 0, 0, -0.25)
        cn.addSolid(charaLine)

        charaColl = NodePath(cn)
        charaColl.reparentTo(centerNode)

By using NodePath, you will maintain a clear understanding of what you are doing. In your case, you are trying to set a method for the CollisionNode to the scene graph object. The way out is to use the node method.

charaColl.node().addSolid(charaLine)

However, this is a strange way. It is better to do it in the correct sequence.

1 Like

I don’t really agree that the use of “attachNewNode” is an inherent problem, but do agree with serega regarding the core of the problem: “addSolid” is a method of the node, not the NodePath. That is, it’s to be added to the node held within the NodePath, and not to the NodePath.

Note I’m not saying that this is the problem. I say that this method breaks the logic.

It really doesn’t. As you yourself showed, just accessing the node allows one to add a solid successfully.

You understand that I am not talking about this problem in general. By directly using NodePath, your logic looks clearer, it’s better to read it.