Messaging
Lately, I've added in a Messaging system. This was primarily due to me needing to communicate between GameObjects. For component-component communication on the same object, I'm just referencing the components directly. I decided to go messaging for intra-object communication because I really didn't want to start tightly coupling other objects to each other. My messaging implementation is different to Unity's. I'll talk about that here.
When a component wants to hook a message, it must call "RegisterMessageHandler" on the GameObject it's attached to.
This has the following prototype:
void RegisterMessageHandler(string messageType, Action handler);
As you can see, it takes a handler function as a parameter. The GameObject itself then listens to any messages hooked by the components and routes them to each component when received.
Each message must implement the interface IGameObjectMessage, which has a single required property - "MessageName". I'm tempted to remove this requirement entirely and make it more like how Components work - requiring the messages to be bound by the message Type. This would give me something such as:
void RegisterMessageHandler(Action);
This feels more elegant. However, this may create an issue if the components are destroyed and don't unhook the messages first. I'm contemplating using C#'s event system and following a Weak Event Pattern. Thoughts are appreciated.
There's a few ways to send a message to other objects. I'll list them out:
- SendMessage - Sends a message to a particular GameObject
- BroadcastMessage - Sends a message to a GameObject and all its children
- SendMessageUpwards - Sends the message to the GameObject's parent
- BroadcastMessageUpwards - Sends the message to the GameObject's parent and it's ancestors.
One thing I'm debating with the Broadcast options is whether they should cascade all the way up and/or down the tree to children of children, etc.
I've also got a GameObjectGraph. Basically, a way of storing and accessing my "root" objects. I'm currently figuring out a nice way of exposing this to the world other than using a Singleton. It's likely to appear as a property on the GameObject in some way.
I need this because it acts as a sort of scene graph. It is the root object of the "world" and is called when I want to both Update() and Render() my scene. It also acts a place to fire global messages to notify all GameObjects in the world about something. At this level, I have:
- BroadcastMessage - sends a message immediately to all objects, starting at the root and cascading downwards
- QueueBroadcastMessage - drops a message onto a queue to be sent the next tick, before Update() is called
The message handlers themselves have the signature:[code=:0]void SomeHandler(IGameObject sender, IGameObjectMessage message);
The first parameter is the GameObject which sent the message. The astute of you will realise that it's actually a Component which sends messages and not the GameObject. It's debatable whether the first parameter should reference the component instead of the GameObject, as this would open up component-component direct communications.
Tagging
I've added a simple Tag collection to each object. These are text labels which can be searched on. A GameObject can have as many as needed. I currently use them for Linq queries over child objects:
[code=:0]foreach(var child in this.Children.Where( (o) => o.HasTag("sometag") ){ // do something with tagged object}
I'm contemplating extending this system to become a Triple Tag, basically: namespace:predicate=value. This would make things nice and flexible, especially when combined with Linq queries. For example, if I had two sides in the game and I wanted to find all units of type "tank", I could write a Linq query such as:
[code=:0]foreach(var tank in this.Children.Where( (o) => o.HasTagNamespace("SideA") && o.HasTagPredicate("UnitType") && o.HasTagValue("Tank")){}
That's about it for now. There's a few current niggles and areas I need to start looking into. These are:
- Object creation via cloning (allows me to have Prefabs)
- Object destruction (queue to kill next frame)
- Object serialisation to JSON/BSON (allows me to save/load object definitions from files)
- General object management and tracking.
The current systemis nice enough, I guess - it has quite a few rough edges and things I'm changing as I go on - I already plan on reusing this part of the system in other projects. I'm also contemplating open sourcing it on GitHub, I'm not sure though...
Note, combining the "HasTagX, Y and Z" method calls as you have shown doesn't do what you would want. You don't want a "has SOMETHING with namespace X" and "SOMETHING with predicate Y" probably. would want a tag row where all 3 are true for the same row. So you need overloads that take multiple parameters, like "HasTagPredicate(namespace, predicate)" for a 2 parter and "HasTagValue(namespace, predicate, value)" for a 3 parter.
good luck.