Development Update - January 11, 2012

Mycal

Staff member
:!: Warning! Very Long Post! :!:

It's been awhile. I'm going to start with the bad news. I've not done really any work on these projects over the past couple of months. In fact, my last code commit was on Nov. 9, 2011. Also, due to recent events, I'll definitely be working on things more often, but Odyssey is being moved to the back burner.

Now for the good news; I've had an epiphany about this project as a whole and have come to several realizations. First let me define a few things.

Our game world (and many other 2d game worlds) is laid out in a very specific format. For every world you have many maps. For every map, you have many tiles. Now our game takes things further and for every tile you have several layers. For every layer you have many properties. These properties define the image used, the color, transparency, rotation, blocking ability, etc. Above maps, we also have something called areas which are simply a collection of maps. This was all part of a grand idea to make everything load and render smoother.

Yesterday I started wondering, what would happen if I made a really large map and loaded it all into memory and drew the entire map onto a buffer and simply moved the camera instead of trying to only load what was needed given on your location. This is a common method for scrolling tiles, but I was never interested in this method since you have a lot more loaded in memory at any given time. However, I decided to try it anyway and kept track of a few things. Well, it worked and it worked very well. Frame rate took a huge hit, but both machines I tested it on, ran it like a champ. So then I thought back to the old VB6 code and remembered I had changed it to only draw when it was needed instead of every frame. I tried this, but due to limitations with XNA it is not possible to do (the way I have it setup) since anytime you minimize the window or switch monitors it needs a redraw. So then I thought, well what if I redraw once a second instead of every frame. FPS shot immediately back up and it wasn't terribly noticeable. Again, I wondered, well what point does it start lowering FPS, and I reduced it to redraw once every 1/100th of a second and now I didn't notice anything at all and FPS was a steady 60 still.

Fast forward to today, during my lunch break at work, I started writing some Gui controls in XNA and then had another curiosity. I wandered, well what if, to totally bypass the having to load surrounding map issues, I just somehow translate every map tile to a coordinate in the world space. This is where I just had a total epiphany. Let's just totally eliminate maps all together and totally redefine the world space. For simplicity sake, let's define the world as 100 x 100 tiles (just an example). Let's say the tiles are EXTREMELY basic instead of horribly complex like they are now. Let's say we have an infinite number of layers to every tile still (because we like to stack images on top of each other to create some nice transitions), but simply put, every layer is just an integer. This is the entirety of the world now. A large 3d array of integers (it'll be a list in production time for the layers, but for the example it'll be this way).

I said that the world was made up entirely of floor tiles, and that is very true, but you may be wondering about other world content. What about the trees, buildings, rocks, etc.? Let's define these as objects (since they will be static on the world). We can construct an object in a separate editor and then place them however and wherever we want on a map. Now, let's totally get rid of tile blocks. They are not needed with the new system. Instead we define on the object the collision boundaries. We also define other stuff like what part is in front of entities and which part is behind.

Next, entities are absolutely no different than they are now, but you may not know what they are. An entity is at the very basic level, something that moves in space. So a player, a monster, an npc, an item, or a projectile is an entity. They can and do move dynamically around the world. A tree would not be an entity since it is static (not moving) in the world, instead it is an object.

So now we have a world, objects, and entities. The world size is not dynamic (technically it will have a finite size, but it will be very large), but objects and entities are. Let's our world is extremely large, say 1,000,000 x 1,000,000 tiles. That is 1x10^12 (or 1 trillion) tiles that we have to load into memory. So how do we do this? We don't, simply put. Instead we will split the world into chunks. Now, since our world is now fairly simple, it is also fairly small and we can load chunks of 100x100 easily in memory.

Ok, things are about to get a little technical, but bear with me. Again, first some definitions:
  • 1 32-bit integer (referred to below as just int) = 4 bytes (this is equal to the usage of one layer in our world)
  • 1024 bytes = 1 kilobyte (KB)
  • 1024 kilobytes = 1 megabyte (MB)
  • 1024 megabytes = 1 gigabyte (GB)
  • Today’s standard computer RAM is around 4GB.
Now to calculate memory usage, our 100x100 chunk is 10,000 tiles. You multiply that by 4 and you get 40,000 bytes. Before converting anything let’s continue and say we have 5 layers total in our world, and realistically, that's a little generous for just floor tiles. Now we are loading 100x100x5 chunks into memory. This changes our equation to (100x100x5)x4 bytes or 50000x4 = 200,000 bytes or roughly 200 kilobytes rounding up. So that is still not much obviously. Now we want as smooth scrolling and seamless loading as possible without noticeable graphic glitches, so ideally we want to have all surrounding chunks (this means the chunks above, below, to the left, to the right, and each of the four corner chunks as well) loaded with our current chunk. This is a total of 8 additional chunks, plus our current chunk. So now we have ((100x100x5)x9)x4=(50000x9)x4=450000x4=1,800,000 bytes. This is 1800 kilobytes or 1.8 megabytes roughly (exactly 1.72mb). This means we have under 2MB of our world loaded in memory at any given time. I would say that’s fairly small and pretty acceptable to me. If you want a comparison, a standard user’s Firefox usage is around 500MB in memory if not more at any given time. That’s less than half of a percent of the usage that Firefox uses just for the world in our game. Heck, we can even increase the layers to say 20 and still be less than 2% of Firefox’s usage.

Given all this, our world is never going to be 1,000,000x1,000,00 in size. I mean, that would be about 72 terabytes of storage if that were the case. I can't give a real estimate, but I would say less than 10,000x10,000 is a good guess. In fact, that's the size I'm going to limit it to for the sake of giving it a finite size. Though, I think we'll shake things up and make the world between -5000 to 5000 in both directions. This way the origin is dead center and not in the far left corner of the map. Thus making negative positions for entities in the world possible.

With all this said, I think I am going to start these changes tomorrow after work, and see how it goes. I'll make a last commit before starting just to so I can easily revert back if this turns out to be a horrible idea, but I think this will make things a lot easier in the long run. Now the world editor is going to be fun to fix and finish now.
 

Mycal

Staff member
I seem to have forgotten something in this new scheme. We will still need to load additional levels, simply because of buildings, caves, etc. But these can be handled relatively simply. Just scripted events to change a player's level is all that is really needed I think.

Additionally, I didn't quite think about how mobs will work. Should I have a huge list of them all around the map always wandering? Should I only load some locally around players? Should they be dynamically loaded or manually created and just respawn after being dead? These are things I'll have to think about sometime, but for now, I'm going to just assume they will be spawned dynamically around the world depending on their properties and whether a player is nearby or not.
 
Top