Optimize Enemy Generator + BOX2D Bodies

Hi Guys!
I have a scrolling game. The camera.position.y is incremented over time.

Each time , the game generates bodies 1 screen ahead of the player so it looks the asteroids were there beforehand.

The problem is, each time they are generated, the FPS drops Which i suppose, its because of the bodies collision on generation.

What can i do?

/**
 *
 * @author André V Lopes
 */
public class EnemyGenerator {

    private int MAX_BODIES_SPAWN = 30;

    private Vector3 tmp;

    /**
     * Size of the screen to be filled with enemies
     */
    private float SCREEN_HEIGHT;

    /**
     * Returns the position of the camera position Y-axis when the last call to
     * enemy generated was started.
     */
    private float LAST_CAMERA_POSITION_Y_ENEMY_GENERATED;

    /**
     * Return falses if update was never called
     */
    private boolean hasStarted = false;

    /**
     *
     * @param level
     */
    public EnemyGenerator(Level level) {

        tmp = new Vector3();
        SCREEN_HEIGHT = ApplicationConfiguration.getHeight() * PTM;

    }

    public void update(Level level) {

        if (!hasStarted) {
            LAST_CAMERA_POSITION_Y_ENEMY_GENERATED = level.getViewport().getCamera().position.y;
            generateEnemies(level);
            hasStarted = true;
        }

        float y = level.getViewport().getCamera().position.y;

        float difference = Math.abs(y - LAST_CAMERA_POSITION_Y_ENEMY_GENERATED);

        System.out.println("Screen_Height =" + SCREEN_HEIGHT);
        System.out.println("LAST_CAMERA_POSITION_Y_ENEMY_GENERATED =" + LAST_CAMERA_POSITION_Y_ENEMY_GENERATED);

        System.out.println("Difference : " + difference);
        if (difference >= SCREEN_HEIGHT) {
            System.out.println("Generating Enemies...");
            generateEnemies(level);

        }

    }

    private void generateEnemies(Level level) {
        LAST_CAMERA_POSITION_Y_ENEMY_GENERATED = level.getViewport().getCamera().position.y;

        for (int i = 0; i < MAX_BODIES_SPAWN; i++) {

            int randomEnemy = randomEnemy();
            Vector3 randomPosition = randomPosition(level);

            if (randomEnemy == GameObject.ASTEROID) {
                Asteroid asteroid = new Asteroid();
                asteroid.addAsteroid(level.getWorld(), randomPosition.x, randomPosition.y);
            }

            if (randomEnemy == GameObject.GIANT_ASTEROID) {
                GiantAsteroid giantAsteroid = new GiantAsteroid();
                giantAsteroid.addAsteroid(level.getWorld(), randomPosition.x, randomPosition.y);
            }

        }

    }

    public Vector3 randomPosition(Level level) {

        Camera camera = level.getViewport().getCamera();

        tmp.set(0, -Gdx.graphics.getHeight() * 0.2f, 0f); // bottom | left
        camera.unproject(tmp);
        float lowerX = tmp.x;
        float lowerY = tmp.y;

        tmp.set(Gdx.graphics.getWidth(), -Gdx.graphics.getHeight(), 0f); // top right + 1 screen
        camera.unproject(tmp);
        float upperX = tmp.x;
        float upperY = tmp.y;

        tmp.y = MathUtils.random(lowerY, upperY);

        tmp.x = MathUtils.random(lowerX, upperX);

        return tmp;

    }

Another thing!
The bodies that arent on the screen anymore, are removed every 5 seconds.


/**
 *
 * @author André V Lopes
 *
 *
 *
 */
public class CleanWorld {

    private Vector3 tmp;

    private FPSTimer fpsTimer;

    public CleanWorld() {
        fpsTimer = new FPSTimer(5);
    }

    public CleanWorld(final Level level) {
        fpsTimer = new FPSTimer(5);
    }

