namespace confusion bullet and panda3d and custom class

hi. still learning. managed to get a protoype of a spaceship flying around using torque for direction adn force as thrust thanks to rdb’s help here.

now i decided to neaten things up and separate the solarsystem and ship in to classes of their own and then reference them in the main World loop.

thing is, and again this is a friggin rookie mistake, but i am not sure how to parent the bulletworld initialised in my main ‘class World(DirectObject)’ in my new ‘class craft()’ classes.

the exact phrase causing the error is

world.attachRigidBody(self.ship_node.node())

with an error code of AttributeError: ‘libpanda.NodePath’ object has no attribute ‘attachRigidBody’

how can i make it so that i can access bullet attributes in a seperate class? does this have something to do with the underlying C++ classes or just (and probably) my lack of knowledge.

I think you just have some general rookie object-oriented design problems. Anyway, for discussion we need some hard fact to talk about. So I will assume the following:

class World(DirectObject):
  ...
     ...
     self.bulletworld = BulletWorld(...)
     ...

Your own class World holds a reference to a BulletWorld instance. Even more, it is responsible for creation of the bulletworld object, i. e. it is it’s owner.

Now you want to have a second class for your spaceship:

class Craft:
  ...
     ...
     self.ship_node = BulletRigidBodyNode(...)
     self.ship_np = NodePath(self.ship_node)
     ...

Ok. I want to emphasis the importance of choosing proper variable names. If a varaible ends with _node then any Panda3D user will assume it is a PandaNode and not a NodePath to a PandaNode. A PandaNode does NOT have a methode .node(). If your variable holds a NodePath then DO NOT name it _node!!!

Now, your class craft needs to access some properties of the BulletWorld, right? BulletWorld is a dependency of Craft. You need to do two things:

  1. Craft needs a member “self.bulletworld”, which holds another reference to the bullet world object.
  2. You need to set the bulletworld reference somehow from your own World class (called “injection”). This can be done in several ways:
    2.1) A setter:
class Craft:
   def setBulletWorld(self.bulletworld):
      self.bulletworld = bulletworld

2.2) Constructor injection:

class Craft:
   def __init__(self, bulletworld):
      self.bulletworld = bulletworld

2.3) IOC container: putting the bulletworld into some singlton object or container (e. g. Python module), and letting the class Craft resolve it’s depencies. Well, this is a bit advanced, so skip this option for now.

Now you can access the Bullet world from within the Craft class. The only thing you have to worry about is how to get rid if the BulletWorld if your program terminates.

class Craft:
   ...
      ...
      self.bulletworld.attachRigidBody(self.ship_node)
      #or
self.bulletworld.attachRigidBody(self.ship_np.node())

Thanks very much ennox! The thing about python is that it allows rookies to achieve results quite quickly. but also get caught up on some things.
what you typed here cleared up a lot for me and taught me tons of just the right kind of information.
Thanks again

another question on this namespace and panda3d please.

i am wanting to have a task where all craft (of their own class craft) are updated

thing is i cant define the task just before the constructor of the class

ie.

]
class craft()
taskMgr.add(self.craftLoop, "craftLoop")

def __init__ (self):

etc, then have down below
    def craftLoop(self,task):
        dt = globalClock.getDt()
        frameTime = globalClock.getFrameTime()

as it gives me an error.

how should i perhaps get these craft classes regularly updated if i cant associated a task manager with them?

and if i try another angle and call an update function inside the craft class from main gameloop i get TypeError: unbound method update() must be called with craft instance as first argument (got nothing instead)

[/code]

edit - however it works if i call it from the main gameloop usng an object ofthe craft class, ie self.craft1.update(dt)

is this the right way to do a regular update? I’m guessing there’s a better way probably

First of all, while I may be mistaken, I don’t think that it makes much sense to put anything other than variable declarations inside a class but outside of any method or function, as you seem to be trying to do in your first example.

You say that you have a main game-loop; what does that look like?

Otherwise, how you update your craft will likely depend to some degree on what, precisely you’re doing with them. If you’re just updating positions from velocities, then you might find that simply doing that in the main game-loop is simple and effective. If you have more to do, then a method declared inside of the “craft” class, either called for all craft from a single task (potentially the main game-loop) or run as a separate task for each craft might make more sense.

As to the type error, it seems to be saying simply that you’re attempting to call a method of a class without specifying which member of that class you want it to affect. For example, if you have a class like this:

class MyClass():
    def __init__(self):
        pass

    def myMethod(self):
        # Print out the object being used
        print "Method called! I am " + str(self)

The following makes little sense:

myMethod()
MyClass.myMethod()

Neither of those tell the method to which instance it should be applied.

Instead, this is what is expected, I believe:

obj = MyClass()
obj.myMethod()

In this case, “obj” is automatically passed in as the first argument (which is, I believe, what your error is complaining about - that first, automatic argument is missing), and so the method has access to the relevant instance.

Now, there are some nuances and alternative structures or calling approaches, but those I leave aside for now in the interests of brevity and clarity.

In addition to what Thaumaturge has already said, I thought I would whip up some pseudo-code for you to study. This should be close to what you want. It does not handle removing crafts from the list. I tried to write it to be “self-explanatory” even without comments.

class Craft:
    all_craft = []
    
    def __init__(self):
        self.velocity = Vec3(0, 50, 0)
        self.ship_np = ... # omitted for brevity, do whatever you do
        Craft.all_craft.append(self)
    
    def update(self, dt):
        self.ship_np.setFluidPos(self.ship_np, self.velocity * dt)

