Unity

Entwickeln Ihres ersten Spiels mit Unity und C#, Teil 2

Adam Tuliper

Laden Sie die Codebeispiele herunter

Willkommen zurück zu meiner Reihe über Unity. Im ersten Artikel ging es um einige Grundbegriffe und die Architektur dieser Entwicklungsumgebung. Nun untersuchen wir die 2D-Funktionalität, die auf den in Unity Version 4.3 eingeführten 2D-Tools basiert. Natürlich gab es 2D-Funktionalität in Unity auch schon vor Version 4.3, ohne Drittanbieter-Toolkits erwies sich dies jedoch als schwieriges Unterfangen. Wenn ich ein Bild mit Drag & Drop in meine Szene ziehe, möchte ich, dass es sich genauso verhält, wie ich es von einer Drag & Drop-Benutzeroberfläche erwarte. Dies ist eine der Neuerungen in Unity 4.3, die ich in diesem Artikel erläutern werde. Während wir ein grundlegendes 2D-Plattformspiel entwickeln, um die Grundlagen von Unity kennenzulernen, werde ich noch auf weitere neue Features zu sprechen kommen.

2D in Unity

Wenn Sie in Unity für die Erstellung eines neuen Projekts 2D-Unterstützung benötigen, wählen Sie im Dialogfeld "2D" aus der Dropdownliste aus. Dadurch werden die Projektstandardeinstellungen auf "2D" gesetzt ("Edit (Bearbeiten) | Project Settings (Projekteinstellungen) | Editor"), und statt als Texturen werden Bilder als Sprites in Ihr Projekt importiert. (Darauf werde ich im nächsten Abschnitt zurückkommen.) Außerdem wechselt die Szenenansicht automatisch in den 2D-Modus. Dies geschieht über eine Hilfsschaltfläche, die Ihr Objekt während der Entwicklung einer Szene an zwei Achsen anheftet, auf das eigentliche Spiel jedoch keine Auswirkung hat. Durch Klicken mit der Maustaste können Sie jederzeit den 2D-Arbeitsmodus aus- und wieder einschalten. Ein 2D-Spiel in Unity ist trotz allem eine 3D-Umgebung, lediglich das Arbeitsmodell ist an einer X- und Y-Achse ausgerichtet. In Abbildung 1 und Abbildung 2 sehen Sie, wie Ihr Bildschirm mit bzw. ohne 2D-Modus aussieht. Ich habe die Kamera hervorgehoben, sodass Sie den Umriss des Kamera-Anzeigebereichs sehen, in der räumlichen Darstellung sieht er jedoch wie ein Quader aus.

2D Mode Selected—Camera Has Focus
Abbildung 1 – 2D-Modus ausgewählt und Kamera fokussiert

2D Mode Not Selected—Camera Has Focus
Abbildung 2 – 2D-Modus nicht ausgewählt und Kamera fokussiert

Die hervorgehobene Kamera ist eine orthogonale Kamera, was einem der beiden Kameramodi in Unity entspricht. Dieser Kameratyp wird häufig im 2D-Modus verwendet und skaliert Objekte nur so weit, wie sie vom menschlichen Auge als 2D wahrgenommen werden können. Von der Kameraposition aus ist keine Tiefe zu sehen. Der andere Kameratyp ist perspektivisch und zeigt Objekte mit Tiefe an, so wie sie vom menschlichen Auge erfasst werden. Verschiedene Gründe sprechen jeweils für den einen oder anderen Kameratyp. Wenn Sie optische Tiefe benötigen, entscheiden Sie sich in der Regel für die perspektivische Sicht, solange Sie Ihre Objekte nicht individuell skalieren möchten. Sie können den Modus einfach ändern, indem Sie die Kamera auswählen und den Projektionstyp ändern. Probieren Sie ruhig aus, wie sich der Anzeigebereich der Kamera ändert, wenn Sie Objekte auf der Z-Achse in den Raum verschieben. Sie können den Modus für das Standardverhalten jederzeit ändern. Diese Einstellung wirkt sich aber erst auf den Import zukünftiger Bilder aus.

Wenn Sie über ein bestehendes Unity-Projekt verfügen oder nicht sicher sind, ob Sie "2D" im Projektdialogfeld ausgewählt haben, können Sie die Projektstandardeinstellungen für 2D wie folgt festlegen: "Edit (Bearbeiten) | Project Settings (Projekteinstellungen) | Editor". Andernfalls müssen Sie den Texturtyp jedes importierten 2D-Bilds manuell ändern, was bei zahlreichen Bildern viel Zeit kostet.

Alles dreht sich um Sprites

