Main instance has no attribute 'world'

Hi, i’ve only recently started learning how to do panda3d/python and it’s been a really rough experience for me. Honestly, it’s one of the toughest engines/languages i’ve ever learnt.

I’ve been having annoying indentation issues since forever, but i just cannot see what is wrong with my code. Maybe another pair of eyes would help. Thanks. :frowning:

import sys
import direct.directbase.DirectStart
import math

from direct.showbase.DirectObject import DirectObject
from direct.showbase.ShowBase import ShowBase
from direct.showbase.InputStateGlobal import inputState
from direct.directtools.DirectGeometry import LineNodePath
from direct.gui.OnscreenText import OnscreenText
from direct.gui.DirectGui import *
from direct.gui.OnscreenImage import OnscreenImage

# the above import helps with keyboard

from panda3d.core import AmbientLight
from panda3d.core import DirectionalLight
from panda3d.core import Vec2
from panda3d.core import Vec3
from panda3d.core import Vec4
from panda3d.core import Material
from panda3d.core import Point3
from panda3d.core import TransformState
from panda3d.core import BitMask32
from panda3d.core import Material

from panda3d.bullet import BulletWorld
from panda3d.bullet import BulletPlaneShape
from panda3d.bullet import BulletBoxShape
from panda3d.bullet import BulletSphereShape
from panda3d.bullet import BulletRigidBodyNode
from panda3d.bullet import BulletDebugNode
from panda3d.bullet import ZUp
from panda3d.bullet import BulletGhostNode

