Do’s And Dont’s In Game Code

There are a few basic things I’ve learned while doing this project. The main one is that things you can get away with in web programming (and even most mobile apps) that just don’t cut it in game programming where every millisecond matters. I’ve touched on one or two here but I thought I’d go into a little more detail.

Most of the examples are illustrated using C++ in the Cocos2d-X framework but they should be pretty universal.

Don’t Query Objects

By this I mean asking for child objects, especially for children with a particular characteristic. Observe the following:

CCArray *enemies = this->enemyLayer->getChildWithTag(TAG);

It seems simple but it’s actually costly because this is what’s happening beneath it:

if(m_pChildren && m_pChildren->count() > 0) {
	CCObject* child;
	CCARRAY_FOREACH(m_pChildren, child) {
		CCNode* pNode = (CCNode*) child;
		if(pNode && pNode->m_nTag == aTag) return pNode;
	}
}
return NULL;

This is what’s called a n+1 problem because, for each child, you potentially add to n until that object can be found and returned. It gets even worse if you write your own method called, say, getChildrenWithTag that loops through everything and places every matching object into a newly created array and returned. This takes even longer. Especially if you wind up with a lot of child nodes.

Don’t Loop In Your Tick Method

There are some loops that are unavoidable, such as updating the physics of each body as per the simulation’s results. Of course, this can be mitigated some by using continue on bodies whose properties won’t change (like static bodies).

What I mean is looping through game objects to modify them right then. Let me give you an example:

CCArray *enemies = this->enemyLayer->getChildren();
CCARRAY_FOREACH(enemies, object) {
	CCNode *enemy = (CCNode*)object;
	float distance = ccpDistance(this->hero->getPosition(), enemy->getPosition());
		
	Character *character = NULL;
	if (Soldier::isSoldierSprite(enemy)) {
		character = (Soldier*)enemy;
	} else if (Walker::isWalkerSprite(enemy)) {
		character = (Walker*)enemy;
	}
	if (character && distance < 500) {
		float deltaY = this->hero->getPosition().y - enemy->getPosition().y;			
		if (deltaY > -100 && deltaY < 100) {
			character->attack();
		}
	} else if (character) {
		character->walk();
	}
}

This is particularly bad. First, as mentioned above, it’s an n+1 issue. Second, it’s casting each object into another but, in C++ checking the cast is really expensive. Third, it’s doing for potentially every object what each object should ostensibly do for itself. Or, not do for itself, as the case may be.

If each object that needed this logic had it in its own tick method, it would solve all of these problems. First, only objects that needed tick updates would request them. Second, it already knows what type of object it is. Third, it could fast exist if the criteria isn’t met.

Do Keep Local Weak References

Object-oriented design is a lot like database normalization. It’s a standard you want to aspire to but sometimes you just have to break the rules. For games, this means keeping a bunch of weak references stored in instance variables that could be accessed in other ways. Why? See the top two don’ts.

If every time in the tick method you’re querying something, think about querying it once and storing it in an instance variable as a weak reference. Thus, you avoid both queries and loops.

The only trick with this is knowing when your references are deallocated so that bad address issues don’t crop up.

Don’t Use Interfaces or Generic Parent Classes

I’ll dive into what should be used with the next point but don’t use interfaces or generic parents. Firstly, if objects share a parent, there’s probably a reason why, they probably share a good deal of common traits. Interfaces though, only share a defined set of methods. Generic parent classes can define instance variables and shelled out methods but, in some cases, this is actually a problem.

Consider the following in C++:

class A {
public:
   virtual void setFoo(bool f) { this->foo = f; };
   virtual void getFoo() { return this->foo; };

protected:
   bool foo;
};

class B : A {
public:
   virtual void setFoo(bool f) { 
      this->foo = f;
      doSomethingElse();
   };
   void doSomethingElse();
};

Looks good in theory. But in C++, there’s a problem. Let’s say we know that object is class A or a child class but we don’t know if they’re of class A or B so we cast to the parent:

A *a = (A*)object;
a->setFoo(true);

Even in this case, if it’s actually class B, it still calls the parent’s setFoo and not the actual object’s. Instead, what would have to happen to ensure it’s type safe would be:

A *a;
if (dynamic_cast(object)) {
   a = (B*)object;
} else {
   a = (A*)object;
}
a->setFoo(true);

The problem here is that dynamic_cast is extremely costly. Especially if dealing with a lot of objects and doing a lot of casting. This is bad, bad, bad. I can’t say it enough. So…

Do Use Abstract Classes

Abstract classes are almost like what I was saying not to do above except it’s just a little bit different. Consider the following:

class A {
public:
   virtual void setFoo(bool f) { 
      this->foo = f;
      doSomethingElse();
   };
   virtual void getFoo() { return this->foo; };
   virtual void doSomethingElse() = 0;

protected:
   bool foo;
};

class B : A {
public:
   virtual void doSomethingElse() {
      /* Do something */
   };
}

Now, what I can do is cast to the parent and be certain that the child’s doSomething method will actually be called. This way, code can be shared but individual methods for handling the results can be written per class. It also eliminates any need for casting because, if everything in the array is a child of class A, then by calling the non-abstract methods, you know the correct abstract ones will be called.

Conclusion

It’s easy to be strict about object-oriented design when writing server code. The cost to accessing data the way the pattern describes is relatively free of cost on the powerful hardware that runs it. It’s not also trying to push a frame to the screen sixty times in a single second, and that’s really where this tuning becomes important. Anyone who watched the Apple announcement the other day knows that even mobile chips are becoming amazingly powerful. It’s trying to run all of the game code in less than 1/60th of a second that’s the challenge.

Hopefully a couple of the things I’ve outlined here will help you tune your game code.