Wenn 3D als Standardverhalten ausgewählt ist, werden die Bilder als Textur erkannt. Eine Textur kann nicht einfach in die Szene gezogen, sondern muss auf ein Objekt angewendet werden. So macht die Entwicklung von 2D-Spielen nicht sonderlich Spaß, denn ich möchte mein Bild einfach mit Drag & Drop in die Szene ziehen. Wenn Sie 2D als Standardmodus verwenden, ist alles jedoch ganz einfach. Ein Bild, das ich mit Drag & Drop in Unity einfüge, wird jetzt automatisch als "Sprite" erkannt.

Auf diese Weise können Sie Ihre Bilder in Unity ziehen und von dort aus mit Drag & Drop in Ihre Szene einsetzen, um Ihr Spiel zu entwickeln. Bei sehr kleinen Bildern können Sie den Wert "Pixels to Units" (Pixel in Einheiten) einfach verringern, anstatt sie an jeder Stelle neu zu skalieren. Dies ist in Unity eine gängige Praxis sowohl für 2D als auch für 3D und in der Regel zeitsparender als die Skalierung von Objekten über die Skalierungseigenschaft des Transformobjekts. 

Beim Einfügen der Objekte werden Sie vielleicht feststellen, dass die Objekte übereinander abgelegt werden. Unity erstellt – auch für 2D-Bilder – eine Reihe von Scheitelpunkten hinter den Szenen, sodass die Zeichenrichtung der verschiedenen Bildteile abweichen kann. Es empfiehlt sich jedoch immer, die Z-Reihenfolge der Bilder explizit anzugeben. Dazu stehen Ihnen drei Methoden zur Verfügung. Sie sehen Sie hier in der Reihenfolge, in der Sprites von Unity gerendert werden:

  1. Festlegen der Eigenschaft "Sorting Layer" (Sortierebene) im Sprinter Renderer
  2. Festlegen der Eigenschaft "Order in Layer" (Reihenfolge innerhalb der Ebene) ebenfalls im Sprinter Renderer
  3. Festlegen des Z-Positionswerts des Transformobjekts

Die Sortierebene hat Vorrang vor allen anderen Einstellungen, gefolgt von der Reihenfolge innerhalb einer Ebene und schließlich vom Z-Wert des Transformobjekts.

Sortierebenen werden in der Reihenfolge gerendert, in der sie definiert wurden. Wenn Sie über "Edit (Bearbeiten) | Project Settings (Projekteinstellungen) | Tags and Layers (Tags und Ebenen)" weitere Ebenen hinzufügen, rendert Unity zuerst die Objekte, die auf der Standardebene gefunden werden (gefolgt von den Objekten mit "Order in Layer" und schließlich denen mit einem Z-Positionswert für das Transformobjekt). Dann erst werden Hintergrund, Plattformen usw. gerendert. Sie können Überlappungen schnell beheben, indem Sie die Bilder auf der Plattformebene anlegen und dem Bild, das zuoberst liegen soll, in "Order in Layer" die Ordnungszahl 1 zuordnen. Dieses Objekt wird dann gerendert, nachdem alle Objekte mit der Ordnungszahl 0 gezeichnet wurden.

Standardfunktionalität

Abbildung 3 zeigt unseren aktuellen Entwicklungsstand mit einigen Plattformen und Hintergrundbildern, die wir mit Drag & Drop eingefügt haben, sowie festgelegten Sortierebenen.

A Game Level
Abbildung 3 – ein Spiellevel

Unser Projekt sieht zwar schon eindrucksvoll aus, funktioniert aber noch nicht wie ein Spiel. Einige Features sind noch nötig, um die Funktionalität herzustellen. In den folgenden Abschnitten werde ich näher darauf eingehen.

Tastatur-, Maus- und Toucheingabe In Unity werden Tastatur-, Maus- und Toucheingaben und die Daten des Beschleunigungssensors vom Eingabesystem gelesen. Sie können Eingabebewegungen, Mausklicks oder Toucheingaben bequem mit einem Skript auswerten, wie im folgenden Beispiel für den Hauptspieler demonstriert (dieses Skript werde ich in Kürze ergänzen):

void Update()
{
  // Returns -1 to 1
  var horizontal = Input.GetAxis("Horizontal");
  // Returns true or false. A left-click
  // or a touch event on mobile triggers this.
  var firing = Input.GetButtonDown("Fire1");
}