class Main(DirectObject):

  #Define the state of the game and level
  gameState = 'INIT'
  gameLevel = 1
  count = 0
  win = False
  timer = 60

  # _____INIT_____
  def __init__(self):
    base.setBackgroundColor(0.1, 0.1, 0.8, 1)
    base.setFrameRateMeter(True)

    #Position of camera
    base.cam.setPos(0, -25, 10)
    base.cam.lookAt(0, 0, 0)
    #base.cam.lookAt(self.boxN.getPos())

    #Splashscreen
    self.imageObject = OnscreenImage(image = 'media/SplashScreen.jpg', pos = (0,0,0), scale = (1.4,1,1))

    # Stuff
    self.lastBullet = 0

    # Light
    alight = AmbientLight('ambientLight')
    alight.setColor(Vec4(0.5, 0.5, 0.5, 1))
    alightNP = render.attachNewNode(alight)

    dlight = DirectionalLight('directionalLight')
    dlight.setDirection(Vec3(1, 1, -1))
    dlight.setColor(Vec4(0.7, 0.7, 0.7, 1))
    dlightNP = render.attachNewNode(dlight)

    render.clearLight()
    render.setLight(alightNP)
    render.setLight(dlightNP)


    # Input

    self.accept('escape', self.doExit)
    self.accept('r', self.doReset)
    self.accept('f1', self.toggleWireframe)
    self.accept('f2', self.toggleTexture)
    self.accept('f3', self.toggleDebug)
    self.accept('f5', self.doScreenshot)

    

    #self.accept('1', self.doShoot, [True])
    #self.accept('2', self.doShoot, [False])
    
    inputState.watchWithModifiers('forward', 'w')
    inputState.watchWithModifiers('up', 'q')
    inputState.watchWithModifiers('reverse', 's')
    inputState.watchWithModifiers('down', 'e')
    inputState.watchWithModifiers('turnLeft', 'a')
    inputState.watchWithModifiers('turnRight', 'd')
    inputState.watchWithModifiers('shoot', 'space')

    self.pFrom = Point3(0,0,0)
    self.pTo = Point3(0,0,0)

    x = 0
    x = 0

    #Continue text
    self.welcomeText = OnscreenText(text = 'Press space key to start', pos = (-0.5, 0.9), scale = 0.07)
    self.welcomeText.hide()
    # ^ hides text

    # Task
    taskMgr.add(self.update, 'updateWorld')

    # Physics
    self.setup()

  # ____HANDLER_____

  def processInput(self, dt): 
      force = Vec3(0,0,0)
      torque = Vec3(0,0,0)

      tankHead = self.boxNP.getH()

      headGradian = tankHead * (math.pi/180.0)

      x = 1.0*math.sin(headGradian)
      y = 1.0*math.cos(headGradian)


      #force.setX(-x)
      #force.setY(y)

      if inputState.isSet('forward'): 
          force.setX(-x)
          force.setY(y)

      if inputState.isSet('reverse'): 
          force.setX(x)
          force.setY(-y)

      #if inputState.isSet('left'): 
      #    force.setX(-headX)
      #    force.setX(headX)

      #if inputState.isSet('right'): 
      #    force.setY(headY)

      if inputState.isSet('turnLeft'): torque.setZ(0.5)
      if inputState.isSet('turnRight'): torque.setZ(-0.5)
      
      if inputState.isSet('up'):
          self.pTo.addZ(0.5)

      if inputState.isSet('down'):
          self.pTo.addZ(-0.5)

      if (self.lastBullet <= 0):
        if inputState.isSet('shoot'):
            self.doShoot(True)

      force *= 20.0
      torque *= 5.0

      self.boxNP.node().setActive(True)
      self.boxNP.node().applyCentralForce(force)
      self.boxNP.node().applyTorque(torque)
  
  def doExit(self):
      self.cleanup()
      sys.exit(1)

  def doReset(self):
      self.cleanup()
      self.setup()
      self.timerText.hide()
      self.text.hide()
      base.cam.setPos(self.boxNP.getPos().x,self.boxNP.getPos().y - 25, self.boxNP.getPos().z + 15)
      base.cam.lookAt(self.boxNP.getPos())
      self.gameState = 'PLAY'
  
  def toggleWireframe(self):
      base.toggleWireframe()

  def toggleTexture(self):
      base.toggleTexture()
   
  def toggleDebug(self):
      if self.debugNP.isHidden():
          self.debugNP.show()
      else:
          self.debugNP.hide()
  
  def doScreenshot(self):
      base.screenshot('Bullet')
  
  def drawLines(self):
      # draws lines for the ray
      self.lines.reset()
      # lines reset for translation purpose
      self.lines.drawLines([(self.pFrom,self.pTo)])
      self.lines.create()

  def drawBounding(self):
      self.boundingNorth.reset()
      self.boundingNorth.drawLines([(self.b1From,self.b1To)])
      self.boundingNorth.create()

      self.boundingSouth.reset()
      self.boundingSouth.drawLines([(self.b2From, self.b2To)])
      self.boundingSouth.create()

      self.boundingWest.reset()
      self.boundingWest.drawLines([(self.b3From, self.b3To)])
      self.boundingWest.create()

      self.boundingEast.reset()
      self.boundingEast.drawLines([(self.b4From, self.b4To)])
      self.boundingEast.create()

  def getDirection(self, getObject, getLength):
      heading = getObject.getH()
      pitch = getObject.getP()

      headingRadian = math.radians(heading)
      pitchRadian = math.radians(pitch)

      z = getLength*math.sin(pitchRadian)
      adjacent = getLength*math.cos(pitchRadian)

      x = adjacent*math.sin(headingRadian)
      y = adjacent*math.cos(headingRadian)

      x*=-1

      return Vec3(x,y,z)

  def doShoot(self, ccd):

      # Get from/to points from mouse click
      #pMouse = base.mouseWatcherNode.getMouse()

      pFrom = self.boxNP.getPos() + self.getDirection(self.boxNP, 1.425)#1.425
      pTo = self.boxNP.getPos() + self.getDirection(self.boxNP, 5.0)

      # initial velocity
      v = pTo - pFrom
      #ratio = v.length()/40
      v.normalize()
      v *= 25.0

      

      # Create bullet
      shape = BulletBoxShape(0.2)
      bullet = BulletRigidBodyNode('Bullet')
      bulletNP = self.worldNP.attachNewNode(bullet)
      bulletNP.node().addShape(shape)
      bulletNP.node().setMass(1.0)
      bulletNP.node().setDeactivationEnabled(False)
      bulletNP.node().setLinearVelocity(v)
      bulletNP.setPos(pFrom)
      bulletNP.setHpr(self.boxNP.getHpr())
      print bulletNP.getPos()

      bulletNP.setCollideMask(BitMask32.allOn())

      self.bulletvisualNP = loader.loadModel('smiley')
      self.bulletvisualNP.setScale(0.3)
      self.bulletvisualNP.clearModelNodes()
      self.bulletvisualNP.reparentTo(bulletNP)

      self.lastBullet = 3
      
      if ccd:
          bulletNP.node().setCcdMotionThreshold(1e-7)
          bulletNP.node().setCcdSweptSphereRadius(0.50);

      self.world.attachRigidBody(bulletNP.node())
      # Remove the bullet again after 10 seconds
      taskMgr.doMethodLater(1,self.doRemove,'doRemove', extraArgs=[bulletNP], appendTask=True)

      


  def playGame(self):
      print "Entered playGame function"
      self.menuImage.hide()
      self.playButton.hide()
      self.instButton.hide()
      self.exitButton.hide()
      self.text.setText("Press ADWS to aim. 1 to fire")
      self.gameState = 'PLAY'

  def Inst(self):
      self.gameState = 'INST'
      print "Entered instructions function"
      self.playButton.hide()
      self.instButton.hide()
      self.exitButton.hide()
      self.backButton = DirectButton( text = ("Back"), scale = .1, command = self.doContinue)

      
  def doContinue(self):
      if (self.gameState == 'INIT') or (self.gameState == 'INST'):
        self.gameState = 'MENU'
        self.imageObject.hide()
        self.menuImage.show()
        # Add Buttons here
        self.playButton = DirectButton( text = ("Start Game"), scale =.2,pos =(0,0,0.5), command =self.playGame)
        self.instButton = DirectButton( text = ("Instructions"), scale = .2, command = self.Inst)
        self.exitButton = DirectButton( text = ("Exit"), scale = .2,pos=(0,0,-0.5), command = self.doExit)
        return
      if self.gameState == 'NEXT':
          self.gamelevel += 1
          self.doReset()


  def update(self, task):
    dt = globalClock.getDt()
    
    if self.gameState == "INIT":
        #Progress bar can be implemented here
        self.timerText.hide()
        if self.count == 0:
            loader.loadModel('smiley')
            self.count +=1
        if self.count == 1:
            loader.loadModel('frowney')
            #splashscreen
            self.menuImage = OnscreenImage( image = 'media/Menu.jpg', pos = (0,0,0), scale=(1.4,1,1))
            self.menuImage.hide()
            self.count+=1
        if self.count == 2:
            self.text.show()
            #self.imageObject.hide()
            self.accept('space', self.doContinue)
            #waits for a key press

    if self.gameState == 'PLAY':
      base.cam.setPos(0,-50,18)
      base.cam.lookAt(self.boxNP.getPos())
      #base.cam.setH(self.boxNP.getH())
      self.processInput(dt)
      self.timer -= dt;
      self.lastBullet -= dt;
      self.timerText.setText("Time Left: %s" % int(self.timer))
      self.timerText.show()
      self.lineCollision()
      self.drawBounding()
      #self.processContacts()
      self.world.doPhysics(dt, 20, 1.0/180.0)
      self.drawLines()

    if self.gameState == 'INST':
      self.processInput(dt)
      

    if self.gameState == 'NEXT':
      self.world.doPhysics(dt, 20, 1.0/180.0)

    return task.cont

    #INIT - load everything
    #PLAY - Process userinput, check collision
    #NEXT - Increase game level and reset game (check doReset, it should go back to gameState)
    #Process Contacts, checks contacts between bullets and boxes.

  def doRemove(self,bulletNP, task):
    self.world.removeRigidBody(bulletNP.node())
    self.bulletvisualNP.removeNode()
    if (self.win == False):
        self.text.setText("Failed! Press Space to continue!")
    return task.done

  def lineCollision(self):
      result1 = self.world.rayTestAll(self.b1From, self.b1To)
      if result1.hasHits():
          for hit in result1.getHits():
              self.boxNP.setPos(self.boxNP.getPos().x, hit.getHitPos().y-2, hit.getHitPos().z)
        
      result2 = self.world.rayTestAll(self.b2From, self.b2To)
      if result2.hasHits():
          for hit in result2.getHits():
              self.boxNP.setPos(self.boxNP.getPos().x, hit.getHitPos().y+2, hit.getHitPos().z)

      result3 = self.world.rayTestAll(self.b3From, self.b3To)
      if result3.hasHits():
          for hit in result3.getHits():
              self.boxNP.setPos(self.boxNP.getPos().x+2, hit.getHitPos().y, hit.getHitPos().z)

      result4 = self.world.rayTestAll(self.b4From, self.b4To)
      if result4.hasHits():
          for hit in result4.getHits():
              self.boxNP.setPos(self.boxNP.getPos().x-2, hit.getHitPos().y+2, hit.getHitPos().z)

  # ____TASK___


  #def processContacts(self):
      #for box in self.boxes:
      #    result = self.world.contactTestPair(box, self.smiley)
      #    if result.getNumContacts() > 0:
      #          self.text.setText("Success")
      #          self.gameState = 'NEXT'
      #          self.win = True

  def cleanup(self):
    self.world = None
    self.worldNP.removeNode()
    self.worldNP = None
    self.lines.reset()
 
    

  # ____SETUP___
  def setup(self):
    self.worldNP = render.attachNewNode('World')

    # Stuff Placement
    self.boxes = []
    self.createBox(Vec3(8,8,1),Vec3(0,0,23))

    # World
    self.debugNP = self.worldNP.attachNewNode(BulletDebugNode('Debug'))
    self.debugNP.show()

    self.world = BulletWorld()
    self.world.setGravity(Vec3(0, 0, -9.81))

    self.world.setDebugNode(self.debugNP.node())

    # Plane
    shape = BulletPlaneShape(Vec3(0, 0, 1), 0)
    body = BulletRigidBodyNode('Ground')
    bodyNP = self.worldNP.attachNewNode(body)
    bodyNP.node().addShape(shape)
    bodyNP.setPos(0, 0, -1)
    bodyNP.setCollideMask(BitMask32.allOn())
    self.world.attachRigidBody(bodyNP.node())

    # Aiming line
    self.lines = LineNodePath(parent = render, thickness = 3.0, colorVec = Vec4(1,0,0,1))
    
    # Bounding
    self.boundingNorth = LineNodePath(parent = render, thickness =5.0, colorVec = Vec4(1,0,0,1))
    self.b1From = Point3(-30,50,0)
    self.b1To = Point3(30,50,0)

    self.boundingSouth = LineNodePath(parent = render, thickness =5.0, colorVec = Vec4(1,0,0,1))
    self.b2From = Point3(-30,-20,0)
    self.b2To = Point3(30,-20,0)

    self.boundingWest = LineNodePath(parent = render, thickness =5.0, colorVec = Vec4(1,0,0,1))
    self.b3From = Point3(-30,50,0)
    self.b3To = Point3(-30,-20,0)

    self.boundingEast = LineNodePath(parent = render, thickness =5.0, colorVec = Vec4(1,0,0,1))
    self.b4From = Point3(30,50,0)
    self.b4To = Point3(30,-20,0)


    # Boxes
    shape = BulletBoxShape(Vec3(1, 1, 1))

    self.boxNP = self.worldNP.attachNewNode(BulletRigidBodyNode('Tank'))
    self.boxNP.node().setMass(5.0)
    self.boxNP.node().addShape(shape)
    self.boxNP.setPos(0, 0, 2)
    self.boxNP.setCollideMask(BitMask32(0x0f))

    self.world.attachRigidBody(self.boxNP.node())

    visualNP = loader.loadModel('media/box.egg')
    tex = loader.loadTexture('media/wood.png') 
    visualNP.setTexture(tex, 1)
    visualNP.setPos(0,0,0)
    visualNP.setScale(2)
    visualNP.reparentTo(self.boxNP)
    self.lines.reparentTo(self.boxNP)

    #self.pFrom = Point3(self.boxNP.getPos() + self.getDirection(self.boxNP, 1.4))
    #self.pTo = Point3(self.boxNP.getPos() + self.getDirection(self.boxNP, 2.0))

    #self.pFrom = Point3(self.boxNP.getPos().x ,self.boxNP.getPos().y-1,self.boxNP.getPos().x)
    #self.pTo = Point3(0,self.boxNP.getPos().y+20,self.boxNP.getPos().z+10)

    self.pFrom = Point3(self.boxNP.getPos().x ,self.boxNP.getPos().y,self.boxNP.getPos().x) + self.getDirection(self.boxNP, 1.0)
    self.pTo = Point3(self.boxNP.getPos()) + self.getDirection(self.boxNP, 5.0)

    #setup text
    self.text = OnscreenText(text = 'Sonicomi!~',pos = (-0.5, 0.5), scale = 0.09)
    self.timerText = OnscreenText(text = '', pos = (0.5,0.5), scale = 0.09)
 
  def createBox(self,size,pos):
    shape = BulletBoxShape(size)
    self.box = BulletRigidBodyNode('Obstacle')
    bodyNP = self.worldNP.attachNewNode(self.box)
    bodyNP.node().addShape(shape)
    bodyNP.node().setMass(1.0)
    bodyNP.node().setFriction(1.0)
    bodyNP.node().setDeactivationEnabled(False)
    bodyNP.setPos(pos)
    bodyNP.setCollideMask(BitMask32.allOn())
    self.world.attachRigidBody(bodyNP.node())
    visNP = loader.loadModel('models/box.egg')
    visNP.setScale(size*2)
    visNP.clearModelNodes()
    visNP.reparentTo(bodyNP)
    self.boxes.append(self.box)    
    
 
