Collisions :: Gamedev Panda3D cookbook

Detect collision between game objects is a crucial practice in (almost) any game. Beside collision detection, is also important to know what to do, when collisions occur.

Game engines, always provide collision detection features, and is very important to watch the “elements” used in this procedures.
Panda3D gives us:
– Collision handlers: out of the box behaviors to be use;
– Collision geometries (sphere, cube, plane, capsule, line, ray …);
– 3D mesh collisions;
– Collision events.

When a collision is detected, events are dispatched through out the system, and those events can be captured by game objects, and trigger some actions.

sample 1:

[code lang=”python”]
from direct.showbase.ShowBase import ShowBase
from direct.task import Task
from pandac.PandaModules import *

class MyApp(ShowBase):

def __init__(self):
ShowBase.__init__(self)

# Create a Traverser and Handlers: step one
base.cTrav = CollisionTraverser()
base.pusher = CollisionHandlerPusher()
base.pusher.addInPattern(‘%fn-into-%in’)

self.cube = self.loader.loadModel(“box”);
self.cube.reparentTo(self.render)
self.cube.setScale(1)
self.cube.setPos(-3,10,-2)

# create colliders: step two
self.cubeCSphere = CollisionNode(“cubeCSphere”)
self.cubeCSphere.addSolid( CollisionSphere(0,0,0, 1.05) )
self.cubeEsfNP = self.cube.attachNewNode(self.cubeCSphere)
self.cubeEsfNP.show()

# Point to the Traverser and to the Handler, which objects must be “watched” to capture it’s collisions: third step
base.cTrav.addCollider( self.cubeEsfNP, base.pusher )
base.pusher.addCollider( self.cubeEsfNP, self.cube )

self.smiley = loader.loadModel(‘smiley’)
self.smiley.reparentTo(self.render)
self.smiley.setScale(0.5);
self.smiley.setPos(3, 10, -1.5)

self.smileyCSphere = CollisionNode(“smileyCSphere”)
self.smileyCSphere.addSolid( CollisionSphere(0,0,0, 1.05) )
self.smileyEsfNP = self.smiley.attachNewNode(self.smileyCSphere)
self.smileyEsfNP.show()

self.taskMgr.add(self.taskFunction, “testing Task”)
self.accept(‘cubeCSphere-into-smileyCSphere’, self.collisionEvent)

def collisionEvent(self, entry):
print “Game objects did collide!!”
print entry

def taskFunction(self, task):
self.cube.setX(self.cube.getX()+0.05)
return Task.cont

app = MyApp()
app.run()
[/code]

In the sample above, a simple collision, between the cube and a smiley, is detected, and because a CollisionHandlerPusher were use as a collision handler, the objects do not cross each other. In addition, an event were dispatched, in order to trigger some action, in this case, an output with collision info were printed in the console.

Lets take a closer look at the Panda3D’s procedures steps:
1- Creation of CollisionTraverser and a CollisionHandler;
2- Creation of: CollisionNode and CollisionSolid;
3- Attach CollisionNodes and CollisionSolid to CollisionTraverser and CollisionHandler.

Steps:
Step 1:

# Creation of CollisionTraverser and a CollisionHandler: step one
base.cTrav = CollisionTraverser()
base.pusher = CollisionHandlerPusher()
base.pusher.addInPattern(‘%fn-into-%in’)

First thing, a CollisionTraveser is created; than, a CollisionHandler is created as a Pusher (the CollisionHandlerPusher do not allow game objects to pass through each other). In the third line, is added to the “Pusher”, an order to dispatch events when collisions occur.

Step 2:

# Creations of colliders: second step
self.cubeCSphere = CollisionNode(“cubeCSphere”)
self.cubeCSphere.addSolid( CollisionSphere(0,0,0, 1.05) )
self.cubeEsfNP = self.cube.attachNewNode(self.cubeCSphere)
self.cubeEsfNP.show()

This step is a bit more complicated.
Literally the code says:
– Create a collision node called “cubeCSphere”, and save the path to it, in the variable self.cubeCSphere;
– Add a collision solid sphere to the collision node “cubeCSphere”.
– Attach the collision node to cube node (self.cube), and save this node path in the variable self.cubeEsfNP.
– render the collision node (as a sphere were set as a collision solid, the engine will render a transparent sphere).

This commands are mandatory, except by the self.cubeEsfNP.show(), which serves only as a pre-view.

Observe that this steps must be set for each game object which will interact through collisions. For example, the smiley object also receives those collision nodes.

Step 3:

# Point to the Traverser and to the Handler, which objects must be “watched” to capture it’s collisions: third step
base.cTrav.addCollider( self.cubeEsfNP, base.pusher )
base.pusher.addCollider( self.cubeEsfNP, self.cube )

