Работа с профилем

Для работы с профилем пользователя в данном фреймворке уже существует готовый модуль. Этот модуль автоматически отправляет все изменения в профиле клиентам, а также имеет базовый механизм сохранения данных. Также, при подключении игрока к игровому серверу/комнате, сервер может получать профиль этого игрока и, после внесения изменений в этот профиль, отправить эти изменения мастер серверу.

Основные принципы работы с профилем

В большинстве случаев при работе с профилем вам понадобится делать следующее:

  • Определить структуру вашего профиля(фабрику профиля) на стороне мастер сервера.
  • (Не обязательно) Получить профиль на стороне клиента, чтобы получать обновления и выводить их на экран пользователя.
  • (Не обязательно) Получить профиль на стороне игрового сервера/комнаты и выполнять его изменения, которые будут отправлены на мастер сервер и затем клиенту.

Определение структуры профиля на стороне мастер сервера

Как уже говорилось ранее для работы с профильными данными мы уже имеем все необходимое. Этим занимается модуль ProfilesModule. Поэтому чтобы не писать код с нуля мы лишь расширим наш класс ProfilesModule и внесем некоторые изменения и дополнения. 

Создайте файл скрипта и в нем класс ProfilesModuleTutorial производным от ProfilesModule, это будет наш модуль работы с профилем. О том как создавать собственные модули описано здесь.

namespace Aevien.Tutorials
{
    public class ProfilesModuleTutorial : ProfilesModule
    {
        
    }
}

Далее нам необходимо создать профильные ключи. Эти ключи помогут нам определять какие свойства профиля были изменены. Мы создадим следующие свойства:

  • DisplayName - отображаемое имя игрока.
  • Avatar - аватар игрока.
  • Bronze - количество бронзы, которое есть у игрока.
  • Silver - количество серебра, которое есть у игрока.
  • Gold- количество золота, которое есть у игрока.

Внесем эти поля как часть нашего модуля работы с профилем в виде перечисления.

namespace Aevien.Tutorials
{
    public enum ObservablePropertiyCodes { DisplayName, Avatar, Bronze, Silver, Gold }

    public class ProfilesModuleTutorial : ProfilesModule
    {
        
    }
}

Далее, как говорилось ранее, нам нужно определить структуру или фабрику нашего профиля. Эта структура будет заполнять первичными данными профиль нового игрока или игрока, который начал игру заново . Для этого в модуле ProfilesModule есть специальное свойство делегат, с которой мы свяжем нашу фабрику. Это поле называется ProfileFactory. Для этого создадим метод, который возвращает ObservableServerProfile. Назовем его, например, CreateProfileInServer() и в него передадим два аргумента. Первый это идентификатор профиля, в нашем случае мы используем имя пользователя взятое из модуля AuthModule, этот модуль уже подключен к нашему модулю работы с профилем. Второй Peer нашего клиента, которому будет принадлежать этот профиль.

Так как наш метод возвращает ObservableServerProfile, то мы должны его сразу инициализировать внутри этого метода и вернуть. Тут то нам и понадобятся наши ключ, которые мы создали ранее. Создаем экземпляр класса ObservableServerProfile и вносим в него все свойства, которые необходимы для нашей игры. Также, при добавлении свойств указываем их начальное значение, можно это сделать через инспектор свойств нашего модуля.

namespace Aevien.Tutorials
{
    public enum ObservablePropertiyCodes { DisplayName, Avatar, Bronze, Silver, Gold }

    public class ProfilesModuleTutorial : ProfilesModule
    {
        [Header("Start Values"), SerializeField]
        private float bronze = 100;
        [SerializeField]
        private float silver = 50;
        [SerializeField]
        private float gold = 50;
        [SerializeField]
        private string avatarUrl = "https://i.imgur.com/JQ9pRoD.png";

