my FPS script awsd+jumping wall sliding


#1

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() 

Need help implementing Collision
How to Create a Gravity and Jumping System
Scene Editing
#2

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.

#3

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.


#4

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


#5

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()

#6

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


#7

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.


#8

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.


#9

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.


#10

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.


#11

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.


#12

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!


#13

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


#14

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!


#15

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:


#16

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!


#17

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.


#18

How about including this example in the sample programs?


#19

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!


#20

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

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