Search code examples
javaobjectcamerajmonkeyengine

How to make the camera follow 2 objects?


I'm making my first 3D game and it's a sort of streetfighter/tekken game. I've seen how the some of the camera modes works ie. chasecam and camnode. What I dont know is how to make the camera follow both players at the same time. I want the camera to zoom in when the players are near each others, and zoom out when they are not.

I would appreciate any help with ideas or possible solutions.

Thanks


Solution

  • You can achieve this fairly easily be manually setting the camera position provided you know two pieces of information, where you want the camera to be, and where you want it to look

    Where the camera should look

    This is the easiest thing to determine, the camera should be looking at the midway position of the two objects. Once you know that you can set where the camera is looking using

    Vector3f boxsCentre=box1.getWorldTranslation().add(box2.getWorldTranslation()).mult(0.5f);
    cam.lookAt(boxsCentre, Vector3f.UNIT_Y);
    

    Where the camera should be

    Where the camera should be is more tricky. You know it should be somewhere on the line that extends from the centre of the two objects in a direction perpendicular to the line between those two objects. Thankfully the cross product gives us this. We want the camera to always be on the same level as the objects so by crossing the seperation vector by a vector going straight up we get that perpendicular line

    Vector3f seperationVector=box2.getWorldTranslation().subtract(box1.getWorldTranslation());
    Vector3f perpendicularFromTheAction= seperationVector.cross(Vector3f.UNIT_Y);
    
    perpendicularFromTheAction.normalizeLocal();
    

    So, that gives us the line, but where on the line should we put the camera. I just played around with this and found that twice the distance between the objects gives a nice look, so

    float distance=2*seperationVector.length();
    Vector3f newCameraLocation=boxsCentre.add(perpendicularFromTheAction.mult(distance));
    

    Then you can set the cameras position

    cam.setLocation(newCameraLocation);
    

    Putting it all together

    I've used this code together with two boxes that are moving on a loop to demostrate this, as you can see you get the effect you want

    import com.jme3.app.SimpleApplication;
    import com.jme3.material.Material;
    import com.jme3.math.*;
    import com.jme3.renderer.RenderManager;
    import com.jme3.scene.Geometry;
    import com.jme3.scene.shape.Box;
    
    public class FightTest extends SimpleApplication {
    
        Geometry box1;
        Geometry box2;
        public static void main(String[] args) {
            FightTest app = new FightTest();
            app.start();
        }
    
        @Override
        public void simpleInitApp() {
    
            //put in some reference boxes
            for(int i=-20;i<=20;i+=20){
                for(int j=-20;j<=20;j+=20){
                    if (j!=0||i!=0){
                        Geometry referenceBox = createBox(ColorRGBA.Red);
                        referenceBox.setLocalTranslation(i, 0, j);
                        rootNode.attachChild(referenceBox);
                    }
    
                }
            }
    
            //put in our two players
            box1 = createBox(ColorRGBA.Blue);
            box1.setLocalTranslation(5, 0, 0);
    
            box2 = createBox(ColorRGBA.Green);
    
            rootNode.attachChild(box1);
            rootNode.attachChild(box2);
        }
    
        private Geometry createBox(ColorRGBA color){
    
            Box b = new Box(Vector3f.ZERO, 1, 1, 1);
            Geometry box = new Geometry("Box", b);
    
            Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
            mat.setColor("Color", color);
            box.setMaterial(mat);
    
            return box;
        }
    
        @Override
        public void simpleUpdate(float tpf) {
            adjustCam();
            movePlayers(tpf);
        }
    
        @Override
        public void simpleRender(RenderManager rm) {
            //TODO: add render code
        }
    
        private void adjustCam(){
            //we want our camera to look at the centre of the boxes
            Vector3f boxsCentre=box1.getWorldTranslation().add(box2.getWorldTranslation()).mult(0.5f);
    
            cam.lookAt(boxsCentre, Vector3f.UNIT_Y);
    
            //we also want our camera to move closer/further away as the boxes seperate.
            //and move around so its always parallel to the action
    
            //parallel to the action means on the line given by the cross product of the
            //box seperation and the upwards vector
    
            Vector3f seperationVector=box2.getWorldTranslation().subtract(box1.getWorldTranslation());
            Vector3f perpendicularFromTheAction= seperationVector.cross(Vector3f.UNIT_Y);
    
            perpendicularFromTheAction.normalizeLocal();
    
            //we could (and you should) get complicated on exactly how far the camera should 
            //move backwards, but I'm just going to make the camera twice as far away as the 
            //objects are seperated 
            float distance=2*seperationVector.length();
    
            Vector3f newCameraLocation=boxsCentre.add(perpendicularFromTheAction.mult(distance));
    
            cam.setLocation(newCameraLocation);
        }
    
        float timeAccumulator=0; 
        private void movePlayers(float tpf){
            //basic movement, just for demo
            timeAccumulator+=tpf;
    
            if (timeAccumulator<2){
                box1.move(new Vector3f(5f*tpf,0,0));
                box2.move(new Vector3f(0,0,5f*tpf));
            }else if (timeAccumulator<4){
                box1.move(new Vector3f(-5f*tpf,0,0));
                box2.move(new Vector3f(0,0,-5f*tpf));
            }else{
                 timeAccumulator=0;
            }
    
        }
    }