danilaxxl danilaxxl

CollectableItemData.cs

[CreateMenuItem(fileName = "newItem", menuName = "Data/Items/Collectable", order = 51]

GoloGames GoloGames

vadya_ivan, рад, что вам игра показалась интересной : )

P.S. Кстати уже доступна бесплатная демо-версия в Steam

vadya_ivan vadya_ivan

Визуал, задумка, музыка , механики, все в цель

GoloGames GoloGames

Ato_Ome, спасибо за позитивные эмоции, будем стараться : )

Ato_Ome Ato_Ome

Потрясающий результат, все так четенько, плавненько)
То ли саунд, то ли плавность напомнили мне игрушку World of Goo, удачи вам в разработке и сил побольше дойти до релиза!)

Cute Fox Cute Fox

Graphics are a little cool, good HD content. But this game doesn't cause nary interest me.
However the game is well done.

GMSD3D GMSD3D

Почему действие после всех условий выполняется?
[step another object]

Zemlaynin Zemlaynin

Jusper, Везде, но наугад строить смысла нет. Нужно разведать сперва территорию на наличие ресурсов.

Jusper Jusper

Zemlaynin, а карьеры можно будет везде запихать?
Или под них "особые" зоны будут?

Zemlaynin Zemlaynin

Это так скажем тестовое строительство, а так да у города будет зона влияния которую нужно будет расширять.

Jusper Jusper

А ссылка есть?

Jusper Jusper

Я не оч понял из скриншота, как вообще работает стройка. У игрока будет как бы поле строительства?

split97 split97

в игру нужно добавить время песочные часы в инвентаре, пока бегаешь наберается усталость и ты очень тормозной мобильный враг просто убевает

split97 split97

в игру нужно добавить время песочные часы в инвентаре, пока бегаешь наберается усталость и ты очень тормозной мобильный враг просто убевает

ViktorJaguar ViktorJaguar

Почему я нигде не могу найти нормальный туториал, где покажут как экипировать предмет (например, меч) в определенную (выделенную под оружие) ячейку???

Логотип проекта Unity

ScriptableObject - что это такое?

Одна из основных задач разработки игр - это хранение информации о игровом контенте. Варианты оружия, брони, различные предметы или может быть даже здания, доступные для строительства. В любом случае большинство разработчиков начинают свой путь с поиска базы данных и часто выбор падает на SQLite или загрузка данных из json файла описания. Но Unity содержит мощный встроенный инструмент для этих задач - ScriptableObject.

Что такое ScriptableObject?

ScriptableObject - это класс, который является основной частью игрового движка Unity и предоставляет возможность хранения игровых данных. В нем используются точно такие же правила сериализации данных, что при написании скриптов в MonoBehaviour. Можно подумать, что ScriptableObject ничем не будет отличаться от префабов с данными, записанными в MonoBehaviour объекте, но это не так. Важно понимать следующее: при создании нового экземпляра префаба, данные полностью копируются, позволяя переопределять для данного экземпляра некоторые значения. При использовании ScriptableObject, данные не копируются, все префабы будут использовать один и тот же ScriptableObject.
Рассмотрим простой пример хранения данных разных видов войск. Начнем с использованием MonoBehaviour и хранением всех войск как Prefab. Для этого создадим скрипт, описания юнита:

Unit.cs
using UnityEngine;

public class Unit : MonoBehaviour
{
    #region Данные юнита
	
    public Sprite Icon;
	
    public string Title;
	
    [Multiline]
    public string Description;
	
	public Mesh Model;
	
    public int MoneyCost;
	
    public int FoodCost;
	
    public float Damage;

    public float AttackCooldown;

    public float MaxHealth;

    public float MovementSpeed;

    public float RotationSpeed;
	
    #endregion

    #region Current state

    public int PlayerOwnerId;

    public float Health;

    #endregion
	
	private void Start() 
	{
		// Some initialization logic
		Health = MaxHealth;
	}
}

