Quantcast
Channel: Little Chicken Game Company Blog
Viewing all articles
Browse latest Browse all 10

Delegation in game code structures

$
0
0

written by: Joris van Leeuwen

Introduction

We’re currently working on a game in a production team of 3 artists and 3 programmers. During the development of the game’s prototype we heavily used delegation in the main code structure. This resulted in such an easy to read codebase that we’ve decided to use delegation in our main code structure for production as well. I decided to make this blog post to share some of our experiences using this technique!

Knowing this technique can help you to make code-bases that are able to rapidly respond to game design changes.

This article is meant for programmers that are interested in pros and cons of delegation. You can expect code examples in C# and solutions for hierarchies in code. Please note that this is not a “you should do it this way” article. Just my thoughts on the subject that I’d like to share!

Coding for redesigns

During the making of a game you always learn how to make it better, this is why you should iterate. Try something out, learn from it and make improvements accordingly. I think Game Programmers should therefore always prepare for redesigns in game mechanics. In my opinion, good game code embraces redesigns by making it very easy to change or rewrite parts of the code structure later on.

Hierarchy and encapsulation

To easily change or rewrite parts of the game you make distinctions between functionality. This is done by creating classes and putting them in a hierarchy. Each class should know as little as possible about other classes, as concealing functionality makes it easier to read code because you don’t have to travel around to other classes for it to make sense. This is called encapsulation.

By creating a hierarchy you effectively distribute the responsibility of functionality to separate classes. Parents manage their children, who serve as parents for their children and so on. Parents should only be able to access what they need from their children (I don’t know how you do it, just get it done!). Children shouldn’t know anything about their parent. This way children can easily be attached to another parent without breaking anything. If there is a redesign in game mechanics, the programmers can decide from what point in the hierarchy the code should be altered or rewritten and the rest of the code can stay as it is.

Communicating upward in a hierarchy

How is it possible to communicate upwards in a hierarchy? Or more explicitly, how can a child say something to its parent if the child may not know about its existence? Let’s say the parent of a child needs to know when the child is hungry so it can enable a pizza alarm. How does the parent know?

Well the parent could, for example, ask the child if it’s hungry every second of the day. If the child would be hungry the parent could enable the pizza alarm. But this does sound kind of strange.. it feels inefficient for the parent to ask his child whether he’s hungry or not every second of the day.

Another option would be for the child to tell its parent to enable the pizza alarm when it feels like it’s hungry. But that would break encapsulation. The child would be able to know the parent is able to enable the pizza alarm. And in addition to that, the child would even know that a pizza alarm exists! What if later in the project the Game Designers want to change the pizza alarm to a mother that actually cooks dinner? Multiple classes would have to be changed just for one change in game mechanics.

What would be much more efficient, is for the parent to tell his child to notify him when he’s hungry, so the parent can react accordingly. The parent would only have to tell this once, and the child doesn’t have to know about what his parent is going to do after it notifies the parent. This is called delegation.

Delegation is a way to communicate upwards in the hierarchy tree, without losing the ability to split context and functionality. It enables parents to “listen” to their children and perform actions when they are triggered.

Syntax

To show how a delegate system looks like in C#, lets start with using the callbacks from the Parent’s perspective.

class Parent {
  PizzaAlarm pizzaAlarm;
  Child child;

  public Parent() {
     pizzaAlarm = new PizzaAlarm();
     child = new Child();

     //Make the child trigger the 
     //OnChildHungryHandler method when it gets hungry
     child.OnHungry += OnChildHungryHandler;
  }

  void OnChildHungryHandler() {
     pizzaAlarm.Enable();
  }
}

The child has a callback that is named OnHungry. In this example the method OnChildHungry is assigned to the callback. When the child internally decides to become hungry it calls the OnHungry callback, triggering the OnChildHungryHandler callback-handler in the parent. Notice the +=? Yes, it is possible to assign multiple handlers to one callback!

The beauty about this is that the parent doesn’t need to know how the child gets hungry. It only needs to know when it does so it can respond to it. The child also doesn’t have to explicitly tell the parent to enable the alarm, enabling us to encapsulate that functionality within the parent class. This way nobody has to search around other classes for the parent class to make sense, making the parent class much easier to read.

