Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Die der Unity-Skripterstellung zugrunde liegenden Technologien C# und .NET wurden seit der Veröffentlichung durch Microsoft im Jahr 2002 fortlaufend aktualisiert. Möglicherweise ist Unity-Entwicklern jedoch der stetige Strom neuer Features nicht bekannt, die der C#-Sprache und .NET Framework hinzugefügt wurden, da Unity vor Unity 2017.1 eine .NET 3.5-äquivalente Skriptlaufzeit verwendet hat, wodurch jahrelang Updates verpasst wurden.
Mit dem Release von Unity 2017.1 stellte Unity eine experimentelle Version der Skriptlaufzeit vor, für die ein Upgrade auf Kompatibilität mit der C#-Version 6.0 für .NET 4.6 durchgeführt wurde. In Unity 2018.1 wird die .NET 4.x entsprechende Laufzeit nicht mehr als experimentell angesehen, während die ältere .NET 3.5 entsprechende Laufzeit nun als veraltete Version betrachtet wird. Zudem ist mit dem Release von Unity 2018.3 geplant, die aktualisierte Skriptlaufzeit zur Standardauswahl zu machen und sogar ein weiteres Update auf C#-Version 7 zu ermöglichen. Weitere Informationen und die neuesten Updates zu dieser Roadmap finden Sie im Blogbeitrag von Unity oder im Forum für Vorschauversionen von experimentellen Skripts. In der Zwischenzeit lesen Sie die folgenden Abschnitte, um mehr über die neuen Features zu erfahren, die ab sofort mit der .NET 4.x-Skriptlaufzeit verfügbar sind.
Voraussetzungen
- Unity 2022.2 oder höher (2022.1.7 empfohlen)
- Visual Studio 2019
Aktivieren der .NET 4.x-Skriptlaufzeit in Unity
Um die .NET 4.x-Skriptlaufzeit zu aktivieren, führen Sie die folgenden Schritte aus:
Öffnen Sie „PlayerSettings“ im Unity-Inspector, indem Sie Edit > Project Settings > Player >Other Settings (Bearbeiten > Projekteinstellungen > Player > Weitere Einstellungen) auswählen.
Klicken Sie unter der Überschrift Konfiguration auf das Dropdownmenü Api-Kompatibilitätsgrad, und wählen Sie .NET Framework aus. Sie werden aufgefordert, Unity neu zu starten.
Wahl zwischen .NET 4.x- und .NET Standard 2.1-Profilen
Nachdem Sie zur .NET 4.x entsprechenden Skriptlaufzeit gewechselt haben, können Sie den Api Compatibility Level (API-Kompatibilitätsgrad) in den PlayerSettings über das Dropdownmenü (Edit > Project Settings > Player (Bearbeiten > Projekteinstellungen > Player)) festlegen. Es gibt zwei Optionen:
.NET Standard 2.1. Dieses Profil entspricht dem von der .NET Foundation veröffentlichten .NET Standard 2.1-Profil. Für neue Projekte empfiehlt Unity die Verwendung von .NET Standard 2.1. Das Profil hat einen kleineren Umfang als .NET 4.x, was für Plattformen mit Größenbeschränkungen von Vorteil ist. Darüber hinaus hat sich Unity verpflichtet, dieses Profil auf allen von Unity unterstützten Plattformen zu unterstützen.
.NET Framework. Dieses Profil bietet Zugriff auf die neueste .NET 4-API. Es enthält den gesamten Code, der in den .NET Framework-Klassenbibliotheken verfügbar ist, und unterstützt zudem .NET Standard 2.1-Profile. Verwenden Sie das .NET 4.x-Profil, wenn es für Ihr Projekt erforderlich ist, dass ein Teil der API nicht im .NET Standard 2.0-Profil enthalten ist. Es kann jedoch sein, dass einige Teile dieser API nicht auf allen Plattformen von Unity unterstützt werden.
Weitere Informationen zu diesen Optionen finden Sie im Blogbeitrag von Unity.
Hinzufügen von Assemblyverweisen bei Verwendung des .NET 4.x-API-Kompatibilitätsgrads
Wenn Sie in der Dropdownliste Api Compatibility Level (API-Kompatibilitätsgrad) die .NET Standard 2.1-Einstellung auswählen, werden alle Assemblys im API-Profil referenziert und können verwendet werden. Wenn Sie jedoch das größere .NET 4.x-Profil verwenden, werden einige der von Unity mitgelieferten Assemblys standardmäßig nicht referenziert. Um diese APIs zu verwenden, müssen Sie manuell einen Assemblyverweis hinzufügen. Sie können die von Unity bereitgestellten Assemblys im Verzeichnis MonoBleedingEdge/lib/mono Ihrer Unity-Editor-Installation anzeigen:
Wenn Sie beispielsweise das .NET 4.x-Profil nutzen und HttpClient
verwenden möchten, müssen Sie für System.Net.Http.dll einen Assemblyverweis hinzufügen. Andernfalls wird der Compiler eine Meldung ausgeben, dass ein Assemblyverweis fehlt:
Bei jedem Öffnen von Unity-Projekten werden in Visual Studio CSPROJ- und SLN-Dateien neu generiert. Folglich können Sie Assemblyverweise nicht direkt in Visual Studio hinzufügen, da sie beim erneuten Öffnen des Projekts verloren gehen. Stattdessen muss eine spezielle Textdatei namens csc.rsp verwendet werden:
Erstellen Sie im Stammverzeichnis Assets Ihres Unity-Projekts eine neue Textdatei namens csc.rsp.
Geben Sie in der ersten Zeile der leeren Textdatei Folgendes ein:
-r:System.Net.Http.dll
. Speichern Sie anschließend die Datei. Sie können „System.Net.Http.dll“ durch jede enthaltene Assembly ersetzen, der möglicherweise ein Verweis fehlt.Starten Sie den Unity-Editor neu.
Vorteile der .NET-Kompatibilität
Zusätzlich zu den neuen C#-Syntax- und Sprachfunktionen bietet die .NET 4.x-Skriptlaufzeit Unity-Benutzern Zugriff auf eine sehr umfangreiche Bibliothek von .NET-Paketen, die mit der veralteten .NET 3.5-Skriptlaufzeit nicht kompatibel sind.
Hinzufügen von Paketen aus NuGet zu einem Unity-Projekt
NuGet ist der Paket-Manager für .NET. NuGet ist in Visual Studio integriert. Unity-Projekte erfordern jedoch einen speziellen Prozess zum Hinzufügen von NuGet-Paketen, da beim Öffnen eines Projekts in Unity die Visual Studio-Projektdateien neu generiert werden, wodurch die erforderlichen Konfigurationen rückgängig gemacht werden. So fügen Sie Ihrem Unity-Projekt ein Paket aus NuGet hinzu:
Durchsuchen Sie NuGet nach einem kompatiblen Paket, das Sie hinzufügen möchten (.NET Standard 2.0 oder .NET 4.x). Dieses Beispiel zeigt das Hinzufügen von Json.NET, einem beliebten Paket für die Arbeit mit JSON, zu einem .NET Standard 2.0-Projekt.
Klicken Sie auf die Schaltfläche Herunterladen:
Suchen Sie die heruntergeladene Datei, und ändern Sie die Erweiterung von .nupkg in .zip.
Navigieren Sie innerhalb der ZIP-Datei zum Verzeichnis lib/netstandard2.0, und kopieren Sie die Datei Newtonsoft.Json.dll.
Erstellen Sie im Stammordner Assets Ihres Unity-Projekts einen neuen Ordner mit dem Namen Plugins. In Unity ist „Plugins“ ein spezieller Ordnername. Weitere Informationen finden Sie in der Dokumentation zu Unity.
Fügen Sie die Datei Newtonsoft.Json.dll in das Verzeichnis Plugins Ihres Unity-Projekts ein.
Erstellen Sie eine Datei namens link.xml im Assets-Verzeichnis Ihres Unity-Projekts, und fügen Sie den folgenden XML-Code hinzu, um sicherzustellen, dass der Bytecode-Strippingprozess von Unity beim Exportieren auf eine IL2CPP-Plattform die erforderlichen Daten nicht entfernt. Obwohl dieser Schritt nur für diese Bibliothek gilt, können auch Probleme mit anderen Bibliotheken auftreten, die auf ähnliche Weise eine Reflektion verwenden. Weitere Informationen finden Sie in den Unity-Dokumenten zu diesem Thema.
<linker> <assembly fullname="System.Core"> <type fullname="System.Linq.Expressions.Interpreter.LightLambda" preserve="all" /> </assembly> </linker>
Nachdem alles vorbereitet ist, können Sie nun das Json.NET-Paket verwenden.
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.
}
}
Dies ist ein einfaches Beispiel für die Verwendung einer Bibliothek, die keine Abhängigkeiten aufweist. Wenn NuGet-Pakete auf andere NuGet-Pakete angewiesen sind, müssen Sie diese Abhängigkeiten manuell herunterladen und auf die gleiche Weise dem Projekt hinzufügen.
Neue Syntax- und Sprachfunktionen
Die Verwendung der aktualisierten Skriptlaufzeit gibt Unity-Entwicklern Zugriff auf C# 8 und eine Vielzahl neuer Sprachfunktionen und Syntaxelemente.
Auto-Eigenschaft-Initialisierer
In der .NET 3.5-Skriptlaufzeit von Unity ist es mit der Auto-Eigenschaft-Syntax einfach, schnell nicht initialisierte Eigenschaften zu definieren. Dabei erfolgt die Initialisierung jedoch an anderer Stelle in Ihrem Skript. Mit der .NET 4.x-Laufzeit ist es nun möglich, Auto-Eigenschaften in derselben Zeile zu initialisieren:
// .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;
Zeichenfolgeninterpolierung
Mit der älteren .NET 3.5-Laufzeit erforderte die Zeichenfolgenverkettung eine umständliche Syntax. Jetzt mit der .NET 4.x-Laufzeit lassen sich dank des Features für die $
-Zeichenfolgeninterpolation Ausdrücke in Zeichenfolgen in einer direkteren und besser lesbaren Syntax einfügen:
// .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}");
Ausdruckskörpermember
Mit der neueren C#-Syntax, die in der .NET 4.x-Laufzeit verfügbar ist, können lambda-Ausdrücke den Funktionskörper ersetzen, um ihn prägnanter zu gestalten:
// .NET 3.5
private int TakeDamage(int amount)
{
return Health -= amount;
}
// .NET 4.x
private int TakeDamage(int amount) => Health -= amount;
Sie können auch Ausdruckskörpermember in schreibgeschützten Eigenschaften verwenden:
// .NET 4.x
public string PlayerHealthUiText => $"Player health: {Health}";
Aufgabenbasiertes asynchrones Muster (TAP, Task-based Asynchronous Pattern)
Die asynchrone Programmierung erlaubt zeitaufwändige Vorgänge, ohne dass dadurch Ihre Anwendung nicht mehr reagiert. Zudem ermöglicht diese Funktionalität, dass auf das Ende zeitaufwendiger Vorgänge gewartet wird, bevor nachfolgender Code ausgeführt wird, der von den Ergebnissen dieser Vorgänge abhängt. Sie können beispielsweise warten, bis eine Datei geladen oder ein Netzwerkvorgang abgeschlossen ist.
In Unity wird die asynchrone Programmierung typischerweise mit Coroutinen durchgeführt. Seit C# 5 ist die bevorzugte Methode der asynchronen Programmierung in der .NET-Entwicklung jedoch das Task-based Asynchronous Pattern (TAP), oder aufgabenbasiertes asynchrones Muster, das async
- und await
-Schlüsselwörter mit System.Threading.Task verwendet. Kurz gesagt: In einer async
-Funktion kann await
genutzt werden, um auf die Fertigstellung eines Tasks zu warten, ohne dass der Rest Ihrer Anwendung an einer Aktualisierung gehindert wird:
// 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 ist ein komplexes Thema mit Unity-spezifischen Details, die Entwickler berücksichtigen sollten. Aus diesem Grund ist TAP kein universeller Ersatz für Coroutinen in Unity, aber es ist ein weiteres Werkzeug, das Entwickler nutzen können. Die Beschreibung aller Featuredetails würde den Rahmen dieses Artikels sprengen, daher sind nachfolgend lediglich einige allgemeinen Best Practices und Tipps aufgeführt.
Erste Schritte mit TAP in Unity
Diese Tipps können Ihnen bei den ersten Schritten mit TAP in Unity helfen:
- Asynchrone Funktionen, auf die gewartet werden soll, sollten den Rückgabetyp
Task
oderTask<TResult>
aufweisen. - Asynchrone Funktionen, die einen Task zurückgeben, sollten das Suffix „Async“ an ihren Namen angehängt haben. Das Suffix „Async“ weist darauf hin, dass auf eine Funktion immer gewartet werden sollte.
- Verwenden Sie den Rückgabetyp
async void
nur für Funktionen, die asynchrone Funktionen aus traditionellem synchronem Code auslösen. Solche Funktionen können selbst nicht erwartet werden und sollten das Suffix „Async“ nicht im Namen haben. - Unity stellt mit UnitySynchronizationContext sicher, dass asynchrone Funktionen standardmäßig im Hauptthread ausgeführt werden. Auf die Unity-API kann außerhalb des Hauptthreads nicht zugegriffen werden.
- Mit Methoden wie
Task.Run
undTask.ConfigureAwait(false)
ist es möglich, Tasks in Hintergrundthreads auszuführen. Diese Technik ist nützlich, um teure Vorgänge vom Hauptthread zu verlagern, um so die Leistung zu verbessern. Die Verwendung von Hintergrundthreads kann jedoch zu Problemen führen, die schwer zu debuggen sind, wie z.B. Racebedingungen. - Auf die Unity-API kann außerhalb des Hauptthreads nicht zugegriffen werden.
- Tasks, die Threads verwenden, werden in Unity-WebGL-Builds nicht unterstützt.
Unterschiede zwischen Coroutinen und TAP
Es gibt einige wichtige Unterschiede zwischen Coroutinen und TAP bzw. dem Warten auf eine asynchrone Funktion:
- Coroutinen können keine Werte zurückgeben,
Task<TResult>
jedoch schon. - Sie können ein
yield
-Element nicht in eine try-catch-Anweisung einfügen, was die Fehlerbehandlung bei Coroutinen erschwert. Eine try-catch-Anweisung funktioniert jedoch mit TAP. - Die Coroutinefunktion von Unity ist nicht in Klassen verfügbar, die nicht aus MonoBehaviour abgeleitet sind. TAP eignet sich hervorragend für die asynchrone Programmierung in solchen Klassen.
- An dieser Stelle wird TAP von Unity nicht als umfassender Ersatz für Coroutinen empfohlen. Eine Profilerstellung ist der einzige Weg, um die spezifischen Ergebnisse eines Ansatzes im Vergleich zum anderen für ein bestimmtes Projekt zu ermitteln.
nameof-Operator
Der nameof
-Operator ruft den Zeichenfolgenamen einer Variablen, eines Typs oder eines Members ab. Beispielsweise ist nameof
bei Protokollierungsfehlern nützlich, oder um den Zeichenfolgenamen einer Enumeration abzurufen:
// 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));
}
Aufruferinformationsattribute
Aufruferinformationsattribute liefern Informationen über den Aufrufer einer Methode. Sie müssen für jeden Parameter, den Sie mit einem Aufruferinformationsattribut verwenden möchten, einen Standardwert angeben:
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-Anweisung
Mithilfe von using static-Anweisungen können Sie statische Funktionen verwenden, ohne deren Klassennamen einzugeben. Durch using static-Anweisungen sparen Sie zudem Platz und Zeit, wenn Sie mehrere statische Funktionen derselben Klasse verwenden müssen:
// .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
}
}
Überlegungen zu IL2CPP
Beim Export Ihres Spiels auf Plattformen wie iOS verwendet Unity seine IL2CPP-Engine, um IL- in C++-Code zu „transpilieren“, der dann mit dem nativen Compiler der Zielplattform kompiliert wird. In diesem Szenario gibt es mehrere .NET-Features, die nicht unterstützt werden, wie etwa Teile von Reflexion und die Verwendung des Schlüsselworts dynamic
. Während sich die Verwendung dieser Features in Ihrem eigenen Code steuern lässt, können Sie auf Probleme mit DLLs und SDKs von Drittanbietern stoßen, die nicht mit Blick auf Unity und IL2CPP geschrieben wurden. Weitere Informationen zu diesem Artikel finden Sie auf der Unity-Website in der Dokumentation zu Einschränkungen bei der Skripterstellung.
Darüber hinaus wird Unity versuchen, wie im obigen Beispiel zu Json.NET erwähnt, ungenutzten Code während des IL2CPP-Exportprozesses zu entfernen. Dieser Vorgang stellt zwar in der Regel kein Problem dar, aber bei Bibliotheken, die Reflexion verwenden, kann es vorkommen, dass zur Laufzeit aufgerufene Eigenschaften oder Methoden versehentlich entfernt werden, wenn diese zum Zeitpunkt des Exports nicht bestimmt werden konnten. Fügen Sie zum Beheben dieser Probleme Ihrem Projekt eine Datei link.xml hinzu, die eine Liste von Assemblys und Namespaces enthält, die während des Prozesses nicht entfernt werden können. Weitere Informationen finden Sie in der Unity-Dokumentation zum Entfernen von Bytecode.
Beispiel für .NET 4.x-Unity-Projekt
Dieser Beispielcode enthält Beispiele für verschiedene .NET 4.x-Features. Auf GitHub können Sie das Projekt herunterladen oder den Quellcode anzeigen.
Zusätzliche Ressourcen
- Unity Blog - Scripting Runtime Improvements in Unity 2018.2 (Unity-Blog: Verbesserungen der Skriptlaufzeit in Unity 2018.2)
- Die Geschichte von C#
- Neues in C# 6
- Asynchronous programming in Unity, Using Coroutine and TAP (Asynchrone Programmierung in Unity unter Verwendung von Coroutine und TAP)
- Unity Forum - Experimental Scripting Previews (Unity-Forum: Vorschauversionen zu experimentellen Skripts)