I’ve been considering if it would be worth expanding PhysicsSpace to include the ability to register a collision event listener that is specific to a Spatial. The intent would be to have the ability to register a callback that gets fired only when a specific Spatial is part of the collision event.
The theory would be to include another map of collisionListeners that would register a class and spatial. When the spatial is part of the collision (nodeA or nodeB), then the class would get called with a routine called spatialCollision(physicsCollisionEvent). A class could register itself multiple times with different spatials if more than 1 spatial was desired.
This way, different classes that have registered collisionListeners would only get called when a spatial they care about is involved in a collision. I think it would make user code easier. My initial thought is to allow spatial controls that have collision listeners to only get called for the spatials they are attached to (i.e. subtract hit points when the spatial gets hit by something, etc).
If the idea is a valid one, I can start working on it. Just didn’t want to waste my time if the idea had some flaws or wasn’t desired.
Any comments?
Seems to me like you could do this with a custom listener that itself has a list of listeners without making any changes to the engine.
Absolutely. Just asking to see if anyone thought the engine could use it. If not, I’ll make my own for my projects.
Not a good idea, you can do this separation already by just attaching a contol to a specific spatial.
I was just thinking it would be nice to have the seperation done automatically so the user didn’t have to. Even if a control with a collision listener is attached to a specific spatial, the seperation will still have to be done in each control instead of once when the collison event is created. I plan to implement a custom listener in my application like pspeed mentioned. The world class will have the real collision listener that does the seperation and then calls a custom listener in the target class if the spatial matches an entry in the listener array.
Thanks for the comments.
Ah, I misunderstood. There actually is a way in bullet to get per-object callbacks, they are called “material callbacks” however they are not very handy and also used for other things so for now I suggest doing that “by hand” as paul suggested.
Just in case anyone wants to comment, here is what I’m thinking about doing. Seems to be working, but not sure it’s the most efficient way to do it.
Class to hold some data about the collision:
[java]
public class SpatialCollisionData {
private Spatial registeredSpatial = null;
private Spatial otherSpatial = null;
private SpatialCollisionListener listener = null;
// registeredSpatial is the main spatial I’m interested in, the otherSpatial is the other spatial in the collision
// The listener is the class instance to call when the collision pair matches the 2 spatials
// The purpose of the registered spatial and other spatial is just to be able to apply damage to otherSpatial
public SpatialCollisionData (Spatial registeredSpatial, Spatial otherSpatial, SpatialCollisionListener listener) {
this.registeredSpatial = registeredSpatial;
this.otherSpatial = otherSpatial;
this.listener = listener;
}
public Spatial getRegisteredSpatial() {
return registeredSpatial;
}
public Spatial getOtherSpatial() {
return otherSpatial;
}
public SpatialCollisionListener getListener() {
return listener;
}
}
[/java]
Main World class to get the physics callback from bullet:
[java]
public class World implements AppState, PhysicsCollisionListener {
…
private Map<Spatial, SpatialCollisionData> spatialCollisionListeners = new HashMap<Spatial, SpatialCollisionData>();
…
public void initScene() {
…
// Loop through the enemies and add an entry for each player / enemy combination
// This registers a collision between the player’s weapon and the enemy as well as
// the enemy weapon to the player
for (BasicCharacter enemy: enemies) {
SpatialCollisionData playerCollisionData = new SpatialCollisionData(
((Fighter)player).getCurrentWeapon().getWeaponNode(),
enemy.getNode(),
player
);
SpatialCollisionData enemyCollisionData = new SpatialCollisionData(
((Fighter)enemy).getCurrentWeapon().getWeaponNode(),
player.getNode(),
enemy
);
spatialCollisionListeners.put(((Fighter)player).getCurrentWeapon().getWeaponNode(), playerCollisionData);
spatialCollisionListeners.put(((Fighter)enemy).getCurrentWeapon().getWeaponNode(), enemyCollisionData);
}
}
…
// collision callback from bullet
public void collision(PhysicsCollisionEvent pce) {
// logger.log(Level.INFO, “Collision between {0} and {1}”,
// new Object[]{pce.getNodeA().getName(), pce.getNodeB().getName()});
Spatial nodeA = pce.getNodeA();
Spatial nodeB = pce.getNodeB();
Iterator it = spatialCollisionListeners.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
if (entry.getKey().equals(nodeA)) {
SpatialCollisionData collisionData = ((SpatialCollisionData)entry.getValue());
if (collisionData.getOtherSpatial().equals(nodeB)) {
collisionData.getListener().spatialCollision(nodeA, nodeB, pce);
}
}
if (entry.getKey().equals(nodeB)) {
SpatialCollisionData collisionData = ((SpatialCollisionData)entry.getValue());
if (collisionData.getOtherSpatial().equals(nodeA)) {
collisionData.getListener().spatialCollision(nodeB, nodeA, pce);
}
}
}
}
[/java]
Player and Enemy class each implement Fighter interface and have the following routine:
[java]
@Override
public void spatialCollision(Spatial registeredSpatial, Spatial otherSpatial, PhysicsCollisionEvent event) {
logger.log(Level.INFO, “Collision between {0} and {1}”,
new Object[]{registeredSpatial.getName(), otherSpatial.getName()});
if (otherSpatial.getUserData(Fighter.class.getName()) != null) {
logger.log(Level.INFO, “otherSpatial is a Fighter”);
// call the routine to subtract hitpoints and display some damage text above the character
((Fighter)otherSpatial.getUserData(Fighter.class.getName())).gotHit(5f);
}
}
[/java]
[EDIT] Forgot to mention that I add the following to the node for the player and each enemy:
[java]
// in each Player and Enemy class, set the node userData to the instance of the class
node.setUserData(Fighter.class.getName(), (Fighter)this);
[/java]
[EDIT2] Also forgot to include the listener interface
[java]
public interface SpatialCollisionListener {
public void spatialCollision(Spatial registeredSpatial, Spatial otherSpatial, PhysicsCollisionEvent event);
}
[/java]
You could create your own listener base class that had the filtering logic and then only called an abstract process style method if it matched the filter.
Then create your anonymous listeners as subclasses of that one having supplied the filtering data in the constructor.
i.e.
[java]
class FilteringCollisionListener {
void collision(blah)
if (filterMatches)
processCollision(blah)
}
abstract processCollision(blah)
}
[/java]
[java]
.addListener(new FilteringCollisionListener(filter data blah) {
void processCollision(blah) {
// Only called if filter matches
}
}
[/java]