Collision with walls.

Hi there, I’ve been working on making a game with Panda3D for the last week or so and I’ve come across a problem. When I try to handle collision with walls, my character jitters hard and sometimes phases through them.

I’ve set up a collision event that seems like it should properly handle this, but it doesn’t. Here’s the event:

def _stageCollide2(self, entry):
       # Calculate collision depth.
       fromNodePath = entry.getFromNodePath()
       colSurfacePt = entry.getSurfacePoint(fromNodePath)
       colInteriorPt = entry.getInteriorPoint(fromNodePath)
       self.collisionDepth = colSurfacePt - colInteriorPt
       #Take the player out of walls, regardless of state.
       self.nodePath.setPos(self.nodePath, self.collisionDepth)

self.nodePath is the character, and setPos is setting the character’s position to collisionDepth relative to itself. Most of these variables are 3D vectors/points. The rest is self-explanatory.

I also have separate movement code. I can post it, but it’s a bit long. I’m not actually sure how often my movement code executes relative to the event, since the events’ timings seem to be a bit nebulous. Maybe the event isn’t called enough? I’m not sure what could be wrong, but it’s driving me up the wall. Any help would be greatly appreciated. :slight_smile:

Hmm… I may be mistaken, but I think that I see two potential problems:

  1. As it stands, your code should move the character’s collider to a position in which it’s just touching the wall collider. If you’ve only registered an “into” event, and not an “again” event, the collision system might see that as a state of continuing collision and so not produce another event for it.

  2. Since you seem to be attempting to set the character’s position to be exactly in contact with the wall, imprecision issues might result in the collider actually being slightly within the wall.

By the way, since your collision code seems to just keep the character out of the wall, have you looked at using CollisionHandlerPusher instead of CollisionHandlerEvent (as I imagine that you’re using now). If you want some of your own code to execute on collision (such as velocity reflection), you should be able to register collision events with a CollisionHandlerPusher just as with a CollisionHandlerEvent, I believe.

Thanks for the response. :slight_smile: I did have an “again” event pointing to the same method, and I tried adding a normalized copy of the collision depth multiplied by .01 to the collision depth to deal with imprecision, but still had the same problem. I’ve tried using a CollisionHandlerPusher, but for some reason it collides in the opposite direction of the stage mesh’s normals, which isn’t what I want of course.

So I got sick of dealing with events and decided to use a queue instead. It solved the problem of the player pushing through walls, at least at high framerates (they easily phase through the wall at 5 fps for instance, which is also a problem), but the player still bounces too far out of the wall. Here’s my entire update method for my character object, most of the relevant code is towards the end, starting with the comment “#Handle collision…”.

