How do I apply physics to the camera in Python?

So, I was trying to code a basic 3D game, and I have been trying to add physics to the camera. Right now you can walk around with the W, A, S, D which will show you debug info (in the console) and looking around probably won’t help but it can be done with Y, G, H, J. I have tried things like putting physics on an invisible object and syncing it to the player. Can anyone help? Thanks in advance.

Code:

from math import pi, sin, cos

from direct.showbase.ShowBase import ShowBase
from direct.task import Task
from direct.actor.Actor import Actor
from direct.interval.IntervalGlobal import Sequence
from panda3d.core import Point3
from panda3d.bullet import BulletWorld
from panda3d.bullet import BulletPlaneShape
from panda3d.bullet import BulletRigidBodyNode
from panda3d.bullet import BulletBoxShape
from panda3d.ode import OdeWorld
from panda3d.ode import OdeBody, OdeMass
import keyboard

class App(ShowBase):
    def moveForward(self,catch):
        print('w key pressed')
        self.current_pos = (self.current_pos[0], self.current_pos[1] + 1,self.current_pos[2])
        print(self.current_pos)
    def moveBackward(self,catch):
        print('w key pressed')
        self.current_pos = (self.current_pos[0], self.current_pos[1] - 1,self.current_pos[2])
        print(self.current_pos)
    def moveLeft(self,catch):
        print('w key pressed')
        self.current_pos = (self.current_pos[0] + 1, self.current_pos[1],self.current_pos[2])
        print(self.current_pos)
    def moveRight(self,catch):
        print('w key pressed')
        self.current_pos = (self.current_pos[0] - 1, self.current_pos[1],self.current_pos[2])
        print(self.current_pos)
    def lookLeft(self,catch):
        self.current_hpr = (self.current_hpr[0] + 90, self.current_hpr[1],self.current_hpr[2])
    def lookRight(self,catch):
        self.current_hpr = (self.current_hpr[0] + 90, self.current_hpr[1],self.current_hpr[2])
    def lookUp(self,catch):
        self.current_hpr = (self.current_hpr[0],self.current_hpr[1] - 1,self.current_hpr[2])

    def moveUp(self,catch):
        self.current_pos = (self.current_pos[0],self.current_pos[1],self.current_pos[2] + 1)
    def lookUp(self,catch):
        self.current_hpr = (self.current_hpr[0],self.current_hpr[1] + 1,self.current_hpr[2])
    def lookDown(self,catch):
        self.current_hpr = (self.current_hpr[0],self.current_hpr[1] - 1,self.current_hpr[2])

    def fire(self,catch):
        print("Fire!")
        if self.pandaPos[0] == self.current_pos[0] and self.pandaPos[2] + 0.5 == self.current_pos[2] and not self.pandaPos[1] == self.current_pos[1] and not self.pandaPos[1] - 1 == self.current_pos[1] and not self.current_pos[1] - 1 == self.pandaPos[1] and not self.current_pos[1] > self.pandaPos[1]:
            print('Oof')
    def __init__(self):
        ShowBase.__init__(self)
        #self.word = BulletWorld()
        
        s = self.loader.loadSfx('Doom Soundtrack - Level 1 (Extended).mp3')
        s.play()
        self.current_pos = (10, 5, 1)
        self.current_hpr = (0,0,0)
        self.physicsMgr.attachPhysicalNode(self.camera)
        keyboard.on_press_key('w', self.moveForward)
        keyboard.on_press_key('s', self.moveBackward)
        keyboard.on_press_key('j',self.lookLeft)
        keyboard.on_press_key('y',self.lookUp)
        keyboard.on_press_key('h',self.lookDown)
        keyboard.on_press_key('g',self.lookRight)
        keyboard.on_press_key('d', self.moveLeft)
        keyboard.on_press_key('a', self.moveRight)
        keyboard.on_press_key('space', self.fire)
        #keyboard.on_press_key('y',self.lookUp)
        keyboard.on_press_key('1',self.moveUp)
        self.camera.setPos(self.current_pos)
        self.camera.setHpr(self.current_hpr)
        self.camera.reparentTo(self.render)
        self.disableMouse()
        self.scene = self.loader.loadModel("models/environment.egg.pz")
        self.scene.reparentTo(self.render)
        self.scene.setScale(0.25,0.25,0.25)
        self.scene.setPos(-8,40,0)
        #self.taskMgr.add(self.spinCameraTask,"SpinCameraTask")
        self.taskMgr.add(self.moveChar,"MoveChar")
        self.taskMgr.add(self.moveCBod,"MoveCBod")
        self.pandaActor = Actor("cubearm.egg",{"walk":"cubearm4-ArmatureAction.egg"})
        self.pandaActor.setScale(0.12,0.12,0.12)
        self.pandaActor.setPos((10,10,0.5))
        self.pandaPos = (10,10,0.5)
        self.pandaActor.reparentTo(self.render)
        self.pandaActor.loop('walk')
        '''
        posInterval1 = self.pandaActor.posInterval(13,Point3(0,-10,0),startPos=Point3(0,10,0))
        posInterval2 = self.pandaActor.posInterval(13,Point3(0,10,0),startPos=Point3(0,-10,0))
        hprInterval1 = self.pandaActor.hprInterval(3,Point3(180,0,0),startHpr=Point3(0,0,0))
        hprInterval2 = self.pandaActor.hprInterval(3,Point3(0,0,0),startHpr=Point3(180,0,0))
        self.pandaPace = Sequence(posInterval1,hprInterval1,posInterval2,hprInterval2,name="pandaPace")
        self.pandaPace.loop()
        '''
    
    def spinCameraTask(self,task):
        print(task.time * 6.0)
        angleDegrees = task.time * 6.0
        angleRadians = angleDegrees * (pi / 180.0)
        self.camera.setPos(20 * sin(angleRadians), -20 * cos(angleRadians),3)
        self.camera.setHpr(angleDegrees,0,0)
        return Task.cont
    def moveChar(self,task):
        self.camera.setPos(self.current_pos)
        self.camera.setHpr(self.current_hpr)

        self.cambody.setPosition(self.fxboy.getPos(self.render))
        self.cambody.setQuaternion(self.fxboy.getQuat(self.render))
        
        # Sync camera and FxBoy
        
    
        return task.cont
    def moveCBod(self,task):
        self.cambody.setPosition(self.camera.getPos(self.render))
        self.cambody.setQuaternion(self.camera.getQuat(self.render))
        self.world.quickStep(1.0 / 90.0)
        # self.world.doPhysics(globalClock.getDt)
        return task.cont
    
