Charactercontrol and drop in FPS

Hi all,

On this small game i am working on, i have 2 clear distinct objects:



Buildings are created in the following way:

  • Load model (spatial) and apply material into a node
  • Attach node to a buildings childnode in the rootnode
  • On placement of the building a rigidbodycontrol (mesh outline) will be made which will be attached to to the node

    Characters are created in the following way:
  • Load model (spatial) and apply material into a node
  • Attach node to a character childnode in the rootnode
  • On placement of the character a CharacterControl (capsule) will be made which will be attached to to the node

    After placing like 50 buildings, there is a FPS drop of about 10 fps (300->290)

    After placing about 10 characters, the FPS is dropped to 10 fps! (300->10).

    If I remove the charactercontrol there is a similar drop in FPS as it would to buildings.

    If i attach a charactercontrol to the building the same effect happens as with the characters.

    At the current stage I have a test case with the only difference in controlset for the object.

    Any idea what this could be?

    Thanks in advance!

Maybe your character model has more polys than the buildings?

Character geometry is currently: Sphere(8, 8, 3);

Building geometry (mesh) is a blender export (way more complex).

Removing the CharacterControl results in no drop in FPS upon placing characters however this makes them unable to interact with the world.

So do you have assertions enabled? They slow down collision detection very much. Also simply consider that you are using physics and collision detection whereas with the buildings you dont.

How can I enable or disable the assertions?

You supply the “-ea” switch to your app to enable them :google:

No success :frowning: It was disabled. If I enable it the average FPS drops with 50% and problem still exists. Removing characters temporary from the phsyicspace (e.g. after they stopped moving) will keep the FPS smooth. As soon as I add them again the problem comes back.

i actually have the same problem, and im just using the HelloCollision tutorial as the base and adding in water, sky. I found the same thing, removing character control resolves the FPS problem, wierdly i didn’t have this in the may build but now im using the june 8th build but i dont think anythings changed.

A possibility that I see is that the system perhaps is way to accurate. I’m trying if that would matter.

No, dont play with the accuracy value, its good.

Is there any example with multiple (>16) CharacterControls implemented that does not have this behaviour?

No, I did a local test with several hundred controls tho back when the last changes on physics were made.

Later tonight ill create a basic app with the bare basics of this issue en post the code here so perhaps we can get a better view at the issue.

Ok, only idea I have what could have changed is the workaround @nehon added for kinematic things… They would not move when they were added while being kinematic, maybe thats provoking additional computations for the character object… But normally it should be recognized as a character object and be added to the physics space with character parameters. Another thing you might want to try is to use the native bullet implementation (see latest post), you will have to run 32bit java on windows for that though.