Indem Sie "Edit (Bearbeiten) | Project Settings (Projekteinstellungen) | Input (Eingabe)" aktivieren, können Sie die Standardwerte für Eingaben (die von Unity für jedes neue Projekt angezeigt werden) anzeigen oder neue Werte festlegen. Abbildung 4 zeigt die Standardwerte für die Auswertung der horizontalen Bewegung. Die Einstellungen "left" (links) und "right" (rechts) beziehen sich auf die linke bzw. rechte Maustaste, aber auch "a" und "d" werden für die horizontale Bewegung verwendet, beispielsweise für Joystickeingaben. Sie können die Standardwerte ändern oder neue Werte hinzufügen. Das Feld "Sensitivity" (Sensitivität) steuert, wie schnell Unity von 0 zu 1 oder -1 wechselt. Wenn der Rechtspfeil gedrückt wird, kann der erste Frame einen Wert von 0,01 erreichen und dann schnell weiter auf 1 ansteigen. Sie können die Geschwindigkeit jedoch auch anpassen, um eine horizontale Geschwindigkeit oder Bewegung Ihres Charakters sofort umzusetzen. In Kürze werde ich Ihnen den Code präsentieren, mit dem Sie diese Werte auf Ihre Spielobjekte anwenden können. Für das Lesen dieser Werte ist keine GameObject-Komponente erforderlich. Sie verwenden einfach das Schlüsselwort "Input" in Ihrem Code, um auf die Funktion zum Auswerten von Eingaben zuzugreifen. Grundsätzlich gilt, dass "Input" anstatt mit "FixedUpdate" mit der Funktion "Update" gelesen werden sollte, damit keine Eingabeereignisse verloren gehen

Horizontal Input Defaults
Abbildung 4 – Standardwerte für die horizontale Bewegung

Lineare Bewegung Objekte müssen sich bewegen können. Bei einem Top-Down-Spiel kommt es auf Schwerkraft in der Regel nicht an – ganz im Gegensatz zu einem Plattformspiel. In beiden Fällen ist die Erkennung von Objektkollisionen jedoch besonders wichtig. Dabei gelten folgende Grundregeln: Eine Rigidbody2D- oder RigidBody-Komponente (für 3D), die einem Spielobjekt hinzugefügt wird, weist der Komponente automatisch eine Masse zu, sodass sie sich nach den Gesetzen der Schwerkraft verhält und Kräfte aufnehmen kann. Laut Wikipedia ist ein starrer Körper in der Physik eine Idealisierung eines Festkörpers, bei dem die Verformung vernachlässigt wird. Anders ausgedrückt, bleibt die Entfernung zwischen zwei Punkten eines starren Körpers unabhängig von den externen Kräften, die auf ihn wirken, konstant. Dasselbe Prinzip gilt für Spiele. Durch das Hinzufügen einer RigidBody-Komponente können Sie Sequenzen wie die in Abbildung 5 umsetzen.

Abbildung 5 – Hinzufügen von Bewegung und Geschwindigkeit

void FixedUpdate()
{
  // -1 to 1 value for horizontal movement
  float moveHorizontal = Input.GetAxis("Horizontal");
  // A Vector gives you simply a value x,y,z, ex  1,0,0 for
  // max right input, 0,1,0 for max up.
  // Keep the current Y value, which increases 
  // for each interval because of gravity.
  var movement = new Vector3(moveHorizontal * 
    _moveSpeed, rigidbody.velocity.y, 0);
  rigidbody.velocity = movement;
  if (Input.GetButtonDown("Fire1"))
  {
    rigidbody.AddForce(0, _jumpForce, 0);
  }
}

Eine Grundregel lautet, dass lineare Bewegungen mit "Update" und Beschleunigungen mit "FixedUpdate" ausgeführt werden. Wenn Sie es als Neueinsteiger verwirrend finden, welche Methode Sie wann verwenden sollen, können Sie mit der linearen Bewegung nichts falsch machen. Die Unterscheidung nach "Update" und "FixedUpdate" bringt jedoch die besten Ergebnisse.

Kollisionserkennung Die Masse eines Objekts wird durch die RigidBody-Komponente bestimmt. Zusätzlich müssen Sie Unity aber auch anweisen, wie Kollisionen mit diesem Objekt ausgeführt werden sollen. Obwohl die Größe und Form Ihrer Bilder oder Modelle dabei keine Rolle spielen, wirkt sich die Skalierung auf die Kräfte aus, die auf das Objekt wirken. Was uns interessiert, ist die Größe und Form der Collider-Komponente, also des Bereichs um, auf oder innerhalb des Objekts, dessen Kollision mit einem anderen Objekt von Unity erkannt werden soll. Diese Funktion benötigen Sie, um zu erkennen, wann ein Spieler in Reichweite eines untätigen Zombies oder aufgetürmter Felsblöcke kommt, die bei Annäherung den Hang herunterpoltern.