app = App()
app.run()

To some degree this may depend on just what you mean by “physics”–specifically, do you want full dynamic physics, with forces and torque and so on, or do you just want to keep characters from passing through walls?

If the latter, you might want to look into Panda’s built-in collision system. The manual discusses it starting here, and my “beginner’s tutorial” covers it starting here.

(Note in the case of the tutorial that this would mean jumping in partway through the lessons, which may leave you lacking context somewhat. It might be worth starting from the beginning.)

If, however, you want complex physics–rolling balls, levers, automatic stacking, etc.–then it might be worth looking into the Bullet physics engine. I believe that the manual starts discussing this here.

(I note, by the way, that you’re importing classes for both Bullet and ODE–two different physics engines. Do you intend to use both?)

I want to use gravity and I have got it to work with an object that isn’t the camera. Also, the ODE and Bullet imports are done because this is anywhere near ready for deployment and I didn’t feel like removing it.

Ah, I see.

Well, the camera is a node like any other, so you should be able to do the same with it as you did with that other object.

Alternatively, you should be able to simply reparent the camera to the object for which you have working gravity–that should result in the camera moving with that object.

Fair enough! :slight_smile:

That’s a smart idea, I’ll try that later.

1 Like

Okay so I used BulletCharacterController and it kinda works…
For some reason everything floats up. Can you help?
Code:

from math import pi, sin, cos

from direct.showbase.ShowBase import ShowBase
from direct.task import Task
from direct.actor.Actor import Actor
from direct.interval.IntervalGlobal import Sequence
from panda3d.core import Point3, Vec3
from panda3d.bullet import BulletWorld
from panda3d.bullet import BulletPlaneShape
from panda3d.bullet import BulletRigidBodyNode
from panda3d.bullet import BulletBoxShape
from panda3d.ode import OdeWorld
from panda3d.ode import OdeBody, OdeMass
import keyboard
from panda3d.bullet import BulletCharacterControllerNode
from panda3d.bullet import BulletCapsuleShape
from panda3d.bullet import ZUp
height = 1.75
radius = 0.4

