Yet another collision example / tutorial

PREFACE:
Two models. Player and the world.
Player hits world, player dies.

    def setupCollisions(self): 
        self.debug = True
        self.collTrav = CollisionTraverser() 
 
        self.playerGroundSphere = CollisionSphere(0,1.5,-1.5,1.5) 
        self.playerGroundCol = CollisionNode('playerSphere') 
        self.playerGroundCol.addSolid(self.playerGroundSphere) 
 
        # bitmasks 
        self.playerGroundCol.setFromCollideMask(BitMask32.bit(0)) 
        self.playerGroundCol.setIntoCollideMask(BitMask32.allOff()) 
        self.world.setCollideMask(BitMask32.bit(0)) 
 
        # and done 
        self.playerGroundColNp = self.player.attachNewNode(self.playerGroundCol) 
        self.playerGroundHandler = CollisionHandlerQueue() 
        self.collTrav.addCollider(self.playerGroundColNp, self.playerGroundHandler) 
 
        # DEBUG 
        if (self.debug == True): 
            self.playerGroundColNp.show() 
            self.collTrav.showCollisions(self.render)
  1. Create a collision traverser. A traverser does the actual work of checking for collisions. It maintains a list of active world objects. When writing your code, it is best to think in terms of ‘from objects’ (or ‘colliders’) and ‘into objects’ (or ‘collide-able’). In our game, the ‘from object’ is the player while the ‘into object’ is the terrain.

  2. Create a collision sphere. The sphere is a kind of Collision Solid. A collision solid is an invisible object created purely for testing collisions. What we need to do is create a sphere and attach it to the player. Whenever that sphere intersects with the terrain, we have a collision (crash). The parameters we passed were 3 centering values (cx,cy,cz) that detail how the sphere will be positioned relative to the player (when we attach it to the player below) and a fourth value for the size of the sphere.

  3. Create a collision node. A collision node can hold any number of collision solids.

  4. Add the sphere to that newly created node. Our collision solid (sphere) has to be held by a collision node.

  5. Setup our bitmasks. Bitmasks are a common source of confusion. A ‘mask’ is used in what we call a bitwise operation. A BitMask32 as used in our code indicates a 32-bit number. Deep down in the heart of your CPU everything is in binary. Binary numbers (or ‘base-2’) use only zeros and ones (or ‘off’ and ‘on’). For example, the number 201202 in binary reads:

    0000 0000 0000 0011 0001 0001 1111 0010

    We are not, at this stage, going to discuss the mechanics of the binary number system but those interested can read more on Wikipedia! What we do need to understand though is that a 32-bit number has 32 binary digits each being zero or one. When we set bit ‘masks’ we are manipulating the digits (known as ‘bits’). For example:

    BitMask32.allOff()
    = 0000 0000 0000 0000 0000 0000 0000 0000

    BitMask32.bit(0)
    = 0000 0000 0000 0000 0000 0000 0000 0001

    BitMask32.bit(1)
    = 0000 0000 0000 0000 0000 0000 0000 0010

    BitMask32.bit(2)
    = 0000 0000 0000 0000 0000 0000 0000 0100

    Each object in Panda3D has two masks – the FROM and the INTO as we discussed above (this can also be considered as ‘source’ and ‘destination’). Objects will not be considered for collision if the masks do not match. It is because of this we set our player and world bitmasks the same (bit zero, the first digit of our binary number reading right to left). In other words, the player and the world can collide!

    setCollideMask is used to set the INTO collision mask on the world (which subsequently sets the mask on all of its child nodes). We can also be more granular than this by applying the collision mask on the actual collision node using setIntoCollideMask and setFromCollideMask. The code snippet illustrates both mechanisms.

    In short, we use bit masks to filter our collisions.

  6. Attach the collision node to the player. Where the player goes, the collision sphere goes. When we call ‘attachNewNode’ a node path is returned and stored in the variable ‘playerGroundColNp’.

  7. Create a collision handler queue for the newly created node. Collisions are stored in queues. There can, on occasion, be many many collisions. You don’t want to miss any, so we put them in a queue to be processed later in our code.

  8. Add that queue to the traverser. We add the new queue and a reference to collision node. When it comes time for the traverser to check for collisions, it now knows to check our new collision node (player into terrain) and if there’s a result – store it in our collision queue.

Finally, in your task manager code, process the queue:

        self.collTrav.traverse(self.render) 
        for i in range(self.playerGroundHandler.getNumEntries()): 
            entry = self.playerGroundHandler.getEntry(i) 
            if (self.debug == True): 
                print "DEAD: "+str(globalClock.getFrameTime())
            # we will later deal with 'what to do' when the player dies

Hope it helps some. :slight_smile:

Regards,
Gary