Mit dem Unity Scripting kann das Verhalten von Objekten und Interaktionen in einer Anwendung gesteuert werden. Über Skripte lassen sich somit Logiken erstellen, um verschiedene Objekte und Elemente mit der Umgebung interagieren zu lassen. In der Unity-Engine wird dafür die Programmiersprache C# verwendet.
Im Wesentlichen handelt es sich bei einem Skript um eine Sammlung von Anweisungen, die von Unity interpretiert und ausgeführt werden. Damit lassen sich beispielsweise
- die Eigenschaften von GameObjects ändern,
- Animationen steuern,
- Kollisionen erkennen,
- Audio abspielen,
- Benutzereingaben verarbeiten,
- und vieles mehr.
Mit C# können komplexe Funktionen mit relativ wenig Code entwickelt werden. Zudem ist eine Nutzung der Unity-API möglich, um auf verschiedene Komponenten der Engine zuzugreifen. Das Unity Scripting ist somit wichtiger Bestandteil für die Entwicklung deiner Anwendungen.
Bevor wir näher auf das Scripting eingehen, möchte ich dir zunächst zeigen, warum es so wichtig ist…
Warum ist das Scripting wichtig?
Wie bereits erwähnt, ist das Unity Scripting für die Entwicklung von Anwendungen und das Game Development sehr wichtig. Hier einige Punkte, die von entscheidender Bedeutung sind:
- Flexibilität und Anpassungsmöglichkeiten: Mit dem Unity Scripting besitzt du die Flexibilität, um das Verhalten von Objekten nach deinen Vorstellungen anzupassen. Damit kannst du komplexe Interaktionen erstellen.
- Interaktivität und Logik: Ohne das Unity Scripting wären Anwendungen sehr statisch. Skripte ermöglichen es, dass sich Objekte bewegen, auf Benutzereingaben reagieren und auf andere Ereignisse reagieren.
- Benutzerfreundlichkeit und GUI: Über Skripte hast du die Möglichkeit, eine benutzerfreundliche GUI zu erstellen. So lassen sich Optionen, Einstellungen, Menüs und vieles mehr darstellen.
- Effizienz und Wiederverwendbarkeit: Durch die Integration von Unity Skripten lassen sich häufig verwendete Funktionen und Abläufe effizient wiederverwenden. Das kann die Entwicklung von Anwendungen deutlich beschleunigen.
- Erweiterbarkeit und Modifizierbarkeit: Das Unity Scripting ermöglicht es dir, Anpassungen vorzunehmen, ohne den Kern der 3D Engine anzupassen. Somit ist die Engine durch Skripte erweiterbar.
Hinweis: Mit dem ultimativen Unity Cheat Sheet erhältst du einen detaillierten Überblick über Tipps & Tricks mit dem Umgang von Unity. Sichere dir jetzt das Cheat Sheet, um deine Anwendungen noch einfacher und effektiver erstellen zu können!
Übersicht Skriptzyklus
Der Skriptzyklus ist beim Unity Scripting ein wichtiger Prozess, der das Verhalten von GameObjects und anderen Elementen steuert. Daher ist es wichtig diesen Zyklus zu verstehen.
Er stellt einen wiederholten Ablauf dar, der mit jedem Frame ausgeführt wird. Ein Frame entspricht einem einzelnen Bild, das auf dem Bildschirm gerendert wird. Dabei wird jeder Frame in diskrete Zeitschritte unterteilt und ein Skript in jedem dieser Zeitschritte ausgeführt.
Ein diskreter Zeitschritt kann grob in folgende Phasen unterteilt werden:
- Start-Phase: Die Start-Methode wird beim Start der Anwendung nur einmal ausgeführt und kann beispielsweise Anweisungen für die Initialisierung beinhalten.
- FixedUpdate-Phase: In dieser Phase werden Skripte mit der FixedUpdate-Methode ausgeführt. Sie erfolgt mit einem festen Zeitintervall und ist besonders für die physikalische Simulation wichtig. Skripte, die die Bewegung oder Interaktion von Objekten basierend auf physikalischen Kräften steuern, sollten in dieser Phase ausgeführt werden.
- Input-Phase: Die Verarbeitung von Benutzereingaben wie Tastatureingaben, Mausklicks oder Touch-Ereignissen erfolgt in der Input-Phase. Unity erfasst die Eingaben und speichert sie in einem Puffer. So können sie in nachfolgenden Phasen verarbeitet werden.
- Update-Phase: Hier werden Skripte mit der Update-Methode ausgeführt. Diese Phase erfolgt in jedem Frame und wird für die meisten Aktualisierungen und Logiken verwendet. Skripte können unter anderem die Eigenschaften von GameObjects ändern, Berechnungen durchführen oder Benutzereingaben verarbeiten.
- LateUpdate-Phase: In dieser Phase werden Skripte mit der LateUpdate-Methode ausgeführt. Sie wird auch in jedem Frame ausgeführt. Diese Methode kann verwendet werden, wenn bestimmte Änderungen erst nach allen anderen Phasen angewandt werden sollen.
- Rendering-Phase: Schließlich wird die Szene in der Rendering-Phase gerendert und auf dem Bildschirm angezeigt. Diese Phase erfolgt nach der Ausführung aller Skripte.
Darstellung Skriptzyklus
Der Skriptzyklus wiederholt sich in jedem Frame, was ein kontinuierliches und flüssiges Erlebnis gewährleistet. Es ist daher sehr wichtig die Skripte effizient zu gestalten. Komplexe oder ineffiziente Skripte reduzieren die Framerate und können auch zu Performanceproblemen führen.
In der nachfolgenden Abbildung findest du den Skriptzyklus in ausführlicher Form.
Übersicht über den Skriptzyklus in Unity (Quelle: https://docs.unity3d.com/Manual/ExecutionOrder.html)
Installation und Nutzung einer IDE (Integrated Development Environment) für das Unity Scripting
Eine Integrated Development Environment (IDE) ist ein unverzichtbares Tool, um für Unity C# Skripte zu entwickeln. Eine IDE stellt eine Umgebung dar, die das Schreiben, Testen und Debuggen von Code ermöglicht. In diesem Abschnitt erhältst du eine kurze Einführung zur Installation und Nutzung einer IDE für das Unity Scripting.
Welche IDEs stehen zur Verfügung?
Es gibt mehrere beliebte IDEs, die du verwenden kannst. Zu den bekanntesten gehören:
- Visual Studio: Visual Studio ist eine voll ausgestattete IDE von Microsoft. Sie bietet eine umfassende Unity C#-Unterstützung, eine Option zur Codevervollständigung und eine integrierte Debugger-Funktionalität.
- Visual Studio Code: Visual Studio Code (VS Code) ist ein leichtgewichtiger, plattformübergreifender Code-Editor von Microsoft. Er bietet ebenfalls eine hervorragende Unterstützung für Unity C# und ist besonders als schnelle und einfache IDE bekannt.
- JetBrains Rider: JetBrains Rider ist eine leistungsstarke IDE, die von JetBrains entwickelt wurde. Sie bietet eine umfangreiche Unity C#-Unterstützung, eine intuitive Benutzeroberfläche und fortschrittliche Debugging-Funktionen.
Installation der IDE
Um eine IDE für Unity Scripting zu verwenden, ist es zunächst wichtig, die gewünschte IDE zu installieren. Die Installation ist in der Regel einfach und unkompliziert:
- Besuche die Webseite der jeweiligen IDE (z. B. Visual Studio, Visual Studio Code oder JetBrains Rider).
- Lade die Installationsdatei für dein Betriebssystem herunter.
- Führe die Installationsdatei aus und folge den Anweisungen des Installationsassistenten, um die IDE auf deinem PC zu installieren.
Oberfläche von Visual Studio Code für Unity C# Scripting
Einrichtung von Unity und Integration der IDE
Nach der Installation must du Unity mit der IDE verbinden, um die Entwicklung von Skripten zu erleichtern. Unity bietet eine nahtlose Integration mit den gängigen IDEs:
- Öffne Unity und erstelle ein neues Projekt oder öffne ein vorhandenes Projekt.
- Navigiere zu Edit → Preferences → External Tools.
- Wähle unter External Script Editor die installierte IDE aus der Liste aus.
Unity IDE einrichten
Grundlegende Nutzung der IDE
Nach der Integration kannst du in Unity problemlos Skripte erstellen oder vorhandene Skripte öffnen:
- Klicke dazu im Unity-Editor mit der rechten Maustaste im Project-Fenster und wähle Create → C# Script. Damit kannst du ein neues Skript erstellen.
- Dieses Skript kannst du nun per Doppelklick öffnen. Es sollte sich automatisch mit der ausgewählten IDE öffnen.
- Erstelle dein erstes Skript oder bearbeite ein vorhandenes Skript.
Grundbestandteile eines Skripts
In Unity C# Skripten werden verschiedene Elemente verwendet, um das Verhalten von GameObjects und die Logik zu steuern. In diesem Abschnitt erkläre ich dir die grundlegenden Bestandteile eines Skripts. Dazu gehören unter anderem:
- Variablen
- Methoden (Funktionen)
- Zugriffsmodifizierer
- Klassen
- Namespaces
- Ereignisse
- Coroutine
Ich werde dir die einzelnen Bestandteile Schritt-für-Schritt erklären. Starten wir gleich mit den Variablen…
Variablen
Variablen sind Speicherorte, in denen Werte gespeichert werden können. Sie dienen dazu, um Daten während der Laufzeit einer Anwendung zu speichern und zu verwalten. Abhängig von den Daten lassen sich in C# verschiedene Datentypen für Variablen verwenden.
Ganzzahlige Datentypen
byte
: 8-Bit-Ganzzahl. Werte von 0 bis 255.short
: 16-Bit-Ganzzahl. Werte von –32.768 bis 32.767int
: 32-Bit-Ganzzahl mit Vorzeichen. Werte von -2.147.483.648 bis 2.147.483.647uint
: 32-Bit-Ganzzahl ohne Vorzeichen. Werte von 0 bis 4.294.967.295long
: 64-Bit-Ganzzahl. Werte von -9.223.372.036.854.775.808 bis 9.223.372.036.854.775.807
public class IntegerVariables : MonoBehaviour
{
public byte smallNumber = 8;
public short temperature = -10;
public int negativeValue = -1000000;
public uint positiveValue = 4212454;
public long largeNumber = 1234567890;
}
Fließkommazahl Datentypen
float
: 32-Bit-Fließkommazahl. Typischerweise verwendet für reale Zahlen mit Nachkommastellen. Genauigkeit ~6–9 Dezimalstellendouble
: 64-Bit-Fließkommazahl. Größere Genauigkeit alsfloat
. Genauigkeit ~15–17 Dezimalstellen
public class FloatVariables : MonoBehaviour
{
public float speed = 5.523f;
public double pi = 3.141592653589793;
}
Boolesche Datentypen
bool
: Boolescher Wert (True/False).
public class BooleanVariables : MonoBehaviour
{
public bool isGameStarted = false;
public bool isGameOver = true;
}
Zeichenketten Datentyp
string
: Eine Zeichenkette, die Text oder Zeichen darstellt.
public class StringVariables : MonoBehaviour
{
public string playerName = "John";
public string welcomeMessage = "Willkommen im Spiel!";
}
Zeichen Datentyp
char
: Ein einzelnes Unicode-Zeichen.
public class CharVariables : MonoBehaviour
{
public char firstLetter = 'A';
public char secondLetter = 'B';
public char specialChar = '@';
public char newline = '\n'; // Zeilenumbruch
public char tab = '\t'; // Tabulator
public char backslash = '\\'; // Backslash
}
Auflistung Datentypen
enum
: Eine Menge benannter Konstantenwerte.
public class EnumVariables : MonoBehaviour
{
public enum GameState { MainMenu, Playing, Paused, GameOver };
GameState currentState = GameState.MainMenu;
}
Diese Auflistung verschafft dir einen guten Überblick über die verschiedenen Datentypen, die sich in Unity C# verwenden lassen. Beachte dabei, dass die richtige Auswahl des Datentyps wichtig ist, um den Speicher effizient zu nutzen. So stellst du sicher, dass Variablen den benötigten Wertebereich abdecken.
Hinweis: Mit dem ultimativen Unity Cheat Sheet erhältst du einen detaillierten Überblick über Tipps & Tricks mit dem Umgang von Unity. Sichere dir jetzt das Cheat Sheet, um deine Anwendungen noch einfacher und effektiver erstellen zu können!
Methoden (Funktionen)
Methoden sind Code-Blöcke, die eine spezifische Aufgabe ausführen. Sie ermöglichen die Strukturierung und die Wiederverwendbarkeit von Code, indem sie eine bestimmte Funktionalität in einer Einheit zusammenfassen. So könnte beispielsweise die Addition von verschiedenen Variablen in einer Methode ausgeführt werden. Die Begriffe Funktionen und Methoden können in der objektorientierten Programmierung manchmal synonym verwendet werden. Funktionen in einer Klasse werden auch als Methoden bezeichnet.
Diese Methoden können Übergabeparameter besitzen, um Daten an sie zu übergeben. Und sie können einen Rückgabewert haben, um ein Ergebnis zurückzugeben. Hier eine Übersicht der allgemeinen Syntax:
// Methode ohne Parameter und ohne Rückgabewert (void)
public void methodName()
{
// Code, der in der Methode ausgeführt wird
}
// Methode mit Parametern und ohne Rückgabewert (void)
public void methodName(dataType parameter1, dataType parameter2, ...)
{
// Code, der die Parameter verwendet
}
// Methode mit Parametern und Rückgabewert
public returnType methodName(dataType parameter1, dataType parameter2, ...)
{
// Code, der die Parameter verwendet
return result; // Rückgabewert vom Datentyp 'returnType'
}
Hierzu ein kleines Beispielskript:
using UnityEngine;
public class SimpleMethods : MonoBehaviour
{
// Methode ohne Rückgabewert und ohne Parameter
void Start()
{
GreetPlayer();
int playerScore = 100;
DisplayScore(playerScore);
int totalPoints = CalculateTotalPoints(playerScore, 50);
Debug.Log("Gesamtpunktzahl: " + totalPoints);
}
// Methode ohne Rückgabewert und ohne Parameter
public void GreetPlayer()
{
Debug.Log("Hallo! Willkommen im Spiel!");
}
// Methode ohne Rückgabewert und mit Parameter
public void DisplayScore(int score)
{
Debug.Log("Punktestand: " + score);
}
// Methode mit Rückgabewert und mit Parametern
public int CalculateTotalPoints(int currentScore, int bonusPoints)
{
int totalPoints = currentScore + bonusPoints;
return totalPoints;
}
}
In diesem Skript verwenden wir drei Methoden:
GreetPlayer
: Diese Methode hat weder einen Rückgabewert noch einen Parameter. Sie gibt eine einfache Begrüßungsnachricht beim Start der Anwendung aus.DisplayScore(int score)
: Diese Methode hat keinen Rückgabewert, aber einen Parameterscore
. Sie gibt den Punktestand des Spielers im Debug-Fenster aus.CalculateTotalPoints(int currentScore, int bonusPoints)
: Diese Methode hat einen Rückgabewert(int)
und zwei ParametercurrentScore
undbonusPoints
. Sie berechnet durch einfache Addition der Parameter die Gesamtpunktzahl. Im Anschluss wird sie über den Rückgabewert zurückgegeben.
Zugriffsmodifizierer
Wie du mit Sicherheit festgestellt hast, gibt es beispielsweise bei Methoden noch den Zusatz public. Das ist der sogenannte Zugriffsmodifizierer. Er definiert die Sichtbarkeit einer Variable oder einer Methode innerhalb eines Skripts.
Es gibt verschiedene Zugriffsmodifizierer, die sich wie folgt verwenden lassen:
- public: Die Variable oder Methode ist von überall zugänglich. Es gibt keine Einschränkung für den Zugriff.
- private: Die Variable oder Methode ist nur innerhalb der Klasse sichtbar und kann von außerhalb der Klasse nicht direkt aufgerufen werden.
- protected: Die Variable oder Methode ist in der Klasse sichtbar und kann auch von abgeleiteten Klassen (Subklassen) aufgerufen werden.
- internal: Die Variable oder Methode ist in der Assembly (DLL oder ausführbare Datei) sichtbar, in der sie definiert ist. Dieser Zugriffsmodifizierer ermöglicht den Zugriff auf Elemente innerhalb des gleichen Projekts, aber nicht in externen Assemblies.
- protected internal: Kombination von protected und internal. Die Variable oder Methode ist in der Assembly sichtbar und kann von abgeleiteten Klassen in derselben oder einer anderen Assembly aufgerufen werden.
- private protected: Die Variable oder Methode ist nur in abgeleiteten Klassen derselben Assembly sichtbar.
Hier ist ein Beispiel für die Verwendung von Zugriffsmodifizierern in Unity C#:
using UnityEngine;
public class MyClass : MonoBehaviour
{
// Zugriff von überall
public int publicVariable;
// Nur innerhalb dieser Klasse zugänglich
private int privateVariable;
// In dieser Klasse und abgeleiteten Klassen zugänglich
protected int protectedVariable;
// Innerhalb der gleichen Assembly zugänglich
internal int internalVariable;
// In dieser Klasse, abgeleiteten Klassen und gleicher Assembly zugänglich
protected internal int protectedInternalVariable;
// Nur in abgeleiteten Klassen derselben Assembly zugänglich
private protected int privateProtectedVariable;
// Zugriff von überall
public void PublicMethod()
{
// Code, der in der Methode ausgeführt wird
}
// Nur innerhalb dieser Klasse aufrufbar
private void PrivateMethod()
{
// Code, der in der Methode ausgeführt wird
}
// In dieser Klasse und abgeleiteten Klassen aufrufbar
protected void ProtectedMethod()
{
// Code, der in der Methode ausgeführt wird
}
// Innerhalb der gleichen Assembly aufrufbar
internal void InternalMethod()
{
// Code, der in der Methode ausgeführt wird
}
// In dieser Klasse, abgeleiteten Klassen und gleicher Assembly aufrufbar
protected internal void ProtectedInternalMethod()
{
// Code, der in der Methode ausgeführt wird
}
// Nur in abgeleiteten Klassen derselben Assembly aufrufbar
private protected void PrivateProtectedMethod()
{
// Code, der in der Methode ausgeführt wird
}
}
Es ist wichtig, den Zugriffsmodifizierer bewusst zu wählen, um die Sichtbarkeit von Variablen und Methoden entsprechend den Anforderungen der Anwendung zu steuern.
Klassen
Beim Unity Scripting ist eine Klasse ein grundlegendes Konzept der objektorientierten Programmierung. Eine Klasse dient als Bauplan oder Vorlage für die Erstellung von Objekten. Sie definiert die Eigenschaften (Variablen) und das Verhalten (Methoden) eines Objekts. Klassen ermöglichen die Strukturierung und Wiederverwendbarkeit des Codes. Hier eine Übersicht der allgemeinen Syntax:
public class ClassName
{
// Klasseneigenschaften (Variablen)
public dataType propertyName;
// Konstruktor
public ClassName()
{
// Initialisierungscode
}
// Klassenmethoden
public returnType MethodName(dataType parameter1, dataType parameter2, ...)
{
// Methodencode
return result; // Rückgabewert vom Datentyp 'returnType'
}
}
Hierzu ein kleines Beispielskript:
using UnityEngine;
// Definition der Klasse Player
public class Player
{
// Klasseneigenschaften (Variablen)
public string playerName;
public int playerHealth;
// Konstruktor
public Player(string name, int health)
{
playerName = name;
playerHealth = health;
}
// Klassenmethode
public void DisplayPlayerInfo()
{
Debug.Log("Spielername: " + playerName);
Debug.Log("Gesundheit: " + playerHealth);
}
}
public class ClassExample : MonoBehaviour
{
void Start()
{
// Erstellung eines Spielerobjekts und Initialisierung mit Werten
Player player1 = new Player("John", 100);
// Aufruf der Methode DisplayPlayerInfo()
player1.DisplayPlayerInfo();
}
}
In diesem Beispiel definieren wir die Klasse Player
. Sie besitzt die Eigenschaften playerName
und playerHealth
sowie eine Methode DisplayPlayerInfo()
. In der Start()-Methode wird ein Spielerobjekt erstellt und mit den Werten John und 100 initialisiert. Anschließend wird die Methode DisplayPlayerInfo()
aufgerufen, um die Informationen des Spielers im Debug-Fenster anzuzeigen.
Hinweis: Wenn du noch tiefer in die Unity C# Programmierung eintauchen möchtest, kannst du den Online-Kurs auf unserer Plattform nutzen. Eine Vorschau dazu findest du hier.
Namespaces
Namespaces werden verwendet, um den Code in logische Gruppen zu organisieren und Namenskonflikte zwischen verschiedenen Klassen und Funktionen zu vermeiden. Sie sind Container, die den Code in separaten Kontexten kapseln. Somit ist eine bessere Strukturierung und Verwaltung des Codes möglich.
Der Namespace einer Klasse ist wie eine Adresse, die die Position dieser im Code angibt. Eine Klasse kann in einem bestimmten Namespace definiert werden. Andere Klassen können wiederum auf diese Klasse zugreifen, indem sie den entsprechenden Namespace verwenden. Hier eine Übersicht der allgemeinen Syntax:
namespace MyNamespace
{
// Klassen und Code innerhalb des Namespaces
public class MyClass
{
// Klassendefinition
}
// Weitere Klassen und Code innerhalb des Namespaces
}
Hier ein Skriptbeispiel, das die Verwendung von Namespaces in Unity C# zeigt:
using UnityEngine;
// Definition des Namespaces "GameEntities"
namespace GameEntities
{
// Klasse "Player" im Namespace "GameEntities"
public class Player
{
public string playerName;
public int playerHealth;
public Player(string name, int health)
{
playerName = name;
playerHealth = health;
}
public void DisplayPlayerInfo()
{
Debug.Log("Spielername: " + playerName);
Debug.Log("Gesundheit: " + playerHealth);
}
}
}
public class NamespaceExample : MonoBehaviour
{
void Start()
{
// Erstellung eines Spielerobjekts im Namespace "GameEntities"
GameEntities.Player player1 = new GameEntities.Player("John", 100);
// Aufruf der Methode DisplayPlayerInfo() aus dem Namespace "GameEntities"
player1.DisplayPlayerInfo();
}
}
In diesem Beispiel haben wir den Namespace GameEntities
definiert, der die Klasse Player
enthält. Diese Klasse ist im Namespace enthalten. Dadurch kann nur durch die Verwendung des entsprechenden Pfads (GameEntities.Player
) darauf zugegriffen werden.
In der Start()
-Methode erstellen wir ein Spielerobjekt, indem wir den Namespace-Pfad GameEntities.Player
verwenden. Anschließend rufen wir die Methode DisplayPlayerInfo()
aus dem Namespace GameEntities
auf.
Ereignisse
Ereignisse (Events) sind ein wichtiges Konzept in der Programmierung. Sie ermöglichen die Kommunikation zwischen verschiedenen Codeabschnitten, ohne dass sie voneinander wissen müssen. Sie sind besonders nützlich, um auf Benutzerinteraktionen oder andere ereignisgesteuerte Aktionen zu reagieren. Durch die Verwendung von Ereignissen wird der Code entkoppelt und die Flexibilität erhöht.
Hier ein Unity C# Beispielskript, das die Verwendung von Ereignissen demonstriert:
Hinweis: Mit dem ultimativen Unity Cheat Sheet erhältst du einen detaillierten Überblick über Tipps & Tricks mit dem Umgang von Unity. Sichere dir jetzt das Cheat Sheet, um deine Anwendungen noch einfacher und effektiver erstellen zu können!
using UnityEngine;
using UnityEngine.Events;
public class PlayerHealth : MonoBehaviour
{
// Die maximale Gesundheit des Spielers
public int maxHealth = 100;
// Die aktuelle Gesundheit des Spielers
private int currentHealth;
// Ereignisdefinition: UnityEvents, die von anderen Skripten abonniert werden können
// Ereignis, das ausgelöst wird, wenn der Spieler Schaden erleidet
public UnityEvent OnPlayerDamaged;
// Ereignis, das ausgelöst wird, wenn der Spieler stirbt
public UnityEvent OnPlayerDied;
void Start()
{
// Initialisierung der Gesundheit zu Beginn mit der maximalen Gesundheit
currentHealth = maxHealth;
}
public void TakeDamage(int damageAmount)
{
// Schaden von der aktuellen Gesundheit abziehen
currentHealth -= damageAmount;
// Überprüfen, ob der Spieler noch am Leben ist (Gesundheit größer als 0)
if (currentHealth > 0)
{
// Falls der Spieler noch am Leben ist, wird das Ereignis OnPlayerDamaged ausgelöst
OnPlayerDamaged.Invoke();
}
else
{
// Falls die Gesundheit des Spielers auf 0 oder darunter fällt, wird das Ereignis OnPlayerDied ausgelöst
Die();
}
}
private void Die()
{
// Methode, die aufgerufen wird, wenn der Spieler stirbt
// Hier wird das Ereignis OnPlayerDied ausgelöst, um anderen Skripten mitzuteilen, dass der Spieler gestorben ist
OnPlayerDied.Invoke();
}
}
In diesem Skript haben wir die Klasse PlayerHealth
, die die Gesundheit des Spielers darstellt. Sie verfügt über zwei Ereignisse: OnPlayerDamaged
und OnPlayerDied
. Diese Ereignisse sind vom Typ UnityEvent
, einem speziellen Unity-Datentyp. Er ermöglicht es auf Ereignisse zu reagieren, ohne dass direkte Verknüpfungen zwischen Klassen erforderlich sind.
In der Start()
-Methode wird die aktuelle Gesundheit des Spielers auf das maximale Gesundheitsniveau (maxHealth
) gesetzt. Die Methode TakeDamage(int damageAmount)
wird aufgerufen, wenn der Spieler Schaden erleidet. Sie reduziert die aktuelle Gesundheit und löst das Ereignis OnPlayerDamaged
aus, wenn der Spieler noch am Leben ist. Wenn die Gesundheit des Spielers auf 0 oder darunter fällt, wird die Methode Die()
aufgerufen, die das Ereignis OnPlayerDied
auslöst.
Andere Klassen können dieses Skript verwenden, indem sie sich für die Ereignisse OnPlayerDamaged
und OnPlayerDied
registrieren. Sie können darauf reagieren ohne direkten Zugriff auf die PlayerHealth
-Klasse haben zu müssen. Hierzu ein weiteres Skript, das die Events abonniert und darauf reagiert:
using UnityEngine;
public class PlayerUI : MonoBehaviour
{
// Referenz zur PlayerHealth-Komponente des Spielers
public PlayerHealth playerHealth;
void OnEnable()
{
// Abonnieren der Ereignisse
playerHealth.OnPlayerDamaged.AddListener(OnPlayerDamaged);
playerHealth.OnPlayerDied.AddListener(OnPlayerDied);
}
void OnDisable()
{
// Abmelden von den Ereignissen, um mögliche Leaks zu vermeiden
playerHealth.OnPlayerDamaged.RemoveListener(OnPlayerDamaged);
playerHealth.OnPlayerDied.RemoveListener(OnPlayerDied);
}
void OnPlayerDamaged()
{
// Reaktion auf das Ereignis "OnPlayerDamaged"
Debug.Log("Der Spieler wurde beschädigt!");
}
void OnPlayerDied()
{
// Reaktion auf das Ereignis "OnPlayerDied"
Debug.Log("Der Spieler ist gestorben!");
}
}
Die Methoden OnPlayerDamaged und OnPlayerDied enthalten die Reaktionen auf die Ereignisse. In diesem Beispiel werden lediglich Debug-Nachrichten ausgeben.
Coroutine
Coroutines sind spezielle Arten von Methoden beim Unity Scripting. Sie ermöglichen es, Aktionen über mehrere Frames hinweg zu verteilen. Normalerweise führt Unity den Code sequentiell aus, sodass eine Zeile nach der anderen abgearbeitet wird.
Coroutines erlauben es, Teile des Codes so zu pausieren, dass andere Aktionen zwischenzeitlich ausgeführt werden können. Sie sind besonders nützlich für zeitabhängige oder animierte Vorgänge, bei denen bestimmte Aktionen schrittweise auszuführen sind.
Hier ein Beispielskript, das die Verwendung von Coroutines zeigt:
using UnityEngine;
using UnityEngine.Events;
using System.Collections;
public class PlayerHealth : MonoBehaviour
{
public int maxHealth = 100;
private int currentHealth;
private bool isInvulnerable = false;
// Ereignisse (Events) definieren
public UnityEvent OnPlayerDamaged;
public UnityEvent OnPlayerDied;
void Start()
{
currentHealth = maxHealth;
}
// Diese Methode wird aufgerufen, wenn der Spieler Schaden erleidet
public void TakeDamage(int damageAmount)
{
// Überprüfen, ob der Spieler gerade unverwundbar ist
if (!isInvulnerable)
{
// Reduziere die Gesundheit um den Schaden
currentHealth -= damageAmount;
// Überprüfen, ob der Spieler noch am Leben ist
if (currentHealth > 0)
{
// Falls der Spieler noch am Leben ist, wird das Ereignis OnPlayerDamaged ausgelöst
OnPlayerDamaged.Invoke();
// Coroutine starten, um den Spieler für eine bestimmte Zeit unverwundbar zu machen
StartCoroutine(InvulnerabilityDuration(2f)); // Spieler für 2 Sekunden unverwundbar machen
}
else
{
// Falls der Spieler tot ist, wird das Ereignis OnPlayerDied ausgelöst
Die();
}
}
}
// Methode, die aufgerufen wird, wenn der Spieler stirbt
private void Die()
{
OnPlayerDied.Invoke();
}
// Coroutine, um den Spieler für eine bestimmte Zeit unverwundbar zu machen
IEnumerator InvulnerabilityDuration(float duration)
{
// Spieler wird unverwundbar
isInvulnerable = true;
Debug.Log("Spieler ist für " + duration + " Sekunden unverwundbar!");
// Warte für die angegebene Zeit in Sekunden
yield return new WaitForSeconds(duration);
// Zeit ist vorbei, Spieler ist wieder verwundbar
isInvulnerable = false;
Debug.Log("Spieler ist nicht mehr unverwundbar!");
}
}
Zu Beginn wird die maximale Gesundheit des Spielers festgelegt und die aktuelle Gesundheit auf diesen Wert gesetzt. Zusätzlich gibt es eine Variable isVulnerable
, die anzeigt, ob der Spieler unverwundbar ist.
Die Methode TakeDamage
wird aufgerufen, wenn der Spieler Schaden erleidet. Wenn der Spieler nicht unverwundbar ist, wird der Schaden von seiner aktuellen Gesundheit abgezogen. Ist die Gesundheit noch größer als null, wird eine Coroutine InvulnerabilityDuration
gestartet, um den Spieler für eine bestimmte Zeit (in diesem Fall 2 Sekunden) unverwundbar zu machen.
Wird die Methode TakeDamage
innerhalb dieser Zeit ausgeführt, wird kein Schaden erzeugt. Denn die Variable isVulnerable
ist für 2 Sekunden true
. Ist die Zeit abgelaufen, wird die Variable auf false
gesetzt und die Coroutine beendet. Die Coroutine InvulnerabilityDuration
macht den Spieler für die angegebene Zeit unverwundbar. Nach Ablauf der angegebenen Zeit wird der Spieler wieder verwundbar gemacht.
Wichtige Klassen für das Scripting
Nachdem wir uns die Grundbestandteile von Skripten angesehen haben, möchte ich dir in diesem Abschnitt wichtige Klassen beim Unity Scripting vorstellen. In Unity C# gibt es eine Vielzahl von wichtigen Klassen, die bei der Entwicklung eine zentrale Rolle spielen.
Diese Klassen bieten Funktionen und Eigenschaften, um die Interaktion zwischen Objekten und Komponenten zu steuern. Dazu gehören unter anderem:
- GameObject
- MonoBehaviour
- Vektoren
- Transform
- Time
- Mathf
- Debug
Es gibt natürlich noch weitere Klassen. Wir wollen uns aber auf die gerade genannten konzentrieren. Starten wir gleich mit der Klasse GameObject…
GameObject
Die Klasse GameObject repräsentiert ein Objekt aus der Szene. Es kann verschiedene Komponenten wie Grafiken, Kollisionserkennungen, Skripte usw. besitzen. Ein GameObject dient als Container für diese Komponenten und ermöglicht die Interaktion und Steuerung von Objekten während der Laufzeit der Anwendung.
Hier ist ein kleines Beispielskript dazu:
using UnityEngine;
public class GameObjectExample : MonoBehaviour
{
void Start()
{
// Erstellen eines GameObjects
GameObject cube = new GameObject("MyCube");
// Hinzufügen einer MeshRenderer Komponente
cube.AddComponent();
// Hinzufügen einer BoxCollider Komponente
cube.AddComponent();
// Position der Komponente ändern
cube.transform.position = new Vector3(0f, 2f, 0f);
}
}
MonoBehaviour
MonoBehaviour ist die Basisklasse für Skripte in Unity. Sie ermöglicht das Erstellen von Skripten, die mit GameObjects interagieren können. Skripte, die von MonoBehaviour erben, können Methoden wie Start
, Update
und FixedUpdate
implementieren. So ist es möglich auf verschiedene Phasen des Skriptzyklus zu reagieren.
Auch hierzu ein kleines Beispiel:
using UnityEngine;
public class MonoBehaviourExample : MonoBehaviour
{
void Start()
{
Debug.Log("Das Skript wurde gestartet!");
}
void Update()
{
transform.Rotate(Vector3.up, 30f * Time.deltaTime);
}
}
Vektoren
Vektoren sind 3D-Objekte, die oft zur Darstellung von Positionen, Richtungen und Bewegungen verwendet werden. Unity bietet verschiedene Vektor-Klassen, wie zum Beispiel Vector2
für 2D-Vektoren und Vector3
für 3D-Vektoren.
Hier ist ein kleines Beispielskript:
using UnityEngine;
public class VectorExample : MonoBehaviour
{
void Start()
{
// 2D-Vektor: Bewegung eines Objektes in der XY-Ebene
Vector2 movement2D = new Vector2(2f, 3f);
Debug.Log("2D-Bewegung: " + movement2D);
// 3D-Vektor: Position eines Objektes im 3D-Raum
Vector3 position3D = new Vector3(1f, 0f, 4f);
Debug.Log("3D-Position: " + position3D);
}
}
Transform
Die Klasse Transform ist Teil des GameObjects und repräsentiert die Position, Rotation und Skalierung eines Objektes in der Unity-Szene. Sie ermöglicht es, die Transformation eines GameObjects zu steuern und zu manipulieren.
Beispielskript:
using UnityEngine;
public class TransformExample : MonoBehaviour
{
void Update()
{
// 2D-Bewegung in der XY-Ebene
Vector2 movement2D = new Vector2(2f, 0f);
transform.Translate(movement2D * Time.deltaTime);
// 3D-Rotation um die Y-Achse
Vector3 rotation3D = new Vector3(0f, 90f, 0f);
transform.Rotate(rotation3D * Time.deltaTime);
}
}
Hier wird unter anderem eine 2D-Bewegung in der XY-Ebene erzeugt. Der Vektor movement2D
hat den Wert (2f, 0f) . Das bedeutet, dass sich das GameObject mit einer Geschwindigkeit von 2 Einheiten pro Sekunde in der positiven X-Richtung und auf der Y-Achse nicht bewegen wird. Time.deltaTime
wird verwendet, um die Bewegung proportional zur Framerate zu machen. So bleibt die Bewegung unabhängig von der Geschwindigkeit der Framerate stabil.
Time
Diese Klasse bietet Funktionen, um die Zeit in Unity zu verfolgen. So beispielsweise die Zeit seit dem Start der Anwendung, die Zeit zwischen Frames oder die Dauer von Coroutine-Wartezeiten.
Beispielskript:
using UnityEngine;
public class TimeExample : MonoBehaviour
{
void Update()
{
// Beispiel 1: Ausgabe der Zeit seit dem Start des Spiels in Sekunden
float timeSinceStart = Time.time;
Debug.Log("Zeit seit dem Start des Spiels: " + timeSinceStart + " Sekunden");
// Beispiel 2: Überprüfung, ob eine bestimmte Zeitdauer seit dem Start vergangen ist
float duration = 5f; // 5 Sekunden
if (Time.timeSinceLevelLoad >= duration)
{
Debug.Log("Bereits " + duration + " Sekunden seit dem Start des Levels vergangen!");
}
// Beispiel 3: Wiederholte Ausführung einer Aktion in bestimmten Zeitabständen (InvokeRepeating)
float repeatRate = 2f; // Wiederholungsrate alle 2 Sekunden
InvokeRepeating("RepeatingAction", 2f, repeatRate);
}
// Beispiel 4: Ausführen einer Aktion nach einer bestimmten Zeitverzögerung (Invoke)
void Start()
{
float delay = 3f; // Aktion nach 3 Sekunden ausführen
Invoke("DelayedAction", delay);
}
// Beispiel 3: Funktion, die wiederholt aufgerufen wird
void RepeatingAction()
{
Debug.Log("Diese Aktion wird wiederholt ausgeführt!");
}
// Beispiel 4: Funktion, die nach einer bestimmten Zeitverzögerung aufgerufen wird
void DelayedAction()
{
Debug.Log("Diese Aktion wurde nach einer Verzögerung ausgeführt!");
}
}
Mathf
Die Klasse Mathf enthält eine Sammlung von mathematischen Funktionen, die häufig verwendet werden. Dazu zählen unter anderem Funktionen zur Trigonometrie, Interpolation oder Rundung.
Hier ist ein kleines Beispielskript:
using UnityEngine;
public class MathfExample : MonoBehaviour
{
void Start()
{
// Beispiel 1: Verwendung von Mathf.Abs für den Betrag einer Zahl
float number = -5f;
float absoluteValue = Mathf.Abs(number);
Debug.Log("Der Betrag von " + number + " ist " + absoluteValue);
// Beispiel 2: Verwendung von Mathf.Clamp, um eine Zahl innerhalb eines bestimmten Bereichs zu begrenzen
float value = 10f;
float minValue = 0f;
float maxValue = 5f;
float clampedValue = Mathf.Clamp(value, minValue, maxValue);
Debug.Log("Der geklammerte Wert von " + value + " ist " + clampedValue);
// Beispiel 3: Verwendung von Mathf.Lerp, um eine lineare Interpolation zwischen zwei Werten durchzuführen
float startValue = 0f;
float endValue = 10f;
float t = 0.5f; // Interpolationsfaktor (0 = Startwert, 1 = Endwert)
float interpolatedValue = Mathf.Lerp(startValue, endValue, t);
Debug.Log("Der interpolierte Wert bei t=" + t + " ist " + interpolatedValue);
// Beispiel 4: Verwendung von Mathf.Round, um eine Dezimalzahl zu runden
float decimalNumber = 3.7f;
float roundedNumber = Mathf.Round(decimalNumber);
Debug.Log("Die gerundete Zahl von " + decimalNumber + " ist " + roundedNumber);
}
}
Debug
Diese Klasse ist ein nützliches Tool zur Ausgabe von Nachrichten und zum Debuggen von Skripten während der Laufzeit.
Beispielskript:
using UnityEngine;
public class DebugExample : MonoBehaviour
{
void Start()
{
// Beispiel: Debug-Nachricht ausgeben
Debug.Log("Start des Skripts");
}
void Update()
{
// Beispiel: Debug-Nachricht mit der aktuellen Position des GameObjects ausgeben
Debug.Log("Aktuelle Position: " + transform.position);
}
}
using UnityEngine;
public class DebugExample : MonoBehaviour
{
void Start()
{
int playerHealth = 50;
int maxHealth = 100;
Debug.Log("Die Spieler-Gesundheit liegt bei 100");
// Beispiel 1: Gibt eine Warnungsnachricht aus, wenn die Spieler-Gesundheit niedrig ist
if (playerHealth < 20)
{
Debug.LogWarning("Achtung: Die Spieler-Gesundheit ist niedrig!");
}
// Beispiel 2: Gibt eine Fehlermeldung aus, wenn die Spieler-Gesundheit null ist
if (playerHealth <= 0)
{
Debug.LogError("Fehler: Der Spieler ist tot!");
}
// Beispiel 3: Verwendung von Debug.LogFormat, um Informationen über die Spieler-Gesundheit auszugeben
Debug.LogFormat("Spieler-Gesundheit: {0}/{1}", playerHealth, maxHealth);
}
}
Unity Input System
Mit dem Unity Input System ist es möglich die Eingabe von Nutzern in der Anwendung zu erfassen. Es bietet eine einheitliche Schnittstelle für verschiedene Eingabegeräte wie Tastatur, Maus, Gamepads, Touchscreens und weitere.
Um das Input System zu verwenden, ist es wichtig das benötigte Package Input System über den Package Manager in Unity installieren. Navigiere dazu zu Window > Package Manager und suche nach Input System.
Nach der Installation kannst du das Input System in deinen Skripten verwenden, indem du die using UnityEngine.InputSystem
Anweisung hinzufügst.
Hier ist ein Beispielskript, das die grundlegende Verwendung zeigt:
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerController : MonoBehaviour
{
private Vector2 moveDirection;
void Update()
{
// Bewegungsrichtung über die Pfeiltasten der Tastatur erfassen
moveDirection = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
// Die Bewegungsrichtung verwenden, um den Spieler zu bewegen
MovePlayer();
}
void MovePlayer()
{
// Den Spieler in die entsprechende Richtung bewegen
float moveSpeed = 5f;
Vector3 movement = new Vector3(moveDirection.x, 0f, moveDirection.y) * moveSpeed * Time.deltaTime;
transform.Translate(movement);
}
}
In der Update
-Methode wird die Bewegungsrichtung über die Pfeiltasten der Tastatur mit Input.GetAxis("Horizontal")
für die X-Achse und Input.GetAxis("Vertical")
für die Z-Achse erfasst. Dabei werden Werte zwischen -1 und 1 zurückgegeben, abhängig davon, welche Tasten gedrückt werden.
In der MovePlayer
-Methode wird die Bewegung basierend auf der erfassten Bewegungsrichtung berechnet und der Spieler entsprechend bewegt.