[Libgdx] Huge world with alot of entities fps drop [solved]

Okay cheers man, well last question. Should i draw rectangle on every collidable object? Since on Tiled you can draw a huge rectangle or a one grid rectangle. Which is the best option? Since this is a grid based game.

I used collision based on this: https://github.com/libgdx/libgdx/blob/master/tests/gdx-tests/src/com/badlogic/gdx/tests/superkoalio/SuperKoalio.java

But question is should i have collision tile layer or collision object layer. Object layer seems more fitting. But how will i then check only area around the enitity and not the whole map with a object layer.

    private void getTiles(int startX, int startY, int endX, int endY, Array<Rectangle> tiles) {
        TiledMapTileLayer layer = (TiledMapTileLayer)map.getLayers().get("collision");
        rectPool.freeAll(tiles);
        tiles.clear();
        for (int y = startY; y <= endY; y++) {
            for (int x = startX; x <= endX; x++) {
                TiledMapTileLayer.Cell cell = layer.getCell(x, y);
                if (cell != null) {
                    Rectangle rect = rectPool.obtain();
                    rect.set(x, y, 1, 1);
                    tiles.add(rect);
                }
            }
        }
    }

I don’t know how to answer that and i doubt it would help you if i say “use this!” or “use that!”.

Is your problem that some objects are stretched across multiple tiles?

Why don’t you use a structure different from the tile-map layers if it doesn’t work within the constraints of tiles?

Exactly. The only right answer to these types of questions is to try it out yourself and see which one works best for you and your game. There isn’t a one-size-fits-all solution. It all depends on your preferences and your exact context, which only you know.

Don’t be afraid to test stuff out. Write a bunch of smaller example programs that try out different approaches. Do some profiling if that’s what you’re worried about. Otherwise just do whatever fits in your head the best.

I agree with you guys, honestly i’m not worried about perfomence since i going to split the world map into several different parts. I think object layer makes it perfect and also be able to fetch properties from it.

While we are talking about this. Is it even possible to have a huge tmx map. Or do i need to break it down into smaller files? Since i remember i tried once and the game got out of memory. Is it possible to store the map inside a quadtree or something?

Why would you want to store the map in a quadtree? Why would you need to test for collision between map tiles?

Yes it’s possible to have a huge map. But it might not be possible to have the whole thing in memory at one time.

Again, the answer is to try it out and see what works for you.

You’re right, thanks for sticking up to me and answer all my questions :slight_smile:

I actually found solution for frame drop, since i looped through the quadtree on everyframe and cleared the tree. Caused that fps drop. Now it works great, but problem is some monsters and flickering. Like they are getting removed and readded, even tho they are inside my screen and that should not happen:

Here is my main loop:

package com.game.rpg;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.maps.MapObject;
import com.badlogic.gdx.maps.MapObjects;
import com.badlogic.gdx.maps.MapProperties;
import com.badlogic.gdx.maps.objects.RectangleMapObject;
import com.badlogic.gdx.maps.tiled.TiledMap;
import com.badlogic.gdx.maps.tiled.TiledMapRenderer;
import com.badlogic.gdx.maps.tiled.TiledMapTileLayer;
import com.badlogic.gdx.maps.tiled.TmxMapLoader;
import com.badlogic.gdx.maps.tiled.renderers.OrthogonalTiledMapRenderer;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import com.game.rpg.Entities.Creature;
import com.game.rpg.Entities.Monster;
import com.game.rpg.Entities.Object;
import com.game.rpg.Entities.Player;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;

public class World {
    private Main game;

    private OrthographicCamera camera;

    private final int EVENT_CREATURE_THINK_INTERVAL = 1000;

    private TiledMap map;
    private TiledMapRenderer mapRenderer;

    // QuadTree Creatures
    private QuadTreeNode quadTree;
    private Array<Creature> blockArray;
    private Array<Creature> entitiesToCheck;

    private Player player;

    private int worldWidth = 0;
    private int worldHeight = 0;

    public World(Main game, OrthographicCamera camera) {
        this.game = game;
        this.camera = camera;

        player = new Player("Player", new Vector2(0, 0), game.asset.getAtlas().findRegion("player"), this);

        // Loading map
        loadWorld("world");

        MapProperties mapProperties = map.getProperties();
        worldWidth = mapProperties.get("width", Integer.class);
        worldHeight = mapProperties.get("height", Integer.class);

        // Set up the quad tree
        quadTree = new QuadTreeNode(0, new Rectangle(0, 0, worldWidth, worldHeight));
        entitiesToCheck = new Array<Creature>(true, QuadTreeNode.MAX_CREATURES);
    }

    private void loadWorld(String mapName) {
        map = game.asset.getAssetManager().get("map/" + mapName + ".tmx");
        mapRenderer = new OrthogonalTiledMapRenderer(map, game.UNIT_SCALE, game.batch);

        blockArray = new Array<Creature>(true, entityCount);
        blockArray.add(player);

        loadMonsters();
        checkCreatures();
    }

    private void loadMonsters() {
        int counter = 200000;
        while (counter > 0) {
            blockArray.add(new Monster("Dragon", new Vector2(0, 3 + counter + counter), game.asset.getAtlas().findRegion("bat"), this));
            counter--;
        }
    }

    public void update(float deltaTime) {
        game.batch.setShader(null);
        mapRenderer.setView(camera);
        mapRenderer.render();

        game.batch.setProjectionMatrix(camera.combined);
        camera.position.set(player.getPosition().x + (player.getWidth() / 2), player.getPosition().y + (player.getHeight() / 2), 0);
        camera.update();
    }

