1

I'm writing a game in Unity3D, and I'm wondering if I should be using events -- or some other construct entirely -- instead of delegates for my web requests.

At certain points in the game's lifecycle, we need to call out to a REST API and await its response. Consider a UI screen that retrieves a list of your friends, and displays the number of friends, along with an entry for each friend. Currently, the workflow looks something like this, using Action delegates as parameters to the various methods of my Server class:

class FriendsUI : MonoBehaviour
{
    [SerializeField]
    private Text friendCount; // A Unity Text component

    private void OnStart()
    {
        Server.GetFriends(player, response => {
            ShowFriendList(response.friends);
        });
    }

    private void ShowFriendList(List<Friend> friends)
    {
        this.friendCount.text = friends.Count.ToString();
        // Display friend entries...
    }
}

The Server.GetFriends method does an HTTP request to our REST API, gets the response, and calls the delegate, returning the response and control to FriendsUI.

This usually works just fine. However, if I closed the friends UI screen before the delegate is called, the friendCount component -- and any other component in the class, for that matter -- has been destroyed, and an NPE is thrown trying to access it.

Knowing that, do you think C#'s event system be a better design for this? Server would expose something like a FriendsEvent event, and FriendsUI would subscribe to it. At the point when Server has validated/transformed the response, it would raise the event. FriendsUI would also clean up after itself and unregister the handler when the GameObject on which the script has been attached is destroyed/disabled. That way, at the point the event is raised in Server, if the event handler is no longer valid in FriendsUI, it just wouldn't get called.

I'm just trying to run this through in my head before implementing it, because it would end up becoming a somewhat large refactor. I'm looking for guidance as to whether or not people think it's a good route to go, if there are any other routes that might turn out better, and any pitfalls I might run into along the way.

Thanks, as ever, for your time.

Monkey34
  • 697
  • 1
  • 6
  • 16
  • learn to use UnityEvent. it couldn't be easier. http://stackoverflow.com/a/36249404/294884 – Fattie Apr 23 '16 at 22:20
  • Thanks for the info, Joe. The UnityEvent system seems a tad overly verbose, but it's probably the way to go. For example, the way I'm thinking about it, I need to expose a `FriendsResponseEvent` class in my `Server` class that extends `UnityEvent`. I also need to declare and instantiate a `FriendsResponseEvent` property in `Server`. Then I add `Server.FriendsResponseEvent.AddListener(ReceivedFriends)` to my `FriendsUI#OnStart` method. That pattern needs to happen for every event `Server` wants to raise, which gets pretty wordy. Or maybe there's a more concise way? – Monkey34 Apr 28 '16 at 19:13
  • As a test do this: (1) type `public UnityEvent teste;` in your server class. (2) somewhere in that class (example, once the connection is complete) go `teste.Invoke()`. Finally (3) LOOK at the Inspector for your server class. Now DRAG anything you want to "teste" (connect as many as you want that way). – Fattie Apr 28 '16 at 20:14
  • never use "AddListener" there is no need for it. – Fattie Apr 28 '16 at 20:15
  • I disagree about the use of `AddListener`. In my opinion, there is absolutely a need for it, when developers want to hook up listeners via code. I am much more comfortable doing that than hooking them up via the inspector. I'm happy to let a game designer wire up certain events through the inspector, but this use case is something the developers need to manage, and code is our preferred channel for doing that. The inspector might be OK if this was a one-developer operation, but that's not the case here. Either way, I'll keep looking at `UnityEvent` as a solution. – Monkey34 Apr 30 '16 at 05:32

1 Answers1

2

Simply use C# delegate and event. Make each script that wants to be notified when friends list is received from the server to subscribe to the event.

public class FRIENDSAPI : MonoBehaviour
{
    public delegate void friendListReceived(List<FRINDSINFO> _friends);
    public static event friendListReceived onFriendListReceived;

    //Function that gets friends from the network and notify subscribed functions with results
    public void getFriends()
    {
      StartCoroutine(getFriendsCoroutine());
    }

    private IEnumerator getFriendsCoroutine()
    {
     List<FRINDSINFO> friendsFromNetwork = new List<FRINDSINFO>();

     /*
     NETWORK CODE

     //FILL friendsFromNetwork with Result
     friendsFromNetwork.Add();

     */


     //Make sure a function is subscribed then Notify every subscribed function
      if (onFriendListReceived != null)
      {
        onFriendListReceived(friendsFromNetwork);
      }

     yield return null;
     }
}

Then in your FriendsUI class, subscribe in the Start() function and unsubscribe in the OnDisable() function. You usually subscribe in the OnEnable() function but sometimes it doesn't work because things don't initialize on time. Doing it in the Start() function is a fix for that.

public class FriendsUI: MonoBehaviour
{

    void Start()
    {
        //Subscribe
        FRIENDSAPI.onFriendListReceived += onFriendsReceived;

        FRIENDSAPI friendAPI = gameObject.GetComponent<FRIENDSAPI>();
        friendAPI.getFriends(); //Get friends
    }

    public void OnDisable()
    {
        //Un-Subscribe
        FRIENDSAPI.onFriendListReceived -= onFriendsReceived;
    }

    //Call back function when friends are received
    void onFriendsReceived(List<FRINDSINFO> _friends)
    {
        //Do Whatever you want with the result here(Display on the Screen)
    }
}
Programmer
  • 121,791
  • 22
  • 236
  • 328
  • Thanks for the comment. It looks like this would be the route I'd go, if I were to use events ultimately. The main thing I wanted to run by people, however, was if events were the approach they thought was best for this type of scenario, or if there's a way to achieve it with delegates (i.e., modifying my current route) or if there's another approach that would suit me better. – Monkey34 Apr 23 '16 at 01:54
  • 1
    @Monkey34 You can use `Action` or do it your own way but the important thing for you to do is to make sure that you unsubscribe each time the subscribed class is destroyed/disabled. Failure to do so will introduce another problem. – Programmer Apr 23 '16 at 08:19