Page 1 of 1

CollisionEvents, projectiles, and removing objects

PostPosted: Wed Oct 10, 2012 5:28 am
by BenChang
We've gotten a few questions about the CollisionHandlerEvent and removing nodes that you collide with, like projectiles and items you can pick up.

First, let's review the idea of "from" and "into" collision objects. You will usually have only a few "from" objects, and a lot of "into" objects. The way the collision system works is for each "from" object, it checks for collisions with all "into" objects. Keeping the number of "from" objects low reduces the number of collision checks each frame. Otherwise, obviously, you get O(n-squared) time.

"From" objects are usually things like the player. "Into" objects are usually things like the environment, projectiles, items, and often enemies. "Into" objects never collide with each other - so this holds true only as long as, for instance, projectiles can't hit each other. if you start wanting to make everything in the world collide with everything else, again, sit tight until we get to Bullet.

Ok, so let's say we're going to make a game about picking up radishes. We'll use the CollisionHandlerEvent to fire an event whenever the player encounters a radish.

First, we make the collision traverser:

Code: Select all
        base.cTrav = CollisionTraverser()
        base.cTrav.showCollisions(render)
        self.player = loader.loadModel('robofarmer')

        PlayerCollision = self.player.attachNewNode(CollisionNode('player'))
        PlayerCollision.node().setIntoCollideMask(BitMask32.allOff())
        PlayerCollision.node().addSolid(CollisionSphere(0,0,0,1))
        PlayerCollision.show()       



Next, create the CollisionEventHandler

Code: Select all
        self.collisionHandler = CollisionHandlerEvent()
        base.cTrav.addCollider(PlayerCollision,self.collisionHandler)


Now here's the important bit of the CollisionHandlerEvent: register the event handler function

Code: Select all
        self.notifier.addInPattern("%fn-in-%in")
        self.accept("player-in-radish",self.pickupRadish)


the pattern is the weird magic here. It's a special formatting string that generates event names. The '%' bits are replacement strings with special meanings.

%fn From Name: the name of the "from" collision node
%in Into Name: the name of the "into" collision node

There are a few other special strings too, which you can read about in the API reference

the "-in-" is arbitrary glue. other valid strings might include:

"%fn-eats-%in"
"%fn-collidewith-%in"
"%fn_%in"
etc.

so anytime a collision happens, events get generated. If you have some collision solids named "radish", you'll get some events with the name "player-in-radish". If you have some collision solids for other vegetables, you'll get ones like "player-in-cucumber", "player-in-cabbage", etc.

Then you set up the event handler to respond to ones you care about. pickupRadish is of course a method we'll make later to handle the picking up and disposal of radishes.

Now, to generate all those radishes, you might do something like this:

Code: Select all
self.radishes=[]
for i in range(0,100):
    r=loader.loadModel("radish.egg")
    radishCollider = r.attachNewNode(CollisionNode("radish"))
    radishCollider.node().addSolid(CollisionSphere(0,0,0,1))
    radishCollider.show()
    r.reparentTo(render)
    r.setPos(random.uniform(-20,20),random.uniform(-20,20),0)
    self.radishes.append(r)


Note the difference between the radish and the player: the radish is NOT added to the traverser. You only need to add "from" objects to the traverser; it automatically walks the whole scene graph and finds all the "into" objects it needs to check against.

Finally, to make the radishes disappear when you collide with them, we'll set up the pickupRadish() method:

Code: Select all
def pickupRadish(self, collisionEntry):
    print "yum, a radish!"
    np=collisionEntry.getIntoNodePath().getParent()
    self.radishes.remove(np)
    np.removeNode()


getIntoNodePath() gets the node that the player collided with. We use getParent() because when the loader inserts an extra grouping node above the actual geometry. note the difference between these two lines:

Code: Select all
print collisionEntry.getIntoNodePath()

give you
Code: Select all
render/Projectile.egg/smiley


Code: Select all
print collsionEntry.getIntoNodePath().getParent()

gives you
Code: Select all
render/Projectile.egg


We have to remove it from two places. First, remove it from the list so we get rid of any references there. Then, call the removeNode() method on the nodepath itself, which will make it remove itself from the scenegraph.

Both are important. Python does behind-the-scenes garbage collection instead of explicit memory freeing; Panda adds its own reference-counting mechanism as well, so basically the idea is that when an object is no longer in the scene graph AND no longer referenced by any other variables, it gets garbage collected. you do still need to pay attention to what's going on to prevent memory leaks.

Ok, enjoy your radishes!