After setting the collision nodes (CollisionNode, steps 1 and 2), is absolutely required to inform the system how to deal with collisions.
The command: base.cTrav.addCollider( self.cubeEsfNP, base.pusher ), says the Traverser that collisions related to the collision node self.cubeEsfNP, must be treated by the handler “Pusher”.
The command: base.pusher.addCollider( self.cubeEsfNP, self.cube ), says to the “Pusher”, when collisions related to the collision node self.cubeEsfNP occurs, the “pusher” behavior, must be applied to the node self.cube.

This is a good example to be use in situations when objects can not pass through other objects, like an character can not pass through a wall.

The three steps we’ve seen thus far, show the basic set up for collisions in Panda3D. Now let’s see a interesting detail: how to dispatch events when collisions occur.

This is possible to be done in Panda3D, thanks to the CollisionHandlerEvent, that dispatches events when a collision is detected. The CollisionHandlerPusher is a subclass of CollisionHandlerEvent, as such, it inherits the collision events features.

When the CollisionHandlerPusher was created (step 1), pay attention at the command: base.pusher.addInPattern(‘%fn-into-%in’). This is the set up command that says to the Pusher, which patterns of collision can generate events.

See the line:
self.accept(‘cubeCSphere-into-smileyCSphere’, self.collisionEvent)
This line is the command used to capture events in the system, in this case, this event is gonna be generated by the Pusher when the cube collide into the smiley (‘cuboCEsfera-into-smileyCEsfera’), than we are gonna capture it, and do something in the self.collisionEvent function.

So, once the event is captured the function self.collisionEvent is called. Pay attention that when a collision event is captured, the function that deals with it, must be ready to deal with an argument “entry”, which gives you the many important informations about the collision itself, like coordinates, normals, etc.

sample 2:

[code lang=”python”]

from direct.showbase.ShowBase import ShowBase
from direct.task import Task
from pandac.PandaModules import *

class MyApp(ShowBase):

def __init__(self):
ShowBase.__init__(self)

# CCreation of CollisionTraverser and a CollisionHandler: step one
base.cTrav = CollisionTraverser()
base.event = CollisionHandlerEvent()
base.event.addInPattern(‘%fn-into-%in’)
base.event.addAgainPattern(‘%fn-again-%in’)
base.event.addOutPattern(‘%fn-outof-%in’)

self.cube = self.loader.loadModel(“box”);
self.cube.reparentTo(self.render)
self.cube.setScale(1)
self.cube.setPos(-3,10,-2)
# Create colliders: step two
self.cubeCSphere = CollisionNode(“cubeCSphere”)
self.cubeCSphere.addSolid( CollisionSphere(0,0,0, 1.05) )
self.cubeEsfNP = self.cube.attachNewNode(self.cubeCSphere)
self.cubeEsfNP.show()
#Point to the Traverser and to the Handler, which objects must be “watched” to capture it’s collisions: third step
base.cTrav.addCollider( self.cubeEsfNP, base.event )

self.smiley = loader.loadModel(‘smiley’)
self.smiley.reparentTo(self.render)
self.smiley.setScale(0.5);
self.smiley.setPos(0, 10, -1.5)

self.smileyCSphere = CollisionNode(“smileyCSphere”)
self.smileyCSphere.addSolid( CollisionSphere(0,0,0, 1.05) )
self.smileyEsfNP = self.smiley.attachNewNode(self.smileyCSphere)
self.smileyEsfNP.show()

self.taskMgr.add(self.taskFunction, “testing Task”)

#Capture Collision Events
self.accept(‘cubeCSphere-into-smileyCSphere’, self.collisionEventIN)
self.accept(‘cubeCSphere-again-smileyCSphere’, self.collisionEventAGAIN)
self.accept(‘cubeCSphere-outof-smileyCSphere’, self.collisionEventOUT)

def collisionEventIN(self, entry):
print “Objects did collide!!”
print entry

def collisionEventOUT(self, entry):
print “Objects did stop colliding!!”
print entry

def collisionEventAGAIN(self, entry):
print “Objects are still colliding!!”

def taskFunction(self, task):
self.cube.setX(self.cube.getX()+0.05)
return Task.cont

app = MyApp()
app.run()
[/code]

In this sample there is a demonstration of the usage of CollisionHandlerEvent. This collision handler do allow objects to pass through each other, so in this case, the cube will pass through the smiley with no problem.

The CollisionHandlerEvent, is a great way to create sensors around the game arena. For example, an avatar pass through a gate, and a trap is triggered.

Observe the changes in steps 1 and 3, and in the event capture set up.
The changes are small. The most import are when we create and when we capture the events. In this sample, not only we set up the ‘into’ event, but also we defined the ‘outof’ and the ‘again’.

Events “into”, occur when a game object collide into another.
Events “outof”, occur when game objects stop colliding each other.
Events “again”, occur when game objects are colliding.

Leave a Reply

Your email address will not be published. Required fields are marked *