Bullet Character Controller

Bullet comes with a simple character controller already included. A character controller is a class intended to provide a simple way of controlling a player (or NPC) object the way we are used to from many first-person-shooters or role-playing-games. Achieving satisfying results for character movement is usually a difficult thing when using “physicals”, e. g. rigid bodies. The solution is to use so-called “kinematic” objects, that is objects which don’t respond to forces, and instead get moved by pushing/turning them around by hand.

Notice: The module panda3d.bullet doesn’t implement it’s own character controller. It simple exposes the character controller which comes with the Bullet physics engine. This character controller is still in an early stage, and it lacks a few features. In particular, Bullet does not implement a proper interaction between dynamic bodies and the character controller.

Setup

The following code will first create a shape with total height of 1.75 units and total width of 0.8 units. We have to subtract twice the radius from the total height in order to get the length of the cylindrical part of the capsule shape.

from panda3d.bullet import BulletCharacterControllerNode
from panda3d.bullet import BulletCapsuleShape
from panda3d.bullet import ZUp

height = 1.75
radius = 0.4
shape = BulletCapsuleShape(radius, height - 2*radius, ZUp)

playerNode = BulletCharacterControllerNode(shape, 0.4, 'Player')
playerNP = self.worldNP.attachNewNode(playerNode)
playerNP.setPos(-2, 0, 14)
playerNP.setH(45)
playerNP.setCollideMask(BitMask32.allOn())

world.attachCharacter(playerNP.node())

Moving

Now that we have a character controller within our scene we need to control it’s movement. The following code snippet shows one way of moving the character controller by keyboard input. Of course a character controller representing a NPC (non-player character) would not read the keyboard state but have the linear velocity (speed) and the angular velocity (omega) computed by some kind of AI algorithm.

def processInput(self):
    speed = Vec3(0, 0, 0)
    omega = 0.0

    if inputState.isSet('forward'): speed.setY( 3.0)
    if inputState.isSet('reverse'): speed.setY(-3.0)
    if inputState.isSet('left'):    speed.setX(-3.0)
    if inputState.isSet('right'):   speed.setX( 3.0)
    if inputState.isSet('turnLeft'):  omega =  120.0
    if inputState.isSet('turnRight'): omega = -120.0

    self.player.setAngularMovement(omega)
    self.player.setLinearMovement(speed, True)

Jumping

Next we want to make the character controller jump. The following code snippet shows a sample method which will make the character jump. We could for example call this method when the player presses a specific key.

After setting the maximum jump height and the initial upward speed we need to trigger the jump using the doJump() method.

def doJump(self):
    self.player.setMaxJumpHeight(5.0)
    self.player.setJumpSpeed(8.0)
    self.player.doJump()

It is possible to check whether the character controller is airborne using the isOnGround() method.

Crouching

Finally we want the character to crouch or duck. To achieve this we simply change the scale of the character’s collision shape. Here in this example we reduce the vertical dimension to 60 percent (0.6) when crouching, while the normal vertical scale is 1.0. We don’t change the horizontal scales. In a more realistic example, one would have the player enter a crouching animation.

Since we have the visual node of the player reparented to the character controller node it will automatically change its scale to match the player.

self.crouching = False

def doCrouch(self):
    self.crouching = not self.crouching
    sz = self.crouching and 0.6 or 1.0

    self.player.getShape().setLocalScale(Vec3(1, 1, sz))

    self.playerNP.setScale(Vec3(1, 1, sz) * 0.3048)
    self.playerNP.setPos(0, 0, -1 * sz)