my FPS script awsd+jumping wall sliding

here is my little script i would like some code review and maybe clean it up:
------------- update ------------------
istrolid.com/p/panda3d/simple-fps.zip
------------- update ------------------


import direct.directbase.DirectStart
from direct.showbase.DirectObject import DirectObject
from pandac.PandaModules import *

class FPS(object,DirectObject):
    def __init__(self):
        self.initCollision()
        self.loadLevel()
        self.initPlayer()
        
    def initCollision(self):
        #initialize traverser
        base.cTrav = CollisionTraverser()
        #initialize pusher
        self.pusher = CollisionHandlerPusher()
        
    def loadLevel(self):
        
        #load level
        # must have
        #<Group> *something* { 
        #  <Collide> { Polyset keep descend } in the egg file
        level = loader.loadModel('level.egg')
        level.reparentTo(render)
        level.setPos(0,0,0)
        level.setTwoSided(True)
        level.setColor(1,1,1,.5)
                
    def initPlayer(self):
        
        #load man
        self.man = loader.loadModel('teapot')
        self.man.reparentTo(render)
        self.man.setPos(0,0,2)
        self.man.setScale(.05)
        base.camera.reparentTo(self.man)
        base.camera.setPos(0,0,0)
        base.disableMouse()
        #create a collsion solid for the man
        cNode = CollisionNode('man')
        cNode.addSolid(CollisionSphere(0,0,0,3))
        manC = self.man.attachNewNode(cNode)
        base.cTrav.addCollider(manC,self.pusher)
        self.pusher.addCollider(manC,self.man, base.drive.node())
        
        speed = 50
        Forward = Vec3(0,speed*2,0)
        Back = Vec3(0,-speed,0)
        Left = Vec3(-speed,0,0)
        Right = Vec3(speed,0,0)
        Stop = Vec3(0)
        self.walk = Stop
        self.strife = Stop
        self.jump = 0
        taskMgr.add(self.move, 'move-task')
        self.accept( "space" , self.__setattr__,["jump",1.])
        self.accept( "s" , self.__setattr__,["walk",Back] )
        self.accept( "w" , self.__setattr__,["walk",Forward])
        self.accept( "s" , self.__setattr__,["walk",Back] )
        self.accept( "s-up" , self.__setattr__,["walk",Stop] )
        self.accept( "w-up" , self.__setattr__,["walk",Stop] )
        self.accept( "a" , self.__setattr__,["strife",Left])
        self.accept( "d" , self.__setattr__,["strife",Right] )
        self.accept( "a-up" , self.__setattr__,["strife",Stop] )
        self.accept( "d-up" , self.__setattr__,["strife",Stop] )
        
        self.manGroundRay = CollisionRay()
        self.manGroundRay.setOrigin(0,0,-.2)
        self.manGroundRay.setDirection(0,0,-1)
        
        self.manGroundCol = CollisionNode('manRay')
        self.manGroundCol.addSolid(self.manGroundRay)
        self.manGroundCol.setFromCollideMask(BitMask32.bit(0))
        self.manGroundCol.setIntoCollideMask(BitMask32.allOff())
        
        self.manGroundColNp = self.man.attachNewNode(self.manGroundCol)
        self.manGroundColNp.show()
        self.manGroundHandler = CollisionHandlerQueue()
        
        base.cTrav.addCollider(self.manGroundColNp, self.manGroundHandler)
        
    def move(self,task):
        
        # mouse
        md = base.win.getPointer(0)
        x = md.getX()
        y = md.getY()
        if base.win.movePointer(0, base.win.getXSize()/2, base.win.getYSize()/2):
            self.man.setH(self.man.getH() -  (x - base.win.getXSize()/2)*0.1)
            base.camera.setP(base.camera.getP() - (y - base.win.getYSize()/2)*0.1)
        # move where the keys set it
        self.man.setPos(self.man,self.walk*globalClock.getDt())
        self.man.setPos(self.man,self.strife*globalClock.getDt())
        
        highestZ = -100
        for i in range(self.manGroundHandler.getNumEntries()):
            entry = self.manGroundHandler.getEntry(i)
            if entry.getIntoNode().getName() == "Cube":
                z = entry.getSurfacePoint(render).getZ()
                if z > highestZ:
                    highestZ = z
        # graity
        self.man.setZ(self.man.getZ()+self.jump*globalClock.getDt())
        self.jump -= 1*globalClock.getDt()
        
        if highestZ > self.man.getZ()-.3:
            self.jump = 0
            self.man.setZ(highestZ+.3)
        
        return task.cont
