Using floor collider to handle wall collisions

Roaming Ralph is repositioned back to his previous position whenever he steps off the terrain or, which is to say the same thing, whenever the floor collider handler queue is empty. In an urban environment floorplans are mostly squares so I am thinking this methodolgy would work for handling walls… if the player’s floor collision queue is empty they have either stepped into open space or hit a wall. In which case I am also thinking there is a performance bonus to handling wall colisions without adding wall colliders and engaging physics engines. So that is what this code is about. The problem with the Roaming Ralph example is that it does not allow for sliding along walls when strafing or moving forward or back. This code adds that functionalilty.

For the purposes here, I have used a CardMaker instance as the floor and collider. In the stuff I am working on I use a model with a floor collider geometry and named empties which determine the origin (lower left) and maximum (upper right) of each floor collider. The empties are necessary unless you know of an easy way of finding these positions from the floor colliders themselves… I gave up and used empties is what I am saying.

from pandac.PandaModules import loadPrcFileData 
from direct.directbase import DirectStart
from direct.showbase.DirectObject import DirectObject
from pandac.PandaModules import *
from direct.gui.DirectGui import * 

import sys
from random import random
import math

"""The Qad class contains information about the current quadrant the player is in. 
At this stage only the min X/Y, max X/Y positions of each floor collider are held,
but floor type, whether the floor is wet, whether it is stairs; the wall types,
whether they are tiles or concrete and therefore how they determine how the player
slides along them can all be included. At this stage the floor colliders of the world
need to be broken up into squares or rectangles. """
class Qad():
    def __init__(self):
      self.maxx=0.0
      self.minx=0.0
      self.maxy=0.0
      self.miny=0.0
      self.maxz=0.0
      self.minz=0.0

    #setters
    def setXyz(self, minPos, maxPos):
      self.minx=minPos[0]
      self.miny=minPos[1]
      self.minz=minPos[2]
      self.maxx=maxPos[0]
      self.maxy=maxPos[1]
      self.maxz=maxPos[2]

    #getters
    def getMaxx(self):
      return self.maxx

    def getMinx(self):
      return self.minx

    def getMaxy(self):
      return self.maxy

    def getMiny(self):
      return self.miny
    

