Transport Tycoon is a simulation game where you control a transport company, place railroad tracks and trains, airports, bus depots, boats etc. Now the game is about 15 years old and I remember running it on a 486sx25 with 4Mb of RAM without any problems.
I was recently thinking about modern simulation game requirements and how high they are for relatively little increase in graphics and gameplay, so how did they managed to pull it with so little resources back in the day?
Does each train car have a thread, that moves it every 100ms, sleeps, wakes, recalculates position and does crash detection, and goes back to sleep?
Or is it one thread that moves every 100ms? What happens if there is too much work to do in each 100ms interval?
Answer
The real question is, how does modern games use so much memory?
I have very sparse knowledge of Transport Tycoon, so the following is only a generalisation of how such games would be made at that time.
The game would be single threaded, split into two primary portions, game logic, and rendering.
In game logic elements are represented as only a few bytes each, the map is probably a 2D array, implemented as a flat space of memory with the implicit mapping that each tile is stored in position Xcoordinate + mapWidth*Ycoordinate
, with probably just a single byte for each tile signifying one of up to 256 possible combinations of terrain and structures. Stuff like trains is a bit more complex, they'd have X and Y coordinate, possibly using 16 bit integers for each getting pixel perfect or better positioning, then there is cargo, speed, rotation, route and possibly some other stuff, but all things that you can express using a few integers.
On each update the game will use a simple loop to go through all trains, move them, increase or decrease speed as required, for collision detection either a simple double iteration over the array of trains will be used, or an array will be filled with with train positions thus requiring some memory to be cleared for every update but avoiding a costly double iteration.
The rendering part will go through the part of the map array that is currently in view, for each tile it will copy the image corresponding to the value of that tile to the output buffer in video card memory. Then the trains in view will be blitted onto the output buffer.
Depending on how the game is written it will if there is not enough processing power to do all the required tasks either simply slow everything down, or skip some of the rendering, thus lowering the framerate but keeping the game itself running at constant speed.
There are some thing from this example that can be used in modern programming:
You do not need fancy stuff like multi threading and events, you should try programming without them, not because they are bad tools, but learning the alternatives broadens your toolbox and helps you to better pick the right tool for the job.
One of my favourites, you can actually count memory use. Some modern languages makes it a bit harder by using a lot of memory that doesn't correspond directly to the resources you use, but it doesn't make it impossible. If you make one 32 bit integer, that is 4 bytes, if you make an array of 1024 such integers that is 4 kB, if you load a 640x480 image into memory as a 32 bit ARGB texture, that is 1200 kB. People should in general count memory use way more that they do, it's a great tool for planning.
Game logic typically doesn't need a lot of memory, some games use a lot anyway because they can, but often that simply means the developers then have to deal with representing all this information in a way that makes it easy to understand for the player. The big memory spender is always graphics, if not you are probably doing something wrong.
And finally to justify my own question, let's count memory use for my description of a game like Transport Tycoon. Let's say for a period typical example, the game use 8 bit colour, the tiles are 16x16 pixels, the biggest map supported is a whooping 256x256, there are 16 different locomotives and 32 different carts, the trains can go 8 different directions, thus 8 images for each locomotive is needed, and 4 for each cart is needed as they look the same rotated 180 degrees, that is 256 images, plus 256 blit masks. All these images are 16x16 pixels. This gives a total of 768 16x16 pixel 8 bit images, thus a total of 192 kB memory for storing all graphics assets.
The map use 64 kB.
Supporting up to 255 stations and 255 trains, with up to 32 stations on each trains route, all will need 16 bytes for a name. The stations will need 2 bytes for coordinates and 32 bytes for stocks of goods, thus 50 bytes total for a station. The trains have up to 6 carts each need 1 byte for type of cart, 1 for amount of content, 4 bytes for position and 1 for rotation. The train need 1 byte for type of locomotive, 32 bytes for route, 1 byte for next stop on route, 1 byte for speed, 4 bytes for position and 1 byte for rotation. 98 bytes total for a whole train. All this sum up to less than 37 kB for trains and stations.
the output buffer is 320x200 pixel at 8 bit per pixel, thus 62.5 kB of video memory, 125 kB to implement switching buffers.
Assume 90 kB of code and we are right around the ½ MB mark total. I have surely forgotten something in this count, but probably nothing major, and it is very typical for a period game to stay below the 640 kB mark, thus avoiding having to deal with expanded or extended memory and the possible problems this would cause for users.
If you do the numbers for a modern game you'd probably end up wondering why PC games need so much memory, indeed the modern consoles seem to do pretty well with only 512 MB of memory, there can be A LOT of textures in that space when you start to think about it.
No comments:
Post a Comment