FPS()
render.setShaderAuto()
run() 

Looks good. Maybe this could be included in the samples, if we could get a better texture for the walls.
A few suggestions:

  • There’s no jumping limiter. I can fly!
  • Add escape key press to quit. Kinda annoying if you can’t quit.

I dont know if using setattr is such a good idea for a example.
I mean it’s good to learn any new command for a newbie, but this might be a bit off-the panda3d topic.
Just would like to hear other opinions on this.

yeah me too. I like using setattr its makes the code much shorter and its clear what it does but i don’t know if its to much of python magic

It’s messy. It can be a lot simpler, since it is … Panda3D ! :smiley:

import direct.directbase.DirectStart
from direct.showbase.DirectObject import DirectObject
from pandac.PandaModules import *
from direct.interval.IntervalGlobal import LerpFunc
import sys

class FPS(object,DirectObject):
    def __init__(self):
        self.winXhalf = base.win.getXSize()/2
        self.winYhalf = base.win.getYSize()/2
        self.initCollision()
        self.loadLevel()
        self.initPlayer()

    def initCollision(self):
        #initialize traverser
        base.cTrav = CollisionTraverser()
        base.cTrav.setRespectPrevTransform(True)
#         base.cTrav.showCollisions(render)
        #initialize pusher
        self.pusher = CollisionHandlerPusher()
        # collision bits
        self.groundCollBit = BitMask32.bit(0)
        self.collBitOff = BitMask32.allOff()

    def loadLevel(self):

        #load level
        # must have
        #<Group> *something* {
        #  <Collide> { Polyset keep descend } in the egg file
        level = loader.loadModel('level.egg')
        level.reparentTo(render)
        level.setPos(0,0,0)
        level.setColor(1,1,1,.5)

    def initPlayer(self):
        #load man
        self.man = render.attachNewNode('man') # keep this node scaled to identity
        self.man.setPos(0,0,0)
        # should be avatar model
#         model = loader.loadModel('teapot')
#         model.reparentTo(self.man)
#         model.setScale(.05)
        # camera
        base.camera.reparentTo(self.man)
        base.camera.setPos(0,0,1.7)
        base.camLens.setNearFar(.1,1000)
        base.disableMouse()
        #create a collsion solid around the man
        manC = self.man.attachCollisionSphere('manSphere', 0,0,1, .4, self.groundCollBit,self.collBitOff)
        self.pusher.addCollider(manC,self.man)
        base.cTrav.addCollider(manC,self.pusher)

        speed = 4
        Forward = Vec3(0,speed*2,0)
        Back = Vec3(0,-speed,0)
        Left = Vec3(-speed,0,0)
        Right = Vec3(speed,0,0)
        Stop = Vec3(0)
        self.walk = Stop
        self.strife = Stop
        self.jump = 0
        taskMgr.add(self.move, 'move-task')
        self.jumping = LerpFunc( Functor(self.__setattr__,"jump"),
                                 duration=.25, fromData=.25, toData=0)
        self.accept( "escape",sys.exit )
        self.accept( "space" , self.startJump)
        self.accept( "s" , self.__setattr__,["walk",Back] )
        self.accept( "w" , self.__setattr__,["walk",Forward])
        self.accept( "s-up" , self.__setattr__,["walk",Stop] )
        self.accept( "w-up" , self.__setattr__,["walk",Stop] )
        self.accept( "a" , self.__setattr__,["strife",Left])
        self.accept( "d" , self.__setattr__,["strife",Right] )
        self.accept( "a-up" , self.__setattr__,["strife",Stop] )
        self.accept( "d-up" , self.__setattr__,["strife",Stop] )

        self.manGroundColNp = self.man.attachCollisionRay( 'manRay',
                                                           0,0,.6, 0,0,-1,
                                                           self.groundCollBit,self.collBitOff)
        self.manGroundHandler = CollisionHandlerGravity()
        self.manGroundHandler.addCollider(self.manGroundColNp,self.man)
        base.cTrav.addCollider(self.manGroundColNp, self.manGroundHandler)

    def startJump(self):
        if self.manGroundHandler.isOnGround():
           self.jumping.start()

    def move(self,task):
        dt=globalClock.getDt()
        # mouse
        md = base.win.getPointer(0)
        x = md.getX()
        y = md.getY()
        if base.win.movePointer(0, self.winXhalf, self.winYhalf):
            self.man.setH(self.man, (x - self.winXhalf)*-0.1)
            base.camera.setP( clampScalar(-90,90, base.camera.getP() - (y - self.winYhalf)*0.1) )
        # move where the keys set it
        moveVec=(self.walk+self.strife)*dt # horisontal
        moveVec.setZ( self.jump )          # vertical
        self.man.setFluidPos(self.man,moveVec)
        # jump damping
        if self.jump>0:
           self.jump = clampScalar( 0,1, self.jump*.9 )

        return task.cont