"""For the purposes here I have dumped what I have spread about 3 classes into this one
class... hope its not too messy."""
class Main(DirectObject):
  def __init__(self):
    #variables
    self.vel=Vec3(0.0,0.0,0.0) 
    self.strafespeed=4.5 
    self.forwardspeed=15.0
    self.backspeed=7.0 
    self.mouseSen=0.1
    #track which qad the player is in
    self.qad="qad01"

    #keep a dictionary of Qad objects that we can access via self.qad (above)
    self.qads={}
        
    #code begins
    base.setBackgroundColor(0.2,0.2,0.2)
    self.props=WindowProperties()
    self.props.setCursorHidden(True) 
    base.win.requestProperties(self.props) 
    base.disableMouse()

    self.initQads()
    self.createHud()
    self.createFloor()
    self.createPlayer()

  """ for every (square) floor collider create a Qad object and give it min max positions.
      The floor collider has to stop short of the wall otherwise the player will stick
      their head into the wall and see beyond it. I haven't implemented that here
      because I have used just one plate, a CardMaker object. You have to fiddle with
      it to get it right. And I haven't tested it on different resolutions.
  """
  def initQads(self):
    self.qads["qad01"]=Qad()
    self.qads["qad01"].setXyz(Point3(-50,-50,0),Point3(50,50,0))

  #just a text field to say whether the player is on the floor or not
  def createHud(self): 
    self.objText = OnscreenText(text = '', pos = (0,0,0), scale = 0.07) 
    self.frame = None
    
  #a card maker suffices as the floor and the collider. I have a model
  #and a series of rectangle floor colliders
  def createFloor(self):
    self.cm=CardMaker("ground") 
    self.cm.setFrame(-50, 50, -50, 50) 
    self.cm.setUvRange((0, 1), (1, 0))
    self.cm.setColor(0.3,0.8,0.3, 1)
    self.ground = render.attachNewNode(self.cm.generate()) 
    self.ground.setPos(0, 0, 0);
    self.ground.lookAt(0, 0, -1)
    
    
  #Setup player, camera, keys, and floor collider colision ray and call task 
  def createPlayer(self):
    #this is FPS without accompanying models... just a player wondering about as a camera
    self.player=NodePath(ActorNode("Player")) 
    self.player.reparentTo(render)
    self.player.setH(base.camera.getH())
    base.camera.reparentTo(self.player) 
    base.camera.setPos(0,-0.2,0)
    self.player.reparentTo(render)
    self.player.setPos(0,0,2)

    #setup event input
    self.accept("escape", sys.exit)
    self.keyMap = {"forward":0,"backward":0,"strafeleft":0,"straferight":0} 
    self.accept("e",self.setKey, ["forward",1]) 
    self.accept("f",self.setKey, ["backward",1]) 
    self.accept("a",self.setKey, ["strafeleft",1]) 
    self.accept("space",self.setKey, ["straferight",1]) 
        
    self.accept("e-up",self.setKey, ["forward",0]) 
    self.accept("f-up",self.setKey, ["backward",0]) 
    self.accept("a-up",self.setKey, ["strafeleft",0]) 
    self.accept("space-up",self.setKey, ["straferight",0])

    #setup collision handlers
    self.cTrav = CollisionTraverser()
    self.cTrav.setRespectPrevTransform(True)

    #floor
    self.playerGroundRay = CollisionRay()
    self.playerGroundRay.setOrigin(0,0,100)
    self.playerGroundRay.setDirection(0,0,-1)
    self.playerGroundCol = CollisionNode('playerRay')
    self.playerGroundCol.addSolid(self.playerGroundRay)
    self.playerGroundCol.setFromCollideMask(GeomNode.getDefaultCollideMask()) 
    self.playerGroundCol.setIntoCollideMask(BitMask32.allOff())
    self.playerGroundColNp = self.player.attachNewNode(self.playerGroundCol)
    self.playerGroundHandler = CollisionHandlerQueue()
    self.cTrav.addCollider(self.playerGroundColNp, self.playerGroundHandler)

    #call the task to update player
    taskMgr.add(self.updatePlayer, "updatePlayer") 




  #map keys
  def setKey(self, key, value): 
    self.keyMap[key] = value

  #task to update player
  def updatePlayer(self, task): 
    elapsed = globalClock.getDt()
    self.objText['text'] = ''
    x=0.0
    y=0.0
    mousex=0.0
    mousey=0.0
    #store the players position prior to moving them so if they are off the floor
    #and at a corner we'll stop them by resetting them 
    startPos=self.player.getPos()
    #this variable is modified (x,y,z) as necessary and fed to the player
    #if they are off the floor, moving, and not at a corner
    modPos=startPos
        

    #move player ford, back, and strafe. We do this regardless of whether the player
    #is on the floor or not. That's why we keep startPos
    if (self.keyMap["strafeleft"]!=0): 
        x = -self.strafespeed
    if (self.keyMap["straferight"]!=0): 
        x = self.strafespeed
    if (self.keyMap["forward"]!=0): 
        y = self.forwardspeed
    if (self.keyMap["backward"]!=0): 
        y = -self.backspeed
    
    self.vel = Vec3(x,y,0)
    self.vel *= elapsed 
    self.player.setFluidPos(self.player, self.vel)
        
        
    #get mouse pos 
    if base.mouseWatcherNode.hasMouse():
        md = base.win.getPointer(0) 
        mousex = md.getX() 
        mousey = md.getY() 

        # rotate the camera based on the mouse coordinates 
        if base.win.movePointer(0, base.win.getXSize()/2, base.win.getYSize()/2): 
            base.camera.setP((base.camera.getP()-(mousey-base.win.getYSize()/2)*self.mouseSen) % 360) 
            self.player.setH((self.player.getH()-(mousex-base.win.getXSize()/2)*self.mouseSen) % 360)
        
    #travers render for floor collisions and handle
    self.cTrav.traverse(render)

    #are we on the floor
    if self.playerGroundHandler.getNumEntries()>0:
        self.playerGroundHandler.sortEntries()
        entry=self.playerGroundHandler.getEntry(0)

        #set the players z position 3.8 above the collision point ray meets floor
        self.player.setZ(entry.getSurfacePoint(render).getZ()+3.8)
        #get name of the floor collider to print in the Hud
        nameOf=entry.getIntoNodePath().getName()
        self.objText['text'] =str(nameOf)
        #and set self.qad so the player knows what floor plate they are on
        if nameOf=="floorCollider01":
            self.qad="qad01"
        elif nameOf=="stairCollider01":
            self.qad="qad02"
        elif nameOf=="floorCollider02":
            self.qad="qad03"
        elif nameOf=="stairCollider02":
            self.qad="qad04"

    #the player is off the floor
    else:
        self.objText['text'] =str("Off floor")
        """ Here is the problem, if the player is facing north west and is into the north wall 
            and moving forward, then they will be sliding down the x axis, but if they are in the 
            same position but strafing to the right, they will be sliding up the x axis. Furthermore, 
            if they are in the north west corner moving forward we don't want them going anywhere.
            Nor do we want them going anywhere if they are facing north west and are trying to strafe
            right while in the north east corner. This is where all that gets done. 
        """
        #flag determines whether the player is at a corner
        flag=0
        #find out where the player is heading
        heading=self.player.getH()

        #are they heading north west
        if heading>0 and heading<90:
            #which keys are pressed (forward, backward, strafe left/right
            if self.keyMap["forward"]!=0:
                #which wall are they touching, north is maxy, west is minx
                floory=self.qads[self.qad].getMaxy()
                floorx=self.qads[self.qad].getMinx()

                #are they on the north wall
                if self.player.getY()>floory:
                    #modify their x position only and flag that the player is on the north wall
                    modPos[0]=self.player.getX()-0.03
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                #are they on the west wall
                if self.player.getX()<floorx:
                    #modify they y position only and flag that the player is on the west wall
                    modPos[1]=self.player.getY()+0.03
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                #is the player at the corner of the north and west walls, if so, reset them
                #to where they were before this cycle of the task
                if flag==2:
                    self.player.setPos(floorx, floory, startPos[2])
            #etc...
            if self.keyMap["backward"]!=0:
                floory=self.qads[self.qad].getMiny()
                floorx=self.qads[self.qad].getMaxx()
                
                if self.player.getY()<floory:
                    modPos[0]=self.player.getX()+0.01
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                if self.player.getX()>floorx:
                    modPos[1]=self.player.getY()-0.01
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                if flag==2:
                    self.player.setPos(floorx, floory, startPos[2])    
            if self.keyMap["straferight"]!=0:
                floory=self.qads[self.qad].getMaxy()
                floorx=self.qads[self.qad].getMaxx()
                    
                if self.player.getY()>floory:
                    modPos[0]=self.player.getX()+0.01
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                if self.player.getX()>floorx:
                    modPos[1]=self.player.getY()+0.01
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                if flag==2:
                    self.player.setPos(floorx, floory, startPos[2])

            if self.keyMap["strafeleft"]!=0:
                floory=self.qads[self.qad].getMiny()
                floorx=self.qads[self.qad].getMinx()
                    
                if self.player.getY()<floory:
                    modPos[0]=self.player.getX()-0.01
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                if self.player.getX()<floorx:
                    modPos[1]=self.player.getY()-0.01
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                if flag==2:
                    self.player.setPos(floorx, floory, startPos[2])

                        
        #southwest
        elif heading>90 and heading<180:
            if self.keyMap["forward"]!=0:
                floory=self.qads[self.qad].getMiny()
                floorx=self.qads[self.qad].getMinx()
                    
                if self.player.getY()<floory:
                    modPos[0]=self.player.getX()-0.03
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                if self.player.getX()<floorx:
                    modPos[1]=self.player.getY()-0.03
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                if flag==2:
                        self.player.setPos(floorx, floory, startPos[2])

            if self.keyMap["backward"]!=0:
                floory=self.qads[self.qad].getMaxy()
                floorx=self.qads[self.qad].getMaxx()
                    
                if self.player.getY()>floory:
                    modPos[0]=self.player.getX()+0.01
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                if self.player.getX()>floorx:
                    modPos[1]=self.player.getY()+0.01
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                if flag==2:
                    self.player.setPos(floorx, floory, startPos[2])

            if self.keyMap["straferight"]!=0:
                floory=self.qads[self.qad].getMaxy()
                floorx=self.qads[self.qad].getMinx()
                    
                if self.player.getY()>floory:
                    modPos[0]=self.player.getX()-0.01
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                if self.player.getX()<floorx:
                    modPos[1]=self.player.getY()+0.01
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                if flag==2:
                    self.player.setPos(floorx, floory, startPos[2])

            if self.keyMap["strafeleft"]!=0:
                floory=self.qads[self.qad].getMiny()
                floorx=self.qads[self.qad].getMaxx()
                   
                if self.player.getY()<floory:
                    modPos[0]=self.player.getX()+0.01
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                if self.player.getX()>floorx:
                    modPos[1]=self.player.getY()-0.01
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                if flag==2:
                    self.player.setPos(floorx, floory, startPos[2])

        #southeast      
        elif heading>180 and heading<270:
            if self.keyMap["forward"]!=0:
                floory=self.qads[self.qad].getMiny()
                floorx=self.qads[self.qad].getMaxx()
                    
                if self.player.getY()<floory:
                    modPos[0]=self.player.getX()+0.03
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                if self.player.getX()>floorx:
                    modPos[1]=self.player.getY()-0.03
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                if flag==2:
                    self.player.setPos(floorx, floory, startPos[2])

            if self.keyMap["backward"]!=0:
                floory=self.qads[self.qad].getMaxy()
                floorx=self.qads[self.qad].getMinx()
                    
                if self.player.getY()>floory:
                    modPos[0]=self.player.getX()-0.01
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                if self.player.getX()<floorx:
                    modPos[1]=self.player.getY()+0.01
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                if flag==2:
                    self.player.setPos(floorx, floory, startPos[2])

            if self.keyMap["straferight"]!=0:
                floory=self.qads[self.qad].getMiny()
                floorx=self.qads[self.qad].getMinx()
                    
                if self.player.getY()<floory:
                    modPos[0]=self.player.getX()-0.01
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                if self.player.getX()<floorx:
                    modPos[1]=self.player.getY()-0.01
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                if flag==2:
                    self.player.setPos(floorx, floory, startPos[2])

            if self.keyMap["strafeleft"]!=0:
                floory=self.qads[self.qad].getMaxy()
                floorx=self.qads[self.qad].getMaxx()
                   
                if self.player.getY()>floory:
                    modPos[0]=self.player.getX()+0.01
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                if self.player.getX()>floorx:
                    modPos[1]=self.player.getY()+0.01
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                if flag==2:
                    self.player.setPos(floorx, floory, startPos[2])

        #northeast
        elif heading>270 and heading<360:
            if self.keyMap["forward"]!=0:
                floory=self.qads[self.qad].getMaxy()
                floorx=self.qads[self.qad].getMaxx()
                    
                if self.player.getY()>floory:
                    modPos[0]=self.player.getX()+0.03
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                if self.player.getX()>floorx:
                    modPos[1]=self.player.getY()+0.03
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                if flag==2:
                    self.player.setPos(floorx, floory, startPos[2])

            if self.keyMap["backward"]!=0:
                floory=self.qads[self.qad].getMiny()
                floorx=self.qads[self.qad].getMinx()
                    
                if self.player.getY()<floory:
                    modPos[0]=self.player.getX()-0.005
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                if self.player.getX()<floorx:
                    modPos[1]=self.player.getY()-0.005
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                if flag==2:
                    self.player.setPos(floorx, floory, startPos[2])

            if self.keyMap["straferight"]!=0:
                floory=self.qads[self.qad].getMiny()
                floorx=self.qads[self.qad].getMaxx()
                    
                if self.player.getY()<floory:
                    modPos[0]=self.player.getX()+0.01
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                if self.player.getX()>floorx:
                    modPos[1]=self.player.getY()-0.01
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                if flag==2:
                    self.player.setPos(floorx, floory, startPos[2])

            if self.keyMap["strafeleft"]!=0:
                floory=self.qads[self.qad].getMaxy()
                floorx=self.qads[self.qad].getMinx()
                    
                if self.player.getY()>floory:
                    modPos[0]=self.player.getX()-0.01
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                if self.player.getX()<floorx:
                    modPos[1]=self.player.getY()+0.01
                    self.player.setFluidPos(modPos)
                    flag=flag+1
                if flag==2:
                    self.player.setPos(floorx, floory, startPos[2])

    return task.cont 



main=Main()
run()

Thanks, this may be useful to something I wanted to try.