game = Main()
run()


Would you mind giving to us the error output, and in particular the line on which it’s failing, please? That might make it easier to find the problem.

You’re probably starting with something too complicated if you’re trying to learn the Python programming language, AND the Panda3D API, and what I assume is copy-pasted code from a demo written by someone else, all at the same time. A brief look at this code reveals that this Main class has a setup method that needs to be called first, so try…

game = Main()
game.setup()
run()

Personally I hate “god” classes like this. There’s only ever one and it does everything, so what’s even the point of it being a class? But that’s a discussion for another time.

From my quick looks it appears that the “setup” method is being called in “init”; I’d guess that it’s more likely a matter of when it’s called than it not being called at all. Kassarin, once again it might help to see the error itself, and in particular the line to which it refers.

Oops, right you are. I did “ctrl-f self.world”, saw that it was created in setup and assumed setup hadn’t been called.

Now I see that in setup another method, createBox, is being called. createBox requires self.world, but self.world isn’t created until further down in setup. Can’t guarantee that’s the only problem, but it’s one!

  # ____SETUP___
  def setup(self):
    self.worldNP = render.attachNewNode('World')

    # Stuff Placement
    self.boxes = []
    self.createBox(Vec3(8,8,1),Vec3(0,0,23))

    # World
    self.debugNP = self.worldNP.attachNewNode(BulletDebugNode('Debug'))
    self.debugNP.show()

    self.world = BulletWorld()
    self.world.setGravity(Vec3(0, 0, -9.81)) 

As said above your creating boxes with physics before the physics world even exists just move it up.
This was all it took to me it work for me.