Używanie platformy .NET 4.x w aparacie Unity

C# i .NET, technologie bazowe skryptów aparatu Unity, nadal otrzymywać aktualizacje od czasu, gdy firma Microsoft pierwotnie wydała je w 2002 roku. Jednak deweloperzy aparatu Unity mogą nie być świadomi stałego strumienia nowych funkcji dodanych do języka C# i programu .NET Framework, ponieważ przed aparatem Unity 2017.1 środowisko Unity używa równoważnego środowiska uruchomieniowego skryptów platformy .NET 3.5, brakuje lat aktualizacji.

Wraz z wydaniem środowiska Unity 2017.1 środowisko Unity wprowadziło eksperymentalną wersję środowiska uruchomieniowego skryptów uaktualnioną do wersji zgodnej z platformą .NET 4.6, C# 6.0. W środowisku Unity 2018.1 środowisko uruchomieniowe równoważne platformy .NET 4.x nie jest już uznawane za eksperymentalne, podczas gdy starsze środowisko uruchomieniowe równoważne platformy .NET 3.5 jest obecnie uważane za starszą wersję. Wraz z wydaniem aparatu Unity 2018.3 aparat Unity przewiduje, że uaktualnione środowisko uruchomieniowe skryptów będzie domyślnie wybrane, a następnie zaktualizuje jeszcze bardziej język C# 7. Aby uzyskać więcej informacji i najnowsze aktualizacje na tej mapie drogi, przeczytaj wpis w blogu aparatu Unity lub odwiedź forum Podglądy skryptów eksperymentalnych. W międzyczasie zapoznaj się z poniższymi sekcjami, aby dowiedzieć się więcej o nowych funkcjach dostępnych teraz za pomocą środowiska uruchomieniowego skryptów platformy .NET 4.x.

Wymagania wstępne

Włączanie środowiska uruchomieniowego skryptów platformy .NET 4.x w środowisku Unity

Aby włączyć środowisko uruchomieniowe skryptów .NET 4.x, wykonaj następujące kroki:

  1. Otwórz aplikację Player Ustawienia w inspektorze aparatu Unity, wybierając pozycję Edytuj > projekt Ustawienia > Player > Inne Ustawienia.

  2. W obszarze Nagłówek Konfiguracja kliknij listę rozwijaną Poziom zgodności interfejsu API i wybierz pozycję .NET Framework. Zostanie wyświetlony monit o ponowne uruchomienie aparatu Unity.

Screenshot showing the Select .NET 4.x equivalent.

Wybieranie między profilami .NET 4.x i .NET Standard 2.1

Po przełączeniu się do równoważnego środowiska uruchomieniowego skryptów platformy .NET 4.x można określić poziom zgodności interfejsu API przy użyciu menu rozwijanego w odtwarzaczu Ustawienia (Edytuj > projekt Ustawienia > Player). Dostępne są dwie opcje:

  • .NET Standard 2.1. Ten profil jest zgodny z profilem .NET Standard 2.1 opublikowanym przez program .NET Foundation. Aparat Unity zaleca platformę .NET Standard 2.1 dla nowych projektów. Jest mniejszy niż .NET 4.x, co jest korzystne dla platform ograniczonych rozmiarem. Ponadto środowisko Unity zobowiązało się do obsługi tego profilu na wszystkich platformach, które obsługuje aparat Unity.

  • .NET Framework. Ten profil zapewnia dostęp do najnowszego interfejsu API platformy .NET 4. Zawiera on cały kod dostępny w bibliotekach klas .NET Framework i obsługuje również profile platformy .NET Standard 2.1. Jeśli projekt wymaga części interfejsu API nieuwzględnianego w profilu .NET Standard 2.0, użyj profilu platformy .NET 4.x. Jednak niektóre części tego interfejsu API mogą nie być obsługiwane na wszystkich platformach aparatu Unity.

Więcej informacji na temat tych opcji można przeczytać we wpisie w blogu aparatu Unity.

Dodawanie odwołań do zestawów podczas korzystania z poziomu zgodności interfejsu API platformy .NET 4.x

