- Unity 2017 Game Optimization(Second Edition)
- Chris Dickinson
- 829字
- 2021-07-02 23:21:10
Message registration
The following code contains a pair of simple classes that register with the Messaging System, each requesting to have one of their methods called whenever certain types of messages have been broadcast from anywhere in our codebase:
public class EnemyManagerWithMessagesComponent : MonoBehaviour {
private List<GameObject> _enemies = new List<GameObject>();
[SerializeField] private GameObject _enemyPrefab;
void Start() {
MessagingSystem.Instance.AttachListener(typeof(CreateEnemyMessage),
this.HandleCreateEnemy);
}
bool HandleCreateEnemy(Message msg) {
CreateEnemyMessage castMsg = msg as CreateEnemyMessage;
string[] names = { "Tom", "Dick", "Harry" };
GameObject enemy = GameObject.Instantiate(_enemyPrefab,
5.0f * Random.insideUnitSphere,
Quaternion.identity);
string enemyName = names[Random.Range(0, names.Length)];
enemy.gameObject.name = enemyName;
_enemies.Add(enemy);
MessagingSystem.Instance.QueueMessage(new EnemyCreatedMessage(enemy,
enemyName));
return true;
}
}
public class EnemyCreatedListenerComponent : MonoBehaviour {
void Start () {
MessagingSystem.Instance.AttachListener(typeof(EnemyCreatedMessage),
HandleEnemyCreated);
}
bool HandleEnemyCreated(Message msg) {
EnemyCreatedMessage castMsg = msg as EnemyCreatedMessage;
Debug.Log(string.Format("A new enemy was created! {0}",
castMsg.enemyName));
return true;
}
}
During initialization, the EnemyManagerWithMessagesComponent class registers to receive messages of the type CreateEnemyMessage, and will process the message through it's HandleCreateEnemy() delegate. During this method, it can typecast the message into the appropriate derived message type and resolves the message in its own unique way. Other classes can register for the same message and resolve it differently through its own custom delegate method (assuming that an earlier listener didn't return true from its own delegate).
We know what type of message will be provided by the msg argument of the HandleCreateEnemy() method, because we defined it during registration through the AttachListener() call. Due to this, we can be certain that our typecasting is safe, and we can save time by not having to do a null reference check, although, technically, there is nothing stopping us using the same delegate to handle multiple message types. In these cases, though, we will need to implement a way to determine which message object is being passed and treat it accordingly. However, the best approach is to define a unique method for each message type in order to keep things appropriately decoupled. There really is little benefit in trying to use one monolithic method to handle all message types.
Note how the HandleEnemyCreated() method definition matches the function signature of MessageHandlerDelegate (that is, it has the same return type and argument list), and that it is being referenced in the AttachListener() call. This is how we tell the Messaging System what method to call when the given message type is broadcast, and how delegates ensure type-safety. If the function signature had a different return value or a different list of arguments, then it would be an invalid delegate for the AttachListener() method, and we would get compiler errors. Also, note that HandleEnemyCreated() is also a private method, and yet our MessagingSystem class is able to call it. This is a useful feature of delegates in that we can allow only systems we give permission to call this message handler. Exposing the method publicly might lead to some confusion in our code’s API, and developers may think that they’re meant to call the method directly, which is not its intended use.
The beautiful part is that we're free to give the delegate method whatever name we want. The most sensible approach is to name the method after the message which it handles. This makes it clear to anyone reading our code what the method is used for and what message object type must be sent in order to call it. This makes future parsing and debugging of our code much more straight-forward since we can follow the chain of events by the matching names of the messages and their handler delegates.
During the HandleCreateEnemy() method, we also queue another event, which broadcasts an EnemyCreatedMessage instead. The second class, EnemyCreatedListenerComponent registers to receive these messages, and then prints out a message containing that information. This is how we would implement a way for subsystems to notify other subsystems of changes. In a real application, we might register a UI system to listen for these types of messages, and update a counter on the screen to show how many enemies are now active. In this case, the enemy management and UI systems are appropriately decoupled such that neither needs to know any specific information about how the other operates in order to do their assigned tasks.
If we now add the EnemyManagerWithMessagesComponent, EnemyCreatorComponent and EnemyCreatedListenerComponent to our Scene, and press the Space Bar several times, we should see log messages appear in the Console window, informing us of a successful test:

Note that a MessagingSystem Singleton object will be created during Scene initialization, when either the EnemyManagerWithMessagesComponent object's or EnemyCreatedListenerComponent object's Start() methods are called (whichever happens first), since that is when they register their delegates with the Messaging System, which accesses the Instance property, and hence creates the necessary GameObject containing the Singleton Component. No additional effort is required on our part to create the MessagingSystem object.
- Getting started with Google Guava
- PHP 7底層設計與源碼實現
- Learning ASP.NET Core 2.0
- The Data Visualization Workshop
- 3D少兒游戲編程(原書第2版)
- 量化金融R語言高級教程
- Scratch3.0趣味編程動手玩:比賽訓練營
- Mastering Docker
- Java 9 with JShell
- MongoDB Cookbook
- iOS Development with Xamarin Cookbook
- Mastering Machine Learning with R
- 精益軟件開發管理之道
- JavaScript語法簡明手冊
- R語言數據分析從入門到實戰