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.
C# und .NET, die Technologien, die Unity-Skripting zugrunde liegen, haben weiterhin Updates erhalten, seit Microsoft sie ursprünglich im Jahr 2002 veröffentlicht haben. Unity-Entwicklern ist jedoch möglicherweise nicht bewusst, wie kontinuierlich der Sprache C# und dem .NET Framework neue Funktionen hinzugefügt wurden, denn vor Unity 2017.1 verwendete Unity eine Skriptlaufzeit entsprechend .NET 3.5, sodass Aktualisierungen aus mehreren Jahren nicht enthalten waren.
Mit der Version von Unity 2017.1 hat Unity eine experimentelle Version der Skripting-Laufzeit eingeführt, die auf eine .NET 4.6, C# 6.0 kompatible Version aktualisiert wurde. In Unity 2018.1 gilt die .NET 4.x-Entsprechungslaufzeit nicht mehr als experimentell, während die ältere .NET 3.5-Äquivalentlaufzeit jetzt als Legacyversion betrachtet wird. Mit der Veröffentlichung von Unity 2018.3 beabsichtigt Unity, die aktualisierte Skript-Laufzeitumgebung zur Standardoption zu machen und zudem ein weiteres Update auf C# 7 vorzunehmen. Weitere Informationen und die neuesten Updates auf dieser Roadmap finden Sie im Blogbeitrag von Unity, oder besuchen Sie das Forum "Experimental Scripting Previews". Schauen Sie sich in der Zwischenzeit die folgenden Abschnitte an, um mehr über die neuen Features zu erfahren, die jetzt mit der .NET 4.x-Skripting-Laufzeit 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
Führen Sie die folgenden Schritte aus, um die .NET 4.x-Skriptlaufzeit zu aktivieren:
Öffnen Sie PlayerSettings im Unity-Inspector, indem Sie Bearbeiten > Projekteinstellungen > Player > Andere Einstellungen auswählen.
Klicken Sie unter der Überschrift "Konfiguration" auf das Dropdownmenü "API-Kompatibilitätsebene", und wählen Sie .NET Framework aus. Sie werden aufgefordert, Unity neu zu starten.
Auswählen zwischen .NET 4.x- und .NET Standard 2.1-Profilen
Nachdem Sie zur .NET-4.x-äquivalenten Skriptlaufzeit gewechselt haben, können Sie die API-Kompatibilitätsstufe über das Dropdown-Menü in den PlayerSettings (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. Unity empfiehlt .NET Standard 2.1 für neue Projekte. Es ist kleiner als .NET 4.x, was für Plattformen mit begrenztem Speicherplatz von Vorteil ist. Darüber hinaus hat Unity sich verpflichtet, dieses Profil auf allen Plattformen zu unterstützen, die Unity unterstützt.
.NET Framework. Dieses Profil bietet Zugriff auf die neueste .NET 4-API. Er enthält den gesamten Code, der in den .NET Framework-Klassenbibliotheken verfügbar ist, und unterstützt auch .NET Standard 2.1-Profile. Verwenden Sie das .NET 4.x-Profil, wenn Ihr Projekt einen Teil der API erfordert, die nicht im .NET Standard 2.0-Profil enthalten ist. Einige Teile dieser API werden jedoch möglicherweise nicht auf allen Unity-Plattformen unterstützt.
Weitere Informationen zu diesen Optionen finden Sie im Blogbeitrag von Unity.
Hinzufügen von Assemblyreferenzen bei Verwendung der .NET 4.x-API-Kompatibilitätsstufe
Bei Verwendung der Einstellung .NET Standard 2.1 im Dropdownmenü "API-Kompatibilitätsebene" werden alle Assemblys im API-Profil referenziert und verwendbar. Bei Verwendung des größeren .NET 4.x-Profils werden jedoch einige der mit Unity ausgelieferten Assemblys standardmäßig nicht referenziert. Um diese APIs zu verwenden, müssen Sie manuell einen Assemblyverweis hinzufügen. Sie können die von Unity mitgelieferten Assemblys im Verzeichnis MonoBleedingEdge/lib/mono Ihrer Unity-Editor-Installation einsehen:
Wenn Sie beispielsweise das .NET 4.x-Profil verwenden und HttpClient verwenden möchten, müssen Sie einen Assemblyverweis auf System.Net.Http.dll hinzufügen. Ohne sie wird der Compiler beanstanden, dass Ihnen ein Assemblyverweis fehlt:
Visual Studio generiert .csproj- und .sln-Dateien für Unity-Projekte jedes Mal neu, wenn sie geöffnet werden. Daher 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 eine neue Textdatei namens "csc.rsp " im Stammverzeichnis des Unity-Projekts.
Geben Sie in der ersten Zeile in der leeren Textdatei Folgendes ein:
-r:System.Net.Http.dllund speichern Sie die Datei. Sie können "System.Net.Http.dll" durch eine beliebige mitgelieferte Assembly ersetzen, bei der möglicherweise ein Verweis fehlt.Starten Sie den Unity-Editor neu.
Nutzen der .NET Kompatibilität
Zusätzlich zu neuen C#-Syntax- und Sprachfeatures bietet die .NET 4.x-Skripting-Laufzeit Unity-Benutzern Zugriff auf eine riesige Bibliothek von .NET Paketen, die mit der älteren .NET 3.5-Skripting-Laufzeit nicht kompatibel sind.
Hinzufügen von Paketen von NuGet zu einem Unity-Projekt
NuGet ist der Paket-Manager für .NET. NuGet ist in Visual Studio integriert. Unity-Projekte erfordern jedoch ein spezielles Verfahren zum Hinzufügen von NuGet-Paketen, da beim Öffnen eines Projekts in Unity dessen Visual-Studio-Projektdateien neu generiert werden, wodurch erforderliche Konfigurationen rückgängig gemacht werden. So fügen Sie ihrem Unity-Projekt ein Paket aus NuGet hinzu:
Durchsuchen Sie NuGet, um ein kompatibles Paket zu suchen, das Sie hinzufügen möchten (.NET Standard 2.0 oder .NET 4.x). In diesem Beispiel wird das Hinzufügen von Json.NET, einem beliebten Paket für die Arbeit mit JSON, zu einem .NET Standard 2.0-Projekt veranschaulicht.
Klicken Sie auf die Schaltfläche " Herunterladen ":
Suchen Sie die heruntergeladene Datei, und ändern Sie die Erweiterung von .nupkg in .zip.
Navigieren Sie in der ZIP-Datei zum Verzeichnis lib/netstandard2.0 , und kopieren Sie die Newtonsoft.Json.dll Datei.
Erstellen Sie im Stammordner "Assets " Ihres Unity-Projekts einen neuen Ordner mit dem Namen Plugins. Plugins ist ein spezieller Ordnername in Unity. Weitere Informationen finden Sie in der Unity-Dokumentation.
Fügen Sie die Newtonsoft.Json.dll Datei in das Plug-In-Verzeichnis Ihres Unity-Projekts ein.
Erstellen Sie eine Datei mit dem Namenlink.xml im Ressourcenverzeichnis Ihres Unity-Projekts, und fügen Sie den folgenden XML-Code hinzu, um sicherzustellen, dass der Bytecode-Strippingprozess von Unity beim Exportieren in eine IL2CPP-Plattform keine erforderlichen Daten entfernt. Obwohl dieser Schritt spezifisch für diese Bibliothek ist, treten möglicherweise Probleme mit anderen Bibliotheken auf, die Reflection auf ähnliche Weise verwenden. Weitere Informationen finden Sie in den Dokumentationen zu Unity in diesem Artikel.
<linker> <assembly fullname="System.Core"> <type fullname="System.Linq.Expressions.Interpreter.LightLambda" preserve="all" /> </assembly> </linker>
Da alles vorhanden ist, können Sie jetzt 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 dem Projekt auf die gleiche Weise hinzufügen.
Neue Syntax- und Sprachfeatures
Durch die Verwendung der aktualisierten Skriptlaufzeit erhalten Unity-Entwickler Zugriff auf C# 8 und eine Vielzahl neuer Sprachfeatures und -syntax.
Initialisierer für automatisch implementierte Eigenschaften
In Unitys .NET-3.5-Scripting-Runtime erleichtert die Auto-Property-Syntax, nicht initialisierte Eigenschaften schnell zu definieren, aber die Initialisierung muss an anderer Stelle im Skript erfolgen. Mit der .NET 4.x-Laufzeit ist es nun möglich, automatische 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
Bei der älteren .NET 3.5-Laufzeit erforderte die Zeichenfolgenverkettung eine ungünstige Syntax. Mit der .NET 4.x-Laufzeit ermöglicht das Feature für die $ Zeichenfolgeninterpolation nun das Einfügen von Ausdrücken in Zeichenfolgen in eine direktere und lesbare Syntax:
// .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 Textkörper von Funktionen ersetzen, um sie 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)
Durch die asynchrone Programmierung können zeitaufwendige Vorgänge ausgeführt werden, ohne dass die Anwendung nicht mehr reagiert. Diese Funktionalität ermöglicht es Ihrem Code auch, auf den Abschluss zeitaufwendiger Vorgänge zu warten, bevor der Code fortgesetzt wird, der von den Ergebnissen dieser Vorgänge abhängt. Sie können z. B. warten, bis eine Datei geladen wurde oder ein Netzwerkvorgang abgeschlossen ist.
In Unity erfolgt die asynchrone Programmierung in der Regel mit Coroutines. Seit C# 5 ist jedoch das aufgabenbasierte asynchrone Muster (TAP) unter Verwendung der Schlüsselwörter async und await mit System.Threading.Task die bevorzugte Methode der asynchronen Programmierung in der .NET-Entwicklung. 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, wobei Unity-spezifische Nuancen Entwickler berücksichtigen sollten. Daher ist TAP kein universeller Ersatz für Koroutinen in Unity; es ist jedoch ein weiteres Werkzeug, das man nutzen kann. Der Umfang dieses Features geht über diesen Artikel hinaus, aber einige allgemeine bewährte Methoden und Tipps finden Sie unten.
Erste Schritte mit TAP in Unity
Diese Tipps helfen Ihnen bei den ersten Schritten mit TAP in Unity:
- Asynchrone Funktionen, auf die gewartet werden soll, sollten den Rückgabetyp
TaskoderTask<TResult>aufweisen. - Asynchrone Funktionen, die eine Aufgabe zurückgeben, sollten das Suffix "Async" an ihre Namen angefügt haben. Das Suffix "Async" gibt an, dass eine Funktion immer erwartet werden soll.
- Verwenden Sie nur den
async voidRückgabetyp für Funktionen, die asynchrone Funktionen aus herkömmlichem synchronem Code auslösen. Solche Funktionen können nicht selbst erwartet werden und sollten nicht das Suffix "Async" in ihren Namen haben. - Unity verwendet unitysynchronizationContext, um sicherzustellen, dass asynchrone Funktionen standardmäßig im Hauptthread ausgeführt werden. Auf die Unity-API kann nicht außerhalb des Hauptthreads zugegriffen werden.
- Es ist möglich, Aufgaben auf Hintergrundthreads mit Methoden wie
Task.RunundTask.ConfigureAwait(false)auszuführen. Diese Technik ist nützlich, um teure Vorgänge aus dem Hauptthread zu entladen, um die Leistung zu verbessern. Die Verwendung von Hintergrundthreads kann jedoch zu Problemen führen, die schwierig zu debuggen sind, z. B. Rennbedingungen. - Auf die Unity-API kann nicht außerhalb des Hauptthreads zugegriffen werden.
- Aufgaben, 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:
- Coroutines können keine Werte zurückgeben, aber
Task<TResult>können. - Sie können ein
yield-Element nicht in eine try-catch-Anweisung einfügen, was die Fehlerbehandlung bei Coroutinen erschwert. Try-Catch funktioniert jedoch mit TAP. - Das Coroutine-Feature von Unity ist in Klassen, die nicht von MonoBehaviour abgeleitet sind, nicht verfügbar. TAP eignet sich hervorragend für die asynchrone Programmierung in solchen Klassen.
- Unity empfiehlt TAP derzeit nicht als vollständigen Ersatz für Coroutines. Profilerstellung ist die einzige Möglichkeit, die spezifischen Ergebnisse eines Ansatzes im Vergleich zum anderen für ein bestimmtes Projekt zu kennen.
nameof-Operator
Der nameof Operator ruft den Zeichenfolgennamen einer Variablen, eines Typs oder eines Elements 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));
}
Attribute für Anruferinformationen
Caller-Info-Attribute stellen Informationen über den Aufrufer einer Methode bereit. Sie müssen für jeden Parameter, den Sie mit einem Caller Info-Attribut 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
Verwenden statischer Daten
Mit statischen Funktionen können Sie statische Funktionen verwenden, ohne den Klassennamen einzugeben. Mit der Verwendung statischer Funktionen können Sie Platz und Zeit sparen, wenn Sie mehrere statische Funktionen aus 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
}
}
IL2CPP-Überlegungen
Wenn Sie Ihr Spiel auf Plattformen wie iOS exportieren, verwendet Unity sein IL2CPP-Modul zum Transpilieren von IL in C++-Code, der dann mit dem systemeigenen Compiler der Zielplattform kompiliert wird. In diesem Szenario gibt es mehrere .NET Features, die nicht unterstützt werden, z. B. Teile von Reflection und die Verwendung des dynamic Schlüsselworts. Sie können zwar die Verwendung dieser Features in Ihrem eigenen Code steuern, aber möglicherweise treten Probleme mit DLLs und SDKs von Drittanbietern auf, die nicht mit Unity und IL2CPP geschrieben wurden. Weitere Informationen zu diesem Artikel finden Sie in den Dokumentationen zu Skripteinschränkungen auf der Unity-Website.
Darüber hinaus versucht Unity, wie im obigen Json.NET Beispiel erwähnt, nicht verwendeten Code während des IL2CPP-Exportvorgangs zu entfernen. Während dieser Prozess in der Regel kein Problem darstellt, können Bibliotheken, die Reflection verwenden, versehentlich Eigenschaften oder Methoden entfernen, die zur Laufzeit aufgerufen werden, die zur Exportzeit nicht bestimmt werden können. Um diese Probleme zu beheben, fügen Sie Ihrem Projekt eine link.xml-Datei hinzu, die eine Liste von Assemblies und Namespaces enthält, auf die der Stripping-Prozess nicht angewendet werden soll. Weitere Informationen finden Sie in den Dokumentationen von Unity zum Entfernen von Bytecode.
.NET 4.x-Beispielprojekt für Unity
Das Beispiel enthält Beispiele für mehrere .NET 4.x-Features. Sie können das Projekt herunterladen oder den Quellcode auf GitHub anzeigen.
Weitere Ressourcen
- Unity Blog – Verbesserungen bei der Skriptlaufzeit in Unity 2018.2
- Geschichte von C#
- Neuerungen in C# 6
- Asynchrone Programmierung in Unity mit Coroutine und TAP
- Unity Forum - Experimental Scripting Previews (Unity-Forum: Vorschauversionen zu experimentellen Skripts)