class App(ShowBase):
    def moveForward(self,catch):
        print('w key pressed')
        self.cy = 3.0
        print(self.current_pos)
    def moveBackward(self,catch):
        print('w key pressed')
        self.cy = -3.0
        print(self.current_pos)
    def moveLeft(self,catch):
        print('w key pressed')
        self.cx = 3.0
        print(self.current_pos)
    def moveRight(self,catch):
        print('w key pressed')
        self.cx = -3.0
        print(self.current_pos)
    def lookLeft(self,catch):
        self.current_omega = 120
    def lookRight(self,catch):
        self.current_omega = -120
    def lookUp(self,catch):
        self.current_hpr = (self.current_hpr[0],self.current_hpr[1] - 1,self.current_hpr[2])

    def moveUp(self,catch):
        self.current_pos = (self.current_pos[0],self.current_pos[1],self.current_pos[2] + 1)
    def lookUp(self,catch):
        self.current_hpr = (self.current_hpr[0],self.current_hpr[1] + 1,self.current_hpr[2])
    def lookDown(self,catch):
        self.current_hpr = (self.current_hpr[0],self.current_hpr[1] - 1,self.current_hpr[2])

    def fire(self,catch):
        print("Fire!")
        if self.pandaPos[0] == self.current_pos[0] and self.pandaPos[2] + 0.5 == self.current_pos[2] and not self.pandaPos[1] == self.current_pos[1] and not self.pandaPos[1] - 1 == self.current_pos[1] and not self.current_pos[1] - 1 == self.pandaPos[1] and not self.current_pos[1] > self.pandaPos[1]:
            print('Oof')

    def jump(self,catch):
        self.player.setMaxJumpHeight(5.0)
        self.player.setJumpSpeed(8.0)
        self.player.doJump()
    def __init__(self):
        ShowBase.__init__(self)
        #self.word = BulletWorld()
        self.cx = 0.0
        self.cy = 0.0
        self.current_omega = 0
        s = self.loader.loadSfx('Doom Soundtrack - Level 1 (Extended).mp3')
        s.play()
        self.current_pos = (10, 5, 1)
        self.current_hpr = (0,0,0)
        #self.fxboy = self.loader.loadModel("cubearm.egg")
        self.world = BulletWorld()
        self.world.setGravity(Vec3(0, 0, -12.0))
        #self.physicsMgr.attachPhysicalNode(self.camera)
        keyboard.on_press_key('w', self.moveForward)
        keyboard.on_press_key('s', self.moveBackward)
        keyboard.on_press_key('j',self.lookLeft)
        keyboard.on_press_key('y',self.lookUp)
        keyboard.on_press_key('h',self.lookDown)
        keyboard.on_press_key('g',self.lookRight)
        keyboard.on_press_key('d', self.moveLeft)
        #self.fxboy.reparentTo(self.render)
        #self.camera.reparentTo(self.render)
        #self.camera.reparentTo(self.fxboy)
        keyboard.on_press_key('a', self.moveRight)
        keyboard.on_press_key('space', self.fire)
        keyboard.on_press_key('2',self.jump)
        #keyboard.on_press_key('y',self.lookUp)
        keyboard.on_press_key('1',self.moveUp)
        
        self.cam = BulletCapsuleShape(radius, height - 2*radius, ZUp)
        self.player = BulletCharacterControllerNode(self.cam,0.4,'Player')
        self.playernp = self.render.attachNewNode(self.player)
        self.world.attachCharacter(self.playernp.node())
        self.camera.reparentTo(self.playernp)
        self.playernp.setPos(self.current_pos)
        self.playernp.setHpr(self.current_hpr)
        self.playernp.setH(45)
        self.disableMouse()

        self.scenes = BulletBoxShape(Vec3(0.25,0.25,0.25))
        self.scenenode = BulletRigidBodyNode('Scene')
        self.scenenode.setMass(12.0)
        self.scenenode.addShape(self.scenes)
        self.scenenp = render.attachNewNode(self.scenenode)
        self.scenenp.setPos(-8,40,0)
        self.world.attachRigidBody(self.scenenode)
        self.scene = self.loader.loadModel("models/environment.egg.pz")
        self.scene.reparentTo(self.render)
        self.scene.setScale(0.25,0.25,0.25)
        self.scene.setPos(-8,40,0)
        self.scene.reparentTo(self.scenenp)
        
        #self.taskMgr.add(self.spinCameraTask,"SpinCameraTask")
        self.taskMgr.add(self.moveChar,"MoveChar")
        self.taskMgr.add(self.moveCBod,"MoveCBod")
        self.pandaActor = Actor("cubearm.egg",{"walk":"cubearm4-ArmatureAction.egg"})
        self.pandaActor.setScale(0.12,0.12,0.12)
        self.pandaActor.setPos((10,10,0.5))
        self.pandaPos = (10,10,0.5)
        self.pandaActor.reparentTo(self.render)
        self.pandaActor.loop('walk')
        '''
        posInterval1 = self.pandaActor.posInterval(13,Point3(0,-10,0),startPos=Point3(0,10,0))
        posInterval2 = self.pandaActor.posInterval(13,Point3(0,10,0),startPos=Point3(0,-10,0))
        hprInterval1 = self.pandaActor.hprInterval(3,Point3(180,0,0),startHpr=Point3(0,0,0))
        hprInterval2 = self.pandaActor.hprInterval(3,Point3(0,0,0),startHpr=Point3(180,0,0))
        self.pandaPace = Sequence(posInterval1,hprInterval1,posInterval2,hprInterval2,name="pandaPace")
        self.pandaPace.loop()
        '''
    
    def spinCameraTask(self,task):
        print(task.time * 6.0)
        angleDegrees = task.time * 6.0
        angleRadians = angleDegrees * (pi / 180.0)
        self.camera.setPos(20 * sin(angleRadians), -20 * cos(angleRadians),3)
        self.camera.setHpr(angleDegrees,0,0)
        return Task.cont
    def moveChar(self,task):
        speed = Vec3(0,0,0)
        omega = self.current_omega
        speed.setX(self.cx)
        speed.setY(self.cy)
        self.player.setAngularMovement(omega)
        self.player.setLinearMovement(speed,True)
        #self.playernp.setPos(self.current_pos)
        self.playernp.setHpr(self.current_hpr)
    
        return task.cont
    def moveCBod(self,task):
        self.world.doPhysics(globalClock.getDt())
        return task.cont
    
