Целевая аудитория: начинающие разработчики
Теги: Unity, ScriptableObject, Json, JsonUtility, Firebase
Предыдущие статьи обязательные для прочтения.
Вступление
В продолжение предыдущей статьи, об удаленном обновлении данных, настало время разобраться с сервисом Firebase, благодаря которому мы будем хранить и получать наши конфиги для ScriptableObject.
Firebase
Ссылка на официальную интеграцию с Unity
Перейдя по ссылке, вы получите инструкцию как создать проект в Firebase, все настроить и интегрировать SDK в Unity. Она довольна простая, из минусов, она на английском языке, но думаю большей проблемой это не будет. Вкратце опишу список действий, которые необходимо выполнить:
- Создать проект в Firebase
- Зарегистрировать приложение Android или iOS приложение, указав bundle id, название приложения
- Скачать файл конфигурации который выдаст вам Firebase после первого этапа регистрации (для android этот файл называется: google-services.json). Остальные шаги при регистрации можно пропустить
- Скачать Firebase Unity SDK
- Импортировать FirebaseAuth.unitypackage, FirebaseRemoteConfig.unitypackage
- Добавить в папку Assets проекта ранее скаченный google-services.json
- На этом конфигурацию можно считать завершенной
Добавления JSON в Firebase
Давайте в продолжение предыдущей статьи добавим JSON для WeaponData в Firebase. Для этого нам необходимо:
- Перейти в раздел Grow/Remote config
- Добавить новый параметр. Где ключ название нашего ScriptableObject в проекте, в данном случае это WeaponDataScriptableObject и значение по умолчанию:
{
"Id":"AK-47",
"Damage":100,
"FireRate":0.1,
"ReloadDuration":2.9,
"AmmoCount":30
}
- Нажать на кнопку "Опубликовать изменения" !!!
На это все необходимые действия в Firebase закончены.
Клиентская часть для Unity
Теперь необходимо написать клиентскую часть, которая будет подключаться к Firebase, получать нужные JSON и десериализовывать их в ScriptableObject. Но для этого нам необходимо внести некоторые изменения.
Добавим интерфейс IDeserializeFromJson, который информирует нас о том, что данный объект (в нашем случае ScriptableObject) умеет десериализовывать себя из JSON:
public interface IDeserializeFromJson
{
void Deserialize(string json);
}
И изменим наш WeaponDataScriptableObject класс из прошлой статьи следующим образом:
using Scipts;
using UnityEngine;
[System.Serializable]
public class WeaponData
{
public string Id;
public int Damage;
public float FireRate;
public float ReloadDuration;
public int AmmoCount;
}
[CreateAssetMenu(fileName = "WeaponData", menuName= "ScriptableObjects/WeaponData")]
[System.Serializable]
public class WeaponDataScriptableObject : ScriptableObject, IDeserializeFromJson
{
public WeaponData WeaponData;
public void Deserialize(string json)
{
WeaponData = JsonUtility.FromJson<WeaponData>(json);
}
}
Жирным выделено то, что было добавлено. А именно:
- WeaponDataScriptableObject теперь не только наследуется от ScriptableObject, но и реализует интерфейс IDeserializeFromJson
- Добавлен метод Deserialize который реализует интерфейс IDeserializeFromJson
JsonUtility.FromJson - должно быть знакомо вам из прошлой статьи.
Теперь напишем класс, который будет иметь ссылки на все ScriptableObject которые необходимо удаленно обновлять, подключаться к Firebase, получать JSON'ы и десериализировать их в необходимые ScriptableObject:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Firebase;
using Firebase.Extensions;
using Firebase.RemoteConfig;
using UnityEngine;
namespace Scipts
{
public class RemoteConfigs : MonoBehaviour
{
[SerializeField] private ScriptableObject[] _scriptableObjects;
private void Start()
{
StartFetchConfigs();
}
public void StartFetchConfigs()
{
var defaultConfigs = _scriptableObjects.ToDictionary(k => k.name, v => (object)string.Empty);
FirebaseRemoteConfig.SetDefaults(defaultConfigs);
FirebaseApp.CheckAndFixDependenciesAsync().ContinueWithOnMainThread(task =>
{
if (task.Result == DependencyStatus.Available)
{
FirebaseRemoteConfig.FetchAsync(TimeSpan.Zero).ContinueWithOnMainThread(FetchComplete);
}
});
}
private void FetchComplete(Task fetchTask)
{
if (fetchTask != null && fetchTask.IsCompleted)
{
var info = FirebaseRemoteConfig.Info;
if (info != null && info.LastFetchStatus == LastFetchStatus.Success)
{
FirebaseRemoteConfig.ActivateFetched();
foreach (var scriptableObject in _scriptableObjects)
{
var json = FirebaseRemoteConfig.GetValue(scriptableObject.name).StringValue;
if (!string.IsNullOrEmpty(json))
{
var deserializer = scriptableObject as IDeserializeFromJson;
if (deserializer != null)
{
deserializer.Deserialize(json);
}
}
}
}
}
}
}
}
Обсудим важные моменты:
[SerializeField] private ScriptableObject[] _scriptableObjects;
храним ссылки на все _scriptableObjects которые хотим удаленно обновлять. Они должны реализовывать интерфейс IDeserializeFromJson. В нашем случае один из таких классов WeaponDataScriptableObject (предоставлен выше).
var defaultConfigs = _scriptableObjects.ToDictionary(k => k.name, v => (object)string.Empty);
FirebaseRemoteConfig.SetDefaults(defaultConfigs);
FirebaseApp.CheckAndFixDependenciesAsync().ContinueWithOnMainThread(task =>
{
if (task.Result == DependencyStatus.Available)
{
FirebaseRemoteConfig.FetchAsync(TimeSpan.Zero).ContinueWithOnMainThread(FetchComplete);
}
});
Делаем запрос к Firebase, с пустыми дефолтными конфигам, т.к в данном случае, для нас это неважно, мы всегда хотим получать данные с сервера. В качестве калбека передаем метод FetchComplete - он будет вызван, когда данные будут получены.
foreach (var scriptableObject in _scriptableObjects)
{
var json = FirebaseRemoteConfig.GetValue(scriptableObject.name).StringValue;
if (!string.IsNullOrEmpty(json))
{
var deserializer = scriptableObject as IDeserializeFromJson;
if (deserializer != null)
{
deserializer.Deserialize(json);
}
}
Если все хорошо, мы переберем все указанные ScriptableObject в цикле, по их имени постараемся получить JSON. Важно: имена ScriptableObject в проекте и Firebase должны совпадать!
Если JSON получен, и ScriptableObject действительно реализует интерфейс IDeserializeFromJson, мы вызовем метод для десериализации, данные будут установлены - готово!
В случае же, если нет интернета или данные по какой-либо причине небыли получены, они останутся теме же, какие вы их сами настроили в Unity.
Чек-лист для удаленного обновления данных
Таким образом, что бы удаленно обновлять нужный ScriptableObject необходимо произвести следующие действия:
- Данные в ScriptableObject должны быть выделены в отдельный класс ( пример WeaponData)
- ScriptableObject должен иметь ссылку на данные (пример WeaponDataScriptableObject):
[CreateAssetMenu(fileName = "WeaponData", menuName = "ScriptableObjects/WeaponData")]
public class WeaponDataScriptableObject : ScriptableObject, IDeserializeFromJson
{
public WeaponData WeaponData;
...
}
- ScriptableObject должен реализовывать интерфейс IDeserializeFromJson, и соответственно иметь метод Deserialize, который десериализирует данные из JSON, пример:
[CreateAssetMenu(fileName = "WeaponData", menuName= "ScriptableObjects/WeaponData")]
public class WeaponDataScriptableObject : ScriptableObject, IDeserializeFromJson
{
public WeaponData WeaponData;
public void Deserialize(string json)
{
WeaponData = JsonUtility.FromJson<WeaponData>(json);
}
}
- Данный ScriptableObject должен быть создан и указан через инспектор в RemoteConfigs
- В Grow/Remote Config должен быть добавлен параметр, с таким же именем как у файла ScriptableObject в проекте:
Итоги
Мы получили готовое решение, которое позволяет нам удаленно обновлять любые ScriptableObject при этом серьезных изменений делать для этого не нужно, только те, что описаны в чек-листе выше! Система вышла очень простой и гибкой, и ее можно использовать в любых проектах. Надеюсь, вам это пригодится а сам материал был полезен и интересен. Спасибо за внимание, подписывайтесь на канал, и ждите новых интересных статей!