Collider sind in verschiedenen Formen verfügbar. Ein Collider für 2D kann ein Kreis (circle), eine Kante (edge), ein Polygon oder ein Viereck (box) sein. Box-Collider sind ideal für Objekte in Quadrat- oder Rechteckform oder zur Erkennung von Kollisionen auf einer quadratischen Fläche. Einen Box-Collider können Sie sich wie eine Plattform vorstellen, auf der Sie stehen. Indem Sie Ihrem Spielobjekt diese Komponente hinzufügen, können Sie physikalisches Kollisionsverhalten simulieren. In Abbildung 6 habe ich dem Charakter einen Circle-Collider und einen RigidBody (starren Körper) und der Plattform einen Box-Collider hinzugefügt. Wenn ich im Editor auf "Play" (Abspielen) klicke, fällt die Spielfigur auf der Plattform zu Boden und bleibt liegen, ohne dass Code programmiert werden muss.

Adding Colliders
Abbildung 6 – Hinzufügen von Collidern

Sie können die Region eines Colliders verschieben oder deren Größe ändern, indem Sie die Eigenschaften der Collider-Komponente bearbeiten. Normalerweise können sich Collider nicht gegenseitig durchdringen (mit Ausnahme von Triggern, auf die ich als Nächstes eingehen werde). Für Kollisionen benötigen beide Spielobjekte eine Collider-Komponente, und mindestens ein Objekt muss über eine RigidBody-Komponente verfügen, sofern sie kein Trigger ist.

Wenn Unity meinen Code bei der ersten Kollisionsinstanz aufrufen soll, füge ich dem Spielobjekt einfach den folgenden Code über eine Skriptkomponente hinzu (wie im vorherigen Artikel beschrieben):

void OnCollisionEnter2D(Collision2D collision)
{
  // If you want to check who you collided with,
  // you should typically use tags, not names.
  if (collision.gameObject.tag == "Platform")
  {
    // Play footsteps or a landing sound.
  }
}

Trigger In machen Situationen möchten Sie eine Kollision jedoch nur erkennen, ohne dass physikalische Kräfte wirken. Stellen Sie sich ein Szenario vor, in dem ein Schatz gefunden wird. Die Münzen sollen nicht einfach vor die Spielfigur ausgeworfen werden, wenn sie in Reichweite kommt, sondern sie sollen aufgesammelt werden, ohne aber die Bewegungen des Spielers zu stören. In diesem Fall verwenden Sie einen als Trigger bezeichneten Collider, der im Prinzip ein Collider mit aktiviertem IsTrigger-Kontrollkästchen ist. Dadurch wird das physikalische Verhalten deaktiviert, und Unity ruft Ihren Code nur auf, wenn Objekt A (das einen Collider enthält) in die Region von Objekt B (das ebenfalls einen Collider enthält) eindringt. In diesem Fall wird anstelle von "OnCollisionEnter2D" die Codemethode "OnTriggerEnter2D" verwendet:

void OnTriggerEnter2D(Collider2D collider)
{
  // If the player hits the trigger.
  if (collider.gameObject.tag == "Player")
  {
    // More on game controller shortly.
    GameController.Score++;
    // You don’t do: Destroy(this); because 'this'
    // is a script component on a game object so you use
    // this.gameObject or just gameObject to destroy the object.
    Destroy(gameObject);
  }
}

Denken Sie jedoch daran, dass von Triggern keine physikalische Interaktion ausgeht, sie lösen eine Aktion lediglich aus. Trigger erfordern keine Rigidbody-Komponente für das Spielobjekt, weil keine Kräfte berechnet werden.

Ein Punkt, der Neuentwicklern häufig Probleme bereitet, ist das Verhalten von RigidBodys beim Hinzufügen von Collidern. Wenn mein Objekt über einen Circle-Collider verfügt und ich das Objekt auf einer geneigten Fläche ablege, beginnt es (wie die Collider-Form bereits vermuten lässt) zu rollen, wie in Abbildung 7 dargestellt. Hier beobachten Sie genau das, was passiert, wenn Sie in der realen Welt ein Rad einen Abhang herunterrollen lassen. Ich verwende für meinen Charakter keinen Box-Collider, weil dieser Kanten hat, die sich mit den Kanten anderer Collider verhaken könnten, wenn sie darüber bewegt werden. Das kann im Endeffekt zu einer ruckeligen Wiedergabe führen. Bei einem Circle-Collider ist die Bewegung gleichmäßiger. Ist eine gleichmäßige Drehbewegung jedoch nicht erwünscht, können Sie die Einstellung "Fixed Angle" (Fester Winkel) für die Rigidbody-Komponente verwenden.

