RTS unit selection and movement help needed

So I’ve started using JMonkeyEngine recently to develop a new kind of RTS game. I have the basic building placement and resource usage, but I can’t figure out how to make every building have it’s own set of stats, and same with units, and to make them selectable (arrays maybe?). I have copied and modified the TestBetterCharacter class, and am planning on using parts of that in my own code, but only after I figure out the movement.

So far, I have made it so that the model will turn towards the select mouse-picked co-ordinate, and move to it. I ran into the problem of having it move way too fast, then slow down exponentially without ever fully stopping. I’ve managed to make it travel at a steady pace towards the target, but after reaching a certain distance from the point, it will still do that annoying slowing down thing. Can anyone tell me what code to use to fix this, or modify my code so that it works, then explain how it worked?

Besides that, I also really need help on the selection front - I need to know how to store the separate units and structures with unique stats, how to use mouse-picking to select the correct unit/building, then to have only that unit move/building perform a set task. Please, any help would be greatly appreciated. :frowning:

Here’s the slightly modified code of the TestBetterCharacter class. I am keeping the movement keys in, because I will later allow the control of individual units using keys/mouse when the unit is selected and the “C” key is pressed.

[java]
package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.collision.shapes.MeshCollisionShape;
import com.jme3.bullet.control.BetterCharacterControl;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.collision.CollisionResult;
import com.jme3.collision.CollisionResults;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Ray;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Sphere;
import com.jme3.system.AppSettings;
import mygame.PhysicsTestHelper;