def update(self):
        #Get time passed since last frame to calculate movement.
        self.dt = self.clock.getDt()
        
        movement = Vec3(0, 0, 0)
        
        #Act based on the current state and choose the next state as necessary.
        
        #RotatingLeft and RotatingRight are functionally identical to Standing, except for the spinning part.
        #They just exist to activate different animations.
        
        if self.state == "Standing":
            self._chooseDirection()
            if self.direction != 0:
                self.request("Walking")
            elif self.input.rotL and not self.input.rotR:
                self.request("RotatingLeft")
            elif self.input.rotR and not self.input.rotL:
                self.request("RotatingRight")
            elif self._isFalling():
                self.request("Falling")
            elif not self._isFalling():
                movement += Vec3(0, 0, self.stagePoint.getZ() - self.facingObj.getPos().getZ())
                
            self.actor.setHpr(0, 0, 0)
            
        if self.state == "RotatingLeft":
            self._chooseDirection()
            if self.direction != 0:
                self.request("Walking")
            elif (not self.input.rotL and not self.input.rotR) or (self.input.rotL and self.input.rotR):
                self.request("Standing")
            elif self.input.rotR and not self.input.rotL:
                self.request("RotatingRight")
            elif self._isFalling():
                self.request("Falling")
            elif not self._isFalling():
                movement += Vec3(0, 0, self.stagePoint.getZ() - self.facingObj.getPos().getZ())
                
            self.facingObj.setHpr(self.facingObj, self.turnSpeed * self.dt, 0, 0)
                                
            self.actor.setHpr(0, 0, 0)
            
        if self.state == "RotatingRight":
            self._chooseDirection()
            if self.direction != 0:
                self.request("Walking")
            elif (not self.input.rotL and not self.input.rotR) or (self.input.rotL and self.input.rotR):
                self.request("Standing")
            elif self.input.rotL and not self.input.rotR:
                self.request("RotatingLeft")
            elif self._isFalling():
                self.request("Falling")
            elif not self._isFalling():
                movement += Vec3(0, 0, self.stagePoint.getZ() - self.facingObj.getPos().getZ())
            
            self.facingObj.setHpr(self.facingObj, -self.turnSpeed * self.dt, 0, 0)
            
            self.actor.setHpr(0, 0, 0)
        
        
        #Walking and Backpedaling are functionally identical save for their animation and direction.
                
        elif self.state == "Walking":
            self._chooseDirection()
            if self.direction == 0:
                self.request("Standing")
            elif self.direction >= 4 and self.direction <= 6:
                self.request("Backpedaling")
            elif self._isFalling():
                self.request("Falling")
            elif not self._isFalling():
                movement += Vec3(0, 0, self.stagePoint.getZ() - self.facingObj.getPos().getZ())
            
            #Based on which direction was chosen, set movement speed and direction
            #as well as the actor's facing relative to the facing object.
            if self.direction == 1:    
                movement += Vec3(0, -self.moveSpeed * self.dt, 0)
                self.actor.setHpr(self.facingObj, 0, 0, 0)
            if self.direction == 2:    
                movement += Vec3(-self.moveSpeed * .7071 * self.dt, -self.moveSpeed * .7071 * self.dt, 0)
                self.actor.setHpr(self.facingObj, -45, 0, 0)
            if self.direction == 8:    
                movement += Vec3(self.moveSpeed * .7071 * self.dt, -self.moveSpeed * .7071 * self.dt, 0)
                self.actor.setHpr(self.facingObj, 45, 0, 0)
            if self.direction == 3:    
                movement += Vec3(-self.moveSpeed * self.dt, 0, 0)
                self.actor.setHpr(self.facingObj, -90, 0, 0)
            if self.direction == 7:    
                movement += Vec3(self.moveSpeed * self.dt, 0, 0)
                self.actor.setHpr(self.facingObj, 90, 0, 0)
                
            #Rotate the character and view if the rotate button is held.
            if self.input.rotL and not self.input.rotR:
                self.facingObj.setHpr(self.facingObj, self.turnSpeed * self.dt, 0, 0)
            if self.input.rotR and not self.input.rotL:
                self.facingObj.setHpr(self.facingObj, -self.turnSpeed * self.dt, 0, 0)
                
        elif self.state == "Backpedaling":
            self._chooseDirection()
            if self.direction == 0:
                self.request("Standing")
            elif self.direction < 4 or self.direction > 6:
                self.request("Walking")
            elif self._isFalling():
                self.request("Falling")
            elif not self._isFalling():
                movement += Vec3(0, 0, self.stagePoint.getZ() - self.facingObj.getPos().getZ())
            
            #Based on which direction was chosen, set movement speed and direction
            #as well as the actor's facing relative to the facing object.
            if self.direction == 5:    
                movement += Vec3(0, self.moveSpeed * self.dt, 0)
                self.actor.setHpr(self.facingObj, 0, 0, 0)
            if self.direction == 4:    
                movement += Vec3(-self.moveSpeed * .7071 * self.dt, self.moveSpeed * .7071 * self.dt, 0)
                self.actor.setHpr(self.facingObj, 45, 0, 0)
            if self.direction == 6:    
                movement += Vec3(self.moveSpeed * .7071 * self.dt, self.moveSpeed * .7071 * self.dt, 0)
                self.actor.setHpr(self.facingObj, -45, 0, 0)
                
            #Rotate the character and view if the rotate button is held.
            if self.input.rotL and not self.input.rotR:
                self.facingObj.setHpr(self.facingObj, self.turnSpeed * self.dt, 0, 0)
            if self.input.rotR and not self.input.rotL:
                self.facingObj.setHpr(self.facingObj, -self.turnSpeed * self.dt, 0, 0)
                
        elif self.state == "Falling":
            if not self._isFalling():
                self.request("LandFreeze")
            
            self._chooseDirection()
            
            #Based on which direction was chosen, set movement speed and direction.
            if self.direction == 1:    
                movement += Vec3(0, -self.airSpeed * self.dt, 0)
            if self.direction == 2:    
                movement += Vec3(-self.airSpeed * .7071 * self.dt, -self.airSpeed * .7071 * self.dt, 0)
            if self.direction == 8:    
                movement += Vec3(self.airSpeed * .7071 * self.dt, -self.airSpeed * .7071 * self.dt, 0)
            if self.direction == 3:    
                movement += Vec3(-self.airSpeed * self.dt, 0, 0)
            if self.direction == 7:    
                movement += Vec3(self.airSpeed * self.dt, 0, 0)
            if self.direction == 5:    
                movement += Vec3(0, self.airSpeed * self.dt, 0)
            if self.direction == 4:    
                movement += Vec3(-self.airSpeed * .7071 * self.dt, self.airSpeed * .7071 * self.dt, 0)
            if self.direction == 6:    
                movement += Vec3(self.airSpeed * .7071 * self.dt, self.airSpeed * .7071 * self.dt, 0)
                
            #Rotate the character and view if the rotate button is held.
            if self.input.rotL and not self.input.rotR:
                self.facingObj.setHpr(self.facingObj, self.turnSpeed * self.dt, 0, 0)
            if self.input.rotR and not self.input.rotL:
                self.facingObj.setHpr(self.facingObj, -self.turnSpeed * self.dt, 0, 0)
                
            #Drop the bass.
            movement += Vec3(0, 0, -self.fallSpeed * self.dt)
                
            #Face the character forward relative to the camera.    
            self.actor.setHpr(self.facingObj, 0, 0, 0)
            
        elif self.state == "LandFreeze":
            
            movement += Vec3(0, 0, self.stagePoint.getZ() - self.facingObj.getPos().getZ())
            
            self.timer -= self.dt
            
            if self.timer <= 0:
                self.request("Standing")  
                
        #Actually move the character based on its set movement.
        self.facingObj.setPos(self.facingObj, movement)
        
        #Handle collision...        
        self.collisionDepth = Vec3(0, 0, 0)
        for i in range(self.collisionHandler.getNumEntries()):
            entry = self.collisionHandler.getEntry(i)
            #...with the stage.
            if entry.getFromNodePath() == self.stageCollideSphere:
                # Calculate collision depth.
                fromNodePath = entry.getFromNodePath()
                colSurfacePt = entry.getSurfacePoint(fromNodePath)
                colInteriorPt = entry.getInteriorPoint(fromNodePath)
                self.collisionDepth += colSurfacePt - colInteriorPt
            if entry.getFromNodePath() == self.stageCollideLine:
                self.stagePoint = entry.getSurfacePoint(render)
        
        #Take the player out of walls, regardless of state.
        self.facingObj.setPos(self.facingObj, self.collisionDepth) 

You can try the Roaming Ralph way, don’t test how deep the collision was, just move the character to the last known position where we has not colliding with walls.