Using a Circle Collider for Smooth Movement
Abbildung 7 – Verwenden eines Circle-Colliders für eine gleichmäßige Drehbewegung

Audio Für die Audiowiedergabe benötigen Sie eine Audio Listener-Komponente, die standardmäßig in jede Kamera eingebaut ist. Sie fügen einem Spielobjekt einfach eine Audio Source-Komponente hinzu und wählen den Audioclip aus. Unity unterstützt die meisten gängigen Audioformate und codiert längere Clips in MP3. Wenn Ihr Unity Editor über eine Vielzahl von Audioquellen mit zugewiesenen Clips verfügt, müssen Sie berücksichtigen, dass alle Clips zur Laufzeit geladen werden. Stattdessen können Sie die Audioclips auch codegesteuert aus einem speziellen Ressourcenordner laden und diesen anschließend löschen.

Ich habe meine Audioclips aus einer WAV-Datei in mein Projekt importiert. In diesem Format werden Audiodaten unkomprimiert gespeichert. Längere Audioclips werden von Unity neu codiert, um Ihnen stets ein optimales Klangerlebnis zu bieten. Dies gilt besonders für kurze Audiodateien wie Soundeffekte, die in Unity uncodiert bleiben. Zudem habe ich meiner Hauptkamera eine Audio Source-Komponente hinzugefügt, die ich auch an ein beliebiges Spielobjekt hätte anhängen können. Anschließend habe ich dieser Audio Source-Komponente den Adventure-Audioclip zugewiesen und "Loop" aktiviert, sodass der Clip in einer Endlosschleife läuft. Dank dieser drei einfachen Schritte verfüge ich jetzt über Hintergrundmusik in meinen Spielen.

GUI/Heads-Up-Display Ein GUI-System vereint viele verschiedene Aspekte eines Spiels, z. B. das Menüsystem, die Lebens- und Punkteanzeigen, das Waffenarsenal und vieles mehr. Normalerweise ist ein GUI-System unabhängig vom Blickwinkel der Kamera immer auf dem Bildschirm sichtbar (diese Einstellung kann jedoch auch geändert werden). Die GUI-Funktionalität von Unity wird derzeit vollständig überarbeitet und soll unter dem neuen Namen uGUI mit der Version Unity 4.6 herauskommen. Da die endgültige Version noch nicht verfügbar ist, beschränke ich mich hier auf einige Grundfunktionen. Aktuelle Infos zum neuen GUI-System können Sie jedoch auf meinem channel9-Blob unter channel9.msdn.com/Blogs/AdamTuliper nachlesen.

Um dem Bildschirm einen einfachen Anzeigetext hinzuzufügen (z. B. Punktestand: 0), habe ich auf "Game Object (Spielobjekt) | Create Other (Weiteres erstellen) | GUI Text" geklickt. Diese Option ist in Unity 4.6 nicht mehr verfügbar, aber Sie können sich in dem erwähnten Video über die äquivalente Option in uGUI informieren. Sie können dem Spielobjekt auch in Version 4.6 eine GUI Text-Komponente hinzufügen, indem Sie auf die Schaltfläche "Add Component" (Komponente hinzufügen) klicken, die Option wird lediglich im Editor-Menü nicht angezeigt. Beim aktuellen (demnächst auslaufenden) Unity GUI-System können Sie Ihre GUI-Objekte nicht in der Szene-, sondern nur in der Game-Ansicht anzeigen, was die Layouterstellung etwas schwierig macht. Sie können Ihr GUI-System aber auch auf der Codeebene nutzen und Widgets mit der GUILayout-Klasse automatisch nachverfolgen. Ich bevorzuge jedoch ein GUI-System, in dem ich Elemente anklicken und ziehen kann. Das macht das neue uGUI-System für mich so interessant. (Vor uGUI war in diesem Bereich ein ziemlich stabiles Drittanbieterprodukt mit dem Namen NGUI führend, das nun auch als grundlegende Codebasis für das neue uGUI dient.)

Die einfachste Möglichkeit, den Anzeigetext zu aktualisieren, besteht darin, das GUI Text-Spielobjekt zu suchen oder im Editor einen Verweis darauf zu platzieren. Anschließend können Sie es als Label in .NET verwenden und die zugehörige Texteigenschaft aktualisieren. So lässt sich der GUI-Text auf dem Bildschirm ganz einfach aktualisieren:

void UpdateScore()
{
  var score = GameObject.Find("Score").GetComponent<GUIText>();
  score.text = "Score: 0";
}