/**

  • A walking physical character followed by a 3rd person camera. (No animation.)

  • @author normenhansen, zathras
    */
    public class TestBetterCharacter extends SimpleApplication implements ActionListener {

    private BulletAppState bulletAppState;
    private BetterCharacterControl physicsCharacter;
    private Node characterNode;
    //private CameraNode camNode;
    boolean rotate = false;
    private Vector3f walkDirection = new Vector3f(0, 0, 0);
    private Vector3f viewDirection = new Vector3f(0, 0, 1);
    boolean leftStrafe = false, rightStrafe = false, forward = false, backward = false,
    leftRotate = false, rightRotate = false;
    private Vector3f normalGravity = new Vector3f(0, -9.81f, 0);
    private Geometry planet;
    private Vector3f lookDirection = new Vector3f(0, 0, 0);
    Node shootables;
    int frame = 0;
    float height = 0;
    private boolean bob = false;
    private Spatial sceneModel;

    public static void main(String[] args) {
    TestBetterCharacter app = new TestBetterCharacter();
    AppSettings settings = new AppSettings(true);
    settings.setRenderer(AppSettings.LWJGL_OPENGL2);
    settings.setAudioRenderer(AppSettings.LWJGL_OPENAL);
    app.setSettings(settings);
    app.start();
    }

    @Override
    public void simpleInitApp() {
    //setup keyboard mapping
    setupKeys();
    initLighting();
    // activate physics

     flyCam.setMoveSpeed(20);
     flyCam.setEnabled(true);
     bulletAppState = new BulletAppState();
     stateManager.attach(bulletAppState);
     bulletAppState.setDebugEnabled(true);
     shootables = new Node("Shootables");
     rootNode.attachChild(shootables);
    
     // init a physics test scene
     PhysicsTestHelper.createPhysicsTestWorldSoccer(rootNode, assetManager, bulletAppState.getPhysicsSpace());
     PhysicsTestHelper.createBallShooter(this, rootNode, bulletAppState.getPhysicsSpace());
     PhysicsTestHelper.createPhysicsTestWorldSoccer(shootables, assetManager, bulletAppState.getPhysicsSpace());
     PhysicsTestHelper.createBallShooter(this, shootables, bulletAppState.getPhysicsSpace());
     setupPlanet();
    
     // Create a node for the character model
     characterNode = new Node("character node");
     characterNode.setLocalTranslation(new Vector3f(4, 5, 2));
    
     // Add a character control to the node so we can add other things and
     // control the model rotation
     physicsCharacter = new BetterCharacterControl(0.3f, 2.5f, 8f);
     characterNode.addControl(physicsCharacter);
     getPhysicsSpace().add(physicsCharacter);
    
     // Load model, attach to character node
     Node model = (Node) assetManager.loadModel("Models/modBasicMan/modBasicMan.j3o");
     model.setLocalScale(1.50f);
     characterNode.attachChild(model);
    
     // Add character node to the rootNode
     rootNode.attachChild(characterNode);
    
     // Set forward camera node that follows the character, only used when
     // view is "locked"
     //camNode = new CameraNode("CamNode", cam);
     //camNode.setControlDir(ControlDirection.SpatialToCamera);
     //camNode.setLocalTranslation(new Vector3f(0, 2, -6));
     Quaternion quat = new Quaternion();
     // These coordinates are local, the camNode is attached to the character node!
     quat.lookAt(Vector3f.UNIT_Z, Vector3f.UNIT_Y);
     //camNode.setLocalRotation(quat);
     //characterNode.attachChild(camNode);
     // Disable by default, can be enabled via keyboard shortcut
     //camNode.setEnabled(false);
     sceneModel = assetManager.loadModel("Scenes/aScene.j3o");
     rootNode.attachChild(sceneModel);
     shootables.attachChild(sceneModel);
    

    }

    @Override
    public void simpleUpdate(float tpf) {
    if (frame == 0) {
    inputManager.deleteMapping(“FLYCAM_RotateDrag”);
    flyCam.setDragToRotate(true);
    inputManager.addMapping(“FLYCAM_RotateDrag”, new MouseButtonTrigger (MouseInput.BUTTON_MIDDLE));
    inputManager.addListener(flyCam, “FLYCAM_RotateDrag”);
    frame++;
    }
    // Apply planet gravity to character if close enough (see below)
    checkPlanetGravity();

     // Get current forward and left vectors of model by using its rotation
     // to rotate the unit vectors
     Vector3f modelForwardDir = characterNode.getWorldRotation().mult(Vector3f.UNIT_Z);
     Vector3f modelLeftDir = characterNode.getWorldRotation().mult(Vector3f.UNIT_X);
    
     // WalkDirection is global!
     // You *can* make your character fly with this.
     walkDirection.set(0, 0, 0);
     if (leftStrafe) {
         walkDirection.addLocal(modelLeftDir.mult(3));
     } else if (rightStrafe) {
         walkDirection.addLocal(modelLeftDir.negate().multLocal(3));
     }
     if (forward) {
         walkDirection.addLocal(modelForwardDir.mult(3));
     } else if (backward) {
         walkDirection.addLocal(modelForwardDir.negate().multLocal(3));
     }
     physicsCharacter.setWalkDirection(walkDirection);
    
     // ViewDirection is local to characters physics system!
     // The final world rotation depends on the gravity and on the state of
     // setApplyPhysicsLocal()
     if (leftRotate) {
         Quaternion rotateL = new Quaternion().fromAngleAxis(FastMath.PI * tpf, Vector3f.UNIT_Y);
         rotateL.multLocal(viewDirection);
     } else if (rightRotate) {
         Quaternion rotateR = new Quaternion().fromAngleAxis(-FastMath.PI * tpf, Vector3f.UNIT_Y);
         rotateR.multLocal(viewDirection);
     }
     physicsCharacter.setViewDirection(viewDirection);
     fpsText.setText("Touch da ground = " + physicsCharacter.isOnGround());
     if (!lockView) {
        // cam.lookAt(characterNode.getWorldTranslation().add(new Vector3f(0, 2, 0)), Vector3f.UNIT_Y);
     }
     float x1 = characterNode.getLocalTranslation().getX();
     float z1 = characterNode.getLocalTranslation().getZ();
     float x2 = lookDirection.getX();
     float z2 = lookDirection.getZ();
     
    
     
     
     if(x1 != x2 && z1 != z2){
         
         float x3 = (x2-x1);
         float z3 = (z2-z1);
         if(x2-x1<0.05 && x2-x1>0){
             x3 = 0;
             x1 = x2;
         }else if(x2-x1>-0.05 && x2-x1<0){
             x3 = 0;
             x1 = x2;
         }
         if(z2-z1<0.05 && z2-z1>0){
             z3 = 0;
             z1 = z2;
         }else if(z2-z1>-0.05 && z2-z1<0){
             z3 = 0;
             z1 = z2;
         }
         height = lookDirection.getY();
         physicsCharacter.setViewDirection(new Vector3f(x3,height,z3));
         physicsCharacter.setWalkDirection(new Vector3f(x3,height,z3));
         System.out.println(new Vector3f(x3,height,z3));
     }else{
         physicsCharacter.setWalkDirection(new Vector3f(x1,characterNode.getLocalTranslation().getY(),z2));
     }
    

    }

    private void setupPlanet() {
    Material material = new Material(assetManager, “MatDefs/Unshaded.j3md”);
    material.setTexture(“ColorMap”, assetManager.loadTexture(“Textures/grass1.jpg”));
    //immovable sphere with mesh collision shape
    Sphere sphere = new Sphere(64, 64, 20);
    planet = new Geometry(“Sphere”, sphere);
    planet.setMaterial(material);
    planet.setLocalTranslation(30, -15, 30);
    planet.addControl(new RigidBodyControl(new MeshCollisionShape(sphere), 0));
    rootNode.attachChild(planet);
    shootables.attachChild(planet);

     getPhysicsSpace().add(planet);
    

    }

    private void checkPlanetGravity() {
    Vector3f planetDist = planet.getWorldTranslation().subtract(characterNode.getWorldTranslation());
    if (planetDist.length() < 24) {
    physicsCharacter.setGravity(planetDist.normalizeLocal().multLocal(9.81f));
    } else {
    physicsCharacter.setGravity(normalGravity);
    }
    }

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

    public void onAction(String binding, boolean value, float tpf) {
    if(binding.equals(“Go”)){
    Vector3f origin = cam.getWorldCoordinates(inputManager.getCursorPosition(), 0.0f);
    Vector3f direction = cam.getWorldCoordinates(inputManager.getCursorPosition(), 0.3f);
    direction.subtractLocal(origin).normalizeLocal();

         Ray ray = new Ray(origin, direction);
         CollisionResults results = new CollisionResults();
         shootables.collideWith(ray, results);
     
     if (results.size() &gt; 0) {
         System.out.println("HIT!");
         CollisionResult closest = results.getClosestCollision();
         lookDirection = closest.getContactPoint();
         Quaternion q = new Quaternion();
         q.lookAt(closest.getContactNormal(), Vector3f.UNIT_Y);
         //mark.setLocalRotation(q);
     }
     } else if(binding.equals("Origin")){
         
     } else if (binding.equals("Strafe Left")) {
         if (value) {
             leftStrafe = true;
         } else {
             leftStrafe = false;
         }
     } else if (binding.equals("Strafe Right")) {
         if (value) {
             rightStrafe = true;
         } else {
             rightStrafe = false;
         }
     } else if (binding.equals("Rotate Left")) {
         if (value) {
             leftRotate = true;
         } else {
             leftRotate = false;
         }
     } else if (binding.equals("Rotate Right")) {
         if (value) {
             rightRotate = true;
         } else {
             rightRotate = false;
         }
     } else if (binding.equals("Walk Forward")) {
         if (value) {
             forward = true;
         } else {
             forward = false;
         }
     } else if (binding.equals("Walk Backward")) {
         if (value) {
             backward = true;
         } else {
             backward = false;
         }
     } else if (binding.equals("Jump")) {
         physicsCharacter.jump();
     } else if (binding.equals("Duck")) {
         if (value) {
             physicsCharacter.setDucked(true);
         } else {
             physicsCharacter.setDucked(false);
         }
     } else if (binding.equals("Lock View")) {
         if (value &amp;&amp; lockView) {
             lockView = false;
         } else if (value &amp;&amp; !lockView) {
             lockView = true;
         }
         flyCam.setEnabled(!lockView);
         //camNode.setEnabled(lockView);
     }
    

    }
    private boolean lockView = false;

    private void setupKeys() {
    inputManager.addMapping(“Strafe Left”,
    new KeyTrigger(KeyInput.KEY_U),
    new KeyTrigger(KeyInput.KEY_Z));
    inputManager.addMapping(“Strafe Right”,
    new KeyTrigger(KeyInput.KEY_X));
    inputManager.addMapping(“Rotate Left”,
    new KeyTrigger(KeyInput.KEY_J),
    new KeyTrigger(KeyInput.KEY_LEFT));
    inputManager.addMapping(“Rotate Right”,
    new KeyTrigger(KeyInput.KEY_L),
    new KeyTrigger(KeyInput.KEY_RIGHT));
    inputManager.addMapping(“Walk Forward”,
    new KeyTrigger(KeyInput.KEY_I),
    new KeyTrigger(KeyInput.KEY_UP));
    inputManager.addMapping(“Walk Backward”,
    new KeyTrigger(KeyInput.KEY_K),
    new KeyTrigger(KeyInput.KEY_DOWN));
    inputManager.addMapping(“Jump”,
    new KeyTrigger(KeyInput.KEY_F),
    new KeyTrigger(KeyInput.KEY_SPACE));
    inputManager.addMapping(“Duck”,
    new KeyTrigger(KeyInput.KEY_G),
    new KeyTrigger(KeyInput.KEY_LSHIFT),
    new KeyTrigger(KeyInput.KEY_RSHIFT));
    inputManager.addMapping(“Lock View”,
    new KeyTrigger(KeyInput.KEY_RETURN));
    inputManager.addMapping(“Origin”,
    new KeyTrigger(KeyInput.KEY_O));
    inputManager.addMapping(“Go”,
    new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
    inputManager.addListener(this, “Go”);
    inputManager.addListener(this, “Origin”);
    inputManager.addListener(this, “Strafe Left”, “Strafe Right”);
    inputManager.addListener(this, “Rotate Left”, “Rotate Right”);
    inputManager.addListener(this, “Walk Forward”, “Walk Backward”);
    inputManager.addListener(this, “Jump”, “Duck”, “Lock View”);
    }

    private void initLighting(){
    AmbientLight ambient = new AmbientLight();
    ambient.setColor(ColorRGBA.White);
    rootNode.addLight(ambient);
    }

    @Override
    public void simpleRender(RenderManager rm) {
    }
    }
    [/java]

Note: I’ve done the z1=z2 stuff as a temporary fix to the problem. It makes the character stop when it reaches a certain distance from the point.

Also, the way I made it travel at a more steady pace was by changing
“physicsCharacter.setWalkDirection(new Vector3f(x3,height,z3));”
to
“physicsCharacter.setWalkDirection(new Vector3f(x3,height,z3).mult(0.4f));”

Selection:
There are 2 ways to pick. Physcs and Geometry.
I use physics RayTest in my game.

I use Ray Casting in my game as well, but it’s the actual checking for which unit/which part of the unit array I have selected that I’m having problems with. :confused: