[Article] Open World Development

Started by
8 comments, last by DevilWithin 10 years, 3 months ago

Hey folks!

My name's Anthony, and I'm the lead dev for a game called Artifex Dei. Now that development is coming to a close, I am currently working on writing bi-weekly articles about some of the materials and experiences I came across while designing and coding this game.

The first article I decided to write is about open world development in a 2d environment. It starts out by explaining the advantages of tiles, and how to properly implement them with an open world approach. The article then describes how to manage millions of tiles in chunks, and some of the challenges you might run into.

I wasn't sure if this was the appropriate forum, or if it belonged in Beginner, but...

You can read the whole thing here: Open World Development

Please let me know if you found it helpful, I love feedback!

About Artifex -

Artifex Dei is a new concept; an open-world tower defense game. It is a full on RPG, including armor, weapons, magic, battles, dungeons, and loot - mixed together with an ongoing tower defense war. I am extremely excited about the concept, and encourage everyone to poke around the website.

We have a short description of the game posted in our About Section,

as well as several screenshots in our Media Gallery

Keep yourself updated from our twitter account @artifexdei, and keep an eye out for our next game design article, explaining tower design in Tower Defense games.

Thanks guys enjoy!

Advertisement

Nice overview of the topic! Your game looks interesting as well, congratulations on getting most of the way through development, and good luck with finishing it off! :)

If you'd like some more exposure for this (or other articles) you might also consider publishing it as an article here on GDNet. Once approved articles are rotated through display on the front page, shared via social media, and of course added to our archives. You're welcome to link back to the original source, and can also provide links to your game in the author bio at the bottom! :)

- Jason Astle-Adams

Nice overview of the topic! Your game looks interesting as well, congratulations on getting most of the way through development, and good luck with finishing it off! smile.png

If you'd like some more exposure for this (or other articles) you might also consider publishing it as an article here on GDNet. Once approved articles are rotated through display on the front page, shared via social media, and of course added to our archives. You're welcome to link back to the original source, and can also provide links to your game in the author bio at the bottom! smile.png

Thank you for the positive feedback, I'll be sure to look into publishing it, solid advice!

I really enjoyed your article, through and through, but there was a little thing that bugged me:

  • Tiles save clock cycles! It is a huge benefit to the GPU to render one tiny image many times instead of rendering one monster background image.

How is it a benefit to draw a tiny image many times (lots of draw calls) over rendering a single rectangle in one draw call, with a big baked texture of the tilemap? Even if the tiles come in one batch of geometry, and are rendered in a single draw call, how will that be faster than just rendering two triangles for the monster background approach? After all, filling the screen with pixels of this tilemap would take about the same time with both approaches right? (fill-rate wise), Or am I missing something?

I really enjoyed your article, through and through, but there was a little thing that bugged me:

  • Tiles save clock cycles! It is a huge benefit to the GPU to render one tiny image many times instead of rendering one monster background image.

How is it a benefit to draw a tiny image many times (lots of draw calls) over rendering a single rectangle in one draw call, with a big baked texture of the tilemap? Even if the tiles come in one batch of geometry, and are rendered in a single draw call, how will that be faster than just rendering two triangles for the monster background approach? After all, filling the screen with pixels of this tilemap would take about the same time with both approaches right? (fill-rate wise), Or am I missing something?

Thank you for reading!

Interesting point you bring up, I appreciate your input.

In my experience, whenever I've tried to render a giant image, there is usually some pop-in delay, meaning it took some extra time to get drawn to the screen. If I then change my approach over to tiles, there is never a delay.

I've also read a couple times that it helps the GPU to stick to smaller images, I could have misremembered this point though.

There is one immediate benefit that I can think of; that is that if you use a large image, parts of it will usually be outside of the viewport out of view, but the GPU will still have to do work on it. In this way, tiles help out on CPU cycles, I suppose I could re-word this part.

Thoughts?

As far as I know, when you draw geometry that falls way outside the viewport, the extra cost for this is relatively constant. This should mean that it doesn't matter too much if you overflow the viewport size by 1000 or 10000, the rasterizer will be smart about it.

As you can see in many games, they use tons of huge images that mostly take all or big parts of the screen, and the framerate is not affected by that. Water planes are usually huge and can cover all the screen and way beyond. Skyboxes are always covering the whole screen. Post processing steps render full screen buffers potentially dozens of times to apply many effects. Terrain usually covers lots of the screen.

The texel fetch step may or may not be slower for bigger images, but this shouldn't be the reason of your slowdowns if you re not doing something fancy and the picked texture sizes are adequate.

From my experience, using a vertex buffer with all the tiles and rendering it in one draw call with a single spritesheet active, that is the most efficient approach, because its very flexible, pretty fast, and uses no extra texture memory. If you pre render tiles into big chunks, you use tons of texture memory, have a bit faster rendering time, but you can't animate or change tiles as quickly.