Dies ist ein etwas gekürztes Beispiel. Zur Leistungsverbesserung speichere ich einfach einen Verweis auf die GUIText-Komponente in der Start-Methode, damit ich sie nicht für jeden Methodenaufruf abfragen muss.

Punktestand Der Punktestand lässt sich ganz einfach nachverfolgen. Sie haben eine Klasse, die eine öffentliche Methode oder Eigenschaften verfügbar macht, die den Punktestand erhöhen. Spiele verfügen normalerweise über ein Game Controller-Objekt, das das Spiel koordiniert. Der Game Controller setzt u. a. einen Trigger für das Laden und Speichern von Szenen und Punkteständen. In diesem Beispiel kann ich einfach eine Klasse verwenden, die eine Punktestandsvariable verfügbar macht, wie in Abbildung 8 dargestellt. Ich weise diese Komponente einem leeren Spielobjekt zu, sodass sie beim Laden der Szene verfügbar ist. Sobald der Punktestand sich ändert, wird die GUI aktualisiert. Die _scoreText-Variable wird im Unity-Editor zugewiesen. Legen Sie einfach ein beliebiges GUIText-Spielobjekt auf diesem Feld ab, oder verwenden Sie das Such-Widget, in dem die Score Text-Variable mittels der Skriptkomponente im Editor verfügbar gemacht wird.

Abbildung 8 – Erstellen der Variablen "_scoreText"

public class GameController : MonoBehaviour
{
  private int _score;
  // Drag a GuiText game object in the editor onto
  // this exposed field in the Editor or search for it upon startup
  // as done in Figure 12.
  [SerializeField]
  private GUIText _scoreText;
  void Start()
  {
    if (_scoreText == null)
    {
      Debug.LogError("Missing the GuiText reference. ");
    }
  }
  public int Score
  {
    get { return _score; }
    set
    {
      _score = value;
        // Update the score on the screen
      _scoreText.text = string.Format("Score: {0}", _score);
    }
  }
}

Ich kann den Triggercode für den Fliegenpilz in diesem Beispiel wie folgt aktualisieren, um den Punktestand bei jedem Aufsammeln zu erhöhen:

void OnTriggerEnter2D(Collider2D collider)
{
  if (collider.gameObject.tag == "Player")
  {
    GameController.Score++;
    Destroy(gameObject);
  }
}

Animationen Wie bei XAML werden Animationen erstellt, indem verschiedene Aktionen in Keyframes ausgeführt werden. Ich könnte ohne weiteres einen ganzen Artikel über Animationen in Unity schreiben, fasse mich aber aus Platzgründen kurz. Unity verfügt über zwei Animationssysteme, das frühere System und das neue Animationssystem Mecanim. Das frühere System verwendet animation(.ani)-Dateien, während Mecanim die Wiedergabe der Animationsdateien zustandsbasiert steuert.

Für 2D wird standardmäßig das Animationssystem Mecanim verwendet. Die einfachste Methode zum Erstellen einer Animation besteht darin, Bilder mit Drag & Drop in die Szene zu ziehen und die Animationen von Unity erstellen zu lassen. Zunächst ziehe ich einige einzelne Sprites in die Unity-Umgebung, woraufhin Unity eine Reihe von Elementen für mich anlegt. Als Erstes wird ein Spielobjekt mit einer Sprite Renderer-Komponente zum Zeichnen der Sprites generiert. Danach folgt die Animationsdatei. Sie können dies verfolgen, indem Sie "Window (Fenster) | Animator" aufrufen und Ihr Spielobjekt hervorheben. Der Animator zeigt die zugewiesene Animationsdatei an. Diese enthält in unserem Beispiel sechs Keyframes, weil ich sechs Bilder in meiner Szene abgelegt habe. Jeder Keyframe steuert mindestens einen Parameter für eine Komponente. Im Beispiel wird dadurch die Sprite-Eigenschaft der Sprite Renderer-Komponente geändert. Animationen sind nichts anderes als einzelne Bilder, die in einer Geschwindigkeit wiedergegeben werden, die vom Auge als Bewegung wahrgenommen wird.

Als Nächstes erstellt Unity eine Animator-Komponente für das Spielobjekt, wie in Abbildung 9 dargestellt.

The Animator Component Pointing to a Controller
Abbildung 9 – Animator-Komponente mit einem Verweis auf einen Controller