        private ObservableServerProfile CreateProfileInServer(string username, IPeer clientPeer)
        {
            return new ObservableServerProfile(username, clientPeer)
            {
                new ObservableString((short)ObservablePropertiyCodes.DisplayName, SimpleNameGenerator.Generate(Gender.Male)),
                new ObservableString((short)ObservablePropertiyCodes.Avatar, avatarUrl),
                new ObservableFloat((short)ObservablePropertiyCodes.Bronze, bronze),
                new ObservableFloat((short)ObservablePropertiyCodes.Silver, silver),
                new ObservableFloat((short)ObservablePropertiyCodes.Gold, gold)
            };
        }
    }
}

Далее необходимо перегрузить метод инициализации модуля и в нем указать нашу фабрику. О том, что такое метод инициализации модуля рассказано здесь.

namespace Aevien.Tutorials
{
    public enum ObservablePropertiyCodes { DisplayName, Avatar, Bronze, Silver, Gold }

    public class ProfilesModuleTutorial : ProfilesModule
    {
        [Header("Start Values"), SerializeField]
        private float bronze = 100;
        [SerializeField]
        private float silver = 50;
        [SerializeField]
        private float gold = 50;
        [SerializeField]
        private string avatarUrl = "https://i.imgur.com/JQ9pRoD.png";

        public override void Initialize(IServer server)
        {
            base.Initialize(server);

            // Set the new factory in ProfilesModule
            ProfileFactory = CreateProfileInServer;
        }

        private ObservableServerProfile CreateProfileInServer(string username, IPeer clientPeer)
        {
            return new ObservableServerProfile(username, clientPeer)
            {
                new ObservableString((short)ObservablePropertiyCodes.DisplayName, SimpleNameGenerator.Generate(Gender.Male)),
                new ObservableString((short)ObservablePropertiyCodes.Avatar, avatarUrl),
                new ObservableFloat((short)ObservablePropertiyCodes.Bronze, bronze),
                new ObservableFloat((short)ObservablePropertiyCodes.Silver, silver),
                new ObservableFloat((short)ObservablePropertiyCodes.Gold, gold)
            };
        }
    }
}

Также есть другой способ создания структуры профиля на стороне сервера. В нем мы можем не расширять, а просто выполнить поиск модуля профилей ProfilesModule в сцене и инициализировать метод фабрики профилей ProfileFactory как показано ниже.

 

var profilesModule = FindObjectOfType<ProfilesModule>();

profilesModule.ProfileFactory = (username, peer) => new ObservableServerProfile(username, peer)
{
    new ObservableString((short)ObservablePropertiyCodes.DisplayName, SimpleNameGenerator.Generate(Gender.Male)),
    new ObservableString((short)ObservablePropertiyCodes.Avatar, avatarUrl),
    new ObservableFloat((short)ObservablePropertiyCodes.Bronze, bronze),
    new ObservableFloat((short)ObservablePropertiyCodes.Silver, silver),
    new ObservableFloat((short)ObservablePropertiyCodes.Gold, gold)
};

Если метод фабрики установлен правильно, то при авторизации пользователя в игре будет создан профиль по умолчанию для каждого пользователя при вызове метода ProfileFactory. Если пользователь не новый, то мастер сервер проверит базу данных на наличие профиля и загрузит его. 

Пока это все, что нам нужно для работы с профилем игрока на стороне мастер сервера. Далее необходимо написать логику получения и обработки профильных данных на стороне клиента.

Прослушивание изменений в профиле на стороне клиента

Инициализация и подписка на изменения это еще не все. Чтобы вы смогли получать изменения профиля с сервера вам необходимо установить связь с этим профилем. Для этого вам необходимо отправить запрос мастер серверу для получения данных вашего профиля. Сделать это можно следующим способом:

Mst.Client.Profiles.GetProfileValues(profile, (isSuccessful, profileError) =>
{
    if (!isSuccessful)
    {
        Logs.Error(profileError);
        return;
    }

    // Listen to property updates
    profile.OnPropertyUpdatedEvent += (code, property) =>
    {
        // Log a message, when property changes
        Logs.Info($"Property changed: {code} - {property}");
    };

});

