Hello, I have a question directed at code structure / code management. What is the best way to manage communication between scripts?
In my particular case, I have a GameManager object which initializes another game object. This new game object runs an animation for short period of time then has to notify GameManager that animation is finished and has to pass back some parameters.
I know of a few ways to do this currently. The simplest one is to have a reference between these objects and communicate via public methods. So, a GameManager instantiates new game object and gives it reference to itself. However, this creates a ācircular referenceā which, as far as I know, is something to avoid.
Another way is to use listeners. Iād have an interface class with some method (i.e. OnFinished(int someParam). GameManager would implement this interface and newly created object would then call listener.OnFinished(2).
However, both these solutions seem āclumsyā and lead to tight coupling and dependencies between my scripts. And at some point in time, itās going to make my code very difficult to manage.
Well youāre right, thereās lots of ways to do this. Instead of saying, āuse this oneā Iāll just say what Iād do. Iād personally use an interface with a IEnumerator. The nice thing about coroutines is you can yield one until another has finished. Like so:
public interface SomeInterface
{
IEnumerator SomeAnimation();
}
public class SomeAnimator : MonoBehaviour, SomeInterface
{
public IEnumerator SomeAnimation()
{
Debug.Log("Started animation");
yield return new WaitForSeconds(5);
}
}
public class SomeGameManager : MonoBehaviour
{
public SomeInterface newInterface;
void Start()
{
StartCoroutine(ActionSomething());
}
IEnumerator ActionSomething()
{
Debug.Log("The animation is about to start");
yield return StartCoroutine(newInterface.SomeAnimation());
Debug.Log("The animation has now finished");
}
}
What heās using here is a singleton EventManager class and various objects which subscribe themselves to UnityEvents. I generally try to avoid singletons in my projects (Iām Android developer by default), but technique shown in this video would really help in reducing some of the clutter and dependencies in my project.
The simplest way to do this is just to add a simple function, OnAnimationFinish or something similar and call it when the animation has finished playing.
Also events and delegates are great way too
That video is actually horrible. It uses a very powerful system (UnityEvent) to create a really shitty string-based event system. Itās also global events, which you should try to avoid as they make the flow of your program hard to follow.
As mentioned, you could use a delegate to add the behaviour to the animation script without having to let the animation script know about the game state. The easiest way to do that is through the Action class. So the relevant parts of your Animation script would be:
public class AnimationScript : MonoBehaviour {
//substitute with the actual types you need
public Action<string, int, float> OnAnimationEnd;
...
//When the animation is over:
OnAnimationEnd(myString, myInt, myFloat);
}
And in your controller script when you create it:
GameObject animThing = Instantiate(animThingPrefab);
animThing.GetComponent<AnimationScript>().OnAnimationEnd += Foo;
...
//This can be private!
private void Foo(string s, int i, float f) {
Debug.Log(s + i + f);
}
Using a delegate is a bit more wordy, but it would allow you to name the arguments to OnAnimationEnd, which could make the code easier to read:
//replace this:
public Action<string, int, float> OnAnimationEnd;
//with this:
public delegate void AnimEndDelegate(string name, int value, float foobar);
public AnimEndDelegate OnAnimatonEnd;
The video I posted uses Unityās event system which works like events / delegates. (I say this based on the video since my experience with C# is still limited).
In my example, user has to type in a word from a list of words. When a user types a word, I have various other scripts that have to do something. UI script removes this word from my scroll view and clears the input field, damage calculation script does some calculations based on word, typing speed, etc, player health script updates slider values and health variables and I also have to send data to server.
Seems like EventManager approach from the video might be the simplest solution to notify all these scripts without them being dependent on each other.
Code would look like this:
// Class that extends UnityEvent
public class WordEvent : UnityEvent<string> {
}
// Event manager class - not attached to game object
public class EventManager {
private static EventManager instance;
private Dictionary<string, WordEvent> events;
private WordEvent wordEvent;
public static EventManager Instance {
get
{
if (instance == null)
instance = new EventManager();
return instance;
}
}
public void RegisterToWordEvent(UnityAction<string> listener)
{
if (wordEvent == null)
wordEvent = new WordEvent();
wordEvent.AddListener(listener);
}
public void TriggerWordEvent(string word)
{
wordEvent.Invoke(word);
}
}
And then various classes would register themselves to this event:
public class SomeClass : MonoBehaviour {
void Start(){
EventManager.Instance.RegisterToWordEvent(WordReceiverMethod);
}
}
Hi, thanks for the reply. I really like this approach and will try it out.
Can you just tell me what you think of singleton EventManager I posted above. It doesnāt rely on that string implementation. Instead there would be a few specific event cases and that is it. āWordEventā as I named is it the most important part in my scene. There would be only a few other events in this particular scene.
It seems like itās a fine solution. Youāll probably want some way to unregister from the world events, but otherwise itās solid.
As I said in my post, over-relying on global events might cause code to be hard to follow. Itās also easy to create situations where an edge-case causes an infinite loop (something that calls the global event āAā is called as a result of calling the same event), so be careful about that.
At the same time, if youāre making a question/answer kind of game, pretty much everything revolves around the player-inputs-answer event, so having it around could make your code less coupled and easier to follow. Goes to show why general advice isnāt always good advice
Iād say go for it. Be aware that since this is a non-monobehaviour singleton, it wonāt get deleted when you change the scene, so events wonāt clear when you go to a new level. Thatās the only big, immediate problem I can spot.
Thanks to everyone for replies. Itās not actually a question/answer kind of game, but as I like to call it āa multiplayer action typingā game.
For the time being, I opted for the approach you posted, the one using Actions. However, I plan on using UnityEvent class and, where possible, add method calls in Unity Editor, same way one would add multiple methods to a UI Button click. Every class that cares about correct word being typed in will be added to this event list. Then Iāll just call wordEvent.Invoke(params) in my GameManager class when needed. Seems like an ok solution for the time being.