W przypadku korzystania z ustawienia .NET Standard 2.1 na liście rozwijanej Poziom zgodności interfejsu API wszystkie zestawy w profilu interfejsu API są przywołyne i można ich używać. Jednak w przypadku korzystania z większego profilu platformy .NET 4.x niektóre zestawy dostarczane z aparatem Unity nie są domyślnie przywoływane. Aby korzystać z tych interfejsów API, należy ręcznie dodać odwołanie do zestawu. Zestawy aparatu Unity są dostarczane z programem w katalogu MonoBleedingEdge/lib/mono instalacji edytora aparatu Unity:

Screenshot showing the MonoBleedingEdge directory.

Jeśli na przykład używasz profilu .NET 4.x i chcesz go użyć HttpClient, musisz dodać odwołanie do zestawu dla biblioteki System.Net.Http.dll. Bez niego kompilator skarży się, że brakuje odwołania do zestawu:

Screenshot showing the missing assembly reference.

Program Visual Studio ponownie generuje pliki csproj i sln dla projektów aparatu Unity za każdym razem, gdy są otwierane. W związku z tym nie można dodawać odwołań do zestawów bezpośrednio w programie Visual Studio, ponieważ zostaną one utracone po ponownym otwarciu projektu. Zamiast tego należy użyć specjalnego pliku tekstowego o nazwie csc.rsp :

  1. Utwórz nowy plik tekstowy o nazwie csc.rsp w katalogu głównym projektu aparatu Unity Assets .

  2. W pierwszym wierszu w pustym pliku tekstowym wprowadź: -r:System.Net.Http.dll a następnie zapisz plik. Możesz zastąpić ciąg "System.Net.Http.dll" dowolnym dołączonym zestawem, który może nie zawierać odwołania.

  3. Uruchom ponownie edytor aparatu Unity.

Korzystanie ze zgodności platformy .NET

Oprócz nowych funkcji składni i języka C# środowisko uruchomieniowe skryptów platformy .NET 4.x zapewnia użytkownikom aparatu Unity dostęp do ogromnej biblioteki pakietów .NET, które są niezgodne ze starszym środowiskiem uruchomieniowym skryptów platformy .NET 3.5.

Dodawanie pakietów z narzędzia NuGet do projektu aparatu Unity

NuGet jest menedżerem pakietów dla platformy .NET. Pakiet NuGet jest zintegrowany z programem Visual Studio. Jednak projekty aparatu Unity wymagają specjalnego procesu dodawania pakietów NuGet, ponieważ po otwarciu projektu w środowisku Unity jego pliki projektu programu Visual Studio są ponownie generowane, cofanie niezbędnych konfiguracji. Aby dodać pakiet z narzędzia NuGet, do projektu aparatu Unity:

  1. Przeglądaj pakiet NuGet, aby zlokalizować zgodny pakiet, który chcesz dodać (.NET Standard 2.0 lub .NET 4.x). W tym przykładzie pokazano dodawanie Json.NET, popularnego pakietu do pracy z plikiem JSON, do projektu .NET Standard 2.0.

  2. Kliknij przycisk Pobierz:

    Screenshot showing the download button.

  3. Znajdź pobrany plik i zmień rozszerzenie z .nupkg na zip.

  4. W pliku zip przejdź do katalogu lib/netstandard2.0 i skopiuj plik Newtonsoft.Json.dll .

  5. W głównym folderze Assets projektu aparatu Unity utwórz nowy folder o nazwie Plugins. Wtyczki to specjalna nazwa folderu w apletie Unity. Aby uzyskać więcej informacji, zobacz dokumentację aparatu Unity.

  6. Wklej plik Newtonsoft.Json.dll do katalogu Plugins projektu Aparatu Unity.

  7. Utwórz plik o nazwie link.xml w katalogu Assets projektu aparatu Unity i dodaj następujący kod XML, zapewniając, że proces usuwania kodu bajtowego aparatu Unity nie usuwa niezbędnych danych podczas eksportowania do platformy IL2CPP. Chociaż ten krok jest specyficzny dla tej biblioteki, mogą wystąpić problemy z innymi bibliotekami, które używają Emocje ion w podobny sposób. Aby uzyskać więcej informacji, zobacz dokumentację aparatu Unity w tym artykule.

    <linker>
      <assembly fullname="System.Core">
        <type fullname="System.Linq.Expressions.Interpreter.LightLambda" preserve="all" />
      </assembly>
    </linker>
    