app = App()
app.run()

I’ve never used the Bullet character controller, I’m afraid: when I’ve made relatively simple games I’ve generally used Panda’s built-in collision system and my own controller logic, and in my more-complex main project I use a custom Bullet character-controller.

Okay, all I need to do is stop gravity on the scene, do you (or anyone else) know how to do it?
Thanks in advance.

If the code is using Bullet’s built-in handling of gravity, then you could try calling “setGravity(0, 0, 0)” on your Bullet-world (see the API reference here). I also see that the character-controller has an option to set the value of gravity for itself, specifically.

For some reason I fall through the floor. My code is here.
Can you help?
Thanks in advance.

Well, I see that you’re still setting gravity–I’d suggest removing the scene-node’s gravity-assignment altogether, and changing the Bullet-world’s gravity-setting to (0, 0, 0).

That said… If I may, if this game is intended to be at all like Doom, even “Doom with jumping”, I’m inclined to suggest that you don’t use “proper physics” at all. Physics can be a finicky thing, I fear, and it seems likely to me to cause more headaches than problems solved.

For a simple first-person-shooter, I’d instead suggest just using Panda’s built-in collision system, along with some simple logic for applying gravity when airborne. That seems likely to me to be far easier to work with, being likely to involve rather fewer caveats than a full physics system.

Of course, if you intend more than just jumping and falling–if you want physics puzzles, or complex character controllers, or the like–then naturally ignore this suggestion and stick with Bullet!

1 Like

The problem was really simple… I forgot to add collision so the player didn’t collide with the ground. However I’ve been trying to implement fire and can’t seem to find where the particle panel is, Can you help?
Thanks in advance

This might be better suited to a separate thread, but the short answer is that it should be found amongst the samples, under “particles”. That said, it may also be worth looking into a community-made particle editor that I recall seeing posted a while ago; a search of the forum might turn it up.

I found the Particle Panel and made my particle but then the particle didn’t appear. I tried the example in the samples folder and nothing happened. I made a different thread for this here.