Mailbag: Plugins and Messaging
There was quite some useful information in the answers to the last few e-mails I’ve received – or I hope so, at least. That’s for you to decide. Time for another mail bag posting!
Extending Duality
How would I start to implement Duality extensions and plugins for others to use?
My suggestion would be to implement each major new system (ParticleFx, Animation, etc.) in its own plugin project, as seen in my old DynamicLighting experiment: A Core Plugin extending engine functionality and an Editor plugin providing more suitable and comfortable editing capabilities.
Approaching new feature sets like this keeps Duality modular and makes it a lot easier to keep track of class interconnection and dependencies. It also keeps down the overhead for users who do not need all of the features: A game without dynamic lighting doesn’t need to boot up the lighting subsystem or clutter the codebase with lighting-specialized classes.
However, implementing a whole new engine feature in your games local plugin first for prototyping isn’t such a bad idea, since it takes away a lot of the “quality code design” pressure. Extracting a finished product later will leave a designated time window for API cleanup and small polishing tasks that doesn’t interfere with the development of your game.
External Libraries
Can I use external libraries in my Duality plugins?
Yes. Simply reference them in your project and place their .dll files in the Plugins directory as well. When you need to set up and shut down external resources globally, override CorePlugin.InitPlugin and CorePlugin.OnDisposePlugin methods.
Inter-Component communication
How would you implement communication between Components? What about a messaging system?
The first approach that likely comes into your mind when thinking about communication between Components is manually retrieving them, store their reference and directly invoking methods on them. That’s pretty strong coupling between the involved classes. Isn’t that bad?
Direct coupling between specific Components looks like an ugly thing from a code design point of view, but is the easiest solution and even fully sufficient in a lot of cases. However, there should be a clear line: Use direct coupling when both objects are, by logic, directly coupled – like a Car and its Wheels. Don’t use direct coupling when you have some generalized behavior in mind – like messages that anyone could listen for.
The second case is where all those ICmp interfaces emerge. Their purpose is to generalize certain kinds of Components by their behavior and / or communication traits. ICmpUpdatable models updating behavior, while on the other hand, ICmpCollisionListener is a mere communication device.
Getting to a generalized messaging system, I’ll just skip ahead and show some code:
public class GameMessage { /* .. Base class for all game messages .. */ }
public class SpecificGameMessage : GameMessage { /* .. Implementation / Data of a specific game message type .. */ }
public interface ICmpMessageHandler
{
void HandleMessage(Component sender, GameMessage msg);
}
public static class ExtMethodsGameObject
{
public static void BroadcastMessage(this Component sender, GameMessage msg, string targetName = null)
{
IEnumerable<ICmpMessageHandler> receivers = null;
if (targetName != null)
{
var targetObj = Scene.Current.FindGameObjects(targetObject);
receivers = targetObj.GetComponentsDeep<ICmpMessageHandler>();
}
else
{
receivers = Scene.Current.FindComponents<ICmpMessageHandler>();
}
foreach (var rec in receivers)
{
rec.HandleMessage(sender, msg);
}
}
}
Here’s the same thing from a Components point of view:
// Send messages
this.BroadcastMessage(new SomeGameplayMessage(/* ... */), "OnlyToThoseWithThatName");
this.BroadcastMessage(new SomeGameplayMessage(/* ... */)); // to all
// Handle messages
public class YourComponent : Component, ICmpHandlesMessages
{
public void HandleMessage(Component sender, GameMessage msg)
{
SomeGameplayMessage specificMessage = msg as SomeGameplayMessage;
if (specificMessage != null) { /* Handle specific message type */ }
}
}
I’m sure there are other ways, but that’s probably how I would’ve approached it. You should also keep in mind that any kind of messaging implemented like this will walk the entire scene graph to retrieve its listeners each time you send a message. Though, it should be fine as long as you don’t send a huge amount of messages each frame.