Teraz możesz użyć pakietu 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.
    }
}

Jest to prosty przykład użycia biblioteki, która nie ma zależności. Gdy pakiety NuGet bazują na innych pakietach NuGet, należy ręcznie pobrać te zależności i dodać je do projektu w taki sam sposób.

Nowa składnia i funkcje językowe

Użycie zaktualizowanego środowiska uruchomieniowego skryptów zapewnia deweloperom aparatu Unity dostęp do języka C# 8 oraz wielu nowych funkcji języka i składni.

Inicjatory autowłaściwych właściwości

W środowisku uruchomieniowym skryptów platformy .NET 3.5 aparatu Unity składnia automatycznej właściwości ułatwia szybkie definiowanie niezainicjowanych właściwości, ale inicjowanie musi nastąpić w innym miejscu skryptu. Teraz w środowisku uruchomieniowym .NET 4.x można zainicjować właściwości automatyczne w tym samym wierszu:

// .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;

Interpolacja ciągów

W przypadku starszego środowiska uruchomieniowego platformy .NET 3.5 łączenie ciągów wymaga niezręcznej składni. Teraz w środowisku uruchomieniowym $ platformy .NET 4.x funkcja interpolacji ciągów umożliwia wstawianie wyrażeń do ciągów w bardziej bezpośredniej i czytelnej składni:

// .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}");

Składowe z wyrażeniem w treści

Po udostępnieniu nowszej składni języka C# w środowisku uruchomieniowym .NET 4.x wyrażenia lambda mogą zastąpić treść funkcji, aby były bardziej zwięzłe:

// .NET 3.5
private int TakeDamage(int amount)
{
    return Health -= amount;
}

// .NET 4.x
private int TakeDamage(int amount) => Health -= amount;

Można również używać elementów członkowskich z wartościami wyrażeń w właściwościach tylko do odczytu:

// .NET 4.x
public string PlayerHealthUiText => $"Player health: {Health}";

Wzorzec asynchroniczny oparty na zadaniach (TAP)

Programowanie asynchroniczne umożliwia wykonywanie czasochłonnych operacji bez powodowania, że aplikacja nie odpowiada. Ta funkcja umożliwia również kodowi oczekiwanie na zakończenie czasochłonnych operacji przed kontynuowaniem kodu, który zależy od wyników tych operacji. Na przykład można poczekać na załadowanie pliku lub ukończenie operacji sieciowej.

W środowisku Unity programowanie asynchroniczne jest zwykle realizowane za pomocą kohroutyn. Jednak ponieważ język C# 5, preferowaną metodą programowania asynchronicznego na platformie .NET jest wzorzec asynchroniczny oparty na zadaniach (TAP) przy użyciu async słów kluczowych i await System.Threading.Task. Podsumowując, w async funkcji można wykonać await zadanie bez blokowania pozostałej części aplikacji z aktualizacji:

// 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 to złożony temat, z uwzględnieniem niuansów specyficznych dla aparatu Unity, które powinni wziąć pod uwagę deweloperzy. W związku z tym funkcja TAP nie jest uniwersalnym zamiennikiem kohroutyn w aproszcie Unity; jednak jest to inne narzędzie do użycia. Zakres tej funkcji wykracza poza ten artykuł, ale poniżej podano niektóre ogólne najlepsze rozwiązania i porady.

Wprowadzenie do interfejsu TAP za pomocą aparatu Unity

Te porady mogą pomóc w rozpoczęciu pracy z interfejsem TAP w środowisku Unity:

  • Funkcje asynchroniczne, które mają być oczekiwane, powinny mieć typ Task zwracany lub Task<TResult>.
  • Funkcje asynchroniczne zwracające zadanie powinny mieć sufiks "Async" dołączony do ich nazw. Sufiks "Async" pomaga wskazać, że funkcja powinna być zawsze oczekiwana.
  • Użyj tylko zwracanego async void typu dla funkcji, które uruchamiają funkcje asynchroniczne z tradycyjnego kodu synchronicznego. Takie funkcje nie mogą być oczekiwane i nie powinny mieć sufiksu "Async" w nazwach.
  • Aparat Unity używa aparatu UnitySynchronizationContext, aby upewnić się, że funkcje asynchroniczne są domyślnie uruchamiane w wątku głównym. Interfejs API aparatu Unity nie jest dostępny poza głównym wątkiem.
  • Istnieje możliwość uruchamiania zadań w wątkach w tle za pomocą metod takich jak Task.Run i Task.ConfigureAwait(false). Ta technika jest przydatna do odciążania kosztownych operacji z głównego wątku w celu zwiększenia wydajności. Jednak użycie wątków w tle może prowadzić do problemów, które są trudne do debugowania, takich jak warunki wyścigu.
  • Interfejs API aparatu Unity nie jest dostępny poza głównym wątkiem.
  • Zadania korzystające z wątków nie są obsługiwane w kompilacjach WebGL aparatu Unity.

