Basic AI Makes Game Lag Horribly

I was making a fairly simple horror fps game for Halloween. I added AI for the enemy based on this:FPS in jMonkey engine update #3 | Mor(e) Blog, but it brings the game down to about 1 fps. I moved the code to another thread using this:https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:advanced:multithreading, but it didn’t help anything. The code for the AI:

public class AIControl extends AbstractControl {
    
    private int mode = 0;
    public static final int MODE_WANDER = 0, MODE_CHASE = 1, MODE_ATTACK = 2, MODE_DEAD = 3;
    private Vector3f wanderPoint, target;
    private Vector3f dir, temp;
    private CharacterControl aiCharacter;
    Future future;
    //Any local variables should be encapsulated by getters/setters so they
    //appear in the SDK properties window and can be edited.
    //Right-click a local variable to encapsulate it with getters and setters.

    public AIControl(Vector3f target, int mode)
    {
        this.target = target;
        this.mode = mode;
        future = Main.executor.submit(setMovement);    //  Thread starts!
    }
    
    @Override
    protected void controlUpdate(float tpf) {
        if(future.isDone())
        {
        target = Main.player.getPhysicsLocation();
            try{

            future = Main.executor.submit(setMovement);    //  Thread starts!
        }
        catch(Exception e){ 
            e.printStackTrace();
        }
        }

        
    }
    
    @Override
    public void setSpatial(Spatial spatial)
    {
        super.setSpatial(spatial);
        aiCharacter = spatial.getControl(CharacterControl.class);
    }
    
    @Override
    protected void controlRender(RenderManager rm, ViewPort vp) {
        //Only needed for rendering-related operations,
        //not called when spatial is culled.
    }
    
    @Override
    public Control cloneForSpatial(Spatial spatial) {
        AIControl control = new AIControl(getTarget(), mode);
        //TODO: copy parameters to new Control
        return control;
    }
    
    @Override
    public void read(JmeImporter im) throws IOException {
        super.read(im);
        InputCapsule in = im.getCapsule(this);
        //TODO: load properties of this Control, e.g.
        //this.value = in.readFloat("name", defaultValue);
    }
    
    @Override
    public void write(JmeExporter ex) throws IOException {
        super.write(ex);
        OutputCapsule out = ex.getCapsule(this);
        //TODO: save properties of this Control, e.g.
        //out.write(this.value, "name", defaultValue);
    }

    /**
     * @return the mode
     */
    public int getMode() {
        return mode;
    }

    /**
     * @param mode the mode to set
     */
    public void setMode(int mode) {
        this.mode = mode;
    }
    
        public double calculateDist(Vector3f p) {
        double a = aiCharacter.getPhysicsLocation().x - p.x;
        double b = aiCharacter.getPhysicsLocation().z - p.z;
        double dist = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
        return dist;

    }

    public double calculate3DDist(Vector3f p) {
        return aiCharacter.getPhysicsLocation().distance(p);
    }
    
        public Vector3f calcDir(Vector3f target) {
        temp = new Vector3f(target.x - aiCharacter.getPhysicsLocation().x, 0, target.z - aiCharacter.getPhysicsLocation().z);
        //this.target = target;
        return temp;
    }

    /**
     * @return the target
     */
    public Vector3f getTarget() {
        return target;
    }

    /**
     * @param target the target to set
     */
    public void setTarget(Vector3f target) {
        this.target = target;
    }
    
    // A self-contained time-intensive task:
private Callable<Void> setMovement = new Callable<Void>(){
    public Void call() throws Exception {
        if (mode == MODE_CHASE) {
            //diretion mob to target
            dir = calcDir(getTarget());
            calculateDist(getTarget());
            aiCharacter.setWalkDirection(new Vector3f(0, 0, 0));
            aiCharacter.setViewDirection(dir);
            if (calculate3DDist(getTarget()) >= 15) {
                Vector3f walkDir = dir.normalize().divide(2);
                aiCharacter.setWalkDirection(walkDir);
            }

        } else if (mode == MODE_WANDER) {

        } else if (mode == MODE_ATTACK) {
        } else if (mode == MODE_DEAD) {
            aiCharacter.setWalkDirection(new Vector3f(0, 0, 0));
        }
        return null;
    }
};
}

I spawn the enemy using this:


         Vector3f loc = player.getPhysicsLocation().add(cam.getDirection().getX() * 6, 2, cam.getDirection().getZ() * 6);
         Spatial enemy = basicEnemy.clone();
         enemy.setLocalTranslation(loc);
         enemy.lookAt(player.getPhysicsLocation().add(0, 3, 0), Vector3f.UNIT_Y);
         CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(1.7f, 10f, 1);
         CharacterControl alien = new CharacterControl(capsuleShape, 0.05f);
         enemy.addControl(alien);
         AIControl ai = new AIControl(new Vector3f(-73, -144, -52), AIControl.MODE_CHASE);
         enemy.addControl(ai);
         bulletAppState.getPhysicsSpace().add(alien);
         enemies.attachChild(enemy);

Can anyone point out to me what might be causing this?

Your “AI” hardly does anything so it’s very strange if you’ve had to move it to a separate thread. The threading overhead alone may suck up all of the small gains you get by moving a few direction calculations to their own thread. (Nevermind the fact that nothing you are doing is thread safe… which isn’t the cause of this issue but will certainly cause various random issues down the road.)

How many enemies do you spawn this way? What was your framerate before? Does commenting out “enemy.addControl(ai);” make the FPS go back up?

But while I’m here… the math is a bit all over the place:

[java]
dir = calcDir(getTarget());
calculateDist(getTarget());
aiCharacter.setWalkDirection(new Vector3f(0, 0, 0));
aiCharacter.setViewDirection(dir);
if (calculate3DDist(getTarget()) >= 15) {
Vector3f walkDir = dir.normalize().divide(2);
aiCharacter.setWalkDirection(walkDir);
}
[/java]

Could be simplified as:
[java]
dir = calcDir(getTarget());
float dist = dir.length();
aiCharacter.setViewDirection(dir.divide(dist));
if( dist <= 15 ) {
Vector3f walkDir = dir.divide(dist * 2); // normalize and divide by 2
aiCharacter.setWalkDirection(walkDir);
} else {
aiCharacter.setWalkDirection(new Vector3f(0, 0, 0));
}
[/java]

…which also fixes the normalization of the viewDir that looked broken.

Either way, I suspect if you add some timing code around either of those blocks you’ll see that it takes almost no time at all… but at least the second one avoids calculating some things multiple times.

As it turns out, the AI was not the cause of my issue. It was actually a loop in simpleUpdate originally used to give the AI the players coordinates. Removing this fixed the problem, but your improved calculations will likely help once I add more units. When two things are added at once, it’s easy to blame the more complicated ones for your issues.

…that’s why the profiler can be helpful in these cases.