Ok, I have only ever programmed OO so I am bound to make some assumptions about other people’s coding styles that are not warranted. In Panda3d you have to import every class that you need (Vec3, Point3, BulletWorld, etc.) If you don’t import any panda3d classes you would essentially just be running a raw python script that has no relation to panda3d. The following line automatically imports certain pre-initialized objects (render, base, taskMgr) for convenience,
import direct.directbase.DirectStart
but nothing about panda3d is implicitly part of your python script to start with. Before you can create a new node you need add that type of node (PandaNode, CollisionNode, GeomNode, etc.) to your import statements at the top of your script and then create a new instance of that node “new_node = PandaNode(‘any_name_here’)” whenever you need one. In this case you need to create a new node for each world so you need access to the class “PandaNode” (as the most basic kind of node).
But I have to reiterate here a point that Thaumaturge raises, there are some issues with your code that need to be cleared up before you’ll be able to accomplish what you’re trying to do.
First of all:
for worldName, worldData in worldsDict.items():
self.worldNP = render.attachNewNode(worldName)
in this loop you are replacing the previous world node you created with each iteration through it. Every time the loop executes the statement “self.worldNP = …” you are destroying the world that was created the pass before (i.e. Mars will replace Earth and so on). (Not only that but you can’t pass the string worldName directly to “attachNewNode” you have to give it a node object, see my examples below.)
In Python when you see the term “self.” before a variable you are referring to an attribute of an object. In this case the object is “game” as an instance of the class “Game”, so the result of your loop would be identical to the following code:
game = Game()
game.worldNP = render.attachNewNode('Earth')
game.worldNP = render.attachNewNode('Mars')
game.worldNP = render.attachNewNode('Jupiter')
Whenenver you try to access “game.worldNP” you will only be able to get the last one created. What you need to do is create a dictionary into which you place each world’s nodepath using its name as the key:
self.world_NP_Dict = {} # dict to hold nodepaths.
self.worldsDict = {"Earth": "data", "Mars": "data"} # attach this to the game object as well.
for worldName, worldData in self.worldsDict.items():
new_world_node = PandaNode(worldName) # Init the node to pass to attachNewNode.
new_world_np = render.attachNewNode(new_world_node)
self.world_NP_Dict[worldName] = new_world_np
Whenever you need to get a handle on, say, Earth’s nodepath you would just use “earth_np = self.world_NP_Dict[‘Earth’]”.
Next, I’m fairly certain that you are misunderstanding the Bullet physics module: a bullet world is not a planet or world like your code seems to imply, it is simply the module’s analogy to panda’s scene graph. That is you should only ever need one instance of a bullet world which works with the scene graph to simulate physics for it. Your worlds or planets would be objects in the bullet world, not bullet worlds themselves.
That being said having looked into the Bullet physics module myself for similar purposes it is not useful for simulating the physics of a solar system. For example, its “gravity” only pulls in one direction meaning that you can’t use it to create gravity for spherical worlds, only for completely horizontal terrains like most video games. Though the module may still be useful for collisions and such in your game.
The native panda3d physics module provides the LinearSinkForce object, but again after experimentation it seems inadequate for simulating gravity (creating a bowl shaped attraction rather than a gravity well.) So you have to simulate gravity yourself and apply it to your bodies (planets, ships, etc.)
Some final thoughts on objects and design: your program layout is really just a functional program stuck inside a class object. This is fine if you are more comfortable with functional programming but you would be handicapping yourself not to take advantage of some of Python’s basic OO features. Why not separate your major conceptual objects into different classes? Your worlds or planets will be large and complex objects in the end (think of all their physical and orbital paramaters and behaviors), you should definitely create a separate class called “World” where you “encapsulate” all this information and behavior. A rough outline with some demo behavior would be:
class Game(DirectObject):
def __init__(self):
self.world_Dict = {} # note we lose the "NP" from the name.
self.world_init_Dict = {"Earth": "data", "Mars": "data"} # change name to avoid confusion.
## more game init code ##
def setup(self):
# Instantiate each world and add it to self.world_Dict.
for worldName, worldData in worldsDict.items():
new_world = World(worldName, worldData)
self.world_Dict[worldName] = new_world
# Example behavior.
def hide_all_worlds(self):
for world in self.world_Dict.values():
world.hide_world()
class World:
def __init__(self, worldName, worldData):
self.node = PandaNode(worldName)
self.NP = render.attachNewNode(self.node)
self.name = worldName
self.moon_list = [] # add planet moons.
self.worldData = worldData
## more init code ##
# Example behavior.
def show_world(self):
print("Now showing world {}".format(self.name))
self.NP.show()
def hide_world(self);
print("Now hiding world {}".format(self.name))
self.NP.hide()
You could then easily access anything about your worlds (including their nodepaths) simply by taking them from the “game.world_Dict” (or “self.world_Dict” if you’re working inside the “Game” class code). For example:
# Get Mars.
mars = self.world_Dict['Mars']
# Access the planet's nodepath.
mars_np = mars.NP
# Attach another node to the planet (i.e. a moon).
phobos_data = {...some data...}
phobos = World("Phobos", phobos_data)
mars.attachNewNode(phobos.NP)
mars.moon_list.append(phobos)
# Print info about mars.
print(mars.worldData)
# Or hide all planets.
self.hide_all_worlds()