FPS()
render.setShaderAuto()
run()

jnjh, you are inheriting from DirectObject, why would you?

ill update with my new script when i get home. I think ynjh_jo made the sliding/jump correct without the down facing ray that would be awsome! Ill check the code tonight and integrate it to the FPS game if it works.

One thing you forgot treeform. Is a way to EXIT the script. I kept hitting ESC and Q key finally I had to do a CTRL_ALT_DEL to get out of it.

Thanks Treeform… this is going to be really useful for me and a lot of people out there I’m sure… I hope that this or something similar will be in the samples dir in a future release.

The default fov makes it a bit wacky though… I changed mine to 70 (hl2’s default I think)… easier to understand what’s going on.

yes Crimity fov also bothered me ill change it. I would also want to get crates, doors and some monsters in. I also want to keep the script as small and as intuitive as possible good thing python makes this easy. Stay tuned for next iteration.

It’s not me, it’s Treeform. What’s the problem anyway ? The scene is too simple, so I think it’s OK to leave it that way.

No, that ray is still around, but I use the simpler construction method in libpandamodules.py, since it’s 1 collision node with 1 solid only.
Treeform, why don’t you fix the reversed normals by hands ? Simply using 2-sided render mode doesn’t solve it entirely. CHPusher uses the normal to determine where to push, so if you’re colliding to the reversed wall, you’d sucked out of the room.

yes i fixed lots of the bugs and cleaned up the code ill use your ray system and post a new sample when i finally have some time!

i have added a new sample take a look if you like
istrolid.com/p/panda3d/simple-fps.zip

Hi, treeform! I think there are some problems in your code:
Still unable to exit with escape key
When i comment render.setShaderAuto() with # it has no effect
Jumping is very slow and not real (some kind of character’s center jumping, not the legs)
There is a mouse cursor visible during the game
No limits to move camera up and down
It’s possible to see teapot cover under the player itself :wink:

PS. IMHO ynjh_jo has made jump process better.
Good luck!

To remove the mouse just add the lines below this line :

“”" create a FPS type game “”"

props = WindowProperties()
props.setCursorHidden(True)
base.win.requestProperties(props)

escape key works on my comp

not bad for a simple fps script…should definetly be improved :wink:

I noticed some odd strafing behaviour. When I change the strafing direction very fast (left <-> right) it seems that the strafing movement gets locked and I dont strafe to the direction I press the key. Anybody else noticing this? Hope its not my keyboard, hehe!

Yes, that’s what he missed. Treeform uses a simple event-driven movement direction assignment.

self.accept( "a" , self.__setattr__,["strife",Left])
self.accept( "d" , self.__setattr__,["strife",Right] )
self.accept( "a-up" , self.__setattr__,["strife",Stop] )
self.accept( "d-up" , self.__setattr__,["strife",Stop] )

It means, each “-up” event simply assigns zero movement vector, regardless of the first key down-state. So, you’d see this “feature” if you hold down A and D (or W and S) in sequence (not necessarily fast), then release one of them, and you’d stay still, since the key-release simply sets the movement vector to zero.

I guess he must intended to cut down the development time by using a simple vector assignment, rather than recording key-down states.

How about including this example in the sample programs?

kind of a noob question…

I am trying to use the script to control my own model, here is the code :

class FPS(object,DirectObject):

    def __init__(self):
        
        self.initCollision()
        self.loadLevel()
        self.initPlayer()
        self.camera()
        
    
    def initCollision(self):
        #initialize traverser
        base.cTrav = CollisionTraverser()
        base.cTrav.setRespectPrevTransform(True)
