- PVSM.RU - https://www.pvsm.ru -
Недавно мы побывали в Амстердаме на конференции Unite Europe 2016 [1], где получили массу эмоций и интересного опыта. На этой конференции было очень много увлекательных докладов по разным направлениям и различного уровня сложности. Темой одного из выступлений была “Overthrowing the MonoBehaviour tyranny in a glorious ScriptableObject revolution”, на котором Ричард Файн (https://twitter.com/superpig [2] / https://github.com/richard-fine [3]), специалист из Unity Technologies, подробно рассказал о ScriptableObject и на примерах показал, как он может быть применен в проекте.
В своем докладе Ричард затронул такие вопросы:
Далее — свободный перевод / пересказ того, о чем рассказывал Ричард, с различными дополнениями.
Общие сведения о MB:
Каковы недостатки?
Чистые статические C# классы?
Но ведь мы же специально используем движок, который предоставляет всё это «из коробки», чтобы избежать неудобств!
Как насчет префабов [9]?
Они решают проблему хранение и переноса от сцены к сцене и между проектами, и не нарушают VCS-грануляцию. Но у этого решения тоже есть недостатки:
SO — это класс, который позволяет хранить большое количество передаваемой информации независимо от образцов скрипта. От этого класса можно унаследоваться, если есть необходимость создавать объекты, которые не будут прикрепляться к GO.
Представим, что есть префаб со скриптом, который имеет массив из миллиона целых чисел. Массив занимает 4 мегабайта памяти и принадлежит префабу. Каждый раз, создавая экземпляр этого префаба, создаётся и экземпляр этого массива. Если создать 10 игровых объектов, тогда размер памяти, занимаемой массивами для этих 10 экземпляров, будет равен 40 мегабайтам.
При использовании SO результат будет совершенно другим. Unity сериализует все типы примитивов, строк, массивов, списков, специфичных типов, таких как Vector3 и пользовательских классов с атрибутом Serializable в качестве копий, относящихся к объекту, в котором они определены. Это означает, что при создании экземпляра класса SO с объявленным в нем массивом из миллиона целых чисел, этот массив будет передаваться вместе с образцом. При этом экземпляры считают, что обладают разными данными. Поля SO или любые UnityEngine.Object-поля, такие как MonoBehaviour, Mesh, GameObject и т.д, хранятся в ссылках, в отличие от значений. Если имеется скрипт, ссылающийся на SO, содержащий миллион целых чисел, то Unity сохранит в данных скрипта лишь ссылку на SO. свою очередь, SO хранит массив. 10 экземпляров префаба, которые ссылаются на класс SO, использующий 4 мегабайта памяти, в итоге заняли бы 4 мегабайта вместо 40, о которых упоминалось выше. Это особенно важно, когда речь идет о большом количестве объектов и/или больших объемах данных в скриптах.
Итак, SO:
Как SO спасает нас от проблем:
Но SO тоже не идеален:
Класс SO необходимо использовать в тех случаях, когда нужно снизить расход памяти путём избежания копирования значений. Но его также можно использовать для определения включаемых наборов данных. Он отлично подходит для конфигурационных файлов, например для настроек уровня, глобальных настроек игры или индивидуальных настроек для персонажей / врагов / зданий и так далее (можно, например, хранить максимальный запас здоровья, урон и прочие параметры). Также SO удобен при написании кастомных инструментов, редакторов уровней и т.д.
Из экземпляров SO можно быстро и удобно создавать кастомные ассеты, переиспользовать их, подгружать по сети и т. д. При объявлении наследника класса SO его можно пометить атрибутом CreateAssetMenu [10], что добавит пункт создания ассета для этого объекта в контекстное меню Assets/Create.
Пример простого скрипта с настройками:
using UnityEngine;
[CreateAssetMenu(fileName="EnemyData", menuName="Prefs/Characters/Enemy", order=1)]
public class EnemyPrefs : ScriptableObject
{
public string objectName = "Enemy";
[Range(10f, 100f)]
public float maxHP = 50f;
[Range(1f, 10f)]
public float maxDamage = 5f;
public Vector3[] spawnPoints;
}
Создание ассета:
Сериализация ассета с данными в инспекторе:
При работе в инспекторе с экземплярами SO можно дважды нажать на поле ссылки, чтобы открыть инспектор для своего SO. Также есть возможность создавать пользовательский редактор для определения вида инспектора своего типа, чтобы помочь управлять данными, которые он представляет.
Экземпляр SO может быть создан без привязки к .asset файлу: программно, при помощи SсriptableObject.CreateInstance<> [11].
Время жизни SO:
Большинство разработчиков считают SO контейнером данных, но на самом деле он представляет из себя нечто большее. Рассмотрим некоторые паттерны его применения.
Как объекты данных и таблицы:
Пример:
class EnemyInfo : ScriptableObject
{
public int MaximumHealth;
public int DamagePerMeleeHit;
}
class Enemy : MonoBehaviour
{
public EnemyInfo info;
}
Как расширяемые перечисления:
Пример:
class AmmoType : ScriptableObject { }
…
if (inventory[weapon.ammoType] == 0)
{
PlayOutOfAmmoSound();
return;
}
…
inventory[weapon.ammoType] -= 1;
...
Двойная сериализация
Как было сказано ранее, одним из достоинств SO является полная совместимость с системой сериализации Unity. При этом:
Пример:
[CreateAssetMenu]
class LevelData : ScriptableObject { /*...*/ }
LevelData LoadLevelFromFile(string path)
{
string json = File.ReadAllText(path);
LevelData result = CreateInstance<>(LevelData);
JsonUtility.FromJsonOverwrite(result, json);
return result;
}
Как синглтоны:
Пример:
class GameState: ScriptableObject
{
public int lives, score;
static GameState _instance;
public GameState Instance
{
get
{
if (!_instance) _instance = FindObjectOfType<GameState>();
if (!_instance) _instance = CreateDefaultGameState();
return _instance;
}
}
}
Как делегаты:
Пример:
abstaract class PowerupEffect : ScriptableObject
{
public abstract void ApplyTo(GameObject go);
}
class HealthBooster : PowerupEffect
{
public int Amount;
public override void ApplyTo(GameObject go)
{
go.GetComponent<Health>().currentValue += Amount;
}
}
class Powerup : MonoBehaviour
{
public PowerupEffect effect;
public void OnTriggerEnter(Collider other)
{
effect.ApplyTo(other.gameObject);
}
}
Таким образом, Powerup MB делегирует свою работу PowerupEffect SO, в чем и заключается паттерн.
Резюмируя вышесказанное, можно сделать вывод, что у SO есть свои плюсы и минусы, часть из которых приведена ниже:
Преимущества:
Недостатки:
SO — очень важный инструмент, который хоть и не может полностью заменить MB, но может быть использован в сочетании с ним, избавляя от его тирании. SO не стоит использовать для всего подряд, но он дает большую гибкость. Куда больше, чем думают те, кто знаком с SO поверхностно или не знаком вообще. Он также предоставляет возможность построить отличный рабочий процесс для вашего проекта, создавать удобные и полезные инструменты и шаблоны для дизайнеров и т. д.
Ричард в начале своего выступления заявил: “MonoBehaviour sucks” (MonoBehaviour отстой :) ). Сложно полностью с этим согласиться, ведь так или иначе, без него (MB) почти не обойтись. Но главное — это понять, что не стоит использовать его всегда и везде, и что существуют различные альтернативы, одной из которых является мощный, гибкий и удобный ScriptableObject. Нужно грамотно выбирать те или иные средства, исходя из поставленной задачи при конкретных условиях.
Автор: NIX Solutions
Источник [15]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/unity3d/165320
Ссылки в тексте:
[1] Unite Europe 2016: https://unite.unity.com/2016/europe
[2] https://twitter.com/superpig: https://twitter.com/superpig
[3] https://github.com/richard-fine: https://github.com/richard-fine
[4] MonoBehaviour : https://docs.unity3d.com/ScriptReference/MonoBehaviour.html
[5] ScriptableObject : http://docs.unity3d.com/ScriptReference/ScriptableObject.html
[6] https://bitbucket.org/richardfine/scriptableobjectdemo: https://bitbucket.org/richardfine/scriptableobjectdemo
[7] GameObject : https://docs.unity3d.com/ScriptReference/GameObject.html
[8] VCSгрануляция : https://en.wikipedia.org/wiki/Version_control
[9] префабов: https://docs.unity3d.com/Manual/Prefabs.html
[10] CreateAssetMenu: http://docs.unity3d.com/ScriptReference/CreateAssetMenuAttribute.html
[11] SсriptableObject.CreateInstance<>: https://docs.unity3d.com/ScriptReference/ScriptableObject.CreateInstance.html
[12] Resources.UnloadUnusedAssets: https://docs.unity3d.com/ScriptReference/Resources.UnloadUnusedAssets.html
[13] HideFlags.HideAndDontSave: https://docs.unity3d.com/ScriptReference/HideFlags.HideAndDontSave.html
[14] JsonUtility: https://docs.unity3d.com/ScriptReference/JsonUtility.html
[15] Источник: https://habrahabr.ru/post/306514/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.