Lesson 9
Mask-erade
In which we make a laser, and learn how to specify what should collide with what.
Our player-character can be hit (ineffectually, but still), but cannot hit back. Let’s do something about that. Specifically, let’s give our character a laser!
In terms of collisions, our laser will be represented by a “CollisionRay”: a line that starts at a given point, and extends indefinitely in a given direction.
We want our laser to keep doing damage as long as it’s held on an enemy, so we want new collision-information each update. Furthermore, we only really want the first hit that it makes on a given update, not any further down its length.
We could perhaps use collision-events for this, but an easier way is have our ray be handled by a “CollisionHandlerQueue”, tell the queue to sort itself so that the first collision is the first entry, and then just use that.
For the moment, since we don’t have a visual representation of the laser, we’ll just print out what it hits.
In “GameObject.py”:
Okay, that’s great–but there’s a problem: Right now, our laser can hit anything. Including the player-character, on occasion. (It’s also not pointing in the correct direction–but we’ll get to that presently.)
So what we want next is a way of telling the collision system which things a given collision object can collide with. In the case of Panda’s built-in collision-system, this is handled via “BitMask” objects.
BitMasks can be thought of as a set of flags (the “bits”), each either on or off, one or zero. When used by the physics system, only collisions in which the colliders share at least one “on” bit are registered. All others are ignored.
Furthermore, remember that Panda3D distinguishes which node in a collision is the source of the collision, the “from” object, and which is the node that was collided with, the “into” object. So each collider in Panda3D’s collision system has two BitMasks: a “from” mask and an “into” mask.
Collisions are thus only registered between two objects when the “from” object’s “from” mask shares at least one “on” bit with the “into” object’s “into” mask.
By default, colliders have all of their first 20 bits set to “on” (i.e. bits 0 through 19, inclusive)–hence our problem, as our ray collides with everything.
Once we’ve created a BitMask, the simplest way to set which bits are “on” is to call “setBit”, providing the index of the bit to be set. If you’re comfortable with converting boolean values to base-ten, you can also just initialise the BitMask with a single number.
By default, a newly-created BitMask object has all its bits set to “off”.
In our case, we want to make sure that the player has a mask, and that the player’s ray has a different mask, so that they don’t collide. Conversely, we want our enemy to have a mask that matches the ray’s, so that they do collide.
Specifically, let’s associate the player with bit 1, and the enemy with bit 2. The ray, therefore, should use bit 2–but not bit 1.
(In a larger project, we might use constants or an enumeration to provide more-intuitive names for our bits. But for the purposes of this tutorial, let’s keep things simple!)
Now our laser should skip the player, but hit both traps and walking-enemies!
We’ll also want some means of representing our laser, and of showing that it has hit something. For that, we’ll use a new model, from the file named “bambooLaser”. It’s a simple quad, narrow, and one unit in length. This we’ll attach to our actor, and just scale it to match the laser’s length.
Of course, right now our laser starts at (0, 0, 0), points in the positive y-direction, and… stays that way. If you lure the “walking enemy” to a point just “above” centre, you may see the laser react, regardless of where you are.
We’ll have our laser fire towards the mouse-cursor, and start at the player-character’s position. Furthermore, we’ll have our character face the direction in which we’re firing. As it happens, these two things work together nicely…