So what does this system look like in the child class?

class Child {

  //Define the delegate type
  public delegate void HungryDelegate();

  //Declare the callback
  public HungryDelegate OnHungry;

  void Update(){
     if (/* insert logic here */){

        //Check if the callback has a handler
        if (OnHungry != null){
           //Perform the callback
           OnHungry();
        }
     }
  }
}

First a delegate type is defined. This is done to enforce the callback-handler to have a certain set of parameters and returning type. In this case only methods with a void returning type and no parameters can serve as a callback-handler.

After defining the delegate type it can be used to declare the callback OnHungry. This is a public field so it can be accessed by the Parent. From within the Child class the OnHungry callback can be treated as if it was a method. Calling a callback does need a callback-handler though, else it would be a null value. When it’s uncertain whether the Parent has assigned a callback-handler a callback must always be null checked before it’s called.

The child has no idea who is using its callback, which is great! If there would be a redesign in the game mechanics, it’s now easy to detach the child from its original parent and attach it to another without having to rewrite anything in the child!

Naming

We had some issues with the naming conventions of our delegates and renamed everything a few times. In essence, there are three types that will need a name. These are the naming conventions that we’re using:

  • Delegate Type: HungryDelegate
  • Callback: OnHungry
  • Handler: OnHungryHandler

The reason why we put “Delegate” as an postfix to our delegate types is because just “Hungry” misses context. The handler still feels a bit long, but when removing the “Handler” it will have the same naming as the callback, creating issues when defining a handler within the class that has the callback itself.

Don’t name a callback something like “OnAlarmPizzaNow”. To take full advantage of using delegation in code it is essential that the naming of a callback does not describe what happens as a result. This is to maintain valid context after redesigns. OnHungry doesn’t say anything about the actions that follow and thus is more reusable after design changes.

Our issues with delegation

There are some downsides to the way that we’re using delegation. One downside is that it can be a wall of text that arises in a class with a lot of different children when assigning all the callbacks. We use newlines between different instances when assigning the handlers but it’s still a big list.

Another issue is when the parent of a parent of a parent needs to be told that something is happening. This would need a big stack of callbacks before it actually triggers the action that should follow. This issue feels awkward but we haven’t found a solution that maintains the distributed responsibility of the parents in the hierarchy. You could suggest to use an event messenger system that skips a few layers of the hierarchy, but using event messages for actual game mechanics always results in spaghetti code in my experience.

Recursive loops are always a thread, but in delegation it is sometimes harder to detect. This happens when a parent listens to a child, and in its handler performs an action on the child which makes the child perform the callback again and keeps on going.

There’s also no clear stack-trace of what is happening. When trying to debug the handler of a callback the entrypoint in the stack-trace is the handler itself, making it hard to trace back where a problem is coming from.

The last big issue we’ve experienced is that you can easily forget to remove the callback-handlers without noticing. If this is not done, the system would never be able to wipe the child’s memory because the parent is still referencing it, resulting in errors that are pretty hard to trace back. This is scary in delegation and should be handled with care. All delegate callback-handlers that are attached to another object should be removed at some point. This responsibility can be given to either the parent to remove the handlers it has given to the child by using -=, or to the child itself to set its callbacks to null when it gets destroyed.

Conclusion

Delegation proved to be a really cool technique which makes implementing redesigns on game mechanics easy without having to remove functionality because it is too tightly coupled.

In the end it always depends on what kind of project you’re working on. How much the game design is already set in stone, the deadline and the size of the team should all have a big impact on your coding strategy. In our case we’re making a game in a team with programmers coming in and out and a project that can constantly get redesigns in game mechanics. We like using delegation and I’d very much recommend experimenting with it when setting up a coding strategy for a comparable project!

Actions

Something that is not discussed here but is very useful when using delegation are called actions. Find more about actions here!

38,918 total views, 19 views today


Viewing all articles
Browse latest Browse all 10

Trending Articles