Diese Komponente verweist auf einen einfachen Zustandsautomat, der als Animationscontroller bezeichnet wird. Dabei handelt es sich um eine von Unity erstellte Datei, die einfach den Standardzustand anzeigt. Dieser lautet immer "idle" (Leerlauf), da dies der einzige verfügbare Zustand ist. Dieser Zustand macht nichts anderes als auf meine Animationsdatei zu verweisen. Abbildung 10 zeigt die tatsächlichen Keyframedaten auf der Zeitachse.

The Idle Animation Data
Abbildung 10 – Animationsdaten im Leerlauf

Für die Wiedergabe einer einfachen Animation mag dies ziemlich kompliziert klingen. Allerdings liegt das Potenzial der Zustandsautomaten darin, dass sie sich über einfache Variablen steuern lassen. Zur Erinnerung: Ein Zustand macht nichts weiter als auf eine Animationsdatei zu verweisen (in 3D können Sie sich jedoch ausleben und Animationen beispielsweise überblenden).

Dann habe ich noch weitere Bilder hinzugefügt, um eine bewegte Animation zu erhalten, und diese auf meinem Yeti-Spielobjekt abgelegt. Da ich bereits über eine Animator-Komponente für das Spielobjekt verfüge, erstellt Unity einfach eine neue Animationsdatei und fügt einen neuen Zustand mit dem Namen "run" (Ausführen) hinzu. Ich kann einfach mit der rechten Maustaste auf "idle" klicken und einen Übergang zu "run" herstellen. Dadurch wird zwischen den beiden Zuständen ein Pfeil eingeblendet. Anschließend füge ich eine neue Variable mit dem Namen "Running" (Wird ausgeführt) hinzu, die sehr anwenderfreundlich ist. Um die Variable zu verwenden, klicken Sie einfach auf den Pfeil zwischen den Zuständen und ändern die Bedingung wie in Abbildung 11 dargestellt.

Changing from the Idle to Run States
Abbildung 11 – Wechsel von "Idle" zu "Run"

Wenn "Running" den Wert "True" hat, ändert sich der Animationszustand von "idle" in "run". Das bedeutet, dass die Animationsdatei wiedergegeben wird. Diese Variablen lassen sich sehr einfach codebasiert steuern. Um eine Animation zu starten, nachdem der Zustandswechsel durch Mausklick ausgelöst wurde, fügen Sie den Code aus Abbildung 12 hinzu.

Abbildung 12 – codebasierter Zustandswechsel

private Animator _animator;
void Awake()
{
    // Cache a reference to the Animator 
    // component from this game object
    _animator = GetComponent<Animator>();
}
void Update()
{
  if (Input.GetButtonDown("Fire1"))
  {
    // This will cause the animation controller to
    // transition from idle to run states 
    _animator.SetBool("Running", true);
  }
}

In meinem Beispiel habe ich einzelne Sprites für die Animation verwendet. Eine beliebte Methode sind jedoch so genannte Sprite-Sheets. Diese bestehen aus einer einzelnen Bilddatei, die mehrere Bilder enthält. Da Sprite-Sheets von Unity unterstützt werden, müssen wir Unity nur noch mitteilen, wie das Sprite unterteilt werden soll, und die einzelnen Slices (Segmente) dann in der Szene platzieren. Die einzige Änderung besteht darin, den Sprite-Modus für die Sprite-Eigenschaften von "Single" (Einfach) in "Multiple" (Mehrfach) zu ändern und den Sprite-Editor zu öffnen. Dort wird das Sprite automatisch segmentiert, und die Änderungen werden übernommen wie in Abbildung 13 dargestellt. Zuletzt erweitern Sie das Sprite (über einen kleinen Pfeil auf dem Sprite-Symbol in der Projektansicht), markieren die resultierenden Sprites und ziehen sie wie bereits zuvor auf die Szene.

Creating a Sprite Sheet
Abbildung 13 – Erstellen eines Sprite-Sheets

Solange Sie Ihr System nicht kennen, können Animationen eine echte Herausforderung sein. Weitere Informationen erhalten Sie in meinem channel9-Blog oder über die zahlreichen Infoquellen auf der Lernwebsite von Unity.

Das Levelende Wenn sich ein Spieler dem Levelende nähert, können Sie ihm durch einen Trigger-Collider ermöglichen, diese Zone zu erreichen. Danach laden Sie einfach einen neuen Level oder wiederholen den aktuellen Level:

void OnTriggerEnter2D(Collider2D collider)
{
  // If the player hits the trigger.
  if (collider.gameObject.tag == "Player")
  {
    // Reload the current level.
    Application.LoadLevel(Application.loadedLevel);
    // Could instead pass in the name of a scene to load:
    // Application.LoadLevel("Level2");
  }
}

