Использование .NET 4.x в Unity
C# и .NET, технологии, лежащие в основе базовых сценариев Unity, получают обновления с тех пор, как корпорация Майкрософт выпустила их в 2002 году. Но разработчики Unity могут не знать устойчивый поток новых функций, добавленных на язык C# и платформа .NET Framework, так как до Unity 2017.1 Unity использовал среду выполнения сценариев .NET 3.5, отсутствующие годы обновлений.
В выпуске Unity 2017.1 Unity представила экспериментальную версию среды выполнения сценариев, обновленную до совместимой версии .NET 4.6, C# 6.0. В Unity 2018.1 аналогичная среда выполнения .NET 4.x уже не считается экспериментальный, а вот более ранняя аналогичная среда выполнения .NET 3.5 теперь считается устаревшей. В выпуске Unity 2018.3 Unity проектируется для создания обновленной среды выполнения скриптов по умолчанию и обновления до C# 7. Дополнительные сведения и последние обновления на этой дорожной карте см. в записи блога Unity или посетите форум по предварительной версии экспериментальных сценариев. А пока прочтите следующие разделы этой статьи и узнайте о новых функциях, появившихся в среде выполнения сценариев .NET 4.x.
Необходимые компоненты
- Unity 2022.2 или более поздней версии (рекомендуется 2022.1.7)
- Visual Studio 2019
Включение среды выполнения сценариев .NET 4.x в Unity
Чтобы включить среду выполнения сценариев .NET 4.x, выполните следующие действия.
Откройте проигрыватель Параметры в инспекторе Unity, выбрав изменить > проект Параметры > проигрывателя > другие Параметры.
В разделе "Конфигурация" щелкните раскрывающийся список уровня совместимости API и выберите платформа .NET Framework. Вам будет предложено перезапустить Unity.
Выбор профилей .NET 4.x и .NET Standard 2.1
После перехода на эквивалентную среду выполнения сценариев .NET 4.x можно указать уровень совместимости API с помощью раскрывающегося меню проигрывателя Параметры (изменение > проекта Параметры > проигрывателя). Существует два варианта.
.NET Standard 2.1. Этот профиль соответствует профилю .NET Standard 2.1, опубликованному .NET Foundation. Unity рекомендует .NET Standard 2.1 для новых проектов. Эта версия ниже, чем .NET 4.x, что полезно для платформ ограниченного размера. Кроме того, Unity обеспечивает поддержку этого профиля на всех поддерживаемых Unity платформах.
платформа .NET Framework. Этот профиль предоставляет доступ к последней версии API .NET 4. Он включает весь код, доступный в библиотеках классов платформа .NET Framework, а также поддерживает профили .NET Standard 2.1. Выбирайте профиль .NET 4.x, если для вашего проекта требуется та часть API, которая не входит в профиль .NET Standard 2.0. При этом некоторые части этого API могут поддерживаться не на всех платформах Unity.
Дополнительные сведения об этих вариантах см. в этой записи блога Unity.
Добавление ссылок на сборки при использовании уровня совместимости API .NET 4.x
При использовании параметра .NET Standard 2.1 в раскрывающемся списке уровня совместимости API все сборки в профиле API ссылаются и доступны для использования. Однако при использовании более крупного профиля .NET 4.x некоторые сборки, поставляемые с Unity, по умолчанию не ссылаются. Чтобы использовать эти API, необходимо добавить ссылку на сборку вручную. Вы можете просмотреть сборки, которые входят в каталог MonoBleedingEdge/lib/mono вашей установки редактора Unity:
Например, если вы используете профиль .NET 4.x и хотите использовать HttpClient
, необходимо добавить ссылку на сборку System.Net.Http.dll. Если этого не сделать, компилятор выдаст сообщение об отсутствующей ссылке на сборку:
Visual Studio повторно создает ФАЙЛЫ CSPROJ и SLN для проектов Unity при каждом открытии. В результате нельзя добавлять ссылки на сборки непосредственно в Visual Studio, так как они будут потеряны при повторном открытии проекта. Вместо этого необходимо использовать специальный текстовый файл csc.rsp :
Создайте текстовый файл csc.rsp в корневом каталоге ресурсов проекта Unity.
В первой строке пустого текстового файла введите:
-r:System.Net.Http.dll
, а затем сохраните файл. Вместо System.Net.Http.dll можно указать любую добавленную сборку с отсутствующей ссылкой.Перезапустите редактор Unity.
Преимущества совместимости с .NET
Помимо новых возможностей синтаксиса и языка C#, среда выполнения сценариев .NET 4.x предоставляет пользователям Unity доступ к огромной библиотеке пакетов .NET, несовместимых с устаревшей средой выполнения сценариев .NET 3.5.
Добавление пакетов из NuGet в проект Unity
NuGet — это диспетчер пакетов для .NET. NuGet интегрирована в Visual Studio. Однако для проектов Unity требуется специальный процесс добавления пакетов NuGet, так как при открытии проекта в Unity файлы проектов Visual Studio создаются повторно, отменяя необходимые конфигурации. Чтобы добавить пакет из NuGet, в проект Unity:
Откройте NuGet и найдите совместимый пакет, который вам нужно добавить (.NET Standard 2.0 или .NET 4.x). В этом примере показано добавление Json.NET (популярного пакета для работы с JSON) в проект .NET Standard 2.0.
Нажмите кнопку Загрузить:
Найдите загруженный файл и измените его расширение с .nupkg на .zip.
В ZIP-файле зайдите в каталог lib/netstandard2.0 и скопируйте файл Newtonsoft.Json.dll.
В папке Assets в корневом каталоге проекта Unity создайте папку с именем Plugins (Подключаемые модули). В Unity "Plugins" — это имя специальной папки. Дополнительные сведения см. в документации по Unity.
Скопируйте файл Newtonsoft.Json.dll в каталог Plugins проекта Unity.
Создайте файл link.xml в каталоге ресурсов проекта Unity и добавьте следующий XML-код, чтобы процесс полоски байт-кода Unity не удалял необходимые данные при экспорте на платформу IL2CPP. Несмотря на то что это действие относится только к данной библиотеке, проблемы могут возникнуть и с другими библиотеками, которые используют отражение подобным образом. Дополнительные сведения см . в документации по Unity в этой статье.
<linker> <assembly fullname="System.Core"> <type fullname="System.Linq.Expressions.Interpreter.LightLambda" preserve="all" /> </assembly> </linker>
Теперь пакет Json.NET готов к работе.
using Newtonsoft.Json;
using UnityEngine;
public class JSONTest : MonoBehaviour
{
class Enemy
{
public string Name { get; set; }
public int AttackDamage { get; set; }
public int MaxHealth { get; set; }
}
private void Start()
{
string json = @"{
'Name': 'Ninja',
'AttackDamage': '40'
}";
var enemy = JsonConvert.DeserializeObject<Enemy>(json);
Debug.Log($"{enemy.Name} deals {enemy.AttackDamage} damage.");
// Output:
// Ninja deals 40 damage.
}
}
Это простой пример использования библиотеки, которая не имеет зависимостей. Если одни пакеты NuGet зависят от других, необходимо вручную загрузить эти зависимости и добавить их в проект таким же образом.
Новые возможности синтаксиса и языка
С помощью обновленной среды выполнения сценариев разработчики Unity получают доступ к C# 8 и узлу новых функций языка и синтаксиса.
Инициализаторы автосвойств
Синтаксис автосвойств в среде выполнения сценариев Unity .NET 3.5 позволял быстро определить неинициализированные свойства, но саму инициализацию нужно прописывать в сценарии отдельно. В среде выполнения .NET 4.x автосвойства можно инициализировать в той же строке:
// .NET 3.5
public int Health { get; set; } // Health has to be initialized somewhere else, like Start()
// .NET 4.x
public int Health { get; set; } = 100;
Интерполяция строк
В более ранней среде выполнения .NET 3.5 синтаксис объединения строк был громоздким. В новой среде выполнения .NET 4.x есть функция $
интерполяции строк, которая позволяет вставлять выражения в строки, используя более прямой и удобочитаемый синтаксис:
// .NET 3.5
Debug.Log(String.Format("Player health: {0}", Health)); // or
Debug.Log("Player health: " + Health);
// .NET 4.x
Debug.Log($"Player health: {Health}");
Элементы, воплощающие выражение
Новый синтаксис C# в среде выполнения .NET 4.x позволяет заменять тело функций на лямбда-выражения и, таким образом, делать их более краткими:
// .NET 3.5
private int TakeDamage(int amount)
{
return Health -= amount;
}
// .NET 4.x
private int TakeDamage(int amount) => Health -= amount;
Члены, заданные выражениями, теперь можно использовать также в свойствах, доступных только для чтения:
// .NET 4.x
public string PlayerHealthUiText => $"Player health: {Health}";
Асинхронный шаблон, основанный на задачах (TAP)
Асинхронное программирование позволяет выполнять длинные операции без зависания приложений. Кроме того, с помощью этой функции можно сделать так, чтобы код, в котором используются результаты ресурсоемких операций, выполнялся только после того, как будут выполнены эти операции, например после загрузки определенного файла или завершения сетевой операции.
В Unity асинхронное программирование обычно выполняется с соподпрограммами. Однако начиная с C# 5 предпочтительным методом асинхронного программирования в среде разработки .NET стал Асинхронный шаблон, основанный на задачах (TAP), с использованием ключевых слов async
и await
в System.Threading.Task. Таким образом, для функции async
можно задать ожидание завершения задачи (await
) без запрета обновлений в остальной части приложения:
// Unity coroutine
using UnityEngine;
public class UnityCoroutineExample : MonoBehaviour
{
private void Start()
{
StartCoroutine(WaitOneSecond());
DoMoreStuff(); // This executes without waiting for WaitOneSecond
}
private IEnumerator WaitOneSecond()
{
yield return new WaitForSeconds(1.0f);
Debug.Log("Finished waiting.");
}
}
// .NET 4.x async-await
using UnityEngine;
using System.Threading.Tasks;
public class AsyncAwaitExample : MonoBehaviour
{
private async void Start()
{
Debug.Log("Wait.");
await WaitOneSecondAsync();
DoMoreStuff(); // Will not execute until WaitOneSecond has completed
}
private async Task WaitOneSecondAsync()
{
await Task.Delay(TimeSpan.FromSeconds(1));
Debug.Log("Finished waiting.");
}
}
TAP — это сложная тема с определенными характерными для Unity нюансами, которые разработчикам необходимо учитывать, В результате TAP не является универсальной заменой для корутинов в Unity; однако это еще один инструмент для использования. Сфера применения этого компонента выходит за рамки данной статьи, но некоторые общие рекомендации и советы вы найдете ниже.
Начало работы с TAP в Unity
Эти советы помогут вам приступить к работе с TAP в Unity.
- Асинхронные функции, выполнения которых код будет дожидаться, должны иметь тип возвращаемого значения
Task
илиTask<TResult>
. - К именам асинхронных функций, возвращающих задачу, необходимо добавлять суффикс Async. Суффикс Async означает, что эта функция требует ожидания.
- Используйте тип возвращаемого значения
async void
только для тех функций, которые запускают асинхронные функции из традиционного синхронного кода. Такие функции не могут ожидать себя и не должны иметь суффикс "Async" в их именах. - Чтобы асинхронные функции по умолчанию выполнялись в основном потоке, в Unity используется unitySynchronizationContext. API Unity за пределами основного потока недоступен.
- Можно выполнять задачи в фоновых потоках с помощью таких методов, как
Task.Run
иTask.ConfigureAwait(false)
. Этот прием пригодится для выгрузки ресурсоемких операций из основного потока и повышения производительности. При этом использование фоновых потоков может привести к возникновению трудно устраняемых проблем, например привести к состоянию гонки. - API Unity за пределами основного потока недоступен.
- Задачи, использующие потоки, в сборках Unity WebGL не поддерживаются.
Различия между соподпрограммами и TAP
Между соподпрограммами и TAP (async-await) есть несколько важных различий.
- Корутины не могут возвращать значения, но
Task<TResult>
могут. - Вы не можете поместить
yield
инструкцию try-catch, что затрудняет обработку ошибок с корутинами. При этом try-catch работает с TAP. - Функция соподпрограмм в Unity недоступна в классах, которые не являются производными от MonoBehaviour. Для асинхронного программирования в таких классах отлично подходит TAP.
- В настоящее время Unity не предлагает TAP как полную замену соподпрограмм. Узнать результаты применения одного или другого подхода для конкретного проекта можно только путем профилирования.
Оператор nameof
Оператор nameof
получает строковое имя, тип или член переменной. В некоторых случаях nameof
удобно использовать для регистрации ошибок и получения строкового имени перечисления.
// Get the string name of an enum:
enum Difficulty {Easy, Medium, Hard};
private void Start()
{
Debug.Log(nameof(Difficulty.Easy));
RecordHighScore("John");
// Output:
// Easy
// playerName
}
// Validate parameter:
private void RecordHighScore(string playerName)
{
Debug.Log(nameof(playerName));
if (playerName == null) throw new ArgumentNullException(nameof(playerName));
}
Информационные атрибуты вызывающего объекта
Информационные атрибуты вызывающего объекта содержат информацию о вызывающем объекте метода. Для каждого параметра, который вы хотите использовать c информационным атрибутом вызывающего объекта, необходимо указать значение по умолчанию.
private void Start ()
{
ShowCallerInfo("Something happened.");
}
public void ShowCallerInfo(string message,
[System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
[System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
[System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
Debug.Log($"message: {message}");
Debug.Log($"member name: {memberName}");
Debug.Log($"source file path: {sourceFilePath}");
Debug.Log($"source line number: {sourceLineNumber}");
}
// Output:
// Something happened
// member name: Start
// source file path: D:\Documents\unity-scripting-upgrade\Unity Project\Assets\CallerInfoTest.cs
// source line number: 10
Директива using static
Директива using static позволяет использовать статические функции, не указывая имя класса. Директива using static помогает сэкономить место и время при использовании нескольких статических функций одного класса.
// .NET 3.5
using UnityEngine;
public class Example : MonoBehaviour
{
private void Start ()
{
Debug.Log(Mathf.RoundToInt(Mathf.PI));
// Output:
// 3
}
}
// .NET 4.x
using UnityEngine;
using static UnityEngine.Mathf;
public class UsingStaticExample: MonoBehaviour
{
private void Start ()
{
Debug.Log(RoundToInt(PI));
// Output:
// 3
}
}
Рекомендации по IL2CPP
При экспорте игры на такие платформы, как iOS, Unity будет использовать его подсистему IL2CPP для транспилирования il в код C++, который затем компилируется с помощью собственного компилятора целевой платформы. В этом сценарии существует несколько функций .NET, которые не поддерживаются, например части Рефлексия и использование dynamic
ключевое слово. Хотя вы можете управлять использованием этих функций в собственном коде, вы можете столкнуться с проблемами, используя сторонние библиотеки DLL и пакеты SDK, которые не были написаны с помощью Unity и IL2CPP. Дополнительные сведения об этой статье см . в документации по ограничениям сценариев на сайте Unity.
Кроме того, как уже говорилось в приведенном выше примере Json.NET, во время экспорта IL2CPP Unity попытается удалить неиспользуемый код. Хотя этот процесс обычно не является проблемой, с библиотеками, используюющими Рефлексия ion, он может случайно удалить свойства или методы, которые будут вызываться во время выполнения, которые не могут быть определены во время экспорта. Чтобы устранить эти проблемы, добавьте файл link.xml в проект, содержащий список сборок и пространств имен, для того чтобы не запускать процесс удаления. Дополнительные сведения см . в документации Unity по разделению байтов.
Образец проекта Unity в .NET 4.x
Данный образец содержит примеры использования нескольких функций .NET 4.x. Загрузить проект или просмотреть исходный код можно в GitHub.