How to communicate between game objects.
Over on Richard “PhotonStorm” Davey’s blog he proposed a simple way to communicate between objects in your game using a “Registry” a class with static variables storing all the major systems of your game. So, for example if you wanted to create a spray of blood when an enemy is hit, in the enemy’s hit() function you would include the line Registry.fx.sprayBlood(x, y) and the FX object stored in the Registry.fx variable would create the blood spray and handle updating etc.
In the comments on Richard’s post, I pointed out that this isn’t a very object-oriented approach: these are basically global variables by a different name. I have used a similar approach myself on quite a few games projects, and overall it works well and is a quick way of getting things done. I have, however, encountered two problems with it.
Firstly, you can end up with all you code in one huge blob or “god class”. For example, if the FX class is responsible for handling any possible visual effect you would want to create, it could end up getting very big. But, other than academic notions of “good” and “bad” code, there’s nothing especially wrong with having big classes. They may be a bit harder to maintain and reuse, but nothing to worry about too much.
Secondly, using static/global variables as a communication method means you can be limited to having only one “game” in a single project. Now in most scenarios this wouldn’t be a problem, but it’s normally best not to assume you will only ever have one of a particular class of object. I’ve worked on several Flash projects that were collections of minigames in a single swf. Where I was relying on static variables in some of my base-classes and utility classes, I started to see clashes between the different games.
Ok so how do I handle the same problems? Well, say an enemy needs a reference to the player in order to chase after them. Rather than looking up Registry.player, I would just have a “player” variable in my enemy class and I would pass in the value of player when I create the enemy, or once I know the player exists. Or if the enemy needed more than a couple of different references from game in order to work, I would just pass in a reference to the game itself, and let the object access whatever information it needs. As Richard points out, the downside to this approach is that you end up with a lot of references in different places. This isn’t really a problem if you null your references when destroying objects, but it is more work, and you can leak memory if you’re sloppy with it.
Secondly, pretty much everything in my game extends a base Entity class. I have a main Game class that manages all these entities. Game has a method called addEntity(entity:Entity) which adds any new entities I create to the array of entities. Every frame I loop through all my entities and call their public update() function (to pause the game, I just don’t run this loop).
So say I have a SpaceShip object that creates a Bullet object every time it fires. How would I let the game know? I wouldn’t call Game.instance.addEntity() because I may have more than one mini-game that extends my base Game class in a single project. Instead, the base Entity class has a function addEntity(entity:Entity) which dispatches an AS3Signal with the entity value. My game is listening for this signal, and directs the entity to its own addEntity function. You could also use an event to achieve the same effect. Now any Entity can itself create more entities and add them to the game.
How would I use this to solve the problem of creating a spray of blood? I would create two Entity classes. One class is BloodDrop which is a single particle of blood. Every update() it moves based on its speed and falls with gravity, and after 30 frames it fades out and destroys itself. The other class is BloodSpray, which lives for a single frame, and in that frame just runs a loop creating 100 instances of BloodDrop, setting their initial position and speed, and calling addEntity(bloodDrop). Now to add my spray of blood in the enemy’s hit() function, I simply create a new BloodSpray, set its position to match the enemies position and call addEntity(bloodSpray). All the entities know how to update and destroy themselves and are pooled for reuse.
This won’t solve all the communication problems in your game, but it will solve a big chunk of them, and now your code is neatly organised into distinct Entity classes which each perform a single purpose.
Happy coding! I think my next conference/user group talk might have to be one about organising game code :)