Get into GameDev:
cheat-codes for coding interviews
━ Patterns ━
Introduction
Preamble
Patterns refer to common approaches to solve common problems in game development. Patterns have a bit of a bad reputation amongst many senior coders. There is a strong belief that novice coders see implementing patterns as an achievement when in reality, the patterns are only an approach to solving a problem. Implementing a pattern does not necessarily solve the problem. Many novice coders are blindly implementing patterns without thinking through their repercussions. I have met some people very emotional about this so I would suggest you never present a project with respect to what patterns you implemented as it may come across as naïve or perhaps dumb for these reasons. But on the other hand, I have had interviewers literally ask me to state the definition for random patterns (an absurd question BTW) so we are going to cover them anyway. But please always keep in mind what an alternative approach might be and understand the relative tradeoffs between approaches. Please be very careful with the topic on interviews, some people are very sensitive on this topic!
Watch this dissent: Youtube Link
Watch this dissent: Youtube Link
Misc Patterns
- Avoid indentation: Youtube Link (this channel actually has a lot of great videos, I'd suggest giving it a binge!)
- Pass large data structures by references to avoid copying them
- RAII - Resource Acquisition is Initialization - A philosophy pushed forward by C++. When C++ introduced classes (originally called “C with classes”), it tied objects directly to resource lifetime (Object-Oriented Programming or OOP). For example, the smart pointer’s constructor and destructor correspond to the bounds of its memory lifetime. Because the runtime lifetime is determining the runtime memory usage, the memory footprint of programs is less determinant and that is inconvenient for game development. So the pushback on that has been called “Data Oriented Programming” (see Mike Acton) which is a counter-approach investing importance back into explicit control of the memory which is, at times, in conflict with OOP.
- Rust is all RAII with super strong types to proves if there are leaks (no pointer aliasing)
- Jai/Zigg are anti RAII languages
- Read this book: Head First Design Patterns
- Rollback networking (and client-side prediction): Youtube Link
- More details (you should read all 4 parts of this article series, they're great): Article Link
SOLID Patterns
Youtube Tutorial
- Single-responsibility principle: "Every class should have only one responsibility”
- Open–closed principle: "Classes should be open for extension, but closed for modification"
- Liskov substitution principle: "Base class instances should be replaceable with derived classes”
- Interface segregation principle: "Too many interfaces is better than too few”
- Dependency inversion principle: "Depend upon abstractions, not concrete instances."
Game Programming Patterns
These patterns are all taken from Nystrom's book. I linked the chapters for each entry and provided a concise summary beside each.
- Double Buffer: Prevents reader outpacing writer by having 2 buffers, reads Current and writes to Next.
- Game Loop: Decouple the progression of game time from user input and processor speed.
- Update Method: Simulate independent objects by telling each to process one frame at a time.
- Bytecode: A series of custom byte-length instructions provided by designers (in between data and code)
- Subclass Sandbox: Base class defines an abstract (must be overridden) sandbox method and useful, completed, helper-method, operations; subclasses implement the sandbox method using the operations.
- Type Object: Each instance represents a different type of object vs enum ranges that are fixed at runtime.
- Component: Entities are simple containers of components. Each component handles one responsibility.
- Event Queue: decouples the sender from the receiver, both statically and in time. e.g. Play sound.
- Service Locator: A global point of access to a service without coupling users to the concrete class that implements it. Singleton alternative. You can send a null service. Doesn’t affect the design of the service class itself. Decorator services can wrap default functionality (log decorator).
- Data Locality: hot/cold splitting of data for cache coherency, structure of array of components better than array of structures containing each component so that all components of a given type are adjacent.
- Spatial Partition: store data structures organized by their world-space positions.
- Dirty Flag: Avoid unnecessary work by deferring it until the result is needed.
- Object Pool: reusing objects from a fixed pool instead of allocating and freeing them individually.
- Factory: a static method to replace an object’s constructor. They can return pre-existing instances, or subclasses (factory methods are often overridden). Ex. Puppy is-a Dog and overrides Adopt() so that we can call Dog’s Adopt() on a Puppy instance and get that subclass override to return us a Puppy, it may be new, or it could be pre-owned and just lying in an object pool. Factories only return one object. Factories can provide dynamic functions as an alternative to polymorphism, for example my class can have a function pointer Bark which can be set to point to PuppyBark or BigDogBark by the factory.
- Abstract Factory: an object (not just a method) whose sole purpose is object creation, it creates a family of objects (not just one) that depend on each other. It is one higher level of abstraction than a regular factory, and often uses a regular factory. Ex. DogFamilyBuilder will call myPup = Puppy.Adopt() and myDog = Dog.Adopt(), then call myPup.SetMother(myDog) to build a maternal relationship.
- Builder: The builder pattern is used to create complex objects with constituent parts that must be created in the same order or using a specific algorithm. The builder has Step1() Step2() methods and an external class, the director, calls the builder's methods.
- Prototype. Instantiates a new object by copying an existing one. Can create deep or shallow copies.
- Singleton. Ensures that only one object of a particular class is ever created.
- Adapter. The adapter pattern is used to provide a link between two otherwise incompatible types by wrapping the "adaptee" with an adapter class that implements an interface required by the client.
- Bridge. Decouples abstract elements of a class from the implementation details, providing the means to replace the implementation details without modifying the abstraction. Example: Vehicle has an Engine* which could point to GasPoweredEngine OR NuclearPoweredEngine
- "Adapter makes things work after they're designed; Bridge makes them work before they are. [GoF, p219]"
- Composite. A composite object is a class within a tree hierarchy (not strictly an is-a relationship) that has at least one child (composites are non-leaf nodes). Composite pattern says composite nodes must be accessible and utilizable in the same way as leafs. Ex we can ask a shopping bag for its price, and it'll sum the price of all groceries it has.
- Decorator. Extend or alter functionality of objects at run-time by wrapping them in an object of a decorator class. This provides a flexible alternative to using inheritance to modify behavior. (Log Decorator)
- Facade. The facade pattern is used to define a simplified interface to a more complex subsystem.
- Flyweight. Like a pool... Flies are immutable (or thread-safe) singletons that are accessed, but pool objects are mutable instances that are lent and must be returned. A pool object can be accessed by only one client, a fly can be used by many clients. Pools can run out of stock (bottleneck), Flies cannot.
- Proxy. Controls and manages access to the object it is protecting. Ex. Only connect to website if allowed.
- Chain of Responsibility. Handlers implement Chain. When receiving, can handle or pass to next Chain.
- Command. A request object, with parameters, which may then be executed immediately or held for later.
- Interpreter. Uses OOP to represent a language or instructions. Ex. “1 + 2”, “+”, and “1” is-a expression
- Iterator. Traverses a collection of items without the need to understand its structure.
- Mediator. Instead of classes communicating directly, the classes send messages via a mediator object.
- Memento. Stores current state of object, so it can be restored later w/o breaking rules of encapsulation.
- Observer. Observing objects subscribe to be immediately notified of any changes to an observee object.
- State. Alters the behavior of an object to apparently change at run-time based on current state.
- Strategy. Creates an interchangeable family of algorithms from which one is chosen at run-time.
- Template Method. (opposite of subclass sandbox) Base class defines a working normal template method and lets derived classes fill in specific details by overriding protected virtual methods.
- Visitor.Separate a complex set of structured data classes from the functionality of working with their data. Ex. ShoppingCartVisitor defines Visit(Fruit f){ returns cost and barcode }, Fruit defines Accept(Visitor v{v.visit(this);} for(ItemElement item : items){ sum = sum + item.accept(visitor); }
Gang of Four Patterns
These patterns all come from the Gang of Four book, linked below. Nystom copied some of them so you may see some repeats from the previous section. Source: Article Link
Creational Design Patterns
Creational Design Patterns
- Abstract Factory. Allows the creation of objects without specifying their concrete type.
- Builder. Uses to create complex objects.
- Factory Method. Creates objects without specifying the exact class to create.
- Prototype. Creates a new object from an existing object.
- Singleton. Ensures only one instance of an object is created.
- Adapter. Allows for two incompatible classes to work together by wrapping an interface around one of the existing classes.
- Bridge. Decouples an abstraction so two classes can vary independently.
- Composite. Takes a group of objects into a single object.
- Decorator. Allows for an object’s behavior to be extended dynamically at run time.
- Facade. Provides a simple interface to a more complex underlying object.
- Flyweight. Reduces the cost of complex object models.
- Proxy. Provides a placeholder interface to an underlying object to control access, reduce cost, or reduce complexity.
- Chain of Responsibility. Delegates commands to a chain of processing objects.
- Command. Creates objects which encapsulate actions and parameters.
- Interpreter. Implements a specialized language.
- Iterator. Accesses the elements of an object sequentially without exposing its underlying representation.
- Mediator. Allows loose coupling between classes by being the only class that has detailed knowledge of their methods.
- Memento. Provides the ability to restore an object to its previous state.
- Observer. Is a publish/subscribe pattern which allows a number of observer objects to see an event.
- State. Allows an object to alter its behavior when its internal state changes.
- Strategy. Allows one of a family of algorithms to be selected on-the-fly at run-time.
- Template Method. Defines the skeleton of an algorithm as an abstract class, allowing its sub-classes to provide concrete behavior.
- Visitor. Separates an algorithm from an object structure by moving the hierarchy of methods into one object.