Różnice między coroutines i TAP

Istnieją pewne ważne różnice między kohroutynami a tap / async-await:

  • Coroutines nie może zwracać wartości, ale Task<TResult> może.
  • Nie można umieścić yield w instrukcji try-catch, co sprawia, że obsługa błędów jest trudna dla coroutines. Jednak funkcja try-catch współpracuje z funkcją TAP.
  • Funkcja coroutine aparatu Unity nie jest dostępna w klasach, które nie pochodzą z klasy MonoBehaviour. Funkcja TAP doskonale nadaje się do programowania asynchronicznego w takich klasach.
  • W tym momencie aparat Unity nie sugeruje, aby TAP był hurtowym zamiennikiem koprutyn. Profilowanie to jedyny sposób poznania konkretnych wyników jednego podejścia w porównaniu do drugiego dla danego projektu.

nameof, operator

Operator nameof pobiera nazwę ciągu zmiennej, typu lub elementu członkowskiego. W niektórych przypadkach przydatne nameof są błędy rejestrowania i pobieranie nazwy ciągu wyliczenia:

// 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));
}

Atrybuty informacji o obiekcie wywołującym

Atrybuty informacji o obiekcie wywołującym zawierają informacje o obiekcie wywołującym metodę. Musisz podać wartość domyślną dla każdego parametru, którego chcesz użyć z atrybutem Info obiektu wywołującego:

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

Używanie statycznego

Użycie funkcji statycznych umożliwia używanie funkcji statycznych bez wpisywania nazwy klasy. Korzystając ze statycznych, możesz zaoszczędzić miejsce i czas, jeśli musisz użyć kilku funkcji statycznych z tej samej klasy:

// .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
    }
}

Zagadnienia dotyczące programu IL2CPP

Podczas eksportowania gry na platformy, takie jak iOS, aparat Unity użyje aparatu IL2CPP do "transpilowania" IL do kodu C++, który jest następnie kompilowany przy użyciu natywnego kompilatora platformy docelowej. W tym scenariuszu istnieje kilka funkcji platformy .NET, które nie są obsługiwane, takie jak części Emocje ion i użycie słowa kluczowegodynamic. Chociaż możesz kontrolować korzystanie z tych funkcji we własnym kodzie, mogą wystąpić problemy z używaniem bibliotek DLL i zestawów SDK innych firm, które nie zostały napisane w środowisku Unity i IL2CPP. Aby uzyskać więcej informacji na temat tego artykułu, zobacz dokumentację dotyczącą ograniczeń skryptów w witrynie aparatu Unity.

Ponadto, jak wspomniano w powyższym przykładzie Json.NET, aparat Unity spróbuje usunąć nieużywany kod podczas procesu eksportu IL2CPP. Chociaż ten proces zwykle nie jest problemem, w przypadku bibliotek używających Emocje ion może przypadkowo usunąć właściwości lub metody, które będą wywoływane w czasie wykonywania, których nie można określić w czasie eksportowania. Aby rozwiązać te problemy, dodaj plik link.xml do projektu, który zawiera listę zestawów i przestrzeni nazw, aby nie uruchamiać procesu usuwania. Aby uzyskać więcej informacji, zobacz Dokumentację aparatu Unity dotyczącą usuwania kodu bajtowego.

Przykładowy projekt aparatu Unity platformy .NET 4.x

Przykład zawiera przykłady kilku funkcji platformy .NET 4.x. Możesz pobrać projekt lub wyświetlić kod źródłowy w witrynie GitHub.

Dodatkowe zasoby