This is replaced by swfobjct.
Jul 11 2008

Improving Your Coding Habits With Design Patterns

Fractal Pattern

“Design patterns” is probably the most used software engineering buzz word out there. This article will give a basic introduction into the concept of software design patterns, describe some of the design patterns that are most useful in game development, and point out some of the challenges when using design patterns.

What is a Design Pattern?

In game development software engineering, the challenge is generally in coming up with reasonably fast real-time algorithms. Outside of those situations, we tend to either be writing the exact same code we did for a previous project, or we are applying a solution that worked for us in the past. Sure, the particulars of this game might have some new approach to physics, or the level format might be different, but 95% of the code on a new project just needs to be written, it doesn’t present us with any new development challenges as they are solved problems.

In the same way that in our head we have a set of game development tools for solving particular problems, it turns out that software engineering has a core set of fundamental solutions that can be applied to a wide range of domain specific problems. These solutions act like blue prints when we’re building a house. If we need to move people in and out of a room, we put a door in the wall. If we need a place to store a car, we build a garage outside. If we need to have two floors, we put in a set of stairs or an elevator. Design Patterns are solutions to generic problems that exist in all disciplines of software engineering.

Examples of Common Problems in Game Development

Here are some common problems we might encounter while working on a game:

  1. We have a large collection of objects that are using up a lot of memory, even though many of their parameters are shared. Example: Entities and Graphical Effects.

  2. We want to change the run-time behavior of a class. Example: Random Level Generation or data driven Entities.

  3. We have two completely independent sub-systems that need to communicate, but we want to avoid creating a dependency between them. Example: A networking layer and our game engine.

  4. We want to use middleware for one of our components, but we anticipate the need to change to a different vendor in the future. Example: A physics engine or a 3d rendering engine.

  5. We want to track a user’s actions and give them an opportunity to undo them. Example: Level editor or turn based strategy game.

Design Patterns To The Rescue

As it turns out, in these situations, and more, having an understanding of design patterns immediately provides us with an obvious solution. Not only that, but if we were working on a team environment, a programmer might approach me about a particular challenge he was facing, and I could just say “Use the Command pattern” and he’d immediately understand exactly what solution would best work for that problem, without me needing to explain any of the intricate details of the implementation.

Let’s create a case study for each of the issues discussed above.

Issue: We have a large collection of objects that are using up a lot of memory, even though many of their parameters are shared.

Pattern: Data pattern. You’re probably already familiar with this design pattern as it is used in the Actionscript library already in the form of Bitmap and BitmapDatas. The core solution is that the “heavy” data that is shared between the objects is stored in a Data version of the class that each instance then refers to. My Scale9 implementation used this same Data pattern because all the bitmap information we were storing uses up a lot of memory. This bitmap data is very memory intensive and we don’t want to be storing it with each instance of that bitmap. This doesn’t just help optimize memory, it can help optimize load/saving and networking as well. If we have 100 identical bombs in a scene, we don’t need to store the collision information, graphics, name and sound effects on each instance. We can instead create a single EntityData class that holds all this “heavy” data, and then create 100 BombEntity classes that have a reference to the EntityData class. The key to remember with the Data pattern is that the instances hold all the instance specific information, like the x/y in the case of a Bitmap, or the Bomb’s time before it explodes, but all the shared and immutable information gets put into a Data class at a fraction of the memory foot print.

Issue: We want to change the run-time behavior of a class.

Pattern: The Strategy pattern gives us a framework for making a class behave differently at run time. To use a Strategy pattern, instead of creating our algorithm inside the class that was executing it, that class would delegate the algorithm execution to a concrete subclass that was instantiated at run-time. As an example, our physics engine might have several culling routines optimized for different scenarios. It might use a quad space culler for large open spaces, or a sphere tree culler for scenes that vary greatly in the Z-Axis. Our physics engine could monitor the genetic make up of the scene and delegate the culling routine to a runtime instantiation of either the QuadSpaceCuller or the SphereTreeCuller depending on the layout of objects. When we get to random level generation, we will be using this design pattern extensively.

Issue: We have two completely independent sub-systems that need to communicate, but we want to avoid creating a dependency between them.

Pattern: The Factory pattern almost always is a part of the solution to this particular problem. In this particular case, we generally will know something about the runtime expectations of the system that we want to interact with. The subsystem creates an interface for an object that will act as the communications hub to the other system. The other system will then ensure that it defines an object that implements this interface and then hands a reference of this object to the other system to process. This comes up a lot when talking about run-time skinning. The UI routines will pass around a reference to an ISkin that knows how to do things like createButtonUpState and getBackgroundColor and that sort of thing, but it doesn’t know anything about the colors, or paddings, or any of that nonsense. In your application code, you define a class that implements ISkin and provides concrete definitions for the buttons and other skin related specifics, hands that to your UI drawing classes, and now you can completely reskin your application (at run time, even), just by changing the type of ISkin you pass the UI subsystem.

Issue: We want to use middleware for one of our components, but we anticipate the need to change to a different vendor in the future.

Pattern: Use the Wrapper pattern. Basically you create a layer of code between your application and the 3rd party library. If you decide to swap out the 3rd party’s library for another later on, you just need to rewire the intermediate layer of code, but your application code does not need to be touched. In practice, this particular pattern tends to be somewhat of a mixed bag. I typically only do it when I know that the subsystem will be changing, because it increases the amount of code you need to write but it doesn’t generate any new functionality in the system. The Adaptor pattern is a related pattern which takes the Wrapper one step further by essentially establishing a new API that might provide additional functionality. Wrap with caution!

Issue: We want to track a user’s actions and give them an opportunity to undo them.

Pattern: There are some people that believe the Command pattern should be central to any engine (game or not). The core concept of the Command pattern is that each action is a subclass of a Command class which requires a “do” method and that if it is “undo”able, it implements an undo method. The application keeps a queue of these Command subclasses that have been executed (memory permitting), and can do, undo and redo as appropriate, down the queue, rolling back or redoing the user actions as desired. In a strategy game, if the user moves a unit but then decides he wants to undo the move, clicking the undo button would cause the engine to grab a reference to the MoveCommand that was just executed and is now in the executed queue, call “undo” on it, which would essentially restore the unit to its original position.

References and Some Warnings

If Design Patterns are new to you, chances are they’ll look like a lot of over-engineered crap when you first learn about them (I know they did to me). The truth is that Design Patterns can help make your code easier to maintain, more readable and easier to share.

Design Patterns are just tools that have been developed to solve problems that crop up among many seemingly different applications. I encourage you to read, read and reread about them. At first they will seem alien and you might think a particular pattern will help you solve a problem, when it will just complicate the core issue. Like anything else, you need to learn when to apply them, and when to go a different direction. They won’t solve all your problems, and they certainly can hamper development when applied inappropriately.

For additional reading, I recommend you google, consult Wikipedia’s entry, or pick up a book on the subject (I can recommend only the Gang of Four book - I’ve not read any others).

Experiment with design patterns, and understand when and how they are used so that you can add them to your effortless development arsenal. Make sure you understand the problem and the pattern that solves it completely, before you apply it, as the misapplication of a design pattern (especially the dangerous ones like the Singleton pattern), can actually hurt your code more than it can help it. Also, investigate and be aware of “anti-patterns”. These are apparent solutions that will actually damage maintainability.

This was a lot to cover, and it was covered quickly so I apologize if I lost you anywhere! Please let me know if you’d like me to explore any particular facet in more detail.

Related Posts:

Comments

No comments yet.

Leave a comment

You must be logged in to post a comment.