Теперь, на основе этого скрипта мы можем создать нужные нам префабы, добавить этот скрипт и указать необходимые параметры. Теперь давайте представим, что мы делаем массовую стратегию, где необходимо размещать от 500 юнитов одновременно. По очень грубым расчетам, один юнит будет занимать от 600 байт данных, соответственно 500 юнитов - 306 Кб. С одной стороны в этом ничего страшного нет, но не стоит забывать, что данных о юните может быть намного больше, в данном примере было отражено лишь самые основные. В реальной ситуации их намного больше. В игре StarPlosion настройки атаки являются сложным объектом, потому что мы храним информацию о типе атаки каждого орудия на корабле: тип атаки, тип снарядов, время вращения турелей, угол обзора, приоритеты целей и многое другое.

Попробуем реализовать тот же сценарий, но с использованием ScriptableObject. Для этого нам необходимо создать новый скрипт - UnitData.cs

UnitData.cs
using UnityEngine;

[CreateAssetMenu(fileName = "NewUnit", menuName = "Data/Unit", order = 51)]
public class UnitData : ScriptableObject
{
    public Sprite Icon;

    public string Title;

    [Multiline]
    public string Description;

    public int MoneyCost;

    public int FoodCost;

    public float Damage;

    public float AttackCooldown;

    public float MaxHealth;

    public float MovementSpeed;

    public float RotationSpeed;
}

Мы вынесли секцию с данными юнита и унаследовали наш класс от ScriptableObject. Исправим класс Unit

Unit.cs
using UnityEngine;

public class Unit : MonoBehaviour
{
    public UnitData Data;

    public int PlayerOwnerId;

    public float Health;
}

Теперь копии юнитов будут занимать не более 32 байт, соответственно для 500 юнитов - 16 Кб, а данные будут храниться в единственном экземпляре на всю игру и занимать около 500 байт.

Теперь для того, чтобы можно было создавать данные непосредственно в редакторе Unity необходимо добавить атрибут CreateAssetMenu у класса объекта данных. Познакомимся с его параметрами, ближе:

  • fileName - Имя создаваемого файла по-умолчанию
  • menuName - Имя пункта меню в редакторе Unity, можно создавать вложенные, использую раделитель - /
  • order - Порядок расположения в меню редактора. По-умолчанию, оно равно 0, тем самым пункт будет находится до пункта меню Folder, что не всегда удобно. Для того, чтобы определить параметр в отдельную секцию используйте значение 51. Это поможет избежать путаницы.
Пункт меню для создания ScriptableObject данных — ScriptableObject - что это такое? — Unity — DevTribe: инди-игры, разработка, сообщество (Unity, Статья, статья)
Пункт меню для создания ScriptableObject данных

Теперь мы с легкостью можем добавлять новых юнитов легким движением руки.

Структура проекта и загрузка всех данных

Одна из очень важных тем - это структура проекта. Каждый в меру своего опыта использует разные структуры папок внутри проекта Unity. Грамотное планирование и используемая стратегия хранения игровых данных очень сильно сказывается на сложности дальнейшего развития вашей игры. Пока игра маленькая и простая, это играет незначительную роль. Но со временем, проект перерастает в большого монстра и без грамотной организации данных, становится трудно что-то найти. К сожалению, я не могу посоветовать идеального решения, возможно его и не существует, но поделитесь пожалуйста в комментариях, какая структура у вашего проекта.

А я расскажу основные практики, которые использую сам при создании прототипов и своих проектов.

/Assets/
/Prefabs - папка со всеми префабами
	/Units
		/Warrior
			Warrior
			Warrior_Texture.png
			Warrior_Material.png
			Warrior_Death.wav
/Resources - данные, которые необходимо загружать на ходу
	/Data - здесь, хранятся все ScriptableObject
		/Units
	/Icons - иконки для динамической загрузки
/Scripts - папка для всех скриптов
	/Core - Основные классы и скрипты, без которых нельзя жить
		/AsyncWorkHandler.cs - Для примера, работа с многопоточностью
		/EventBus.cs - Для примера, шина сообщений
	/Data - Описание структуры данных
		/UnitData.cs - Для примера, описание данных юнита
	/Game - Игровые механики
		/Units
			/Unit.cs
			/SelectableUnit.cs
		/UI
			/UnitSelectionController.cs
	/Scenarious - Скрипты, отвечающие за логику уровней
		/Campaing
			/Level1_Intro.cs

