Relationships and the friends list

Tutorial

intermediate

+10XP

25 mins

Unity Technologies

Relationships and the friends list

In this tutorial, you will learn how to set up a friends list in your Unity project.

1. Set up the friends list code

The friends list is a player’s way of seeing all of their friends in your game. They can see friends they’ve made through your game as well as their current Discord friends. Creating a friends list for your game is the main social building block that allows you to add messaging and invites, which keep players in your game and helps bring in new players through their friends.

To set up the friends list using code, follow these instructions:

1. In the Unity Editor, in the Project window, open the Assets folder.

2. Right-click and select Create > MonoBehavior Script and name it “FriendUI”.

Note: Depending on your Unity version, the path to create a MonoBehaviour script might be Create > C# Script or some other variation.

3. Open the FriendUI script and add the following lines of code:

using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Networking;
using TMPro;
using Discord.Sdk;

public class FriendUI : MonoBehaviour
{
    [SerializeField]
    private TextMeshProUGUI friendNameText;

    [SerializeField]
    private TextMeshProUGUI friendStatusText;

    [SerializeField]
    private Image friendAvatarImage;

    private Client client;
    public RelationshipHandle relationshipHandle { get; private set; }

    public void Initialize(Client client, RelationshipHandle relationshipHandle)
    {
        this.client = client;
        this.relationshipHandle = relationshipHandle;
        friendNameText.text = relationshipHandle.User().DisplayName();
        friendStatusText.text = relationshipHandle.User().Status().ToString();
        StartCoroutine(LoadAvatarFromUrl(relationshipHandle.User().AvatarUrl(UserHandle.AvatarType.Png, UserHandle.AvatarType.Png)));
    }

    public void UpdateFriend()
    {
        friendNameText.text = relationshipHandle.User().DisplayName();
        friendStatusText.text = relationshipHandle.User().Status().ToString();
    }

    private IEnumerator LoadAvatarFromUrl(string url)
    {
        using (UnityWebRequest request = UnityWebRequestTexture.GetTexture(url))
        {
            yield return request.SendWebRequest();

            if (request.result == UnityWebRequest.Result.Success)
            {
                Texture2D texture = DownloadHandlerTexture.GetContent(request);
                Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f));
                friendAvatarImage.sprite = sprite;
            }
            else
            {
                Debug.LogError($"Failed to load profile image from URL: {url}. Error: {request.error}");
            }
        }
    }
}

The FriendUI class keeps track of the RelationshipHandle for one friend and the UI for that friend in the friends list in Unity. A RelationshipHandle represents the relationship between the current player and a target player on Discord. Relationships include friends, blocked users, and friend invites.

A player’s information is accessed through RelationshipHandle.User() and in this script DisplayName, Status, and AvatarUrl are all used to set up the UI on the attached GameObject.

LoadAvatarFromUrl() is a helper function to request the player’s avatar from Discord’s servers.

Since a FriendUI script handles the relationship for a single friend, we’ll want a way to display and organize all of a player’s friends. To do that we’ll create a FriendsList script.

4. Right-click Create > MonoBehavior Script and name it “FriendsList”.

Note: Depending on your Unity version, the path to create a MonoBehaviour script might be Create > C# Script or some other variation.

5. Open the FriendsList script and add the following lines of code:

using UnityEngine;
using Discord.Sdk;
using System.Collections.Generic;

public class FriendsList : MonoBehaviour
{
    [SerializeField]
    GameObject friendUIPrefab;

    [SerializeField]
    Transform friendListContentTransform;

    List<GameObject> friendUIObjects = new List<GameObject>();

    public void LoadFriends(Client client)
    {
        RelationshipHandle[] relationships = client.GetRelationships();
        foreach (var relationship in relationships)
        {
            GameObject friendUI = Instantiate(friendUIPrefab, friendListContentTransform);
            friendUI.GetComponent<FriendUI>().Initialize(client, relationship);
            friendUIObjects.Add(friendUI);
        }

        SortFriends();
    }

    // Discord users can change their name or online status, use this to keep the UI up to date
    public void UpdateFriends()
    {
        foreach (var friendUIObject in friendUIObjects)
        {
            friendUIObject.GetComponent<FriendUI>().UpdateFriend();
        }
    }

