Tips and Parables From the Gamedev Members

Started by
7 comments, last by Zipster 8 years, 6 months ago

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.

Advertisement

Honestly, I think this would have worked as an article, but that is just my personal opinion.

I'm not sure how to make an article, but I was also hoping that other's would donate some time.

As post or thread it's a bit useless as very soon it's non-findable in the sea of other topics of the past.

I don't have lessons, but I do have tactics in programming, which can be expressed in lessons if so desired ;)

My main tactic is "don't plan for the far future". It is relatively easy to aim at a generic piece of code that is extendable in every direction for all needs you have in the future. While this is fine in itself, you likely don't need 7+ of the 10 directions, at least not until a long time in the future.
In the mean time, you're dragging all these possible extension code through all changes, and have to re-implement them if you break them by some other changes. All work that does not pay off immediately.

One risk is that you find you missed some crucial bit before you reach the future where you get to use your extensions, forcing you to move to a different structure/design. You'll have to reconsider all extensions as well, and also make new extension for the new design.
Another risk is, by the time you arrive in the future, you find you don't actually need the extension at all, ideas and circumstances have changed. You need a different 11th extension instead!


My tactic is to not implement anything I don't need today. I build a nice clean solution for my problem today, and will see tomorrow how to do the change of tomorrow.
In practice, "today" and "tomorrow" are a bit more blurry, and I do build hooks today that I surely need tomorrow, but not many.

The 1010 Command Statements

I, the Algorithm, am the Lord thy Programming God. Thou shalt never have any programming languages before me.
Thou Shalt Remember The Beer and Pizza Day.
Thou Shalt Honor Thy IDE and Debugger.
Thou Shalt Not Ignore Warnings.
Thou Shalt Not Not Comment.
Thou Shalt Share Code.
Thou Shalt Not Ask For Which Google Can Answer.
Thou Shalt Not Bear False Performance Against Thy Neighbor's Programming Language.
Thou Shalt Optimize Algorithm Before Code.
Thou Shalt Understand The Problem Before Coding The Solution.

Beginner in Game Development?  Read here. And read here.

 

1. Get a spec first.
2. Implement the simplest thing that can possibly work.
3. Documentation may lie to you.
4. Write unit tests or never let anyone else touch your code.
5. The maximum number of people on a team without interpersonal conflicts is 1.

Find a unique space for your game. Game is full of crowded spaces (genres)

And its tough to stand out in the crowd

At the design stage ask yourself: what would make my game unique? (not just better graphics, nor rehash of some old stuff)

Be creative in your game play design.

Its cruel to spend several mental-energy-months in development only to find that you don't do too well in sales because you are just one of several hundreds (or thousands) that look the same

can't help being grumpy...

Just need to let some steam out, so my head doesn't explode...

Moving from For Beginners. From the rules: "This forum is for beginners to ask questions, not for posting educational material aimed at beginners".

Here's some advice based on my experience throughout the years in no particular order:

1) Perfect is the enemy of good. Get it done and working first, then iterate later if there's time and/or necessity. Keep in mind that performance isn't always the first priority.

2) As a corollary to (1), try to come up with more than one solution to a given problem, in case one turns out to be a dead-end or you unexpectedly have to change course. I personally try to have at least three; one that represents the "ideal" solution I'd use if I had infinite time and resources, one that represents the fastest possible solution to get things functional if I was crunching and had to be finished yesterday, and one that's some trade-off between the two. The final solution is usually somewhere between two of the three, and you've established multiple possible starting points for future iteration.

2) Always keep your customers' needs in mind. That doesn't just mean end-users; it includes everyone on your team, even your future self. If that means physically collocating yourself so you can watch a content creator work for a day or two, do it.

3) Demand full specs for every feature. Get clarification for any circumstances or edge cases not covered. Have this information readily accessible to everyone on the team. Miscommunication can be very expensive. Ideally, you should make sure that the information is in a format that makes verification as easy as possible for the testing team (although an addendum with testing procedures also works just fine). Go so far as to follow this testing procedure yourself so you can catch any obvious and low-hanging fruit and eliminate the overhead of sending it down the line.

4) Everything should be owned by someone. Whether that be a feature, a process, a document, a system, a tool, the creative vision of the entire game, etc., there must be someone who has a vision of where it's going and has the authority to make decisions. This sometimes takes the form of one person per discipline, i.e. one engineer, one designer, and one artist, each making decisions on behalf of their respective department. Design by committee can and will grind development to a halt. Vision and decisiveness keeps things moving smoothly.

5) Know where your development time is going. You may have a very robust process in place for scheduling tasks and determining time estimates, but if you don't track how much time it takes to fix bugs, for instance (or allow non-critical bugs to become known issues and backlogged for later once you've spent too much time on them), then you really don't have a full picture of your time costs and it's easier to fall behind.

6) Track everything (partly a corollary to (5)). Get you and your team some decent project tracking software (at work we use JIRA) and don't be afraid to use it. You'll have quantitative measurements of your team's progress and you'll always know who's working on what. Plus, if someone has some extra time between builds or reaches a stopping point, they might find an issue they can knock out a fix for real quick.

7) Don't take unnecessary shortcuts based on guesstimated time savings. Make an honest assessment of how much longer the correct approach will take versus the fast approach, including future maintenance effort and bug risk. Ask yourself if that extra few hours is really worth it. More often than not, it isn't, and the extra time to do something right saves you and your team lots of headaches down the line.

8) Be wary of data that begins to feel too much like code, unless it's actually supposed to be code (like scripts). You tend to see this kind of data with large game-logic systems that "do all the things!", where multiple games over the years have added their one-off features to the system but tried to make them general-purpose under the assumption that it would be used again, but often never is. At some point, the system grows so large and complex that the behavior of the system is defined more by the data than the code, and it becomes a nightmare to work with for everyone involved.

9) Not everything has to be built to last forever. Acknowledge that a lot of game code is throwaway and can't/shouldn't be used again for another project. Over time, your experience will tell you which code and systems these are. You can always go back and get old code if you realize a need for it in the future.

10) Be as consistent as possible. Don't use different solutions to multiple similar problems. Even if you do something wrong, and you repeat that error in multiple places, it's still easier to fix a consistent mistake than an inconsistent one. Human beings are built to recognize patterns, after all, no matter what they are.

This topic is closed to new replies.

Advertisement