Resources зачем она нужна?

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

Для того, чтобы загрузить данные всех возможных юнитов непосредственно внутри игры можно воспользоваться следующим кодом:

	var allUnitsData = Resources.LoadAll<UnitData>("Units");

Метод LoadAll<...>(...) позволяет загружать данные нужного типа из указанной папки, включая все вложенные под-папки. также можно загрузить и один экземпляр объекта при необходимости, используя метод Load<...>(...).

Послесловие

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

О возможностях использования ScriptableObject можно почитать в следующих статьях:

Смотрите также:


Комментарии



лёш, ты же не против если я дополню? )

скриптаблы, для понимания могут храниться на уровне ассета и на уровне сцены.
ниже про кейс со сценой.
может создаться иллюзия, что раз скриптаблы обеспечивают ссылочность, то они могут быть хорошим решением для работы внутри ваших компонентов (тем убедительнее, что юнити сами юзают их таким образом в одном их своих видео). Но это не так и использование скриптаблов таким образом это плохая практика. По мануалам юнити рекомендует (и не без оснований) использовать скриптаблы только как ассеты.

Самая большая разница в сериализации между MonoBehaviour и ScriptableObject в том как они относятся к сцене. Скриптаблы вопреки заявлению ничем не отличаются от монобех в формате юзания простого Object.Instantiate или обращения по ссылке. Более того - скриптаблы на уровне хранения это MonoBehaviour с определенным скриптом, не закрепленные за конкретным go (как бы ни было странным). Самый простой способ (пусть и чуток неверный) понять поведение скриптаблов - это представить что это компоненты, навешанные на саму сцену со всеми вытекающими. Так например, если мы сдублируем компоненты в редакторе - ссылка будет общей. Если мы попытаемся сделать перенос в другую сцену - ссылки сломаются (т.к. мы переносим GO, а скриптабл приаттачен к самой сцене).
Для случая когда нам просто нужна ссылочность есть смысл смотреть в сторону сторонних сериализаторов (привет OdinInspector)

Так же ты не упомянул несколько важных деталей про скриптейблы:

  • в рамках языка это объект с ручным управлением, который нужно вычищать из памяти руками, если сорите им в рантайме. Для создания используем ScripableObject.CreateInstance<T>, для очистки всякие UnityEngine.Object.DestroyImmediate() и т.п.
  • т.к. это UnityEngine.Object то для него обязательно учитывать операции Undo, помечать объект грязным при изменениях, и всё вытекающее.

И еще немного:

  • скриптаблы не заменяют собой json, иногда нужно вынести конфиги за сам билд, тут в любом случае не будет никакого скриптабла.

Devion, только рад за дополнение, Спасибо)

Если мы попытаемся сделать перенос в другую сцену - ссылки сломаются (т.к. мы переносим GO, а скриптабл приаттачен к самой сцене).

хм, а вот это интересно, не пробовал никогда такой кейс.

т.к. это UnityEngine.Object то для него обязательно учитывать операции Undo, помечать объект грязным при изменениях, и всё вытекающее.

А это я так понимаю для ситуации, когда пишется кастомный редактор для них, так?

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

Все верно, но зато их можно использовать совместно с AssetBundle, конечно это не дает такую же гибкость, как и чистый json, но позволяет поставлять данные частично. Об этом я конечно хотел бы отдельно написать.
Просто на мой взгляд, ScriptableObject предоставляет гору полезных фич, причем из коробки. Да, может быть это не самое лучшее решение их использовать, но не знать об их существовании - странно.

согласен, есть кейсы которые без них вообще не делаются, не знать или избегать их определенно неправильно

А это я так понимаю для ситуации, когда пишется кастомный редактор для них, так?

Так точно, либо любое изменение в объекте на стороне редактора.

Справка