    public void cleanWorld(final Level level, float delta) {

//        if (level.isThereBodiesToBeRemoved()) {
//            System.out.println("Impossible to clean World at this moment!");
//            return;
//        }
        fpsTimer.update(delta);

        if (fpsTimer.isDone()) {
            fpsTimer.reset();

            Camera camera = level.getViewport().getCamera();

            tmp = new Vector3();
            tmp.set(0, Gdx.graphics.getHeight(), 0f); // bottom left
            camera.unproject(tmp);
            float lowerX = tmp.x;
            float lowerY = tmp.y;
            tmp.set(Gdx.graphics.getWidth(), -Gdx.graphics.getHeight(), 0f); // top right + 1 screen
            camera.unproject(tmp);
            float upperX = tmp.x;
            float upperY = tmp.y;

            final Array<Body> bodies = new Array<Body>();
            level.getWorld().getBodies(bodies);

            System.out.println("Cleaning World at this moment! Bodies :" + bodies.size);

            QueryCallback queryCallback = new QueryCallback() {
                @Override
                public boolean reportFixture(Fixture fixture) {

                    Body body = fixture.getBody();

                    bodies.removeValue(body, true);

                    return true;
                }

            };

            level.getWorld().QueryAABB(queryCallback, lowerX, lowerY, upperX, upperY);

            // Be sure to not let Player be removed from the level.
            bodies.removeValue(level.getPlayer().getSpaceShipBody(), true);

            level.addBodiesToBeRemoved(bodies);
            //System.out.println("World cleaned -> BodiesToBeRemoved :" + level.bodiesToBeRemoved());
        }
    }

    public boolean removeBodies(Level level) {
        for (Body body : level.getBodiesToBeRemoved()) {
            body.getWorld().destroyBody(body);
            level.getBodiesToBeRemoved().removeValue(body, true);
        }

        return true;

    }

}

Let us see the enemy constructor, that code you linked is not the problem.

package br.lopes.box2d.objects;

import br.lopes.effects.SmokeEffect;
import br.lopes.game.CollisionLogic.CollisionLogic;
import br.lopes.game.Level;
import br.lopes.player.Weapons.RedLaser;
import br.lopes.settings.GameSettings;
import br.lopes.tools.AssetAccessor;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.BodyDef;
import com.badlogic.gdx.physics.box2d.BodyDef.BodyType;
import com.badlogic.gdx.physics.box2d.ContactImpulse;
import com.badlogic.gdx.physics.box2d.FixtureDef;
import com.badlogic.gdx.physics.box2d.World;
import net.dermetfan.gdx.graphics.g2d.Box2DSprite;
import net.dermetfan.gdx.physics.box2d.Box2DUtils;

/**
 *
 * @author André V Lopes
 */
public class Asteroid extends GameObject {

    public Asteroid() {
        this.setType(ASTEROID);
        health = 100;
        setScore(5);

    }

    public Body addAsteroid(World world, float x, float y) {

        // 0. Create a loader for the file saved from the editor.
        short asteroidType = (short) MathUtils.random(0, 8);
        String asteroidFile = "";

        if (asteroidType == 0) {
            sprite = new Box2DSprite(AssetAccessor.getAtlas().findRegion("gameobjects/asteroids/1346944555"));
            asteroidFile = "Asteroid1346944555.json";
        } else if (asteroidType == 1) {
            sprite = new Box2DSprite(AssetAccessor.getAtlas().findRegion("gameobjects/asteroids/1346944684"));
            asteroidFile = "Asteroid1346944684.json";
        } else if (asteroidType == 2) {
            sprite = new Box2DSprite(AssetAccessor.getAtlas().findRegion("gameobjects/asteroids/1346945753"));
            asteroidFile = "Asteroid1346945753.json";
        } else if (asteroidType == 3) {
            sprite = new Box2DSprite(AssetAccessor.getAtlas().findRegion("gameobjects/asteroids/1346946509"));
            asteroidFile = "Asteroid1346946509.json";
        } else if (asteroidType == 4) {
            sprite = new Box2DSprite(AssetAccessor.getAtlas().findRegion("gameobjects/asteroids/1346945276"));
            asteroidFile = "Asteroid1346945276.json";
        } else if (asteroidType == 5) {
            sprite = new Box2DSprite(AssetAccessor.getAtlas().findRegion("gameobjects/asteroids/1346946235"));
            asteroidFile = "Asteroid1346946235.json";
        } else if (asteroidType == 6) {
            sprite = new Box2DSprite(AssetAccessor.getAtlas().findRegion("gameobjects/asteroids/1346944994"));
            asteroidFile = "Asteroid1346944994.json";
        } else if (asteroidType == 7) {
            sprite = new Box2DSprite(AssetAccessor.getAtlas().findRegion("gameobjects/asteroids/1346945972"));
            asteroidFile = "Asteroid1346945972.json";
        } else if (asteroidType == 8) {
            sprite = new Box2DSprite(AssetAccessor.getAtlas().findRegion("gameobjects/asteroids/1346943991"));
            asteroidFile = "Asteroid1346943991.json";
        }

        sprite.getTexture().setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest);