    public void SortFriends()
    {
        // Sort friends by online status and then by display name
        friendUIObjects.Sort((a, b) =>
        {
            FriendUI friendA = a.GetComponent<FriendUI>();
            FriendUI friendB = b.GetComponent<FriendUI>();

            RelationshipHandle relationshipA = friendA.relationshipHandle;
            RelationshipHandle relationshipB = friendB.relationshipHandle;

            StatusType statusA = relationshipA.User().Status();
            StatusType statusB = relationshipB.User().Status();

            if (statusA != statusB)
                return statusA.CompareTo(statusB);

            return relationshipA.User().DisplayName().CompareTo(relationshipB.User().DisplayName());
        });

        // Reorder the friend UI elements in the hierarchy after sorting
        for (int i = 0; i < friendUIObjects.Count; i++)
        {
            friendUIObjects[i].transform.SetSiblingIndex(i);
        }
    }
}

The FriendsList script handles keeping track of each friend and their associated UI GameObject in Unity and sorting them by online status and name. When the DiscordManager script is authenticated, it calls LoadFriends() in this script, which uses client.GetRelationships() in the Social SDK to load the RelationshipHandle for each of the player’s friends. The FriendsList script instantiates UI prefabs for each friend, saves them for future use, and then sorts them.

UpdateFriends() is used to keep each friend’s UI in sync with their actual Discord display name and status.

SortFriends() is used to follow Discord’s best practices for the friends list. This includes keeping friends sorted by their online status and then alphabetically by name. Once sorted the UI GameObjects are then moved around in game to follow that sorting.

Friends can change their display name and their online status, which triggers a callback in the Social SDK Client. To add this functionality, you’ll add code to the DiscordManager to handle that callback and send the update to the FriendsList.

6. Open the DiscordManager script and add the following variable to the top of the class under the richPresence variable:

[SerializeField]
private FriendsList friendsList;

7. At the end of the OnStatusChanged() method, inside the if (status == Client.Status.Ready) code block, add the following line of code:

friendsList.LoadFriends(client);

This will load all of the player’s friends once they’ve authenticated in the Discord Application.

8. Add the following OnUserUpdated() function to the bottom of the DiscordManager class by adding the following lines of code:

private void OnUserUpdated(ulong userId)
{
    friendsList.UpdateFriends();
    friendsList.SortFriends();
}

9. At the end of the Awake() method in DiscordManager, add the following line of code:

client.SetUserUpdatedCallback(OnUserUpdated);

The SetUserUpdatedCallback() callback is invoked whenever any player the current session knows about changes. For example, when a player’s friends come online, go offline, or start playing the game.

When that happens, OnUserUpdated() is called, which will update the UI for each friend and then sort them properly in the friends list.

2. Set up the friends list UI

To set up the UI for the friends list in Unity, follow these instructions:

1. In the Hierarchy window, right-click the Canvas GameObject, select UI > Scroll View, and rename the Scroll View GameObject “Friends List”.

2. In the Inspector window, in the Rect Transform component, set the Width property to 500. Then select the Anchor presets button, hold Alt (macOS: Option) and select stretch right.

This anchors the Friends List GameObject to the right side of the Canvas GameObject.

3. In the Scroll Rect component, open the Movement Type dropdown and select Clamped. Then disable the Horizontal property.

This ensures that players can’t scroll past the Scroll View GameObject.

4. In the Hierarchy window, use the foldouts (triangles) to expand the Friends List > Viewport GameObjects and select the Content GameObject.

5. In the Inspector window, select the Anchor presets button, hold Alt (macOS: Option) and select stretch stretch.

6. In the Inspector window, select the Add Component button and search for and add a Vertical Layout Group component.

The Vertical Layout Group component allows the friends list to stack each friend vertically.

7. In the Vertical Layout Group component, enable the Control Child Size Width property and disable the Child Force Expand Height property.

8. In the Inspector window, select the Add Component button and search for and add a Content Size Fitter component.

9. In the Content Size Fitter component, open the Vertical Fit property dropdown and select Preferred Size.

3. Create the friend prefab

To set up the prefab for each friend’s UI GameObject, follow these instructions:

1. In the Hierarchy window, right-click the Content GameObject, select UI > Panel, and rename the Panel GameObject “FriendUI”.

2. Right-click the FriendUI GameObject, select UI > Image, and rename the Image GameObject “Avatar”.

3. Right-click the FriendUI GameObject, select UI > Text - TextMeshPro, and rename the Text (TMP) GameObject “Display Name”.

4. Right-click the FriendUI GameObject, select UI > Text - TextMeshPro, and rename the Text (TMP) GameObject “Status”.

5. In the Hierarchy window, select the FriendUI GameObject, then in the Inspector window, set the Rect Transform component’s Height property to 100.

6. In the Hierarchy window, select the Avatar GameObject, then in the Inspector window, select the Anchor presets button, hold Alt (macOS: Option) and select middle left.

7. In the Hierarchy window, select the Display Name GameObject, then in the Inspector window, select the Anchor presets button, hold Alt (macOS: Option) and select top stretch. Then set the Left property to 110.