So yeah, I don't think you're doing it wrong !

"The seasoned programmer may have also noticed that we changed the Tile from a class to a struct. This is a very important change, because we no longer need a memory reference to the Tile, since it's stored on the Stack memory segment. That's another 4 bytes of savings, or 500 MB total. "

Since we are on it, I'd also like to question you about that paragraph. You said that changing from a class to a struct makes a difference in the size of the data, I don't find this to be true, as a c++ programmer, but I am not absolutely sure you are using something else than c++, right?

edit:posted in wrong thread

o3o


Tiles save clock cycles! It is a huge benefit to the GPU to render one tiny image many times instead of rendering one monster background image.

In general, this won't be true when it comes strictly to rendering performance. The pixel shader will be run the exact same number of times in either case (there would be a possible difference in texture fetch speed due to the texture cache, but I wouldn't expect this to favor the tile-based approach). Rendering lots of tiles also means you have more geometry to process. This would be a relatively minor cost though. I would expect the "one monster background image" to have a minor/insignificant performance benefit - when it comes strictly to rendering.

When it comes to memory usage though, obviously it's going to take a lot more memory. And thus a lot more time to load into the GPU when it's loaded. So "one monster background image" would have a negative performance impact when new sections of map are loaded.


There is one immediate benefit that I can think of; that is that if you use a large image, parts of it will usually be outside of the viewport out of view, but the GPU will still have to do work on it. In this way, tiles help out on CPU cycles, I suppose I could re-word this part.

The only work the GPU will need to do is for the vertex shader, which would be irrelevant in this case. There's no cost for "rendering" pixels that are outside the viewport.


The seasoned programmer may have also noticed that we changed the Tile from a class to a struct. This is a very important change, because we no longer need a memory reference to the Tile, since it's stored on the Stack memory segment. That's another 4 bytes of savings, or 500 MB total.

I'm assuming this is C# then? Presumably you have an array of Tile structs, in which case they exist in the heap (but in one single allocation, not millions of individual allocations), not the stack.

And now, a question for you:

How do you deal with world simulation of parts of the world that are outside the currently loaded section? Or, say, if an NPC near the edge of the loaded section, chasing the player, and then that section gets unloaded? Obviously what you need here depends on your gameplay, but it seems like it would be interesting to talk about.


"The seasoned programmer may have also noticed that we changed the Tile from a class to a struct. This is a very important change, because we no longer need a memory reference to the Tile, since it's stored on the Stack memory segment. That's another 4 bytes of savings, or 500 MB total. "

Since we are on it, I'd also like to question you about that paragraph. You said that changing from a class to a struct makes a difference in the size of the data, I don't find this to be true, as a c++ programmer, but I am not absolutely sure you are using something else than c++, right?

In this case, I am using C#. As far as my knowledge of memory management goes, doesn't this also apply to C++?

In both cases, when we create a new object, the object data sits on the heap, while a reference to that data goes on the stack. When we change over to a struct, all of the data is stored on one segment and we no longer need a reference variable for it, it's essentially "inlined."

All of those memory references add up over a few million tiles.


The seasoned programmer may have also noticed that we changed the Tile from a class to a struct. This is a very important change, because we no longer need a memory reference to the Tile, since it's stored on the Stack memory segment. That's another 4 bytes of savings, or 500 MB total.

I'm assuming this is C# then? Presumably you have an array of Tile structs, in which case they exist in the heap (but in one single allocation, not millions of individual allocations), not the stack.

And now, a question for you:

How do you deal with world simulation of parts of the world that are outside the currently loaded section? Or, say, if an NPC near the edge of the loaded section, chasing the player, and then that section gets unloaded? Obviously what you need here depends on your gameplay, but it seems like it would be interesting to talk about.

You're correct.

This is actually a complex question, as I handle different things in different ways.

For free roaming creatures, if they are not within a 5 tile buffer of the viewport edge, they don't do anything. This is never an issue because adjacent chunks are never loaded unless the player is within 10 tiles of the boundary, so 5 will always be safely loaded already.

This essentially means that the player's computer has 10 tiles to load a chunk before they start seeing white space and the game crashes.

For path faring creatures, they are always simulated, because they need to make their way through the towers and to the center of the map. I don't deal with rendering anything they do, that includes damage numbers, animations, names, health bars, etc.

For towers, they are also always simulated so they can fire on path creatures. If they aren't visible to the player, no projectile is launched, and no animations are dealth with. The damage is simply subtracted from the target(s).

For NPC's they are similiar to free roaming creatures, they are not simulated outside of the viewport.

In C++, it doesn't apply indeed, therefore my confusion. In c++ you allocate blocks of contiguous data for your arrays or objects. There is no differentiation between struct and class, except for the OOP programming constructs. An array is merely a pointer to the first element of the data allocated in memory.

This topic is closed to new replies.

Advertisement