Simulation games are usually attempting to simulate real-world systems in such a way as to mimic the essence of the system while accounting for accuracy along with physical performance. It’s a balancing act. The goal is to approximate the real-world counterpart in a believable and resource efficient way.
The key to simulating anything is to cut it down to its core components. For games, this usually means cutting a system down to those core components or processes that are either interesting, fun or both. Anything else is chaff and should be treated as such; ignored. Accuracy is far less important when it comes to a game than you might at first think. It is less important that a thing works perfectly than it simply looks like it works even if it’s abstracted and simple.
In Eden, there are a lot of different systems working together at any given time to bring the world to life. Creatures vie for food and water while also protecting themselves and challenging others. Fires may spawn from weather systems and spread to anything flammable, engulfing forests and creatures alike in flame and destruction. Water is simulated such that it flows naturally which may result in dangerous floods. Merchant caravans come and go, eliciting barter and trade. There are a host of other interesting systems and more to come. Each of these systems is treated as accurately as reasonable in order to produce the most fun and interesting emergent behavior within the game. Flooding and the spread of fires is not scripted, so the results may be beneficial to the player (hurting their enemy) or very detrimental; contextually these systems combine to create very interesting and unexpected or unforeseen situations.
Where it concerns simulations from a technical perspective; keeping this all in memory and processing in a timely and efficient manner, can be difficult. In Eden I am to maintain a consistent 60 frame per second (FPS) in order to provide a smooth experience to the player where visuals and interaction are concerned. Realistically 30 FPS wouldn’t be terrible or anything, I just aim for high fidelity while providing a rich experience.
Due to this self-imposed limitation, I have had to focus heavily on optimizing these various systems such that they work very efficiently. This has required an extensive amount of profiling and measuring the performance of the game at run-time. Through constant profiling and optimizing I’ve been able to strike that balance between realism and performance.
Simulating Complex Systems
I have found that while it is generally standard practice to throw any kind of logic in the XNA/Monogame Update() method; it is usually not necessary for that code to be called that often. Update() gets called constantly, often many times more than Draw(). For really high fidelity games, or games that aren’t consuming all that much in the way of processing power; using Update() is probably fine. However in Eden, it’s a complicated game with a lot of moving parts so I’ve had to find simple effective ways to cut down on wasted processing and one of those was to just call things at intervals or spread out processing over time. Doing so removes the logic from being run for every call to Update() and instead calls it perhaps once a second or does a lot of work overall spread out across many calls to Update() (for example, pushing a lot of work into a queue and then de-queuing one item per Update() so as to spread out costly calculations over time). The result of these relatively simple changes is a massive increase in performance and throughput; all the work gets done eventually and its really quite unnoticeable to the player because it is logic occurring under the hood; the key to good game simulation is reasonable approximation. You don’t have to do everything super accurately, or even on-time; the player if they are playing a good fun game, isn’t going to care or even notice if all the fire processing has occurred EACH Update() call, they don’t even know what that is most likely; as long as the fire behaves in a way they might expect; such as it spreads to nearby combustible objects; or rains and water put fires out; fires hurt things, etc.
Simulating Artificial Intelligence
On the topic of artificial intelligence (AI), again the key to pretty simple performance gains is to not calculate AI every Update(). Usually AI processing can be somewhat intensive depending of course on your game and the environment it takes place in. For example in Eden, the world can be quite large and that leads to expensive path-finding. Rather than calculating path-finding for each creature, each Update(), you can just calculate path-finding to a thing or location, store that and until that goal is met, don’t bother re-calculating the path again! Now while it’s still an expensive call, we aren’t calling it nearly as often and only when necessary. Additionally, AI is another one of those things that doesn’t have to be done in real-time. The player isn’t going to know or care if entities in your game are processing AI 100 times a second, or once a second, or perhaps even once very 3 seconds. As long as it looks and feels believable, that’s good enough and that allows you to pull more performance out of your game pretty cheaply.
In a game like Eden, there are a lot of objects in the world, creatures, trees, the actually terrain blocks (as the world is not a static background but actually exists and can be transformed etc., items, etc. This becomes a lot of data that needs to be searched quite frequently. Creatures need to seek out food and water if they are in need of it, they have to search out directions to follow that the player has put forth (gather an item, build something, perform some other action etc.). Searching all of the objects can become quite costly as the game scales up. In order to keep searching nice and fast, the simple solution is to instead of storing one massive list of all objects is to break that list down into a collection of smaller ones. That turned out to work quite well with Eden, there are internal lists of all the creatures, blocks, plants, etc. When a creature is looking for food for example and they are an plant-eater, we just search through nearby plants rather than a bunch of other irrelevant objects (buildings and such).
As the game world increases in size, even these smaller lists can become large themselves and the searching bottleneck is re-introduced. In order to support larger worlds I had to once again figure out a way to keep the lists small and quick to search. The solution I came up with was to sub-divide the world into what I call regions; these are basically squares of blocks so that if the world is composed of say 1000 blocks, those 1000 blocks are then distributed across 10 different 10×10 square regions. Then each region keeps track of the items, creatures, etc. within them. When it comes time for a creature to search out food, it simply looks in the region it is in for food that fits its criteria, if it finds something, great it’s done! If it doesn’t, then it just checks each adjacent region, and so on until it finds what its looking for. Obviously the worst case scenario is it searches for something that simply doesn’t exist in the world. To combat that, first I do a more generic search of say all plants in the world that fit the criteria; if nothing is found, then there is no reason to perform the region by region search. This is a bit of a balance of larger lists against smaller lists and it also helps find objects closer to the creature rather than really far away which would look weird to the observant player. as an added bonus.
A Note About ‘Chunking’
It is a common technique in a lot of games, most notably Minecraft to split the world into ‘chunks’. The active chunk or set of chunks are the ones currently loaded into memory and as the player moves around the world; new chunks are loaded in and old ones are removed. This is what makes the game feel so large because it is infinite but only a small set of chunks are loaded at any one time; the transition is seamless to the player. At any given time there is a fixed amount of world data in memory, only the world information that directly surrounds the player and nothing far enough away that they can’t see it anyway. I would imagine the similar kinds of things are happening in Breath of the Wild, Grand Theft Auto, Skyrim, etc.
In Eden however, I am actually interested in the whole world being simulated at all times. The reason for this is that it’s a simulation game rather than an adventure game, the player is in loose control of a whole colony of folks rather than in control of a single avatar. Additionally I want the world to function and live beyond what the player is directly playing with. For example, if there is a nearby clan of goblins I want the simulation to consider them and their behavior and not just turn it off because they aren’t on screen. Think of a game like Terreria where it’s this really huge world but a lot of the logic is only happening to whatever is on screen. If you run somewhere and leave, then come back later, similar creatures might show up but it’s because they are part of that biome or area, not because they have persisted all along. This is a key difference with Eden and these other games, I’m not just simulating what is on screen but everything in the world, at all times. The downside is the world isn’t going to be nearly as large as some other games, but the upside is that it’s a living, functional world with persistence rather than instancing on demand. Also note, the worlds in Eden are plenty large enough, probably still too large honestly, but many people like that.
There is no chunking in Eden; I don’t plan to implement it and never intended to have massive worlds; at best each ‘world’ could eventually be considered part of a greater whole that is not simulated except when loaded; think Dwarf Fortress and RimWorld, where the worlds are made up of many different sections. Currently the ‘world’ in Eden could eventually be one of those sections as well. That’s a far-off goal and would depend on player interest.