Communication between scripts?

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.

You guys have any tips for this?

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");
    }
}

Hey, thanks for the reply. So, thatā€™s similar to what Iā€™ve been doing (using interface classes as listeners).

What do you think of approach shown in this video:
https://unity3d.com/learn/tutorials/modules/intermediate/live-training-archive/events-creating-simple-messaging-system

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;
1 Like

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 :stuck_out_tongue:

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.

2 Likes

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. :slight_smile:

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.