        BodyEditorLoader loader = new BodyEditorLoader(Gdx.files.internal("asteroids/" + asteroidFile));

        // 1. Create a BodyDef, as usual.
        BodyDef bd = new BodyDef();
        bd.position.set(x, y);

        bd.type = BodyType.DynamicBody;

        // 2. Create a FixtureDef, as usual.
        FixtureDef fd = new FixtureDef();
        fd.density = 15f;
        fd.friction = 1f;
        fd.restitution = 0.3f;
        fd.filter.categoryBits = CollisionLogic.getCATEGORY_GAME_OBJECTS();
        fd.filter.maskBits = CollisionLogic.getMASK_GAME_OBJECTS();

        // 3. Create a Body, as usual.
        body = world.createBody(bd);

        float width = 3 + GameSettings.PTM;

        // 4. Create the body fixture automatically by using the loader.
        loader.attachFixture(body, "asteroid.png", fd, width);

        body.setUserData(this);
        return body;
    }

    @Override
    public void onPreSolveCollision(Object obj, Level current_level) {

        if (isToRemove()) {
            return;
        }

        if (obj instanceof RedLaser) {
            setCollided(true);
            RedLaser laser = ((RedLaser) obj);
            health -= laser.getDamage();

            if (health < 0) {
                setToRemove(true);
                current_level.addBodyToBeRemoved(body);
                float width = Box2DUtils.width(body);
                float height = Box2DUtils.height(body);

                current_level.addSmokeEffect(new SmokeEffect(width, height, body.getWorldCenter().x, body.getWorldCenter().y, Color.WHITE, 0.01f));
                health = 0;
                current_level.addScoreToPlayer(getScore());
            }
        }

    }

    @Override
    public void onPostSolveCollision(Object obj, Level currentLevel, ContactImpulse impulse) {

        if (obj instanceof Asteroid) {
            setCollided(true);
            Asteroid asteroid = ((Asteroid) obj);
            asteroid.setHealth(asteroid.getHealth() - 1);
            setHealth(health - 1);
            if (impulse.getNormalImpulses()[0] > 0) {
                //System.out.println("G.A RC : Normal Impulse : " + impulse.getNormalImpulses()[0]);
            }

        } else if (obj instanceof GiantAsteroid) {
            setCollided(true);
            GiantAsteroid asteroid = ((GiantAsteroid) obj);
            asteroid.setHealth(asteroid.getHealth() - 1);
            setHealth(health - 1);
            //System.out.println("G.A RC : Normal Impulse : " + impulse.getNormalImpulses()[0]);
        }

    }

    public void setHealth(float health) {
        this.health = health;
        if (health < 0) {
            health = 0;
        }

    }

    @Override
    public String toString() {
        return "Asteroid { Health :" + health + '}';
    }

}

It really lags when asteroids overlap each others or collide.
maybe i should decrease world.step values?

This is the normal step options that most people use:

world.step(1/60f, 6, 2);

Use a profiler like VisualVM to find hot spots. :point:

Im currently using :

/**
 * @param velocityIterations for the velocity constraint solver.
 */
private static int velocityIterations = 6;

/**
 * @param positionIterations for the position constraint solver.
 */
private static int positionIterations = 4;

and 1/60f

I will use netbeans profiler and decrease those to half half

3 and 2

I will post results shortly!

By the way, is there anyway i can do to avoid them colliding and overlapping on spawn ?

It seems its better with these settings :


 /**
     * @param velocityIterations for the velocity constraint solver.
     */
    private static int velocityIterations = 3;

    /**
     * @param positionIterations for the position constraint solver.
     */
    private static int positionIterations = 2;

Why don’t you just not spawn them on top of each other?

The box2D spec does say that you should avoid creating fixtures on top of existing ones due to the stress required to separate them.

Because i dont know how.

Use a coordinate system to disallow spawning of objects if a pervious object is within that area.

So something spawns at 5, 5 and had a radius of 1, given that box2d works from a centre origin, this would mean any coordinate that is less than 1 would be an invalid spawn location.