Search code examples
javacollisionjmonkeyengine

Jmonkey Collision Detection


I am new to Jmonkey programming and I would like to ask a question about collision interaction as my code seems to finds collisions possibly from the terrain and i do not know how to solve this out. My goal is player as a first person to be detected if he collides with an enemie's ghost control to display a message as an output. My code displays a continues collision and then it crashes...

package test;

//imports...

public class test extends SimpleApplication
implements ActionListener,PhysicsTickListener{
    private MotionPath path;
    private MotionPath path2;
    private MotionTrack motionTrack;
    private MotionTrack motionTrack2;
    private AnimChannel channel2;
    private AnimControl control2;
    private AnimControl control3;
    private AnimChannel channel3;
    private BulletAppState bulletAppState;
    private RigidBodyControl landscape;
    private CharacterControl player;
    private Vector3f walkDirection = new Vector3f();
    private boolean left = false, right = false, up = false, down = false;
    private TerrainQuad terrain;
    private Material mat_terrain;
    private GhostControl ghost;
    static test app;
    Material matMarker;
    public static void main(String[] args) {
        app = new test();
        app.start();

    }
    float displacement=60;
    int score = 0;
    int robotHealth=0;
    Geometry mark;
    Node shootables;
    Node pickUpObject1;
    BitmapText hudText;
    @Override
    public void simpleInitApp() {

        createScene();
        enemies();
        pickUptype1();
        initCrossHairs(); // a "+" in the middle of the screen to help aiming
        initKeys();       // load custom key mappings
        initMark();       // a red sphere to mark the hit



        hudText = new BitmapText(guiFont, false);
        hudText.setSize(guiFont.getCharSet().getRenderedSize());      // font size
        hudText.setColor(ColorRGBA.Red);                             // font color

        hudText.setLocalTranslation(600, 700, 0); // position
        guiNode.attachChild(hudText);


        DirectionalLight sun2 = new DirectionalLight();
        sun2.setDirection(new Vector3f(-0.1f, -0.7f, -1.0f));
        int width = settings.getWidth();           //width is the width of the gui
        int height = settings.getHeight();         //height is the height of the gui
    }


    protected Geometry makeCube(String name, float x, float y, float z) {
        Box box = new Box(new Vector3f(x, y, z), 3f, 3f, 3f);
        Geometry cube = new Geometry(name, box);

        Material mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        Texture tex_ml = assetManager.loadTexture("Interface/Logo/Monkey.jpg");
        mat1.setTexture("ColorMap", tex_ml);
        cube.setMaterial(mat1);

        return cube;
    }

    private PhysicsSpace getPhysicsSpace() {
        return bulletAppState.getPhysicsSpace();
    }

    /**
    * This is the main event loop--walking happens here.
    * We check in which direction the player is walking by interpreting
    * the camera direction forward (camDir) and to the side (camLeft).
    * The setWalkDirection() command is what lets a physics-controlled player walk.
    * We also make sure here that the camera moves with player.
    */
    @Override
    public void simpleUpdate(float tpf) {
        hudText.setText("SCORE \n" + "    " + score);// the text
        Vector3f camDir = cam.getDirection().clone().multLocal(0.6f);
        Vector3f camLeft = cam.getLeft().clone().multLocal(0.4f);
        walkDirection.set(0, 0, 0);
        if (left)  { walkDirection.addLocal(camLeft); }
        if (right) { walkDirection.addLocal(camLeft.negate()); }
        if (up)    { walkDirection.addLocal(camDir); }
        if (down)  { walkDirection.addLocal(camDir.negate()); }


        player.setWalkDirection(walkDirection);
        cam.setLocation(player.getPhysicsLocation());
        path.setCycle(true); // Make path a complete circuit
        path2.setCycle(true);
        motionTrack.setLoopMode(LoopMode.Loop);
        motionTrack2.setLoopMode(LoopMode.Loop);

    }

    public Node robot(){

        Node monster = (Node) assetManager.loadModel("Models/Oto/Oto.mesh.xml");
        monster.scale(1.5f, 1.5f, 1.5f);
        monster.rotate(0.0f, -3.0f, 0.0f);
        // Create a appropriate physical shape for it

        return monster;
    }


    public void createScene(){

        /** Set up Physics */
        bulletAppState = new BulletAppState();
        stateManager.attach(bulletAppState);
        //bulletAppState.getPhysicsSpace().enableDebug(assetManager);

        flyCam.setMoveSpeed(100);
        setUpKeys();


        terrain = new TerrainQuad("my terrain", 65, 513, heightmap.getHeightMap());


        /** 6. Add physics: */
        // We set up collision detection for the scene by creating a
        // compound collision shape and a static RigidBodyControl with mass zero.*/
        CollisionShape terrainShape =
        CollisionShapeFactory.createMeshShape((Node) terrain);
        landscape = new RigidBodyControl(terrainShape, 0);
        terrain.addControl(landscape);


        CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(1.5f, 6f, 1);
        player = new CharacterControl(capsuleShape, 0.05f);
        player.setJumpSpeed(20);
        player.setFallSpeed(30);
        player.setGravity(30);
        player.setPhysicsLocation(new Vector3f(145f, -28f, 10f));
        player.setCollisionGroup(PhysicsCollisionObject.COLLISION_GROUP_01);
        player.addCollideWithGroup(PhysicsCollisionObject.COLLISION_GROUP_01);


        setUpLight();
        rootNode.attachChild(SkyFactory.createSky( assetManager,
        "Textures/Sky/Bright/BrightSky.dds", false));
    }


    public void enemies(){

        shootables = new Node("Shootables");
        rootNode.attachChild(shootables);


        Node Robot1 = robot();
        Node Robot2 = robot();


        CapsuleCollisionShape capsule = new CapsuleCollisionShape(4f, 10f);
        RigidBodyControl robot1Cap = new RigidBodyControl(capsule, 0.01f);

        Robot1.addControl(robot1Cap);


        getPhysicsSpace().add(robot1Cap);

        bulletAppState.getPhysicsSpace().add(robot1Cap);
        bulletAppState.getPhysicsSpace().enableDebug(assetManager);

        robot1Cap.setMass(100f);
        robot1Cap.setKinematic(true);

        CapsuleCollisionShape capsule2 = new CapsuleCollisionShape(4f, 10f);
        RigidBodyControl robot2Cap = new RigidBodyControl(capsule, 0.01f);

        Robot2.addControl(robot2Cap);

        getPhysicsSpace().add(robot2Cap);

        bulletAppState.getPhysicsSpace().add(robot2Cap);
        bulletAppState.getPhysicsSpace().enableDebug(assetManager);

        robot2Cap.setMass(100f);
        robot2Cap.setKinematic(true);

        ghost = new GhostControl(
        new BoxCollisionShape(new Vector3f(8f,8f,8f)));  // a box-shaped ghost
        Robot1.addControl(ghost);

        ghost.setCollisionGroup(PhysicsCollisionObject.COLLISION_GROUP_01);
        ghost.setCollideWithGroups(PhysicsCollisionObject.COLLISION_GROUP_01);


        getPhysicsSpace().add(ghost);

        getPhysicsSpace().addTickListener(this);

        control2 = Robot1.getControl(AnimControl.class);
        channel2 = control2.createChannel();
        channel2.setAnim("Walk");

        control3 = Robot2.getControl(AnimControl.class);
        channel3 = control3.createChannel();
        channel3.setAnim("Walk");
        path = new MotionPath();

        path.addWayPoint(new Vector3f(500f,-83f,3f));
        path.addWayPoint(new Vector3f(350f,-79f, 3f));
        path.enableDebugShape(assetManager,rootNode);

        // Initialize our motionTrack object
        motionTrack = new MotionTrack(Robot1, path);

        motionTrack.setDirectionType(MotionTrack.Direction.Path);
        // Enable the motionTrack
        motionTrack.setEnabled(true);


        path2 = new MotionPath();



        path2.addWayPoint(new Vector3f(180f,-50f,-100f));
        path2.addWayPoint(new Vector3f(200f, -55f, -30f));
        path2.enableDebugShape(assetManager,rootNode);

        // Initialize our motionTrack object
        motionTrack2 = new MotionTrack(Robot2, path2);
        motionTrack2.setDirectionType(MotionTrack.Direction.Path);
        // Enable the motionTrack
        motionTrack2.setEnabled(true);

        shootables.attachChild(Robot1);
        shootables.attachChild(Robot2);


    }

    public void physicsTick(PhysicsSpace space, float f) {
        if (ghost.getOverlappingObjects().size() > 0) {
            final Vector3f bPoint = ghost.getPhysicsLocation();
            try {
                app.enqueue(new Callable<Boolean>() {
                    public Boolean call() throws Exception {
                        app.addMarker(bPoint);
                        return true;
                    }
                });
            } catch (Exception ex) {
            }
        }
    }


    public void pickUptype1(){
        pickUpObject1 = new Node("pickUpObject1");
        rootNode.attachChild(pickUpObject1);


        Node cube1 = new Node();
        cube1.attachChild(makeCube("the Deputy", 220f, -63f, -150f));
        Node cube2 = new Node();
        cube2.attachChild(makeCube("the Deputy2", 410f, -89f, -270f));


        RigidBodyControl floor_phy = new RigidBodyControl(0.0f);
        cube1.addControl(floor_phy);

        RigidBodyControl floor_phy2 = new RigidBodyControl(0.0f);
        cube2.addControl(floor_phy2);
        bulletAppState.getPhysicsSpace().add(floor_phy);
        bulletAppState.getPhysicsSpace().add(floor_phy2);
        pickUpObject1.attachChild(cube1);
        pickUpObject1.attachChild(cube2);
    }


}

