Decoupling direct calls from Unity classes
Background
When I was working on unity games embedded in React Native app, I saw a lot of potential for combining hybrid apps with unity. This app help users to develop the skills they need to achieve their individual goals.
So we bring ours technical experience in another area to unity games. The project is a Greenfield, so we start to develop games from scratch.
A lot of calls between classes
A few weeks later, when games are more and more complicated. The more things happen.
One of the everyday tasks is to change the color smoothly, run particle system, spawn new objects, recalculating the score. So the problem was direct calls between classes. How to eliminate it?
Answer (and TL;DR)
The answer is Events.
Just attach a script with EventBus. We decided to use github.com/ThomasKomarnicki/GameEventBus
using GameEventBus;
public class GameController : MonoBehaviour {
public static EventBus Bus = new EventBus();
}
Now each class can send an event, and any interested class can subscribe to itself to interact with it.
public class Hud : MonoBehaviour {
void Start() {
GameController.Bus.Subscribe(OnBulletCollision);
}
void OnBulletCollision(BulletCollisionEvent event) {
Debug.Log("Our HUD was notified of a bullet collision!")
}
void OnDestroy() {
GameController.Bus.Unsubscribe(OnBulletCollision);
}
}
Now ‘using’ in our classes are cleaner and dependencies between them too!
Example
In Tetris-like game states of Tetris box are manage by Finite-state machine pattern. So boxes are changing from one form to another in response to some inputs. But each state triggers action like play state transition music, show some particles, start a graphic transition between them, and more stuff to bring life to the game.
It is also possible to associate action with a state and an entry action when entering the state or exit action state.
How Tetris box class looks like now:
using MDevelopers.Unity.Statemachine;
using UnityEngine;
namespace MDevelopers.Unity.Tetris
{
public class TetrisBox : MonoBehaviour
{
internal FallingDownState fallingState;
internal MoveLeftState moveLeftState;
internal MoveRightState moveRightState;
internal MoveDownState moveDownState;
internal MoveRotateState rotateState;
internal StateMachine stateMachine;
void Start()
{
InitStateMachine();
}
(…)
}
Ok. But show me how classes change with events bus.
For example, earlier, I have something like an EndGame state it small and simple:
using MDevelopers.Unity.Distractors;
using MDevelopers.Unity.Statemachine;
using MDevelopers.Unity.Utilities;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.SceneManagement;
internal class EndGameState : State
{
(…)
private void EndGame()
{
//Create result object
//Calculate results
//Send message to ReactNative
//End background transition
//Change scene
}
}
Just by using we can see, there is something wrong with this class. On the other hand, what endgame has in common with the Tetris box is now a box. The box can end the game by cross the up line, but it's not an internal state of the box. So I realize I need to remove it. And after refactoring to event-based. I remove this class. And MoveDawn and FallingDown state using standard type to check the correctness of the move and sending an event about ending the game. Here is what the content of the method looks like:
using MDevelopers.Unity.Statemachine;
using UnityEngine;
namespace MDevelopers.Unity.Tetris
{
public static class Falling
{
internal static void Down(TetrisBox box, StateMachine machine, ParticleSystem particleSystem)
{
if (TetrisPlayfield.IsGameEnd(box.transform))
{
TetrisGameController.Bus.Publish(new EndGameEvent());
return;
}
TetrisGameController.Bus.Publish(new SpawnNextEvent());
(…)
And yes. I tried not to use the ‘else’ keyword. To not necessarily nested my code. But it is a topic to cover in the next blog post.
Conclusion
Event systems can help decouple direct calls between classes, add structure to the code, clean up dependencies in scripts, and quickly add callbacks.
Adrian Kujawski