Collision Handlers
You will need to create a CollisionHandler that specifies what to do when a collision event is detected. Each object can only have one collision handler associated with it. There are several possible kinds of CollisionHandler available.
CollisionHandlerQueue
The simplest kind of CollisionHandler, this object simply records the collisions
that were detected during the most recent traversal. You can then iterate
through the list using queue.entries
:
queue = CollisionHandlerQueue()
traverser.addCollider(fromObject, queue)
traverser.traverse(render)
for entry in queue.entries:
print(entry)
By default, the Collision Entries appear in the queue in no particular
order. You can arrange them in order from nearest to furthest by calling
queue.sort_entries()
after the
traversal.
CollisionHandlerEvent
This is another simple kind of CollisionHandler. Rather than saving up the collisions, it generates a Panda event when collision events are detected.
There are three kinds of events that may be generated: the “in” event, when a particular object collides with another object that it didn’t in the previous pass, the “out” event, when an object is no longer colliding with an object it collided with in the previous pass, and the “again” event, when an object is still colliding with the same object that it did in the previous pass.
For each kind of event, the CollisionHandlerEvent will construct an event name out of the names of the from and into objects that were involved in the collision. The exact event name is controlled by a pattern string that you specify. For instance:
handler.addInPattern('%fn-into-%in')
handler.addAgainPattern('%fn-again-%in')
handler.addOutPattern('%fn-out-%in')
In the pattern string, the following sequences have special meaning:
%fn |
the name of the “from” object’s node |
%in |
the name of the “into” object’s node |
%fs |
‘t’ if “from” is declared to be tangible, ‘i’ if intangible |
%is |
‘t’ if “into” is declared to be tangible, ‘i’ if intangible |
%ig |
‘c’ if “into” is a CollisionNode, ‘g’ if it is an ordinary GeomNode |
%(tag)fh |
generate event only if “from” node has the indicated tag |
%(tag)fx |
generate event only if “from” node does not have the indicated tag |
%(tag)ih |
generate event only if “into” node has the indicated tag |
%(tag)ix |
generate event only if “into” node does not have the indicated tag |
%(tag)ft |
the indicated tag value of the “from” node. |
%(tag)it |
the indicated tag value of the “into” node. |
You may use as many of the above sequences as you like, or none, in the pattern
string. In the tag-based sequences, the parentheses around (tag) are literal;
the idea is to write the name of the tag you want to look up, surrounded by
parentheses. The tag is consulted using the
nodePath.get_net_tag()
interface.
In any case, the event handler function that you write to service the event should receive one parameter (in addition to self, if it is a method): the CollisionEntry. For example:
class MyObject(DirectObject.DirectObject):
def __init__(self):
self.accept('car-into-rail', handleRailCollision)
def handleRailCollision(self, entry):
print(entry)
Note that all of the following versions of CollisionHandler also inherit from CollisionHandlerEvent, so any of them can be set up to throw events in the same way.
CollisionHandlerPusher
This is the first of the more sophisticated handlers. The CollisionHandlerPusher, in addition to inheriting all of the event logic from CollisionHandlerEvent, will automatically push back on its from object to keep it out of walls. The visual effect is that your object will simply stop moving when it reaches a wall if it hits the wall head-on, or it will slide along the wall smoothly if it strikes the wall at an angle.
The CollisionHandlerPusher needs to have a handle to the NodePath that it will
push back on, for each from object; you pass this information to
pusher.add_collider()
.
This should be the node that is actually moving. This is often, but not always,
the same NodePath as the CollisionNode itself, but it might be different if the
CollisionNode is set up as a child of the node that is actually moving.
smiley = loader.loadModel('smiley.egg')
fromObject = smiley.attachNewNode(CollisionNode('colNode'))
fromObject.node().addSolid(CollisionSphere(0, 0, 0, 1))
pusher = CollisionHandlerPusher()
pusher.addCollider(fromObject, smiley)
Don’t be confused by the call to
pusher.add_collider()
; it looks a
lot like the call to
traverser.add_collider()
, but it’s
not the same thing, and you still need to add the collider and its handler to
the traverser:
traverser.addCollider(fromObject, pusher)
smiley.setPos(x, y, 0)
If you are using Panda’s drive mode to move the camera around (or some other
node), then you also need to tell the pusher about the drive node, by adding
it into the pusher.add_collider()
call:
fromObject = base.camera.attachNewNode(CollisionNode('colNode'))
fromObject.node().addSolid(CollisionSphere(0, 0, 0, 1))
pusher = CollisionHandlerPusher()
pusher.addCollider(fromObject, base.camera, base.drive.node())
PhysicsCollisionHandler
This kind of handler further specializes CollisionHandlerPusher to integrate
with Panda’s Physics Engine. It requires that
the NodePath you pass as the second parameter to
pusher.add_collider()
actually contains an ActorNode, the type of node that is moved by forces in the
physics system.
anp = render.attachNewNode(ActorNode('actor'))
fromObject = anp.attachNewNode(CollisionNode('colNode'))
fromObject.node().addSolid(CollisionSphere(0, 0, 0, 1))
pusher = PhysicsCollisionHandler()
pusher.addCollider(fromObject, anp)
Whenever you have an ActorNode that you want to respond to collisions, we recommend that you use a PhysicsCollisionHandler rather than an ordinary CollisionHandlerPusher. The PhysicsCollisionHandler will keep the object out of walls, just like the CollisionHandlerPusher does, but it will also update the object’s velocity within the physics engine, which helps to prevent the physics system from becoming unstable due to large accumulated velocities.
CollisionHandlerFloor
This collision handler is designed to serve one very specialized purpose: it keeps an object on the ground, or falling gently onto the ground, even if the floor is not level, without involving physics.
It is intended to be used with a CollisionRay
or CollisionSegment
. The
idea is that you attach a ray to your object, pointing downward, such that the
topmost intersection the ray detects will be the floor your object should be
resting on. Each frame, the CollisionHandlerFloor simply sets your object’s z
value to the detected intersection point (or, if it is so configured, it slowly
drops the object towards this point until it reaches it).
Using the CollisionHandlerFloor can be an easy way to simulate an avatar walking over uneven terrain, without having to set up a complicated physics simulation (or involve physics in any way). Of course, it does have its limitations.
smiley = loader.loadModel('smiley.egg')
fromObject = smiley.attachNewNode(CollisionNode('colNode'))
fromObject.node().addSolid(CollisionRay(0, 0, 0, 0, 0, -1))
lifter = CollisionHandlerFloor()
lifter.addCollider(fromObject, smiley)
CollisionHandlerGravity
This handler is very similar to CollisionHandlerFloor, but rather than positioning objects directly at the floor, it can apply an acceleration to make them fall gradually to the ground.
The main parameter to adjust is the gravity
property, which sets the
acceleration. If your scene unit is metres, and your simulation takes place on
earth, then you will want to set this to a value of around 9.81.
For the full list of parameters, see CollisionHandlerGravity
in the
API reference.