November 15, 2012

Design Patterns in Game Programming (II): Strategy

Here are some use cases for the strategy design pattern in game programming, all of them leading to a more dynamic game setup, making configuration easier and decoupling the game core from implementation details of various algorithms.

Paint Strategy
In Java2d define an interface to render images with two implementations:
  • simply call Graphics#drawImage()
  • use java.awt.image.BufferStrategy
Win Rule
A game can be played with different rules to actually win the game, like:
  • LAST_MAN - the last survior wins
  • HIGHSCORE - the highest score wins
  • TIMESPAN - highest score wins after playing for a fixed time
  • INFINITY - play forever, useful for development and testing
The game has one current win rule object which is called on a regular basis to find out whether the game should be finished. In case the win rule is fullfilled, a list of the winners is returned.

Path Finding
Call the path finder only through an PathFinder interface to be able to easily swap between different algorithm at runtime like A* or Dijkstra.

Field Of View
Call the field of view calculation through an interface, thus making it easy to change the algorithm at runtime according to defined game performance options or machine specs. Add mock implementations to support testing.

Collision Checking
Isolate collision check algorithms in concrete strategy implementations.

Navigation
Moving a sprite from point a to point b is possible in different ways:
  • straight
  • by following a tile path
  • by following calculated spline nodes

Some Thoughts about (Object Oriented) Game Programming

Some of my favorite best practices for object oriented programming. Actually, they also apply to programming in general, and no matter if it is business software or a game.

Understandability, Maintainability, Readability
This is the most important practice of all, to avoid:
  • cryptic, chaotic, over engineered and under engineered code
  • bad formatting, no formatting rules, weired naming, thousands of lines per method
  • undocumented code, useless comments, wrong comments
  • code blocks without empty lines to let the reader easily grasp its meaning
  • methods broken into smaller parts and thus hurting understandibility
  • names for variables, methods, classes, packages which are not self-documenting
  • names being too short or too long
  • methods hard to use because of too many parameters
  • errors and exceptions not treated properly, not logged, sent into nirvana
  • keeping users totally uninformed about errors, showing useless information ("an error occured") or a pile of technical messages
  • a cumbersome environment with slow, annoying and error prone development cycles

Knowing all kinds of fancy programming techniques, patterns and the like is not enough. One should always think of how an application will behave when being run in an production environment for years and years - possibly long after the creators have moved on.
What kind of problems might occur ? Will they be easy to track down ? Is the given architecture and environment comfortable to use for enhancements, or will new developers just think wtf and create arbitrary bypasses and thus worsen the situation ?

Use simple solutions whenever possible and complex ones if appropriate.
Follow common sense.

Data Encapsulation
Hide your data from the others. Otherwise harm will be done - sooner or lateer.
Do not use the public scope for class members. And do not mechanically add getters and setters for all members, as that breaks encapsulation as well.
Do not store everything as member variable, just for the case that it might be useful some day. Favor method parameters instead and pass data around.

Static is toxic
There are rare use cases for static methods and class members, exceptions are constants in Java or, for example, a collection of stateless mathematical functions.
Stateful methods need to store information somewhere. If they are static, they can only access static members, which in consequence leads either to shared states or to a singleton class design. Both options are seldom practicable and useful.

Static methods are not overridable and as such do not allow to change implementations any time later, neither for the whole code base nor for selected modules. Keeping software flexible and open for changes is very valuable. This is also essential for good frameworks and the infamous game engine.

Remember that any variable marked as public static is nothing but a global variable.
Keep in mind what it means when multiple objects of a class are creatable and some of the class members are static. All objects share those members, they are no longer private to each object, no longer encapsulated into each object. A good way to introduce side effects.

Multithreaded programming will become more and more important in the future. To benefit from multiple threads and/or cores, static variables are to avoided as well. If not, hard-to-find bugs are the probable consequence.

Anybody who is starting to learn programming with an object oriented environment like Java should never use statics. Later on, with more experience, one can decide on when to make exceptions to this rule. Otherwise, you won't learn how to set up, handle and maintain object relations and understand the design of existing libraries.

Static is an exception to the default way, you need to add a keyword to your code. There should always be a good reason to leave the main path.

Modules and Dependencies
Split up the code into classes, packages and modules. Limit responsibilities and tasks of classes and methods (Separation of concerns).
Keep relations between classes and packages as low as possible. Stay away from cyclic dependencies.
Use interfaces for communicating between layers, packages or modules. Do not create dependencies across packages into concrete implementations.
Keep interfaces lean, typically do not add lifecycle methods for creating and setting up objects, as those are mostly implementation dependent and are only called at places where concrete classes are dealed with anyway.

OOP != Inheritance
OOP is much more than class inheritance. Setting up an inheritance hierarchy of seven levels is not the way to go. Favor object composition which can be dynamic at runtime as opposed to static compile time inheritance.
Lego brick programming.

