Jusper Jusper

Случайно сделали битву сварщиков
https://devtribe.ru/p/slash-polygon

Jusper Jusper

Внезапно на сайте потерялся 15-й выпуск. Перевыложили.

Jusper Jusper

Я вам еще не закончил писать обратную связь по боевке, есть много вещей которые немного смутили, но они поправимы. Завтра, если перестанут дергать - чиркану. Спасибо за демку!

Jusper Jusper

ruggaraxe,

Да, в этом плане все ок. Логично, что графен на старой машине, если не упарываться, не взлетит. Но я рад, что это было не 5 фпс, как даже в некоторых АА (типа Pillars of Eternity в некоторых схватках...

Jusper Jusper

ruggaraxe,

Подкреплю ее к публикации.

ruggaraxe ruggaraxe

Jusper, вот ссылка на анкету (я затупил со ссылкой с топике, сорри)
https://docs.google.com/forms/d/e/1FAIpQLSd_Wn53lJFrnfGpWI2IX...

ruggaraxe ruggaraxe

Jusper, честно говоря, да на 800х600 даже не проверяли... :) сорри. Ориентировались на FullHD и выше. Хотя над интерфейсом конечно же надо еще хорошенько поработать.
Тултипы постараемся сделать обязательно к следующей версии...

GenElCon GenElCon

Jusper,

Наверное. В прошлом они сделали Endless Legend - посмотри и сразу станет ясно в какую сторону они работают.

Jusper Jusper

GenElCon,

Я не очень понял по трейлеру геймплей. Это что-то типа цивы? Или это RTS?

GenElCon GenElCon

Humankind от разработчиков Endless Legends (и Space, но тут важно именно Legends).
А также согревающие сердца олдов трейлеры Port Royal 4 и Knights of Honor.

Jusper Jusper

Похвалю темную атмосферу, отличную работу с освещением, тенями и шейдерами. Происходящее на экране корректно задает атмосферу до тех пор, пока не начнется сам экшон, об это напишу далее. Управление персонажем отзывчивое...

Jusper Jusper

Первое, оно же самое тяжелое - UI. Я конечно, понимаю, что 800x600 совсем уже не в моде (завтра проверю на нормальной широформатной машине). Заблюренный текст я еще прочитать могу, но вот конкретно размер его крайне мал...

...
Jusper Jusper

ruggaraxe, я поиграл на старом маке 2012 года (Macbook Pro, Intel HD 4000), рад что с учетом довольно нагруженной по свету и теням картинке игруля не лагает как последняя сволочь (лагает конечно, но очень терпимо...

Jusper Jusper

Вот тут можно посмотреть игровой процесс. Видно, что в Новиграде просаживается FPS.

Jusper Jusper

С учетом тотального количества наигранных на свиче часов, думаю, что именно Switch станет для меня платформой, где я пройду Ведьмака.

Jusper Jusper

alexprey, это первое. Второе это постэффект, которыЙ засвечивает весь песок.

alexprey alexprey

Jusper,

Да, по мне так перебор с интенсивностью освещения

Логотип проекта 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 предоставляет гору полезных фич, причем из коробки. Да, может быть это не самое лучшее решение их использовать, но не знать об их существовании - странно.

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

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

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

Справка