JMonkey - Shooting in Direction of Crosshairs

1.7k Views Asked by At

How can I shoot in the direction that a cross-hair is pointing at?

Using the JMonkey engine, I am creating a game where I need a ship to shoot other ships.

So, I created cross-hairs that can move on the screen (up, down, left, right) according to user input, so the user can aim on a certain place.

Now I need I to shoot a cannon from my ship, in the direction that the cross-hair is standing at.

How can I shoot at the place that the cross-hair is pointing at?

2

There are 2 best solutions below

0
On

My reading of this question is that the aim is not to shoot where the camera is facing but where a cursor (not in the centre of the screen) is pointing.

This can be achieved using the cam.getWorldCoordinates(screenPosition, zDepth); command, this returns the 3D point in space that would end up at the point screenPosition on the screen. So if we create a point at zDepth of zero and a point at zDepth of one we can create a ray coming from the cursor position travelling outwards so anything that the curser is "over" is selected. screenPosition is in pixels from the bottom left of the window

An example program that uses this technique is as follows and is based upon the second part of hello picking.

In my example the cursor is moved using the keyboard (H,J,K,U) but a mouse click could also be used (but I'm using the mouse for looking around)

import com.jme3.app.SimpleApplication;
import com.jme3.collision.CollisionResults;
import com.jme3.font.BitmapText;
import com.jme3.input.KeyInput;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Ray;
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;

public class Main extends SimpleApplication {

    public static KeyBindings keyBindings;
    public Vector2f cursorPosition=new Vector2f(100,100);
    public Node shootables=new Node();

    public Node crossHairNode=new Node();

    public static void main(String[] args) {
        Main app = new Main();
        app.start();
    }

    @Override
    public void simpleInitApp() {
        //bind keys to move cursor
        keyBindings=new KeyBindings(inputManager); //for managing keystrokes
        keyBindings.registerKeyBinding(KeyInput.KEY_SPACE, "fire");
        keyBindings.registerKeyBinding(KeyInput.KEY_U, "up"); 
        keyBindings.registerKeyBinding(KeyInput.KEY_J, "down");
        keyBindings.registerKeyBinding(KeyInput.KEY_H, "left");
        keyBindings.registerKeyBinding(KeyInput.KEY_K, "right");

        initGui();

        Box b = new Box(Vector3f.ZERO, 2, 2, 2);
        Geometry geom = new Geometry("BigBox", b);

        Box b2 = new Box(Vector3f.ZERO, 1, 1, 1);
        Geometry geom2 = new Geometry("SmallBox", b2);
        geom2.setLocalTranslation(3, 0, 3);


        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", ColorRGBA.Blue);
        geom.setMaterial(mat);
        geom2.setMaterial(mat);
        rootNode.attachChild(shootables);

        shootables.attachChild(geom);
        shootables.attachChild(geom2);
    }

    @Override
    public void simpleUpdate(float tpf) {

        updateCrossHairs();

        if (keyBindings.getBinding("fire").hasBecomeTrueSinceLastAccess()){
            CollisionResults results = new CollisionResults();

            Vector3f cursor3dLocation = cam.getWorldCoordinates(
                new Vector2f(cursorPosition.x, cursorPosition.y), 0f).clone();
            Vector3f dir = cam.getWorldCoordinates(
                new Vector2f(cursorPosition.x, cursorPosition.y), 1f).subtractLocal(cursor3dLocation).normalizeLocal();
            Ray ray = new Ray(cursor3dLocation, dir);
            shootables.collideWith(ray, results);

            if (results.size()>0){
                resultsText.setText("Hit: " + results.getClosestCollision().getGeometry().getName());
                resultsText.setLocalTranslation(settings.getWidth()-resultsText.getLineWidth(),resultsText.getLineHeight(), 0);
            }else{
                resultsText.setText("Missed");
                resultsText.setLocalTranslation(settings.getWidth()-resultsText.getLineWidth(),resultsText.getLineHeight(), 0);                
            }

        }



    }

    private void updateCrossHairs(){
        if (keyBindings.getBinding("up").getValue()==true){
            cursorPosition.y+=1;
        }
        if (keyBindings.getBinding("down").getValue()==true){
            cursorPosition.y+=-1;
        }

        if (keyBindings.getBinding("left").getValue()==true){
            cursorPosition.x+=-1;
        }
        if (keyBindings.getBinding("right").getValue()==true){
            cursorPosition.x+=+1;
        }

        crossHairNode.setLocalTranslation(cursorPosition.x - crossHair.getLineWidth()/2,cursorPosition.y + crossHair.getLineHeight()/2, 0);

    }

    BitmapText crossHair;
    BitmapText instructions;
    BitmapText resultsText;

    private void initGui() {
        setDisplayStatView(false);
        guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
        crossHair = new BitmapText(guiFont, false);
        crossHair.setSize(guiFont.getCharSet().getRenderedSize() * 2);
        crossHair.setText("+"); // crosshairs

        crossHairNode.setLocalTranslation(cursorPosition.x - crossHair.getLineWidth()/2,cursorPosition.y + crossHair.getLineHeight()/2, 0);


        guiNode.attachChild(crossHairNode);
        crossHairNode.attachChild(crossHair);

        instructions= new BitmapText(guiFont, false);
        instructions.setSize(guiFont.getCharSet().getRenderedSize());
        instructions.setText("Move cross hairs with U,H,J,K keys, fire with space \n (WSAD moves camera position and look with mouse)"); 
        instructions.setLocalTranslation(0, settings.getHeight(), 0);

        guiNode.attachChild(instructions);

        resultsText= new BitmapText(guiFont, false);
        resultsText.setSize(guiFont.getCharSet().getRenderedSize());
        resultsText.setText("Press Space to fire"); 
        resultsText.setLocalTranslation(settings.getWidth()-resultsText.getLineWidth(),resultsText.getLineHeight(), 0);

        guiNode.attachChild(resultsText);



   }

    @Override
    public void simpleRender(RenderManager rm) {
        //TODO: add render code
    }
}

Keybindings, used only to control cursor movement:

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.MouseButtonTrigger;
import java.util.ArrayList;
import java.util.Locale;

public class KeyBindings  implements ActionListener{

    private InputManager inputManager;
    private ArrayList<String> bindingString=new ArrayList<String>(100);
    private ArrayList<KeyBinding> binding=new ArrayList<KeyBinding>(100);

    public KeyBindings(InputManager inputManager){
        this.inputManager=inputManager;
    }

    public void registerKeyBinding(int key,String bindingName){
        bindingName=preprocess(bindingName);
        inputManager.addMapping( bindingName, new KeyTrigger(key));
        inputManager.addListener(this,  bindingName);

        binding.add(new KeyBinding());
        bindingString.add(bindingName);
    }

   public void registerMouseBinding(int button,String bindingName){
        bindingName=preprocess(bindingName);
        inputManager.addMapping( bindingName, new MouseButtonTrigger(button));
        inputManager.addListener(this,  bindingName);

        binding.add(new KeyBinding());
        bindingString.add(bindingName);
    }


    public KeyBinding getBinding(String bindingName){
        //get which binding we're after
        bindingName=preprocess(bindingName);

        int index=bindingString.indexOf(bindingName);

        if (index!=-1){
            return binding.get(index);
        }else{
            return null;
        }
    }

    public void onAction(String  bindingName, boolean isPressed, float tpf) {
        bindingName=preprocess(bindingName);

        //get which binding we're after
        int index=bindingString.indexOf(bindingName);

        if (index!=-1){
            binding.get(index).setValue(isPressed);
        }
    }


    private String preprocess(String string){
        return string.toUpperCase();
    }

}


public class KeyBinding {

    private boolean value;
    private boolean changedSinceLastAccess; //to avoid multiclicks etc
    private boolean valueTrueSinceLastAccess;


    public void setValue(boolean value){
         this.value=value;
         changedSinceLastAccess=true;

         if (value==true){
             valueTrueSinceLastAccess=true;
         }
    }

    public boolean hasChangedSinceLastAccess(){
        return changedSinceLastAccess;
    }

    public boolean hasBecomeTrueSinceLastAccess(){
        //this collects any trues since the last access, is good for things like mouse clicks,
        //which are instantaneous and you want then recorded on the next tick
        if (valueTrueSinceLastAccess==true){
            valueTrueSinceLastAccess=false;
            return true;
        }else{
            return false;
        }
    }


    public boolean getValue(){
        changedSinceLastAccess=false;
        valueTrueSinceLastAccess=false;
        return value;
    }

    public boolean getValue_withoutNotifying(){
        return value;
    }

}
0
On

You can get camera direction with:

directionXYZ=cam.getDirection(); //Vector3f form

and can get position from:

positionXYZ=cam.getLocation(); //Vector3f

You can have a ray casting:

 Ray ray = new Ray(directionXYZ, positionXYZ);

then can collect data for collisions:

shootables.collideWith(ray, results)

where shootable is a "Node".

Finally, check for what you want:

 for (int i = 0; i < results.size(); i++) {
      // For each hit, we know distance, impact point, name of geometry.
      float dist = results.getCollision(i).getDistance();
      Vector3f pt = results.getCollision(i).getContactPoint();
      String hit = results.getCollision(i).getGeometry().getName();
      System.out.println("* Collision #" + i);
      System.out.println("  You shot " + hit + " at " + pt + ", " + dist + " wu away.");
    }

Taken from jmonkey wiki