# global functions...

def update_all_craft(task):
    dt = globalClock.getDt()
    
    for craft in Craft.all_craft:
        craft.update(dt)
    
    return task.cont

def begin_tasks():
    taskMgr.add(update_all_craft, "craft loop")

def end_tasks():
    taskMgr.remove("craft loop")

Thanks Thaumaturge and Hollowed. that makes so much sense I’m kicking myself for not thinking it through properly. learnt something here.

one more question on my name space confusion. i am applying torque inside the class but the nodes arent moving.

under class craft() i have the init(self, bulletworld) call self.draw_craft() which refers to:

def draw_craft(self):
        shape =  BulletBoxShape(Vec3(0.5,0.5,0.5))
        self.cube_node = render.attachNewNode(BulletRigidBodyNode("obj_cube_node_"+str(self.ID)) )
        print ('cube node is ', self.cube_node)
        #self.bulletworld.attachRigidBody(self.cube_node.node())
        self.cube_node.node().setMass(1)
        #self.cube_node.node().setFriction(0.50)
        self.cube_node.node().addShape(shape)


        self.cube_mesh1 = loader.loadModel(craft.craft_types[0])
        self.cube_mesh1.reparentTo(self.cube_node)
        self.cube_mesh1.setHpr(180,0,0)
        whiteTex = loader.loadTexture("whiteTexture.png")
        self.cube_mesh1.setTexture(whiteTex,1)
        craft.craft_list.append(self.cube_node)
        print ("ctaft node is", self.cube_node)
        print ("list of craft ", craft.craft_list)
        return self.cube_node

which then is then called in main World class setup funtion as

def setUp(self):
        self.setLight()
        self.turn_debug_on()
        self.first = craft(self.world)
        self.first.move_craft(5,5,0)
        self.next = craft(self.world)
        self.next.move_craft(9,-5,19)


        total = 10
        for each in range(0,total):
            self.each = craft(self.world)
            

then using gameloop still in main i call

     def gameLoop(self,task):
        dt = globalClock.getDt()
        frameTime = globalClock.getFrameTime()
        self.processInput(dt)
        for each in craft.craft_list:
            self.each.accell()
        self.world.doPhysics(dt)
        return Task.cont

and that each.accell() code is inside craft class as

    def accell(self):
        force =  Vec3(0, 0, 0) # Forward
        torque = Vec3(0, 0 ,0)
       #curVec = render.getRelativeVector(self.cube_node,Vec3(0,1,0))
        force.setY(1.0)


        self.cube_node.node().setActive(True)

        torque = render.getRelativeVector(self.cube_node,torque)
        self.cube_node.node().applyTorque(torque)

        force = render.getRelativeVector(self.cube_node, force)
        print ("force is", force , " for ID" , self.cube_node)
        self.cube_node.node().applyCentralForce(force)

thing is, the craft do not move. where am i going wrong? probably 100 places, but why aren’t the craft moving?

I’m not familiar with Bullet, so I may well be missing issues there, but one thing does jump out at me:

In “gameLoop” you have the following:

        for each in craft.craft_list:
            self.each.accell() 

This doesn’t look correct to me (although I may well be mistaken): “each” here is a local variable, and so I would expect it to be referenced simply as “each”, not “self.each”.

However, there seems to me to be more that’s odd there. For one, I’m a little surprised that those lines as you have them there run without error: first there’s the issue that I mention above, and second it appears to me that “accell” is expecting to be called on a “craft” object, while you appear to be storing NodePaths in “craft_list”.

Given this, I suspect that “craft_list” may not be working properly. You appear to be using it as a class attribute rather than an instance attribute - would we please see how you’re declaring it, and where?

I also suggest storing “craft” objects in that list, rather than NodePaths - if you continue to use “craft_list” as you are, then I recommend changing what you have to “craft.craft_list.append(self)”.

thanks for taking the time to help out.

my craft list is initialised as:

class craft():
    """all craft defined here"""
    craft_list = []
    craft_types = ["rocket"]

I tried dropping the self from .each.acell() in the gameloop

    def gameLoop(self,task):
        dt = globalClock.getDt()
        frameTime = globalClock.getFrameTime()
        self.processInput(dt)
        for each in craft.craft_list:
            self.each.accell()
        self.world.doPhysics(dt)
        return Task.cont

i am getting access to the craft as i have a turning script that rotates all craft to face a common object and it works fine for them. although on 2nd thoughts that is called when the objects are generated.

but i reckon you are onto something. I’ll try figure out how to pass each craft into the list. then share know how it goes.

sorry to reply to my own thread, but i solved the issue of the ships not moving. it was another rookie mistake. i had left off a critical line in the bullet part of the ship definition file.

self.world.attachRigidBody(self.ship_root.node())

so the code should have read.

    def load_ship(self):
        shape =  BulletBoxShape(Vec3(0.5,0.5,0.5))
        self.ship_root = render.attachNewNode(BulletRigidBodyNode('shipNode'+str(len(self.ship_list))))
        self.ship = loader.loadModel('rocket1')
        self.ship_root.node().setMass(1)
        self.ship_root.node().addShape(shape)
        self.ship_list.append(self.ship_root)
        self.ship.reparentTo(self.ship_root)
        self.ship_root.setPos(0,0,0)
        self.ship_root.setScale(0.1*self.sizescale)
        self.world.attachRigidBody(self.ship_root.node())
        return self.ship_root

hope that helps someone who might find same probelm i had.