#         base.cTrav.showCollisions(render)
        #initialize pusher
        self.pusher = CollisionHandlerPusher()
        # collision bits
        self.groundCollBit = BitMask32.bit(0)
        self.collBitOff = BitMask32.allOff()

    
    def loadLevel(self):

        level = loader.loadModel('mine/road.egg')
        level.reparentTo(render)
        level.setPos(-10,-10,-6)
        level.setColor(1,1,1,.5)
        level.setScale(10)
    
    def initPlayer(self) :
        self.man = Actor("mine/toxotis",
                                 {"run":"mine/toxotis_run",
                                  "walk":"mine/toxotis_idle"})
        self.man.reparentTo(render)
        self.man.setScale(1)
        #self.man.setPos(0,0,-0.02)

        #create a collsion solid around the man
        manC = self.man.attachCollisionSphere('manSphere', 0,0,1, .4, self.groundCollBit,self.collBitOff)
        self.pusher.addCollider(manC,self.man)
        base.cTrav.addCollider(manC,self.pusher)

        speed = 6
        Forward = Vec3(0,-speed*2,0)
        Back = Vec3(0,speed,0)
        Left = Vec3(-speed,0,0)
        Right = Vec3(speed,0,0)
        Stop = Vec3(0)
        self.walk = Stop
        self.strife = Stop
        self.jump = 0
        taskMgr.add(self.move, 'move-task')
        self.jumping = LerpFunc( Functor(self.__setattr__,"jump"),
                                 duration=.25, fromData=.25, toData=0)
        self.accept( "escape",sys.exit )
        self.accept( "space" , self.startJump)
        self.accept( "s" , self.__setattr__,["walk",Back] )
        self.accept( "w" , self.__setattr__,["walk",Forward])
        self.accept( "s-up" , self.__setattr__,["walk",Stop] )
        self.accept( "w-up" , self.__setattr__,["walk",Stop] )
        self.accept( "a" , self.__setattr__,["strife",Left])
        self.accept( "d" , self.__setattr__,["strife",Right] )
        self.accept( "a-up" , self.__setattr__,["strife",Stop] )
        self.accept( "d-up" , self.__setattr__,["strife",Stop] )

        self.manGroundColNp = self.man.attachCollisionRay( 'manRay',
                                                           0,0,.6, 0,0,-1,
                                                           self.groundCollBit,self.collBitOff)
        self.manGroundHandler = CollisionHandlerGravity()
        self.manGroundHandler.addCollider(self.manGroundColNp,self.man)
        base.cTrav.addCollider(self.manGroundColNp, self.manGroundHandler)
        
           
            

    def startJump(self):
        if self.manGroundHandler.isOnGround():
           self.jumping.start()

    def move(self,task):
        dt=globalClock.getDt()
        # mouse
        md = base.win.getPointer(0)
        x = md.getX()
        y = md.getY()
        
        # move where the keys set it
        moveVec=(self.walk+self.strife)*dt # horisontal
        moveVec.setZ( self.jump )          # vertical
        self.man.setFluidPos(self.man,moveVec)
        # jump damping
        if self.jump>0:
           self.jump = clampScalar( 0,1, self.jump*.9 )
        
        # If man is moving, loop the run animation.
        # If he is standing still, stop the animation.
        
        if self.walk == Forward:
            self.man.loop("run")
                       
        return task.cont
    
    def camera(self) :
    
        # Create a floater object.  We use the "floater" as a temporary
        # variable in a variety of calculations.
        
        #self.floater = NodePath(PandaNode("floater"))
        #self.floater.reparentTo(render)

        # Set up the camera
        
        #base.disableMouse()
        #base.camera.setPos(-100,-20,20)
        #base.camera.setPos(self.man.getX(),self.man.getY()+10,2)
        
        
        

        # If the camera is too far from ralph, move it closer.
        # If the camera is too close to ralph, move it farther.

        camvec = self.man.getPos() - base.camera.getPos()
        camvec.setZ(0)
        camdist = camvec.length()
        camvec.normalize()
        if (camdist > 10.0):
            base.camera.setPos(base.camera.getPos() + camvec*(camdist-10))
            camdist = 10.0
        if (camdist < 5.0):
            base.camera.setPos(base.camera.getPos() - camvec*(5-camdist))
            camdist = 5.0

               
        # The camera should look in ralph's direction,
        # but it should also try to stay horizontal, so look at
        # a floater which hovers above ralph's head.
        
        #self.floater.setPos(self.man.getPos())
        #self.floater.setZ(self.man.getZ() + 2.0)
        base.camera.lookAt(self.man)

        
        return Task.cont


FPS()
render.setShaderAuto()
run()

I now try to loop the animation of the model and I go to the move function, and I say :


if self.walk == Forward:
   self.man.loop("run")

An error is produced because Forward is not a global variable. I realize that is only declared in the function initplayer. How can i declare it glabally?

thanx!

Forward is just Vec3(0,1,0) nothing magical

You can move the definitions into the top of the file.