Solution

  • You include a lot of unnecessary code and judging from the style you are new to programming in general, I edited your question to make it readable so people could try and help (didn't fix the indenting though that would have taken far too much patience. The code should be stripped down to include only the physics code, member variables and setup for context. If your ghost control didn't rest on top of the ground it would fall through and leave your robot so its going to be constantly colliding with the ground or falling through the void after an initial pass through the ground.

    You should be using getOverlappingObjects() to get the list of objects it's colliding with and then checking that list for the characters representation in the physics space.

    Also this below is bad form. If your method is going to throw an exception you should know what it can throw and throw those exact exceptions and handle each one. Otherwise you're basically strapping on body armour and running around with your eyes closed because believe you won't get hurt.

    public Boolean call() throws Exception
    

    Also your enemies function is poorly designed. Functions should preferably be small, and should definitely only fulfill one purpose, whereas your function creates a node with shootables, creates two enemies and sets up controls.

    Creating the node and attaching it to the root node should be in the constructor or an initialise function that is called near the start. And then you should have a function called something like addEnemy that adds a single enemy. For example:

    /*
    * adds an enemy and returns a reference to that enemy
    */
    Spatial addEnemy()
    {
        //Characters are spatials typically in JMonkey
        Spatial enemy=new Spatial(); 
        CapsuleCollisionShape collisionShape=new CapsuleCollisionShape(4.0f, 10.0f);
        CharacterControl characterControl = new CharacterControl(collisionShape, stepHeight);
    
        enemy.addControl(characterControl);
        getPhysicsSpaceState(characterControl);
        shootables.attachChild(enemy);
    }
    

    Now you notice this is really different right?

    Well in JMonkey Characters use Spatials (a class derived from node) and also they don't use rigid body physics, things like terrain are too bumpy, instead they use character controls and these keep them facing upright so they don't topple and allow you to set walk direction and view direction. The character control also gives access to a collision shape that you can run collides with on to get when character controls collide with each other.

    For controlling your Spatial you'll want to subclass AbstractControl, using that you can get access to other controls and update your Spatial every so often. In my game all character are Spatials with different skins set the difference is in the controls.

    The player has:

    CharacterControl
    //to send keyboard input to the character controller to move it
    KeyboardControl 
    GunControl
    

    Whereas the AI has

    CharacterControl
    //does path planning to get a route and steers character control along it
    RouteController  
    GunControl
    //AI to determine where/if I want to walk, what to shoot at, where to aim
    BehaviourControl
    

    You should be splitting a lot of this functionality into a path to make it easier to maintain, learning more about how the JMonkey API works by looking at examples and also learning how Object Oriented design works and how to structure things so they are easy to read and easy to maintain. If you're interested the book clean code by Robert C Martin is a great guide on a clean maintainable coding style :)

    Any questions feel free to ask