Das Spielobjekt mit seinen Eigenschaften wird in Abbildung 14 dargestellt. Die Höhe des Colliders ist so festgelegt, dass der Spieler ihn nicht überspringen kann. Zudem wurde der Collider als Trigger festgelegt.

The Game Object and Its Properties
Abbildung 14 – Spielobjekt mit Eigenschaften

Spielablauf In einem einfachen 2D-Spiel wie diesem ist der Ablauf relativ einfach. Die Spielfigur startet. Durch Einwirkung von Schwerkraft auf den RigidBody fällt sie zu Boden. Spielfigur und Plattform verfügen über einen Collider, sodass die Figur liegen bleibt. Tastatur-, Maus- und Toucheingaben werden ausgewertet und sorgen für die Bewegung. Die Spielfigur springt zwischen Plattformen, indem durch "rigidbody.AddForce" eine Kraft angewendet wird, sie bewegt sich nach links oder rechts, indem "Input.GetAxis­("Horizontal")" ausgewertet und die Ergebnisse auf "rigidbody.velocity" übertragen werden. Die Spielfigur sammelt Pilze, hinter denen sich Trigger-Collider verbergen. Wenn diese von der Figur berührt werden, erhöht sich der Punktestand, und das Objekt löst sich auf. Erreicht der Spieler das Levelende, wird der aktuelle Level durch einen Collider/Trigger neu geladen. Eine Verbesserung steht noch aus: ein großer Collider, der sich im Boden befindet und erkennt, wenn der Spieler von der Plattform fällt, und den Level daraufhin neu startet.

Prefabs (Vorlagen) Die Wiederverwendung ist bei der Codierung genauso wichtig wie der Entwurf. Nachdem Sie mehrere Komponenten zugewiesen und Ihre Spielobjekte angepasst haben, wäre es von Vorteil, sie innerhalb einer Szene oder sogar szenen- oder spielübergreifend wiederzuverwenden. Sie können entweder eine weitere Instanz eines Spielobjekts in der Szene anlegen oder eine Instanz eines Prefabs erstellen, das in der Szene noch nicht vorhanden ist. Denken Sie in diesem Zusammenhang an Plattformen und die zugehörigen Collider. Im Moment ist es nicht möglich, sie in anderen Szenen wiederzuverwenden. Bei Prefabs sieht dies jedoch anders aus. Sie ziehen einfach ein beliebiges Spielobjekt von der Hierarchie zurück auf den Projektordner, um eine neue Datei mit der Erweiterung ".prefab" zu erstellen, die alle untergeordneten Hierarchieebenen umfasst. Sie können die Datei jetzt in Ihre Szenen ziehen und dort wiederverwenden. Das Originalobjekt nimmt eine blaue Farbe an, was auf die Verbindung mit einem Prefab hinweist. Durch die Aktualisierung der .prefab-Datei werden alle Instanzen in den Szenen aktualisiert. Außerdem können Sie die Änderungen eines modifizierten Prefabs aus der Szene zurück auf die .prefab-Datei übertragen.

Wenn Sie auf das Prefab klicken, werden die enthaltenen Spielobjekte wie in Abbildung 15 dargestellt angezeigt. Wenn Sie an dieser Stelle Änderungen vornehmen, werden alle Instanzen in der Szene aktualisiert.

Viewing the Contents of a Prefab
Abbildung 15 – Anzeigen des Inhalts eines Prefabs

Zusammenfassung

Die Entwicklung von Spielen umfasst eine Vielzahl allgemeiner Schritte. In diesem Artikel haben Sie die Grundlagen eines Plattformspiels erlernt, z. B. Collider, RigitBodys, Animationen, Punktestand, grundlegender GUI-Text und das Auswerten von Benutzereingaben, um die Spielfigur durch Kräfteeinwirkung zu bewegen. Diese Bausteine lassen sich auf die unterschiedlichsten Spieltypen übertragen. Folgen Sie unserer Diskussion über 3D in meinem nächsten Artikel.

Weitere Lernthemen

 


Adam Tuliper arbeitet bei Microsoft als Senior Technical Evangelist und lebt im sonnigen Kalifornien. Er ist Entwickler für Indie Games, Co-Administrator beim Orange County Unity Meetup und Autor auf pluralsight.com. Seine Frau und er werden bald ihr drittes Kind bekommen, Sie können ihn dennoch in einer freien Minute unter adamt@microsoft.com oder auf Twitter unter twitter.com/AdamTuliper erreichen.

Unser Dank gilt den folgenden technischen Experten für die Durchsicht dieses Artikels: Matt Newman (Subscience Studios), Tautvydas Žilys (Unity)