8. In the TextMeshPro - Text (UI) component, set the Font Size property to 26, set the Font Style property to Bold, and set the Alignment property to Middle.

9. In the Hierarchy window, select the Status GameObject, then in the Inspector window, select the Anchor presets button, hold Alt (macOS: Option) and select bottom stretch. Then set the Left property to 110.

10. In the TextMeshPro - Text (UI) component, set the Font Size property to 20 and set the Alignment property to Middle.

11. In the Hierarchy window, select the FriendUI GameObject, then in the Inspector window, select the Add Component button and search for and add the FriendUI script component.

12. From the Hierarchy window, drag the Display Name GameObject into the FriendUI script component’s Friend Name Text property box, the Status GameObject into the Friend Status Text property box, and the Avatar GameObject into the Friend Avatar Image property box.

13. In the Project window, right-click in the Assets folder and select Create > Folder, then rename the new folder “Prefabs”.

14. Click and drag the FriendUI GameObject from the Hierarchy window into the Prefabs folder, then delete the Friend UI GameObject from the Hierarchy window.

4. Connect the friends list

To connect all pieces of the friends list together and run it, these instructions:

1. Right-click in the Hierarchy window, select Create Empty GameObject, and rename it “FriendsList”.

2. In the Inspector window, Select the Add Component button, and search for and add the Friends List script component.

3. In the Project window, open the Prefabs folder and click and drag the FriendUI prefab into the Friends List script component’s Friend UI Prefab property box.

4. In the Hierarchy window, click and drag the Content GameObject into the Friends List script component’s Friend List Content Transform property box.

5. In the Hierarchy window, select the DiscordManager GameObject, then click and drag the FriendsList GameObject into the Discord Manager script component’s Friends List property box.

Now, when the Discord Manager GameObject gets the client ready status, it can communicate with the FriendsList GameObject to create GameObjects for all the friends, put them in the UI, and keep them up to date.

Note: Make sure to use the FriendsList GameObject you just created, not the Friends List GameObject.

6. Press the Play button to enter Play mode.

7. Select the Connect to Discord button in the UI.

8. Select the Authorize button in the Discord Application.

9. Return to Unity and the player’s friends will all load and display in the friends list panel.

5. Wrapping up the friends list

Now that you’ve got the friends list running, here are some features you can add to make it even more useful!

Before moving on, take a moment to check that your scripts are correct.

The full FriendUI script:

using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Networking;
using TMPro;
using Discord.Sdk;

public class FriendUI : MonoBehaviour
{
    [SerializeField]
    private TextMeshProUGUI friendNameText;

    [SerializeField]
    private TextMeshProUGUI friendStatusText;

    [SerializeField]
    private Image friendAvatarImage;

    private Client client;
    public RelationshipHandle relationshipHandle { get; private set; }

    public void Initialize(Client client, RelationshipHandle relationshipHandle)
    {
        this.client = client;
        this.relationshipHandle = relationshipHandle;
        friendNameText.text = relationshipHandle.User().DisplayName();
        friendStatusText.text = relationshipHandle.User().Status().ToString();
        StartCoroutine(LoadAvatarFromUrl(relationshipHandle.User().AvatarUrl(UserHandle.AvatarType.Png, UserHandle.AvatarType.Png)));
    }

    public void UpdateFriend()
    {
        friendNameText.text = relationshipHandle.User().DisplayName();
        friendStatusText.text = relationshipHandle.User().Status().ToString();
    }

    private IEnumerator LoadAvatarFromUrl(string url)
    {
        using (UnityWebRequest request = UnityWebRequestTexture.GetTexture(url))
        {
            yield return request.SendWebRequest();

            if (request.result == UnityWebRequest.Result.Success)
            {
                Texture2D texture = DownloadHandlerTexture.GetContent(request);
                Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f));
                friendAvatarImage.sprite = sprite;
            }
            else
            {
                Debug.LogError($"Failed to load profile image from URL: {url}. Error: {request.error}");
            }
        }
    }
}

The full FriendsList script:

using UnityEngine;
using Discord.Sdk;
using System.Collections.Generic;

public class FriendsList : MonoBehaviour
{
    [SerializeField]
    GameObject friendUIPrefab;

    [SerializeField]
    Transform friendListContentTransform;

    List<GameObject> friendUIObjects = new List<GameObject>();

    public void LoadFriends(Client client)
    {
        RelationshipHandle[] relationships = client.GetRelationships();
        foreach (var relationship in relationships)
        {
            GameObject friendUI = Instantiate(friendUIPrefab, friendListContentTransform);
            friendUI.GetComponent<FriendUI>().Initialize(client, relationship);
            friendUIObjects.Add(friendUI);
        }

        SortFriends();
    }