Frameworks, Libraries, Game Engines
This is the ultimate challenge. To create something useful for a broad audience, without knowing each and every use case.
Good frameworks give support for use cases the users choose while at the same time do not force any unwanted features and clutter upon them. Features should be selectable which is only possible with a good dependency management (see above) of the whole work.
Classes must be accessible and extendable (no final modifier, no package scope) without breaking encapsulation by making each and every member protected.
Again, object composition is to be preferred as opposed to force inheritance on library users and only give a limited set of overwritable methods (template method pattern).
All of this is very difficult and takes a lot of experience to achieve.
 
Documentation
Eh, what ? Yes, the part we all love the most. Keep in mind that there is never more than one person (at most) who thinks your code is self-documenting, clear as it can be and just ingenious...

What else
A frequent use of the instanceof operator is an indicator of incomplete class design and thus preventing to benefit from polymorphism.

October 28, 2012

Design Patterns in Game Programming (I): Observer / MVC

Design patterns are well established in business software, but there is no reason to not benefit from using some in game programming. After all, programming is programming.

Design patterns are often either loved or hated but neither position should have its place in professional software development. You shouldn't work with a pattern check list at hand and integrate them without reason. And if you do not like patterns, there are most likely more of them hidden in your very own programming work that you are aware of.
They are also a means of communication and common language and knowledge between developers.

Patterns are not restricted to programming, but can be found in many other domains. Human behaviour often follows established patterns as do created novels, movies and songs.

 
The observer is probably - after the infamous singleton - the best known design pattern in software development.

Here is a simple but useful example.
Let's design a game which has the following objects:
  • a scene to represent the whole game world full of acting characters and the environment
  • several views for the visual part, like the main game view, HUD view for statistics, a debug view 
  • controller for the game logic
Whenever the controller decides to modify the world, like for instance spawn another evil monster or remove it again after being killed by the player, the changes must be reflected by all views.
First, the controller modifies the scene. Then, we make the views passively observe the game world.

public interface IPuppetLifecycleListener {
    void onPuppetAdded(Puppet puppet);
    void onPuppetRemoved(Puppet puppet);
}


Each observer implements this interface and gets called whenever characters are added to or removed from the game scene.
For example, we have two views: a main game view and an additional small radar sub view to let the player see at a glimpse where all the enemies are hiding.

public class TileMapView implements IPuppetLifecycleListener {
    private List spritePuppets;
   
    @Override
    public void onPuppetAdded(Puppet puppet) {       
       SpritePuppet viewPuppet = 

          puppetHouse.createViewPuppet(puppet);
       ...
    }
      
   
    @Override
    public void onPuppetMoved(Puppet puppet) {

       ...
    }

public class OverlayHudView implements IPuppetLifecycleListener {
    private ListViewPuppetCast radarPuppets;
   
    @Override
    public void onPuppetAdded(Puppet puppet) {       
        if (puppet.getActorClass().equals(ActorClasses.BIONIC)) {
            rivalCount++;
            RadarPuppet radarPuppet = new RadarPuppet(puppet);
            radarPuppets.addPuppet(radarPuppet);
        }
    }


    @Override
    public void onPuppetMoved(Puppet puppet) {

       ...
    }
    ....
}


Both view types have their own kinds of visual puppets. For the tile map are sprites created, for the radar view small filled circles are drawn.

The publisher of any puppet event is the game scene which holds a list of observers:

public class GameScene {
    private PuppetCast puppets;

    private List<IPuppetLifecycleListener > puppetLifecycleListeners;
 

    public GameScene () {          
        puppets = new PuppetCast();
        puppetLifecycleListeners = new ArrayList<>();
    }

 

    public void addPuppetLifecycleListener(IPuppetLifecycleListener listener) {
         puppetLifecycleListeners.add(listener);
    }

 

    public Puppet addPuppet(Puppet puppet) {
        puppets.add(puppet);

        
        for (int i = 0; i < puppetLifecycleListeners.size(); i++) {
            IPuppetLifecycleListener listener = puppetLifecycleListeners.get(i);
            listener.onPuppetAdded(puppet);    

        } 
     ....
}

Very simple and very useful. Actually, this is is a model-view-controller implementation for games.
Benefits are:
  • observers can take it easy and only get active when called
  • on each event individual additional actions can happen like playing animations or sounds
  • dependencies are reduced, no global object sets are referenced throughout the code base
  • the internal representation of the game scene is hidden from the views
  • only one clearly defined place to handle each external event
Here, no dependency is pointing from the scene to the views. The scene does not know - and does not need to know - whoever cares about its puppet events. The scene could even run without any views at all in a distributed multiplayer environment.
Thus, the complete scene package can be independent of the view package. Note, that to accomplish that, all observer interfaces are placed into the scene package and not into the view package.

The same technique can be applied for the other way round, for handling UI events created in views:
  • views allow observers to register for UI events
  • view interfaces are placed into the view package and are implemented by controller classes
  • thus, views do not need dependencies to their controllers

Observers can be added once at game start or during level changes.

Class sketch:


MVC roles:
  • model: ClientScene, Puppet, PuppetListener
  • controller: SceneController
  • view: SceneView, TileMapView, SpritePuppet, OverlayHudView, RadarPuppet