Hi @sgold, I need your help figuring out how to properly use getOverlappingObjects()
method of GhostControl.
The game prototype I would like to replicate is the kamikaze, which detonates itself in the proximity of the player, also damaging the other enemies.
To realize this idea I thought of using a GhostControl, to apply the damage to all physical in-game objects included in the explosive beam.
For convenience, in this example, I move the kamikaze. By pressing the ENTER key, I print on the console all the objects obtained with the GhostControl, but in the result I only get the floor and of course the kamikaze himself, but not the boxes and NPCs.
I wrote an example test case to try to show you my doubt as clearly as possible.
The scenario is this, I have:
- A floor with MeshCollisionShape, mass = 0, default COLLISION_GROUP_01
- 15 boxes with BoxCollisionShape, mass = 15, COLLISION_GROUP_02
- 8 NPC with BetterCharacterControl, mass = 40, COLLISION_GROUP_02
- A player with a BetterCharacterControl, mass = 40, default COLLISION_GROUP_01 and a GhostControl with COLLISION_GROUP_04
Uncommenting the statemen 1 ghostControl.setCollideWithGroups(PhysicsCollisionObject.COLLISION_GROUP_02);
I thought I get only group 2 objects, instead I get group 1 objects too. I canât understand the logic. What is the correct setup to get only the objects of a certain group?
Here is the test case 1:
/**
*
* @author capdevon
*/
public class Test_OverlappingSphere extends SimpleApplication implements ActionListener {
/**
*
* @param args
*/
public static void main(String[] args) {
Test_OverlappingSphere app = new Test_OverlappingSphere();
AppSettings settings = new AppSettings(true);
settings.setResolution(800, 600);
settings.setFrameRate(60);
settings.setSamples(4);
settings.setBitsPerPixel(32);
settings.setGammaCorrection(true);
app.setSettings(settings);
app.setShowSettings(false);
app.setPauseOnLostFocus(false);
app.start();
}
private BulletAppState physics;
private Node player;
private GhostControl ghostControl;
@Override
public void simpleInitApp() {
viewPort.setBackgroundColor(new ColorRGBA(0.5f, 0.6f, 0.7f, 1.0f));
initPhysics();
createFloor();
createCharacters(8);
createBoxItems(15);
setupPlayer();
setupChaseCamera();
setupKeys();
}
/**
* Initialize the physics simulation
*
*/
public void initPhysics() {
physics = new BulletAppState();
stateManager.attach(physics);
physics.getPhysicsSpace().setAccuracy(0.01f); // 10-msec timestep
physics.getPhysicsSpace().getSolverInfo().setNumIterations(15);
physics.setDebugAxisLength(1);
physics.setDebugEnabled(true);
}
private void createFloor() {
Box box = new Box(20, 0.2f, 20);
Geometry floorGeo = new Geometry("Floor.GeoMesh", box);
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", ColorRGBA.DarkGray);
floorGeo.setMaterial(mat);
rootNode.attachChild(floorGeo);
CollisionShape collShape = CollisionShapeFactory.createMeshShape(floorGeo);
RigidBodyControl rBody = new RigidBodyControl(collShape, 0f);
floorGeo.addControl(rBody);
physics.getPhysicsSpace().add(rBody);
}
private void createCharacters(int tot) {
float radius = 5f;
for (int i = 1; i <= tot; i++) {
float x = FastMath.sin(FastMath.QUARTER_PI * i) * radius;
float z = FastMath.cos(FastMath.QUARTER_PI * i) * radius;
Node node = new Node("Character." + i);
node.attachChild(createLabel("Ch" + i, ColorRGBA.Green));
node.setLocalTranslation(x, 1f, z);
rootNode.attachChild(node);
BetterCharacterControl bcc = new BetterCharacterControl(.5f, 2f, 40f);
node.addControl(bcc);
physics.getPhysicsSpace().add(bcc);
bcc.getRigidBody().setCollisionGroup(PhysicsCollisionObject.COLLISION_GROUP_02);
}
}
private void createBoxItems(int nFragments) {
float halfExtent = 0.4f;
// Common fields
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", ColorRGBA.randomColor());
Box mesh = new Box(halfExtent, halfExtent, halfExtent);
CollisionShape collShape = new BoxCollisionShape(halfExtent);
for (int i = 0; i < nFragments; i++) {
Node node = new Node("Box." + i);
Geometry geo = new Geometry("Box.GeoMesh." + i, mesh);
geo.setMaterial(mat);
node.attachChild(geo);
node.attachChild(createLabel("Bx" + i, ColorRGBA.Red));
node.setLocalTranslation(getRandomPoint(10).setY(4));
rootNode.attachChild(node);
RigidBodyControl rBody = new RigidBodyControl(collShape, 15f);
node.addControl(rBody);
physics.getPhysicsSpace().add(rBody);
rBody.setCollisionGroup(PhysicsCollisionObject.COLLISION_GROUP_02);
}
}
private Vector3f getRandomPoint(int radius) {
int dx = FastMath.nextRandomInt(-radius, radius);
int dz = FastMath.nextRandomInt(-radius, radius);
return new Vector3f(dx, 0, dz);
}
public Spatial createLabel(String text, ColorRGBA color) {
BitmapText bmp = new BitmapText(guiFont, false);
bmp.setText(text);
bmp.setColor(color);
bmp.setSize(1);
bmp.setBox(new Rectangle((-bmp.getLineWidth() / 2) * bmp.getSize(), 0f, bmp.getLineWidth() * bmp.getSize(), bmp.getLineHeight()));
bmp.setQueueBucket(RenderQueue.Bucket.Transparent);
bmp.setAlignment(BitmapFont.Align.Center);
bmp.addControl(new BillboardControl());
Node label = new Node("Label");
label.attachChild(bmp);
label.setLocalTranslation(0, 2, 0);
label.scale(0.5f);
return label;
}
private void setupPlayer() {
player = new Node("Player");
Box box = new Box(0.3f, 1.5f, 0.3f);
Geometry body = new Geometry("Floor.GeoMesh", box);
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", ColorRGBA.Orange);
body.setMaterial(mat);
player.attachChild(body);
rootNode.attachChild(player);
BetterCharacterControl bcc = new BetterCharacterControl(.5f, 2f, 40f);
player.addControl(bcc);
physics.getPhysicsSpace().add(bcc);
ghostControl = new GhostControl(new SphereCollisionShape(radius));
player.addControl(ghostControl);
physics.getPhysicsSpace().add(ghostControl);
ghostControl.setCollisionGroup(PhysicsCollisionObject.COLLISION_GROUP_04);
// -statement 1
//ghostControl.setCollideWithGroups(PhysicsCollisionObject.COLLISION_GROUP_02);
PlayerBaseControl baseControl = new PlayerBaseControl(inputManager);
player.addControl(baseControl);
}
private void setupChaseCamera() {
// disable the default 1st-person flyCam!
stateManager.detach(stateManager.getState(FlyCamAppState.class));
flyCam.setEnabled(false);
ChaseCamera chaseCam = new ChaseCamera(cam, player, inputManager);
//Uncomment this to look 2 world units above the target
chaseCam.setLookAtOffset(Vector3f.UNIT_Y.mult(2));
chaseCam.setMaxDistance(15);
chaseCam.setMinDistance(6);
chaseCam.setRotationSpeed(4f);
}
private void setupKeys() {
addMapping("OverlapSphere", new KeyTrigger(KeyInput.KEY_RETURN));
addMapping("TogglePhysxDebug", new KeyTrigger(KeyInput.KEY_0));
}
private void addMapping(String mappingName, Trigger... triggers) {
inputManager.addMapping(mappingName, triggers);
inputManager.addListener(this, mappingName);
}
@Override
public void onAction(String name, boolean isPressed, float tpf) {
//To change body of generated methods, choose Tools | Templates.
if (name.equals("TogglePhysxDebug") && isPressed) {
boolean debugEnabled = physics.isDebugEnabled();
physics.setDebugEnabled(!debugEnabled);
} else if (name.equals("OverlapSphere") && isPressed) {
System.out.println("---overlapRequest---");
for (PhysicsCollisionObject pco : ghostControl.getOverlappingObjects()) {
String userObj = pco.getUserObject().toString();
System.out.println(userObj);
}
}
}
}
PlayerControl
import com.jme3.bullet.control.BetterCharacterControl;
import com.jme3.input.InputManager;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.Trigger;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
/**
*
* @author capdevon
*/
public class PlayerBaseControl extends AbstractControl implements ActionListener {
private InputManager inputManager;
private BetterCharacterControl bcc;
boolean _StrafeLeft, _StrafeRight;
boolean _MoveForward;
boolean _MoveBackward;
boolean _TurnLeft;
boolean _TurnRight;
boolean ducked;
float m_MoveSpeed = 2.5f;
Vector3f walkDirection = new Vector3f(0, 0, 0);
Vector3f viewDirection = new Vector3f(0, 0, 1);
Vector3f dz = new Vector3f();
Vector3f dx = new Vector3f();
Quaternion tempRot = new Quaternion();
/**
*
* @param inputManager
*/
public PlayerBaseControl(InputManager inputManager) {
this.inputManager = inputManager;
}
@Override
public void setSpatial(Spatial sp) {
super.setSpatial(sp);
if (spatial != null) {
this.bcc = spatial.getControl(BetterCharacterControl.class);
registerInputs();
}
}
@Override
public void controlUpdate(float tpf) {
walkDirection.set(Vector3f.ZERO);
if (_StrafeLeft || _StrafeRight) {
// float k = _StrafeLeft ? 1f : -1;
// spatial.getWorldRotation().mult(Vector3f.UNIT_X, dx);
// walkDirection.addLocal(dx.multLocal(k * m_MoveSpeed));
}
if (_MoveForward || _MoveBackward) {
float k = _MoveForward ? 1 : -1;
spatial.getWorldRotation().mult(Vector3f.UNIT_Z, dz);
walkDirection.addLocal(dz.multLocal(k * m_MoveSpeed));
}
if (_TurnLeft || _TurnRight) {
float k = _TurnLeft ? FastMath.PI : -FastMath.PI;
tempRot.fromAngleNormalAxis(tpf * k, Vector3f.UNIT_Y).multLocal(viewDirection);
bcc.setViewDirection(viewDirection); //Turn!
}
bcc.setWalkDirection(walkDirection); //Walk!
}
@Override
public void onAction(String name, boolean isPressed, float tpf) {
if (name.equals("StrafeLeft")) {
_StrafeLeft = isPressed;
} else if (name.equals("StrafeRight")) {
_StrafeRight = isPressed;
} else if (name.equals("MoveForward")) {
_MoveForward = isPressed;
} else if (name.equals("MoveBackward")) {
_MoveBackward = isPressed;
} else if (name.equals("RotateLeft")) {
_TurnLeft = isPressed;
} else if (name.equals("RotateRight")) {
_TurnRight = isPressed;
} else if (name.equals("Ducked") && isPressed) {
ducked = !ducked;
bcc.setDucked(ducked);
} else if (name.equals("Jump") && isPressed) {
bcc.jump();
}
}
public void stop() {
// _RunForward = false;
_MoveForward = false;
_MoveBackward = false;
_TurnLeft = false;
_TurnRight = false;
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
//To change body of generated methods, choose Tools | Templates.
}
/**
* Custom Keybinding: Map named actions to inputs.
*/
private void registerInputs() {
addMapping("StrafeLeft", new KeyTrigger(KeyInput.KEY_Q));
addMapping("StrafeRight", new KeyTrigger(KeyInput.KEY_E));
addMapping("RunForward", new KeyTrigger(KeyInput.KEY_SPACE));
addMapping("MoveForward", new KeyTrigger(KeyInput.KEY_W));
addMapping("MoveBackward", new KeyTrigger(KeyInput.KEY_S));
addMapping("RotateLeft", new KeyTrigger(KeyInput.KEY_A));
addMapping("RotateRight", new KeyTrigger(KeyInput.KEY_D));
addMapping("Ducked", new KeyTrigger(KeyInput.KEY_Z));
}
private void addMapping(String mapping, Trigger... triggers) {
inputManager.addMapping(mapping, triggers);
inputManager.addListener(this, mapping);
}
}
Another strange case, If the GhostControl is associated with an object that never moves, the getOverlappingObject()
method always returns all physical objects, regardless of which collision group they belong to.
Here is the test case 2:
import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.collision.PhysicsCollisionObject;
import com.jme3.bullet.collision.shapes.BoxCollisionShape;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.collision.shapes.SphereCollisionShape;
import com.jme3.bullet.control.BetterCharacterControl;
import com.jme3.bullet.control.GhostControl;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.util.CollisionShapeFactory;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.Trigger;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Box;
/**
*
* @author capdevon
*/
public class Test_CollisionGroups extends SimpleApplication implements ActionListener {
/**
* @param args
*/
public static void main(String[] args) {
Test_CollisionGroups app = new Test_CollisionGroups();
app.start();
}
private BulletAppState physics;
private GhostControl ghostControl;
@Override
public void simpleInitApp() {
cam.setLocation(Vector3f.UNIT_XYZ.mult(15f));
cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y);
flyCam.setMoveSpeed(20f);
viewPort.setBackgroundColor(new ColorRGBA(0.5f, 0.6f, 0.7f, 1.0f));
initPhysics();
setupScene();
setupKeys();
}
/**
* Initialize the physics simulation
*
*/
public void initPhysics() {
physics = new BulletAppState();
stateManager.attach(physics);
physics.getPhysicsSpace().setAccuracy(0.01f); // 10-msec timestep
physics.getPhysicsSpace().getSolverInfo().setNumIterations(15);
physics.setDebugAxisLength(1);
physics.setDebugEnabled(true);
}
private void createFloor() {
Box box = new Box(20, 0.2f, 20);
box.scaleTextureCoordinates(new Vector2f(10, 10));
Geometry floorGeo = new Geometry("Floor.GeoMesh", box);
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", ColorRGBA.DarkGray);
floorGeo.setMaterial(mat);
rootNode.attachChild(floorGeo);
CollisionShape collShape = CollisionShapeFactory.createMeshShape(floorGeo);
RigidBodyControl rBody = new RigidBodyControl(collShape, 0f);
floorGeo.addControl(rBody);
physics.getPhysicsSpace().add(rBody);
}
private void setupScene() {
createFloor();
float radius = 5f;
createCharacters(radius);
createBoxItems(radius);
createPlayer(radius);
}
private void createCharacters(float radius) {
for (int i = 1; i <= 8; i++) {
float x = FastMath.sin(FastMath.QUARTER_PI * i) * radius;
float z = FastMath.cos(FastMath.QUARTER_PI * i) * radius;
Node node = new Node("Character." + i);
node.setLocalTranslation(x, 1f, z);
rootNode.attachChild(node);
BetterCharacterControl bcc = new BetterCharacterControl(.5f, 2f, 40f);
node.addControl(bcc);
physics.getPhysicsSpace().add(bcc);
bcc.getRigidBody().setCollisionGroup(PhysicsCollisionObject.COLLISION_GROUP_02);
}
}
private void createBoxItems(float radius) {
float halfExtent = 0.4f;
Box mesh = new Box(halfExtent, halfExtent, halfExtent);
CollisionShape collShape = new BoxCollisionShape(halfExtent);
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", ColorRGBA.randomColor());
for (int i = 1; i <= 8; i++) {
float x = FastMath.sin(FastMath.QUARTER_PI * i) * radius/2;
float z = FastMath.cos(FastMath.QUARTER_PI * i) * radius/2;
Node cube = new Node("Box." + i);
Geometry geo = new Geometry("Box.GeoMesh." + i, mesh);
geo.setMaterial(mat);
cube.attachChild(geo);
cube.setLocalTranslation(x, 1f, z);
rootNode.attachChild(cube);
RigidBodyControl rBody = new RigidBodyControl(collShape, 25f);
cube.addControl(rBody);
physics.getPhysicsSpace().add(rBody);
rBody.setCollisionGroup(PhysicsCollisionObject.COLLISION_GROUP_04);
}
}
private void createPlayer(float radius) {
Node player = new Node("Player");
rootNode.attachChild(player);
BetterCharacterControl bcc = new BetterCharacterControl(.5f, 2f, 40f);
player.addControl(bcc);
physics.getPhysicsSpace().add(bcc);
ghostControl = new GhostControl(new SphereCollisionShape(radius));
player.addControl(ghostControl);
physics.getPhysicsSpace().add(ghostControl);
ghostControl.setCollisionGroup(PhysicsCollisionObject.COLLISION_GROUP_06);
// ghostControl.setCollideWithGroups(PhysicsCollisionObject.COLLISION_GROUP_02);
}
private void setupKeys() {
addMapping("OverlapSphere", new KeyTrigger(KeyInput.KEY_RETURN));
addMapping("TogglePhysxDebug", new KeyTrigger(KeyInput.KEY_0));
}
private void addMapping(String mappingName, Trigger... triggers) {
inputManager.addMapping(mappingName, triggers);
inputManager.addListener(this, mappingName);
}
@Override
public void onAction(String name, boolean isPressed, float tpf) {
//To change body of generated methods, choose Tools | Templates.
if (name.equals("TogglePhysxDebug") && isPressed) {
boolean debugEnabled = physics.isDebugEnabled();
physics.setDebugEnabled(!debugEnabled);
} else if (name.equals("OverlapSphere") && isPressed) {
System.out.println("--getOverlappingObjects");
for (PhysicsCollisionObject pco : ghostControl.getOverlappingObjects()) {
String userObj = pco.getUserObject().toString();
System.out.println("\t" + userObj);
}
}
}
@Override
public void simpleUpdate(float tpf) {
//TODO: add update code
}
@Override
public void simpleRender(RenderManager rm) {
//TODO: add render code
}
}