    public void render(float deltaTime) {
        game.batch.begin();
        renderCreatures(deltaTime);
        game.batch.end();
    }

    private void renderCreatures(float deltaTime) {
        entitiesToCheck.clear();

        // Retrieve the entities we might be able to compare against
        quadTree.retrieve(entitiesToCheck, player);

        for (Creature creature : entitiesToCheck) {
            creature.onWalk(deltaTime);
            game.batch.draw(creature.getTexture(), creature.getPosition().x, creature.getPosition().y, creature.getWidth(), creature.getHeight());
        }
    }

    private void checkCreatures() {
        new Timer().scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                updateEntities();
                for (int i = 0; i < entitiesToCheck.size; i++) {
                    Creature creature = entitiesToCheck.get(i);
                    //creature.onThink(EVENT_CREATURE_THINK_INTERVAL);
                }
            }
        }, 1000, 1000);
    }

    private void updateEntities() {
        // Clear out the quadtree
        quadTree.clear();

        for (int i = 0; i < blockArray.size; i++) {
            // Fetch the current block
            Creature creature = blockArray.get(i);

            // Update objects based on player can see creature, insert them into the quad tree
            if (player.canSee(creature.getPosition())) {
                quadTree.insert(creature);

                if (!creature.isObject())
                    creature.setIdle(false);
            } else {
                creature.setIdle(true);
            }
        }
    }

    public Player getPlayer() {
        return player;
    }

    public void dispose() {
    }
}

I’m using this quadtree:

How can i solve the issue, since i try to update the quadtree once a second, but it causing the flickering issue.

Here’s your problem:

new Timer().scheduleAtFixedRate(...

Using an asynchronous timer fucks around with the rest of your game logic. Do you know when this is getting executed? Yeeea…nope… The timer might call this function at any time and then it modifies the quad-tree, etc. Maybe even while you are rendering or fetching objects from the tree.

Also, do you really think this will actually remove lag? If updating every frame causes stutter, so will updating every 20 frames. It will just be “lag every second” vs “lag every frame”, it’s still not fixed, just reduced. If you want to remove lag, you must split the work between frames or between threads.

Thanks for the information, could you please provide some example on this issue?

I found this about thread: https://github.com/libgdx/libgdx/wiki/Threading
You mean i should add the quadtree update into it?

Also i read that i should not update quadtree every frame, thats why putting it into a schedule event which fixed the issue but caused other issues

Edit:
About frame update you told me, how i understood it and it fixed the flickering issue and my fps increased from < 50 fps due to update everyframe to 700 fps. But maybe it cause undefined behavior, which i have not discoverd yet? Also if this is the way, should i do the same for my onThink method, which i want to execute once a second.

        dt += deltaTime;
        if (dt >= 0.1f) {
            updateEntities();
            dt = 0f;
        }

[quote]You mean i should add the quadtree update into it?
[/quote]
You could do that, keep the timer and use it to submit the update-runnable once every second to the postRunnable method, so that libgdx executes it at the beginning of the next frame.

This will remove your speed benefit though (it’s not getting executed concurrently anymore) but will solve your flickering issues.

[quote]Also i read that i should not update quadtree every frame, thats why putting it into a schedule event which fixed the issue but caused other issues
[/quote]
Read my previous answer … you’re executing stuff in parallel (with the timer!!!) without synchronization, this is guaranteed to bombard you with bugs.
You should read up on synchronization and threading so that you know why that stuff can be dangerous.

[quote]About frame update you told me, how i understood it and it fixed the flickering issue and …
[/quote]
https://en.wikipedia.org/wiki/Word_salad ???

I see, but the new way i just posted. Should also be fine as a solution? Since i’ve tested it and found no issues.

Maybe i take too much water over my head, but i really would love to make a Open world game. If i’m able to tackle this issue, the next one would be the world map. Since loading a huge map too the memory at once seems not be ideal.

Oh ok, now i understand :smiley: The new way is the simplest way to do it :slight_smile:
I would just change one little thing:

dt += deltaTime;
if (dt >= 0.1f) {
   updateEntities();
   dt -= 0.1f; // Don't throw away the rest of dt, it might be >0.1
}

Cheers mate, i think this case has been solved then. Since the map loading is a seperate topic.

I hadn’t even considered the fixing the delta time.

What do you mean? That this is a bad solution?

I’m 99% sure he means that it’s a very subtle mistake.

The timing gets a bit more accurate with the fix.

Without the fix your timer will not be called every dt seconds, but somewhere between every dt and dt+1/fps seconds, depending on the fps your game is running at. The timing error created by that accumulates if you are doing anything time sensitive like counting or spawning with this simple timer.

The fix reduces this error (of how often it’s called in x seconds, resolution is still 1/fps) to the accuracy of your float arithmetic and time measuring, which is way less than for example 16ms (=1/60fps).

In practice that means players with higher fps will be able to spawn units slightly faster or get more resources if you use the set-dt-to-zero timer.

Ah thanks for the explanation

What if i made this game a online game, then every computer has different delta. Maybe i should tackle the issue by doing like this:

    private long startTime = System.nanoTime();
    public void update(float deltaTime) {
        long elapsedTime = (System.nanoTime() - startTime) / 1000000;
        if (elapsedTime >= 100) {
            updateEntities();
            startTime = System.nanoTime();
        }