    // Discord users can change their name or online status, use this to keep the UI up to date
    public void UpdateFriends()
    {
        foreach (var friendUIObject in friendUIObjects)
        {
            friendUIObject.GetComponent<FriendUI>().UpdateFriend();
        }
    }

    public void SortFriends()
    {
        // Sort friends by online status and then by display name
        friendUIObjects.Sort((a, b) =>
        {
            FriendUI friendA = a.GetComponent<FriendUI>();
            FriendUI friendB = b.GetComponent<FriendUI>();

            RelationshipHandle relationshipA = friendA.relationshipHandle;
            RelationshipHandle relationshipB = friendB.relationshipHandle;

            StatusType statusA = relationshipA.User().Status();
            StatusType statusB = relationshipB.User().Status();

            if (statusA != statusB)
                return statusA.CompareTo(statusB);

            return relationshipA.User().DisplayName().CompareTo(relationshipB.User().DisplayName());
        });

        // Reorder the friend UI elements in the hierarchy after sorting
        for (int i = 0; i < friendUIObjects.Count; i++)
        {
            friendUIObjects[i].transform.SetSiblingIndex(i);
        }
    }
}

The full DiscordManager script:

using UnityEngine;
using UnityEngine.UI;
using Discord.Sdk;

public class DiscordManager : MonoBehaviour
{
    [SerializeField]
    private ulong applicationId;
    [SerializeField]
    private RichPresence richPresence;
    [SerializeField]
    private FriendsList friendsList;

    private Client client;
    private string codeVerifier;

    void Awake()
    {
        client = new Client();
        client.AddLogCallback(OnLog, LoggingSeverity.Error);
        client.SetStatusChangedCallback(OnStatusChanged);
        client.SetUserUpdatedCallback(OnUserUpdated);
    }

    private void OnDestroy()
    {
        client.Disconnect();
    }

    private void OnLog(string message, LoggingSeverity severity)
    {
        Debug.Log($"Log: {severity} - {message}");
    }

    private void OnStatusChanged(Client.Status status, Client.Error error, int errorCode)
    {
        Debug.Log($"Status changed: {status}");

        if (error != Client.Error.None)
        {
            Debug.LogError($"Error: {error}, code: {errorCode}");
        }

        if (status == Client.Status.Ready)
        {
            richPresence.UpdateRichPresence(client);
            friendsList.LoadFriends(client);
        }
    }

    public void StartOAuthFlow()
    {
        var authorizationVerifier = client.CreateAuthorizationCodeVerifier();
        codeVerifier = authorizationVerifier.Verifier();

        var args = new AuthorizationArgs();
        args.SetClientId(applicationId);
        args.SetScopes(Client.GetDefaultCommunicationScopes());
        args.SetCodeChallenge(authorizationVerifier.Challenge());
        client.Authorize(args, OnAuthorizeResult);
    }

    private void OnAuthorizeResult(ClientResult result, string code, string redirectUri)
    {
        if (!result.Successful())
        {
            Debug.Log($"Authorization result: [{result.Error()}]");
            return;
        }
        GetTokenFromCode(code, redirectUri);
    }

    private void GetTokenFromCode(string code, string redirectUri)
    {
        client.GetToken(applicationId, code, codeVerifier, redirectUri, OnGetToken);
    }

    private void OnGetToken(ClientResult result, string token, string refreshToken, AuthorizationTokenType tokenType, int expiresIn, string scope)
    {
        if (token == null || token == string.Empty)
        {
            Debug.Log("Failed to retrieve token");
        }
        else
        {
            client.UpdateToken(AuthorizationTokenType.Bearer, token, OnUpdateToken);
        }
    }

    private void OnUpdateToken(ClientResult result)
    {
        if (result.Successful())
        {
            client.Connect();
        }
        else
        {
            Debug.LogError($"Failed to update token: {result.Error()}");
        }
    }

    private void OnUserUpdated(ulong userId)
    {
        friendsList.UpdateFriends();
        friendsList.SortFriends();
    }
}

6. Next Steps

You’ve got the basics of the Discord Social SDK! There’s so much more you can explore to bring Discord’s social features to your game. In the next tutorial you’ll set up game lobbies, invites, and direct messages to bring the social pieces of your game to life.

Discord also has a prebuilt sample project with prefabs covering everything in this course. It’s built with best design practices in place and can be dropped into any commercial game.

For help with the Discord Social SDK, there’s a #social-sdk-dev-help channel in the Discord Developers server.

Resources

Complete this Tutorial