Example code (modified

It creates the main (controllable) otis unit as used in the example and 20 low poly units.

Bullets with effects & wall have been removed to just have the terrain (cam & light) and the models.

Issue can been seen here.

pre type="java"

 Copyright © 2009-2010 jMonkeyEngine

  All rights reserved.


  Redistribution and use in source and binary forms, with or without

 modification, are permitted provided that the following conditions are



   Redistributions of source code must retain the above copyright

    notice, this list of conditions and the following disclaimer.


   Redistributions in binary form must reproduce the above copyright

    notice, this list of conditions and the following disclaimer in the

   documentation and/or other materials provided with the distribution.


  Neither the name of ‘jMonkeyEngine’ nor the names of its contributors

   may be used to endorse or promote products derived from this software

    without specific prior written permission.














package mygame;

import com.jme3.animation.AnimChannel;

import com.jme3.animation.AnimControl;

import com.jme3.animation.AnimEventListener;

import com.jme3.animation.LoopMode;

import com.jme3.bullet.BulletAppState;


import com.jme3.bounding.BoundingBox;

import com.jme3.bullet.PhysicsSpace;

import com.jme3.bullet.collision.PhysicsCollisionEvent;

import com.jme3.bullet.collision.PhysicsCollisionListener;

import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;

import com.jme3.bullet.collision.shapes.SphereCollisionShape;

import com.jme3.bullet.control.CharacterControl;

import com.jme3.bullet.control.RigidBodyControl;

import com.jme3.bullet.util.CollisionShapeFactory;

import com.jme3.effect.EmitterSphereShape;

import com.jme3.effect.ParticleEmitter;

import com.jme3.effect.ParticleMesh.Type;

import com.jme3.input.ChaseCamera;

import com.jme3.input.KeyInput;

import com.jme3.input.controls.ActionListener;

import com.jme3.input.controls.KeyTrigger;

import com.jme3.light.DirectionalLight;

import com.jme3.material.Material;

import com.jme3.math.ColorRGBA;

import com.jme3.math.FastMath;

import com.jme3.math.Quaternion;

import com.jme3.math.Vector2f;

import com.jme3.math.Vector3f;



import com.jme3.renderer.Camera;

import com.jme3.renderer.queue.RenderQueue.ShadowMode;

import com.jme3.scene.Geometry;

import com.jme3.scene.Node;

import com.jme3.scene.Spatial;

import com.jme3.scene.shape.Box;

import com.jme3.scene.shape.Sphere;

import com.jme3.scene.shape.Sphere.TextureMode;

import com.jme3.terrain.geomipmap.TerrainLodControl;

import com.jme3.terrain.geomipmap.TerrainQuad;

import com.jme3.terrain.heightmap.AbstractHeightMap;

import com.jme3.terrain.heightmap.ImageBasedHeightMap;

import com.jme3.texture.Texture;

import com.jme3.texture.Texture.WrapMode;

import com.jme3.util.SkyFactory;

import java.util.ArrayList;

import java.util.List;

import jme3tools.converters.ImageToAwt;



 @author normenhansen


public class Main extends SimpleApplication implements ActionListener, AnimEventListener {

    private BulletAppState bulletAppState;


    CharacterControl character;

    Node model;

    //temp vectors

    Vector3f walkDirection = new Vector3f();


    TerrainQuad terrain;

    RigidBodyControl terrainPhysicsNode;


    Material matRock;

    Material matWire;

    Material matBullet;


    AnimChannel animationChannel;

    AnimChannel shootingChannel;

    AnimControl animationControl;

    float airTime = 0;


    boolean left = false, right = false, up = false, down = false;

    ChaseCamera chaseCam;


    Sphere bullet;

    SphereCollisionShape bulletCollisionShape;


    ParticleEmitter effect;

    //brick wall

    Box brick;

    float bLength = 0.8f;

    float bWidth = 0.4f;

    float bHeight = 0.4f;

    FilterPostProcessor fpp;

    public static void main(String[] args) {

        Main app = new Main();




    public void simpleInitApp() {

        bulletAppState = new BulletAppState();








        for (int number = 1; number <= 20; number++) {








    private void setupFilter() {

        FilterPostProcessor fpp = new FilterPostProcessor(assetManager);

        BloomFilter bloom = new BloomFilter(BloomFilter.GlowMode.Objects);




    private PhysicsSpace getPhysicsSpace() {

        return bulletAppState.getPhysicsSpace();


    private void setupKeys() {

        inputManager.addMapping(“wireframe”, new KeyTrigger(KeyInput.KEY_T));

        inputManager.addListener(this, “wireframe”);

        inputManager.addMapping(“CharLeft”, new KeyTrigger(KeyInput.KEY_A));

        inputManager.addMapping(“CharRight”, new KeyTrigger(KeyInput.KEY_D));

        inputManager.addMapping(“CharUp”, new KeyTrigger(KeyInput.KEY_W));

        inputManager.addMapping(“CharDown”, new KeyTrigger(KeyInput.KEY_S));

        inputManager.addMapping(“CharSpace”, new KeyTrigger(KeyInput.KEY_RETURN));

        inputManager.addMapping(“CharShoot”, new KeyTrigger(KeyInput.KEY_SPACE));

        inputManager.addListener(this, “CharLeft”);

        inputManager.addListener(this, “CharRight”);

        inputManager.addListener(this, “CharUp”);

        inputManager.addListener(this, “CharDown”);

        inputManager.addListener(this, “CharSpace”);

        inputManager.addListener(this, “CharShoot”);


    private void createLight() {

        Vector3f direction = new Vector3f(-0.1f, -0.7f, -1).normalizeLocal();

        DirectionalLight dl = new DirectionalLight();


        dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f));



    private void createSky() {

        rootNode.attachChild(SkyFactory.createSky(assetManager, “Textures/Sky/Bright/”, false));


    private void createTerrain() {

        matRock = new Material(assetManager, “Common/MatDefs/Terrain/TerrainLighting.j3md”);

        matRock.setBoolean(“useTriPlanarMapping”, false);

        matRock.setBoolean(“WardIso”, true);

        matRock.setTexture(“AlphaMap”, assetManager.loadTexture(“Textures/Terrain/splat/alphamap.png”));

        Texture heightMapImage = assetManager.loadTexture(“Textures/Terrain/splat/mountains512.png”);

        Texture grass = assetManager.loadTexture(“Textures/Terrain/splat/grass.jpg”);


        matRock.setTexture(“DiffuseMap”, grass);

        matRock.setFloat(“DiffuseMap_0_scale”, 64);

        Texture dirt = assetManager.loadTexture(“Textures/Terrain/splat/dirt.jpg”);


        matRock.setTexture(“DiffuseMap_1”, dirt);

        matRock.setFloat(“DiffuseMap_1_scale”, 16);

        Texture rock = assetManager.loadTexture(“Textures/Terrain/splat/road.jpg”);


        matRock.setTexture(“DiffuseMap_2”, rock);

        matRock.setFloat(“DiffuseMap_2_scale”, 128);

        Texture normalMap0 = assetManager.loadTexture(“Textures/Terrain/splat/grass_normal.png”);


        Texture normalMap1 = assetManager.loadTexture(“Textures/Terrain/splat/dirt_normal.png”);


        Texture normalMap2 = assetManager.loadTexture(“Textures/Terrain/splat/road_normal.png”);


        matRock.setTexture(“NormalMap”, normalMap0);

        matRock.setTexture(“NormalMap_1”, normalMap2);

        matRock.setTexture(“NormalMap_2”, normalMap2);

        matWire = new Material(assetManager, “Common/MatDefs/Misc/WireColor.j3md”);

        matWire.setColor(“Color”, ColorRGBA.Green);

        AbstractHeightMap heightmap = null;

        try {

            heightmap = new ImageBasedHeightMap(ImageToAwt.convert(heightMapImage.getImage(), false, true, 0), 0.25f);


        } catch (Exception e) {



        terrain = new TerrainQuad(“terrain”, 65, 513, heightmap.getHeightMap());

        List<Camera> cameras = new ArrayList<Camera>();


        TerrainLodControl control = new TerrainLodControl(terrain, cameras);



        terrain.setModelBound(new BoundingBox());


        terrain.setLocalScale(new Vector3f(2, 2, 2));

        terrainPhysicsNode = new RigidBodyControl(CollisionShapeFactory.createMeshShape(terrain), 0);





    private void createCharacter() {

        CapsuleCollisionShape capsule = new CapsuleCollisionShape(1.5f, 2f);

        character = new CharacterControl(capsule, 0.01f);

        model = (Node) assetManager.loadModel(“Models/Oto/Oto.mesh.xml”);



        character.setPhysicsLocation(new Vector3f(-140, 10, -10));





    private void createNewCharacter(int i) {

        CapsuleCollisionShape capsule = new CapsuleCollisionShape(1.5f, 2f);

        CharacterControl character = new CharacterControl(capsule, 0.01f);

        Sphere s = new Sphere(4, 4, 3);

        Geometry model = new Geometry(“BallBeast”);


        Material mat = new Material(assetManager, “Common/MatDefs/Misc/SolidColor.j3md”);

        ColorRGBA alphaGreen = new ColorRGBA(0.2f,0.2f,0.5f,1f);

        mat.setColor(“Color”, alphaGreen);



        character.setPhysicsLocation(new Vector3f((-140 +(i
5)), (10+(i)), -10));




    private void setupChaseCamera() {


        chaseCam = new ChaseCamera(cam, model, inputManager);


    private void setupAnimationController() {

        animationControl = model.getControl(AnimControl.class);


        animationChannel = animationControl.createChannel();

        shootingChannel = animationControl.createChannel();






    public void simpleUpdate(float tpf) {

        Vector3f camDir = cam.getDirection().clone().multLocal(0.2f);

        Vector3f camLeft = cam.getLeft().clone().multLocal(0.2f);

        camDir.y = 0;

        camLeft.y = 0;

        walkDirection.set(0, 0, 0);

        if (left) {



        if (right) {



        if (up) {



        if (down) {



        if (!character.onGround()) {

            airTime = airTime + tpf;

        } else {

            airTime = 0;


        if (walkDirection.length() == 0) {

            if (!“stand”.equals(animationChannel.getAnimationName())) {

                animationChannel.setAnim(“stand”, 1f);


        } else {


            if (airTime > .3f) {

                if (!“stand”.equals(animationChannel.getAnimationName())) {



            } else if (!“Walk”.equals(animationChannel.getAnimationName())) {

                animationChannel.setAnim(“Walk”, 0.7f);





    public void onAction(String binding, boolean value, float tpf) {

        if (binding.equals(“CharLeft”)) {

            if (value) {

                left = true;

            } else {

                left = false;


        } else if (binding.equals(“CharRight”)) {

            if (value) {

                right = true;

            } else {

                right = false;


        } else if (binding.equals(“CharUp”)) {

            if (value) {

                up = true;

            } else {

                up = false;


        } else if (binding.equals(“CharDown”)) {

            if (value) {

                down = true;

            } else {

                down = false;


        } else if (binding.equals(“CharSpace”)) {


        } else if (binding.equals(“CharShoot”) && !value) {




    public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {

        if (channel == shootingChannel) {




    public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {




Anyone else has the same result with the code? Or rather, anyone that has not :).

Ok, I have a small (yet unwanted) fix for the problem.

If you change the radius in

CapsuleCollisionShape capsule = new CapsuleCollisionShape(1.5f, 1f);

to very small values like
CapsuleCollisionShape capsule = new CapsuleCollisionShape(0.1f, 1f);

There is no drop in FPS anymore. However this results in strange behaviour of the characters (bouncing on the gound).

Edit: More unwanted behaviour: Characters fall though the ground on unexpected moments.