В этой статье попробуем разобрать что такое Scriptable object, зачем они нужны и как их использовать.
Файлы и данные
Работать с файлами в редакторе unity достаточно просто. Каждый файл отображает только свои параметры, если это изображение, то мы видим настройки разрешения, детализации и тд, если это текст, то мы можем увидеть, что написано в нем, а если это 3D модель, то можем увидеть ее в окошке Preview и даже покрутить. Все это конечно удобно, но что если нам понадобится особый объект который мог бы сочетать в себе несколько типов данных одновременно, к примеру сделать так, чтобы в одном файле хранилась и картинка и, допустим, текст с ее описанием? Для решения этой проблемы и был введен особый тип объектов в unity – Scriptable object.
Scriptable object (скриптовый объект) это по сути, объект определенного типа, который может хранить пользовательские данные любого типа в виде файла. Для тех кто знаком немного с сериализацией, может возникнуть небольшое противоречие, ведь зачем мне скриптовый объект, если я и так смогу посредством сериализации сохранить и картинку и текст, и что угодно в любой файл!? Дело в том что скриптовый объект работает по такому же принципу как и любой другой объект в unity, за счет чего между ними сохраняется взаимосвязь, где вам не нужно создавать дополнительные, специальные инструменты для того, чтобы разные объекты могли взаимодействовать со скриптовый объектом.
Попробуем на примере разобрать, как выглядят скриптовые объекты и чем они отличаются от любых других объектов unity. Создадим простой скрипт ImageData, который будет обычным классом и хранить в себе изображение Sprite, и текст с его описанием.
- public sealed class ImageData {
- public Sprite image;
- public string description = “Description here”;
- }
Если создать экземпляр данного класса с помощью оператора new:
ImageData data = new ImageData();
то этот экземпляр будет существовать только в оперативной памяти компьютера до тех пор, пока программа не завершит свою работу, после чего память очистится и объект ImageData будет уничтожен. Чтобы можно было постоянно хранить этот объект в проекте после создания, необходимо унаследовать ImageData от класса ScriptableObject и создать уже не просто “виртуальный” экземпляр объекта, а его постоянную форму в виде файла.
- public sealed class ImageData : ScriptableObject {
- public Sprite image;
- public string description = “Description here”;
- }
Создавать экземпляры скриптовых объектов можно вызвав статический метод CreateInstance класса ScriptableObject. Выполнять это действие необходимо только в редакторе, для чего воспользуемся методами “быстрого” доступа – это статические методы, которые вызываются с помощью контекстного меню внутри редактора.
- #if UNITY_EDITOR
- using UnityEditor;
- #endif
- public sealed class ImageData : ScriptableObject {
- public Sprite image;
- public string description = “Description here”;
- #if UNITY_EDITOR
- [MenuItem(“Assets/Create/New image data instance”, false, 1)]
- public static void CreateImageData() {}
- #endif
- }
Атрибут MenuItem добавит статический метод CreateImageData в специальное контекстное меню которое будет доступно в разделе Assets -> Create -> New image data instance.
В самом методе создадим экземпляр скриптового объекта ImageData.
- #if UNITY_EDITOR
- using UnityEditor;
- #endif
- public sealed class ImageData : ScriptableObject {
- public Sprite image;
- public string description = “Description here”;
- #if UNITY_EDITOR
- [MenuItem(“Assets/Create/New image data instance”, false, 1)]
- public static void CreateImageData() {
- ImageData data = ScriptableObject.CreateInstance<ImageData>();
- }
- #endif
- }
При вызове статического метода CreateInstance, в скобки передается тип создаваемого объекта, в нашем случае это тип ImageData. После того как экземпляр создан, необходимо его сохранить в проекте, для чего воспользуемся специальным классом по работе с ассетами в редакторе – AssetDatabase и его статичеким методом CreateAsset для сохранения объектов в виде файла.
- #if UNITY_EDITOR
- using UnityEditor;
- #endif
- public sealed class ImageData : ScriptableObject {
- public Sprite image;
- public string description = “Description here”;
- #if UNITY_EDITOR
- [MenuItem(“Assets/Create/New image data instance”, false, 1)]
- public static void CreateImageData() {
- ImageData data = ScriptableObject.CreateInstance<ImageData>();
- AssetDatabase.CreateAsset(data, “Assets/New image data.asset”);
- AssetDatabase.Refresh();
- }
- #endif
- }
В метод CreateAsset передаем объект ImageData, который необходимо создать и указываем путь, где он будет создан, в данном случае объект будет создан в корневой папке Assets. Чтобы протестировать процесс создания скриптового объекта, можно перейти в контекстное меню Assets -> Create -> New image data instance и нажать его.
Теперь в папке Assets у нас появился новый файл скриптового объекта в который мы можем поместить изображение и текст с описанием.
Итак, экземпляр скриптового объекта ImageData перешел из “виртуального” вида и хранится теперь в постоянной памяти вместо оперативной. Этот файл мы можем редактировать, удалять, перемещать и тд, в каком то смысле теперь объект стал “осязаем”, что намного облегчает работу с ним. Самое главное преимущество скриптовых объектов перед другими видами файлов в том, что эти объекты могут хранить самые необходимые данные, точнее – только те, которые вы укажете.
Для сравнения возьмем обычный префаб. Префаб – это сложный GameObject который может содержать в себе другие GameObject’ы, а также кучу компонентов, которые вам могут показаться совершенно бесполезными.
В скриптовом объекте вы указываете только те данные, которые вас интересуют, без лишних компонентов и других данных, при этом сохраняются все возможности работы со скриптовым объектом как и с любым другим unity объектом.
Во вторых – скриптовые объекты очень хорошо оптимизируют процесс работы игры, тем что отлично экономят память, когда большое кол-во объектов ссылаются на одни и те же данные.
К примеру: на сцене есть 1000 GameObject’ов, у каждого объекта есть компонент, в котором необходимо указать ссылку на изображение, ее размеры Vector2 и текст с ее описанием, в этом случае все 1000 объектов будут создавать свои уникальные ссылки на эти данные, что в общем кол-ве будет давать 3000 (1000 объектов умножить на 3 вида данных) уникальных ссылок, что совершенно не нужно, так как достаточно просто создать один скриптовый объект со всеми этими данными и передавать в компонент одну ссылку на скриптовый объект вместо 3х.
И в третьих – заменяемость. Для создания копий экземпляра скриптового объекта, достаточно использовать Instantiate, как и при создании копии любого префаба, после чего получаем абсолютно новую и независимую копию объекта.
Использовать скриптовый объекты так же легко как и любые другие объекты в unity.
В примере используется массив скриптовых объектов, чтобы переключать изображения по щелчку мыши. Панель UI с изображениями на сцене одна как и текст с описанием, а компонент, в свою очередь, просто имеет ссылки на скриптовые объекты с другими изображениями и описаниями к ним.
В самом компоненте, для получения ссылок на скриптовые объекты необходимо просто указать тип ImageData.
- public sealed class ImageControl : MonoBehaviour {
- public ImageData[] data = new ImageData[5];
- }
Детали
Мы уже разобрали способ создания скриптовых объектов через статический метод. Есть еще один способ создания экземпляров скриптовых объектов – через специальный атрибут CreateAssetMenu.
- [CreateAssetMenu(fileName = “New image data”, menuName = “New image data”, order = 0)]
- public sealed class ImageData : ScriptableObject {
- public Sprite image;
- public string description = “Description here”;
- }
Результат будет такой же, как если бы создавали через статический метод контекстного меню. Достаточно просто поместить данный атрибут вверху класса ImageData, после чего указать название файла при создании и места, где будет располагаться его контекстное меню. В данном случае там же в Assets -> Create -> New image data. Единственный недостаток такого решения в том, что нельзя будет указать место создания файла, по умолчанию это будет корневая папка Assets.
Скриптовые объекты и ООП
В начале статьи мы разбирали простой класс ImageData, который еще не был унаследован от ScriptableObject, и создавали экземпляры данного класса с помощью оператора new. Экземпляры скриптовых объектов также можно создавать с помощью этого оператора.
К примеру, усовершенствуем немного класс ImageData и добавим ему конструктор, в котором будет указывать изображение и текст с описанием.
- #if UNITY_EDITOR
- using UnityEditor;
- #endif
- public sealed class ImageData : ScriptableObject {
- public Sprite image;
- public string description = “Description here”;
- public ImageData(Sprite image, string description) {
- this.image = image;
- this.description = description;
- }
- #if UNITY_EDITOR
- [MenuItem(“Assets/Create/New image data instance”, false, 1)]
- public static void CreateImageData() {}
- #endif
- }
Теперь можно создать экземпляр класса ImageData использую его конструктор.
- Sprite image;
- string description;
- ImageData data = new ImageData(image, description);
Данная конструкция создает “виртуальный” экземпляр скриптового объекта, это именно то, с чем мы боролись использую скриптовые объект – перейти от “виртуального” типа к “физическому”. Хотя такая конструкция создания очень удобная при создании сложных экземпляров, сохранять такие экземпляры нельзя. Для решения данной проблемы необходимо поместить “виртуальный” экземпляр в “физический” шаблон, созданный через статический метод CreateInstance.
- public sealed class ImageData : ScriptableObject {
- public Sprite image;
- public string description = “Description here”;
- public ImageData(Sprite image, string description) {
- this.image = image;
- this.description = description;
- }
- #if UNITY_EDITOR
- [MenuItem(“Assets/Create/New image data instance”, false, 1)]
- public static void CreateImageData() {
- ImageData original = new ImageData(null, “Some description”);
- SaveInstance(original);
- }
- private static void SaveInstance(ScriptableObject obj) {}
- #endif
- }
В методе CreateImageData создается новый экземпляр скриптового объекта ImageData, только в этот раз с помощью оператора new. После чего экземпляр передаем в метод SaveInstance для сохранения его в виде постоянного файла. Так как сохранять “виртуальные” экземпляры не получится, необходимо создать его “физический” шаблон скриптового объекта такого же типа как и оригинал, после чего просто передать на него ссылку и сохранить.
- public sealed class ImageData : ScriptableObject {
- public Sprite image;
- public string description = “Description here”;
- public ImageData(Sprite image, string description) {
- this.image = image;
- this.description = description;
- }
- #if UNITY_EDITOR
- [MenuItem(“Assets/Create/New image data instance”, false, 1)]
- public static void CreateImageData() {
- ImageData original = new ImageData(null, “Some description”);
- SaveInstance(original);
- }
- private static void SaveInstance(ScriptableObject obj) {
- ScriptableObject saveObject = ScriptableObject.CreateInstance(obj.GetType());
- saveObject = obj;
- AssetDatabase.CreateAsset(saveObject, “Assets/New image data.asset”);
- AssetDatabase.Refresh();
- }
- #endif
- }
В переменную saveObject помещаем новый “физический” шаблон такого же типа как и оригинал, для этого в новом статическом метод CreateInstance указываем тип передаваемого объекта, вызвав его метод GetType.
После создания шаблона, достаточно передать ему ссылку на оригинал после чего можно сохранять объект в виде файла в проекте при помощи AssetDataBase.
Заключение
Скриптовые объекты могут вам очень пригодится когда необходимо создать постоянную базу данных в виде файла, которую удобно редактировать, перемещать и передавать другим объектам. Эти объекты одновременно совмещают два способа работы: программный – на уровне кода, и файловый.
СКачать исходник проекта можно отсюда.