Как вы можете увидеть после установления связи с профилем мы сразу же зарегистрировали прослушку изменения всех его свойство. Также можно прослушивать конкретное свойство профиля отдельно.

// Get property by casting it to ObservableFloat
var bronzeProperty = profile.GetProperty<ObservableFloat>((short)ObservablePropertiyCodes.Bronze);

// Register listening to changes
bronzeProperty.OnDirtyEvent += property =>
{
    Logs.Info($"Bronze changed to: {bronzeProperty.GetValue()}");

    // OR
    Logs.Info($"Bronze changed to: {property.CastTo<ObservableFloat>().GetValue()}");
};

Итак, мы настроили профиль для работы с ним на мастер сервере и на клиенте. Теперь необходимо вносить изменения в это профиль. Например, добавлять золото через определенный интервал, сменить отображаемое имя игрока. Эту процедуру можно выполнять как на мастер сервере, так и в игровом сервере/комнате. Мы рассмотрим выполнение изменения профиля игрока на стороне игрового сервера/комнаты.

Изменение профильных данных на стороне игрового сервера/комнаты

В данном случае мы будем выполнять изменение профильных данных исключительно на стороне игрового сервера/комнаты. Как и в случае с клиентской частью нам необходимо создать экземпляр профиля игрока и выполнить запрос на мастер сервер для получения всех его свойств. 

// Use username to get profile
var username = "playerUsername";

// Construct the profile
var profile = new ObservableServerProfile(username)
{
    new ObservableString((short)ObservablePropertiyCodes.DisplayName),
    new ObservableString((short)ObservablePropertiyCodes.Avatar),
    new ObservableFloat((short)ObservablePropertiyCodes.Bronze),
    new ObservableFloat((short)ObservablePropertiyCodes.Silver),
    new ObservableFloat((short)ObservablePropertiyCodes.Gold)
};

// Fill profile values
Mst.Server.Profiles.FillProfileValues(profile, (successful, error) =>
{
    if (!successful)
    {
        Logs.Error(error);
        return;
    }
});

Здесь мы берем имя пользователя и создаем профиль для него. Далее отправляем запрос на мастер сервер, чтобы заполнить этот профиль данными. Имя пользователя здесь играет роль идентификатора, по которому вы будете получать информацию в профиль от мастер сервера. Если вы посмотрите на код модуля ProfilesModule, то можете увидеть, что все профили пользователей хранятся в словаре, ключом которого является имя пользователя. Смотрим пример ниже.

/// <summary>
/// List of the users profiles
/// </summary>
public Dictionary<string, ObservableServerProfile> ProfilesList { get; protected set; }

private async void OnUserLoggedInEventHandler(IUserPeerExtension user)
{
    ....

    // Create a profile
    ObservableServerProfile profile;

    if (ProfilesList.ContainsKey(user.Username))
    {
        // There's a profile from before, which we can use
        profile = ProfilesList[user.Username];
        profile.ClientPeer = user.Peer;
    }
    else
    {
        // We need to create a new one
        profile = CreateProfile(user.Username, user.Peer);
        ProfilesList.Add(user.Username, profile);
    }
}

После того как профиль подключенного к игровому серверу/комнате игрока будет получен мы свободно можем вносить в его свойства изменения. Например, чтобы добавить золото или изменить отображаемое имя пользователя в нашем случае мы делаем следующее:

// Add 10 of gold
profile.GetProperty<ObservableFloat>((short)ObservablePropertiyCodes.Gold).Add(10);

// Change user display name
profile.GetProperty<ObservableString>((short)ObservablePropertiyCodes.DisplayName).Set("New Display Name");

В данном случае это все, что необходимо знать о работе с профилем в данном фреймворке.