After basically learning from trial and error, and bugging a particular K-man on the gamedev IRC chat. I eventually came to a number of parables and hard learned lessons in game programming. Given that this board is quite active, and a lot of common questions are generally asked from this board, I felt it might be necessary to leave a few tricks, parables, and lessons for those to come. I hope other forum members follow in suit!
For people leaving tips. Please note that...
- Abstract concepts are perfectly ok! Just be sure to make it fairly easy to understand. Remember not everyone has 10+ years of experience in the industry.
- Code is not required. Remember the idea is to leave tips, hints, and parables. Though if you feel it necessary to make a lesson, then by all means do so.
- Diagrams may be deferred. If wish to make a diagram, and not have it in ascii, just leave a note that there will be one later.
- Links to blogs are ok, just remember to break down the concept if it's too technical.
So here we go!
---
First some background about me.
I am currently a computer science student in my junior year, and I really don't have any experience in professionally making games. I have made some clones however. My current project is just a hobby, as well as a tiny experiment. But because it's also going to be part of my portfolio of software, I'm not disclosing anything out of paranoia.
The project is a game engine with a different approach, inspired by an engine made by Almost Human, and used for Legend of Grimrock 1 and 2. And also love 2D. The engine is using C++ as a framework. C manages low-level systems such as rendering, audio, resource loading, and TCP/UDP connections. But everything that defines a game, including higher level components of the engine will be done in Lua. The benefit was for rapid programming, and skipping the compiling steps in between.
Here are some of my lessons, and tips for you.
Lesson 1:
Entity-Component design is only a pattern. A guideline. Not absolute.
When designing my game engine, I actually fell into the trap of believing that Entity-Components were the most widely and most accepted solution to game design. Also that it's cache friendly.
Entity Components are in fact a widely used design pattern. Read this wiki page if you don't know what the ECS is. But it turns out that the approach commonly seen in tutorials, libraries, and open source games can actually be a bad idea depending on your needs. Commonly, everything must be an entity in your scene along with three major approaches I have seen, and each of them can get pretty frustrating.
- Store all components in arrays, and blast through the update cycle by having a system process it's array one at a time. This is very cache friendly, update cycle is easily modified. Optimization in the form of data sets. Problems? Strong dependencies become an issue when entity update order is random -Who owns what-, a solution is to use fibers or coroutines but this gets complicated fast. Threading means you run into the pipeline problem, which is only somewhat faster than a single threaded process. This is the issue I ran into. The other problem was that Lua has some small overhead for threading due to communications. Which prompted an immediate change.
- Make everything a component. And have a lot of systems in the update loop. The truth is.., not everything needs to be a component in it's own right. Components should be seen as a collection of data for behaviors. And... what I hadn't realized till I played around a bit... is that a function can be data as well. You can play around with function pointers, virtuals, or scripts for that bit. Also only systems that need to be updated in real time should be in the main game loop. Remember that there are situations where you have no idea when something might crop up, so have an event system ready for those systems. e.g. ClickableComponent, TriggerComponent. In Lua, a variable can hold a function. The game object and function can be described by JSON, or Lua file.
- Avoiding the use of maps. There are a good number of reasons to avoid using a map. But the wrong reason is for speed. Surprisingly, this actually fell in the concept of premature optimization. Hash Maps are slower than arrays, but it's not a problem if you're not running a triple A game. And they can actually make life easier. With this in mind, you can throw away integer IDs, and use something more human friendly. The approach is still data oriented, makes debugging easier, and you can still refer to entities by IDs. Plus for custom level editors, you can allow the engine to assign a random UUID, or you can assign your own. I stopped bothering with arrays when I recalled that Lua's data is usually passed by reference. And tables are actually crossbred Arrays and Hash Maps.
So... to fix most of these designs after a recent refactor, I changed the ECS system into something similar to what Unity, Unreal, and Killzone does. Entities are updated as a whole, but they can now have dependencies on other entities. Because most dependencies are of an acylic nature, we can easily schedule them accordingly. Dependencies are treated like stacks. Last in first out. We want to fire a bullet, which comes from a gun... that comes from a soldier. Well... bullet is dependent on gun. Gun is dependent on soldier. So the order is Soldier, gun, bullet. Branching dependencies are dispatched to separate threads.
Lesson 2:
Find your own damn solution
Don't read it literally. It means do not directly copy someone elses solution. I got burned twice on this. The first time at the start of the project, the second on my scene graph.
These solutions are generally designed for a specific architecture, something of which you need to be mindful of.
When I ran into this, I had so many issues with my scene graph. It was a direct decedent of the one produced by bitsquid. Which later evolved into a scene manager. It's job was to handle parent child relationships, load cells (Level chunks, like terrains or dungeons) and their entities, and filter out unneeded cells when dumping into the rendering pipeline. It was implemented into C++ as I assumed something like this benefited from speed. This design however... wound up screwing me.
- Game Critical Data was separated from the enviorment. WTF!?
- Level loading and saving was harder than it seriously needed to be.
- Turned the code into a mess. While getting transforms was easy... a lot of accessing just suddenly devolved into a daisy chain.
- Animation transforms had to be calculated twice.
I eventually threw it out, and put the scene manager into Lua. The scene manager, thanks to the threading system, is no longer responsible for managing transforms of updating entities. It's job is now to hold onto static geometry data, and entity references for loading, but it still handles filtering cells.
Lesson 3:
If you plan on threading... implement it early.
First... I was lucky on this. I waited on this a bit before implementing threading.
I did not actually know a lot about threading. I actually thought you could only have as many threads as what your processor could process. Turns out you can have a thousand if you want... which makes life easier. When it came time to thread, (because I recently had a class on this in college) this constituted on a rewrite of a lot of code. But one of the more interesting bits I have noticed... was that threading can actually have a HUGE impact on architecture.
Because of this... a number of things previously written... just suddenly became redundant. And once you have a system... it can be fairly easy to thread anything.
But because I learned that you can have as many threads as you wanted... I made sure to spawn a few extra threads with jobs that made no sense to be in a task system. Sound System, having this on a job thread can actually severely stall things. Resource loading, this honestly... depends. I believe this shouldn't be jobified. To many items being loaded at once could slow things down. But I could just jobify only small item loading, and let the Resource loading thread handle large objects.
Tip 1:
Object Oriented programming is still a must. Do not get carried away with Data Oriented design. The idea is to design around your data for efficient processing or rapid production. Not turn everything into a programmer hell made with arrays, structs, rewriting code, going back to C days (with the delusion that it's faster than C++)
Tip 2:
It's ok to make game engines. It's a learning process. Just be aware that you shouldn't try to make Unity or Unreal. And there are a few situations that it does make sense to make a game engine specific to the game. Modding support, easy to do with Unreal... but requires opening up some source code to the public, can't do it with Unity. Engine features, Unity is a pain in the arse to implement new features, Unreal's codebase is too massive to effectively add something without screwing up the engine.
I'm personally experimenting with turning shadow volumes into actual physical meshes for a game mechanic.
Tip 3:
Avoid making Open World games without a good reason for it to be an open world. First, let me define the term open world. A world without a set path that allows the player to travel from point a to point b in any way he chooses. These things are freaking monsters. Programming it is the easy part, making something good out of it will make hell seem like a nice place to spend an eternity in. And making a game "Open World" could easily turn a good game, into a boring one.
Why does it work for The Elder Scrolls? It's not because it's an RPG. It's because the company that made it spent a lot of time and effort into every single detail. Paced everything so carefully, that the world would not be over saturated with enemies, missions, locations, etc. But there is a LOT to do.
Why does it work for Minecraft? It's a game of creativity, where you destroy portions of the world to build. The procedural generation of the world is also what makes it so interesting.
Why does it work for Farcry? Farcry honestly does not have much to do. It simply works with the theme of the game.
Why does it not work so well for Shelter 2? The first Shelter is a game where you play as a mother badger leading her... cubs(?) through a journey to safety and through life. The game is one big train-track, but it made you feel incredibly emotional. Each level had some sort of challenge to overcome, and failure could easily mean the death of your cubs. Shelter 2, you play as a mother lynx. And while the concept is the same... the open world just felt boring. And the trials were almost none existent.
Why does it work poorly for Elder Scrolls Online? This is what most aspiring open world games may become... a running simulator. Elder Scrolls Online had a massive world... but there was barely anything to do in that world for miles on end. Barely anything interesting to see for some time. And there was no form of fast travel system. Getting a horse was over 15000 septim... and you usually only get 1-3 septim from a boss fight of any level.
Why does a lot of open world indie games fail? The reason above, and much much worse. It's an open world for the sake of being an open world. You have the freedom to wander, but you will wonder if there is anything to do.