How does terraria have so big worlds and still has silky smooth fps?

Viewed 1251

I was working on my own procedural generator and finished making it. Here is how I did it, generate the entire world at the beginning, store info about spawn positions and tiles positions into arrays and data structures

Now obviously, I have to loop through all the arrays, to render/update stuff, this is creating problems, I tried dealing with this problem by applying a condition that if any object is visible, only then render update else don't, but it does not make much of a difference.

my TILE_SIZE is 1 world units

so when my world size was a)500 by 500,fps was 60 b)2000 by 2000, fps was 50 c)5000 by 5000 fps decreased further and so on

If only I could know how Terraria does it, or any way it done, would really help thanks.

2 Answers

I have to loop through all the arrays, to render/update stuff

You don't have to do that.

For rendering stuff you really only need to consider the blocks that are close to the player. Here is some code(x, y are the player coordinates, width, height are the dimensions of your world, render(x, y) is a placeholder for your rendering functionality, and for simplicity I assumed the player can see 100 blocks in each direction):

for (int i = Math.max(x-100, 0); i ≤ Math.min(x+100, width); i++) {
    for (int j = Math.max(y-100, 0); j ≤ Math.min(y+100, height); j++) {
        render(i, j);
    }
}

The process of updating can also be simplified:

For npcs you only need to check a few blocks around it.

For block updates(liquid flowing, sand falling, corruption spread, …) there are actually two possibilities:

  1. Iterate through a different part of the array each update:
    Block updates don't have to be that frequent. For fluid flow it might be good enough to update only every 50th block in each update.
  2. You can store the blocks that might need to be updated in an ArrayList(I suggest storing only the coordinates, not the blocks itself).
    Then when the update function is called you go through that list and check wether the block really needs an update. If that's the case, you add the blocks neighbors to the list, which will then get updated in the next iteration.
    Make sure to remove the blocks you just updated from the list and avoid adding duplicates!

While the second approach is normally faster(ususally the list is only determined by player actions and even if the player decides to drain the entire ocean the whole list would probably take less than ¹⁄₂₀ of the entire world).

But the first approach is certainly easier to implement and will be faster if for whatever reason you have lots of block updates.

  1. Anything you do not see on screen is stored as values. This means that all tiles that are not visible in your viewbox do not get rendered. They are stored as objects (e.g. {x, y, type}); Make sure you count how many tiles are on screen VS how many you are rendering each loop in your code. Never render something that is not visible to the user as this will eat up your memory.

  2. Any tile groups that cannot be rendered soon (i.e. by moving left/right) are not looped through. So say you are using tile sectors to group your tiles, and you're in sector D. Moving left will put you in C, and moving right will put you in E. So these sectors should be loaded in to arrays (but not rendered) There is no need for you to loop through any other sectors (e.g. A or B). So these can be stored externally (e.g. as text files). So they are available, but not taking up space in your games memory.

Good luck. :)

Related