Tower Defense zu einem Einheitsspiel machen - Teil 1

Bild

Tower Defense-Spiele werden immer beliebter, und das ist nicht verwunderlich - wenig kann mit dem Vergnügen verglichen werden, eigene Verteidigungslinien zu beobachten, die böse Feinde zerstören! In diesem zweiteiligen Tutorial erstellen wir ein Tower Defense-Spiel auf der Unity- Engine!

Sie lernen, wie Sie Folgendes tun:

  • Erschaffe Wellen von Feinden
  • Lassen Sie sie Routenpunkten folgen
  • Baue und verbessere Türme und lehre sie, wie man Feinde in kleine Pixel zerlegt

Am Ende bekommen wir den Rahmen des Spiels, der weiterentwickelt werden kann!

Hinweis : Sie benötigen grundlegende Unity-Kenntnisse (z. B. müssen Sie wissen, wie Assets und Komponenten hinzugefügt werden, welche Fertighäuser vorhanden sind) und die Grundlagen von C # . Um all dies zu lernen, empfehle ich Ihnen, die Tutorials zu Unity von Sean Duffy oder die Reihe Beginning C # with Unity von Brian Mockley durchzugehen.

Ich werde in Unity für OS X arbeiten, aber dieses Tutorial ist auch für Windows geeignet.

Durch die Fenster des Elfenbeinturms


In diesem Tutorial erstellen wir ein Tower Defense-Spiel, in dem Feinde (kleine Bugs) zu einem Cookie kriechen, der Ihnen und Ihren Schergen gehört (natürlich sind dies Monster!). Der Spieler kann Monster an strategischen Punkten platzieren und sie für Gold verbessern.

Der Spieler muss alle Bugs töten, bis sie zum Cookie gelangen. Jede neue Welle von Feinden wird immer schwieriger zu besiegen. Das Spiel endet, wenn Sie alle Wellen überleben (Sieg!) Oder wenn fünf Feinde zu den Keksen kriechen (Verlust!).

Hier ist ein Screenshot des fertigen Spiels:


Monster, vereinigt euch! Schützen Sie den Cookie!

An die Arbeit gehen


Laden Sie dieses Projekt leer herunter, entpacken Sie es und öffnen Sie das TowerDefense-Part1-Starter- Projekt in Unity.

Der Projektentwurf enthält zahlreiche Grafiken und Sounds, vorgefertigte Animationen und mehrere nützliche Skripte. Die Skripte stehen nicht in direktem Zusammenhang mit Tower Defense-Spielen, daher werde ich hier nicht darauf eingehen. Wenn Sie jedoch mehr über das Erstellen von 2D-Animationen in Unity erfahren möchten, lesen Sie dieses Unity 2D-Lernprogramm .

Das Projekt enthält auch Fertighäuser, die wir später hinzufügen werden, um Charaktere zu erstellen. Schließlich gibt es im Projekt eine Szene mit einem Hintergrund und einer benutzerdefinierten Benutzeroberfläche.

Öffnen Sie die GameScene im Ordner " Szenen " und stellen Sie den Spielemodus auf ein Seitenverhältnis von 4: 3 ein, damit alle Beschriftungen korrekt zum Hintergrund passen. Im Spielemodus sehen Sie Folgendes:


Autorschaft:

  • Die Grafiken für das Projekt stammen aus dem kostenlosen Wiki Wenderlich Pack! Weitere grafische Arbeiten finden Sie auf ihrer Website gameartguppy .
  • Großartige Musik von BenSound , die andere großartige Soundtracks enthält!
  • Ich danke auch Michael Jesper für die sehr nützliche Kameraverwacklungsfunktion.
.

Der Ort ist mit einem Kreuz markiert: der Ort der Monster


Monster können nur auf Punkte gelegt werden, die mit einem x markiert sind.

Um sie der Szene hinzuzufügen, ziehen Sie Images \ Objects \ Openspot aus dem Projektbrowser in das Szenenfenster . Während die Position für uns nicht wichtig ist.

Nachdem Sie in der Hierarchie Openspot ausgewählt haben, klicken Sie im Inspektor auf Komponente hinzufügen und wählen Sie Box Collider 2D . Im Szenenfenster zeigt Unity einen rechteckigen Collider mit einer grünen Linie an. Wir werden diesen Collider verwenden, um Mausklicks an dieser Stelle zu erkennen.


Fügen Sie die Komponente Audio \ Audio Source auf die gleiche Weise zu Openspot hinzu . Wählen Sie für den AudioClip- Parameter der Audio Source-Komponente die Datei tunnel_place aus , die sich im Audio- Ordner befindet, und deaktivieren Sie Play On Awake .

Wir müssen 11 weitere Punkte schaffen. Obwohl die Versuchung besteht, all diese Schritte zu wiederholen, hat Unity eine bessere Lösung: Fertighaus !

Ziehen Sie Openspot aus der Hierarchie in den Ordner Prefabs im Projektbrowser . Sein Name wird in der Hierarchie blau, was bedeutet, dass er an das Fertighaus angehängt ist. Ungefähr so:


Nachdem wir das vorgefertigte Leerzeichen haben, können wir so viele Kopien erstellen, wie wir möchten. Ziehen Sie Openspot einfach per Drag & Drop aus dem Prefabs- Ordner im Projektbrowser in das Szenenfenster . Wiederholen Sie dies 11 Mal und 12 Openspot-Objekte werden in der Szene angezeigt.

Verwenden Sie nun den Inspektor , um diese 12 Openspot-Objekte mit den folgenden Koordinaten festzulegen:

  • (X: -5,2, Y: 3,5, Z: 0)
  • (X: -2,2, Y: 3,5, Z: 0)
  • (X: 0,8, Y: 3,5, Z: 0)
  • (X: 3,8, Y: 3,5, Z: 0)
  • (X: -3,8, Y: 0,4, Z: 0)
  • (X: -0,8, Y: 0,4, Z: 0)
  • (X: 2,2, Y: 0,4, Z: 0)
  • (X: 5,2, Y: 0,4, Z: 0)
  • (X: -5,2, Y: -3,0, Z: 0)
  • (X: -2,2, Y: -3,0, Z: 0)
  • (X: 0,8, Y: -3,0, Z: 0)
  • (X: 3,8, Y: -3,0, Z: 0)

Wenn Sie dies tun, sieht die Szene folgendermaßen aus:


Wir platzieren Monster


Um die Platzierung zu vereinfachen, befindet sich im Prefab- Ordner des Projekts ein Monster- Fertighaus.


Monster Prefab Gebrauchsfertig

Im Moment besteht es aus einem leeren Spielobjekt mit drei verschiedenen Sprites und Schießanimationen als Kinder.

Jedes Sprite ist ein Monster mit unterschiedlichen Stärken. Das Fertighaus enthält auch die Audio Source- Komponente, die gestartet wird, um Sound abzuspielen, wenn ein Monster einen Laser abfeuert.

Jetzt werden wir ein Skript erstellen, das Monster auf Openspot hostet .

Wählen Sie im Projektbrowser das Openspot- Objekt im Ordner Prefabs aus . Klicken Sie im Inspektor auf Komponente hinzufügen , wählen Sie Neues Skript aus und benennen Sie das Skript PlaceMonster . Wählen Sie C Sharp als Sprache und klicken Sie auf Erstellen und Hinzufügen . Da wir das Skript zum Openspot- Prefab hinzugefügt haben, haben alle Openspot-Objekte in der Szene jetzt dieses Skript. Großartig!

Doppelklicken Sie auf das Skript, um es in der IDE zu öffnen. Fügen Sie dann zwei Variablen hinzu:

public GameObject monsterPrefab; private GameObject monster; 

Wir werden eine Instanz des in monsterPrefab gespeicherten monsterPrefab erstellen, um das Monster zu erstellen, und es in monster speichern, damit es während des Spiels manipuliert werden kann.

Ein Monster pro Punkt


Fügen Sie die folgende Methode hinzu, damit nur ein Monster auf einen Punkt gesetzt werden kann:

 private bool CanPlaceMonster() { return monster == null; } 

In CanPlaceMonster() wir überprüfen, ob die monster noch null . Wenn ja, dann gibt es kein Monster an der Stelle, und wir können es platzieren.

Füge nun den folgenden Code hinzu, um das Monster zu platzieren, wenn der Spieler auf dieses GameObject klickt:

 //1 void OnMouseUp() { //2 if (CanPlaceMonster()) { //3 monster = (GameObject) Instantiate(monsterPrefab, transform.position, Quaternion.identity); //4 AudioSource audioSource = gameObject.GetComponent<AudioSource>(); audioSource.PlayOneShot(audioSource.clip); // TODO:   } } 

Dieser Code findet das Monster, wenn Sie mit der Maus klicken oder den Bildschirm berühren. Wie arbeitet er?

  1. Unity ruft OnMouseUp automatisch OnMouseUp wenn ein Spieler den physischen Collider GameObject berührt.
  2. Beim Aufruf setzt diese Methode ein Monster, wenn CanPlaceMonster() true zurückgibt.
  3. Wir erstellen ein Monster mit der Instantiate Methode, die eine Instanz des angegebenen Fertighauses mit der angegebenen Position und Drehung erstellt. In diesem Fall kopieren wir monsterPrefab , geben ihm die aktuelle GameObject-Position und keine Drehung, übertragen das Ergebnis auf GameObject und speichern es in monster
  4. Am Ende rufen wir PlayOneShot auf, um den Soundeffekt PlayOneShot , der an die AudioSource Komponente des Objekts angehängt ist.

Jetzt kann unser PlaceMonster Skript ein neues Monster haben, aber wir müssen noch ein Fertighaus angeben.

Verwenden des richtigen Fertighauses


Speichern Sie die Datei und kehren Sie zu Unity zurück.

Um die Variable monsterPrefab festzulegen , wählen Sie zuerst das Openspot- Objekt aus dem Ordner Prefabs im Browser des Projekts aus.

Klicken Sie im Inspektor auf den Kreis rechts neben dem Feld Monster Prefab der PlaceMonster (Script) -Komponente und wählen Sie im angezeigten Dialogfeld Monster aus .


Das ist alles. Starte die Szene und erstelle Monster an verschiedenen Orten, indem du mit der Maus klickst oder den Bildschirm berührst.


Großartig! Jetzt können wir Monster erschaffen. Sie sehen jedoch wie ein seltsames Durcheinander aus, da alle Kindergeister des Monsters gezeichnet sind. Jetzt werden wir es beheben.

Erhöhe das Level der Monster


Die folgende Abbildung zeigt, dass Monster mit zunehmendem Level immer furchterregender aussehen.


Was für eine Süße! Aber wenn Sie versuchen, seine Kekse zu stehlen, wird dieses Monster zum Mörder.

Das Skript wird als Grundlage für die Implementierung des Systems der Monsterlevel verwendet. Es verfolgt die Macht des Monsters in jedem Level und natürlich das aktuelle Level des Monsters.

Fügen Sie dieses Skript hinzu.

Wählen Sie das Prefabs / Monster Prefab im Projektbrowser . Fügen Sie ein neues C # -Skript namens MonsterData hinzu . Öffnen Sie das Skript in der IDE und fügen Sie den folgenden Code über der MonsterData Klasse hinzu.

 [System.Serializable] public class MonsterLevel { public int cost; public GameObject visualization; } 

Also erstellen wir MonsterLevel . Es gruppiert den Preis (in Gold, den wir unten unterstützen werden) und eine visuelle Darstellung des Levels des Monsters.

Wir fügen zusätzlich zu [System.Serializable] damit Klasseninstanzen im Inspektor geändert werden können. Dadurch können wir schnell alle Werte der Level-Klasse ändern, auch wenn das Spiel läuft. Dies ist unglaublich nützlich, um das Spiel auszugleichen.

Monster Level einstellen


In unserem Fall speichern wir das angegebene MonsterLevel in List<T> .

Warum nicht einfach MonsterLevel[] ? Wir benötigen den Index eines bestimmten MonsterLevel Objekts mehrmals. Obwohl es einfach ist, Code dafür zu schreiben, müssen wir dennoch IndexOf() , das die Lists Funktionalität implementiert. Es macht keinen Sinn, das Rad neu zu erfinden.


Das Fahrrad neu zu erfinden ist normalerweise eine schlechte Idee.

Fügen Sie oben in MonsterData.cs using Konstrukts Folgendes hinzu:

 using System.Collections.Generic; 

Es gibt uns Zugriff auf verallgemeinerte Datenstrukturen, sodass wir die List<T> -Klasse im Skript verwenden können.

Hinweis : Verallgemeinerungen sind ein leistungsfähiges C # -Konzept. Mit ihnen können Sie typsichere Datenstrukturen angeben, ohne den Typ einhalten zu müssen. Dies ist nützlich für Containerklassen wie Listen und Mengen. Weitere Informationen zu generischen Strukturen finden Sie im Buch Einführung in C # Generics .

MonsterLevel nun MonsterData die folgende Variable hinzu, um die MonsterLevel Liste zu MonsterLevel :

 public List<MonsterLevel> levels; 

Dank Verallgemeinerungen können wir garantieren, dass die List von level nur MonsterLevel Objekte enthält.

Speichern Sie die Datei und wechseln Sie zu Unity, um die einzelnen Ebenen zu konfigurieren.

Wählen Sie Prefabs / Monster im Projektbrowser . Der Inspektor zeigt jetzt das Feld Ebenen der MonsterData (Script) -Komponente an. Stellen Sie die Größe auf 3 ein .


Als nächstes legen Sie die Kosten für jede Ebene fest:

  • Element 0 : 200
  • Element 1 : 110
  • Element 2 : 120

Jetzt weisen wir die Werte der visuellen Anzeigefelder zu.

Erweitern Sie Prefabs / Monster im Projektbrowser, um die untergeordneten Elemente anzuzeigen . Ziehen Sie das untergeordnete Monster0 in das Feld Visualisierungselement 0 .

Als nächstes setzen Sie Element 1 auf Monster1 und Element 2 auf Monster2 . Das GIF zeigt diesen Prozess:


Wenn Sie Prefabs / Monster auswählen, sollte das Prefab folgendermaßen aussehen:


Aktuelle Stufe einstellen


Gehen Sie in der IDE zurück zu MonsterData.cs und fügen Sie MonsterData eine weitere Variable hinzu.

 private MonsterLevel currentLevel; 

In der privaten Variablen currentLevel speichern wir das aktuelle Level des Monsters.

currentLevel Sie nun currentLevel und machen Sie es für andere Skripte sichtbar. Fügen Sie MonsterData die folgenden Zeilen zusammen mit der Deklaration von Instanzvariablen hinzu:

 //1 public MonsterLevel CurrentLevel { //2 get { return currentLevel; } //3 set { currentLevel = value; int currentLevelIndex = levels.IndexOf(currentLevel); GameObject levelVisualization = levels[currentLevelIndex].visualization; for (int i = 0; i < levels.Count; i++) { if (levelVisualization != null) { if (i == currentLevelIndex) { levels[i].visualization.SetActive(true); } else { levels[i].visualization.SetActive(false); } } } } } 

Ziemlich großer Teil des C # -Codes, oder? Nehmen wir es in der Reihenfolge:

  1. currentLevel Sie die Eigenschaft der privaten Variablen currentLevel . Durch Festlegen der Eigenschaft können wir sie wie jede andere Variable aufrufen: entweder als CurrentLevel (innerhalb der Klasse) oder als monster.CurrentLevel (außerhalb). Wir können jedes Verhalten in der Getter- oder Setter-Methode einer Eigenschaft definieren, und indem wir nur einen Getter, Setter oder beide erstellen, können wir die Eigenschaften der Eigenschaft steuern: schreibgeschützt, schreibgeschützt und schreiben / lesen.
  2. Im Getter geben wir den Wert von currentLevel .
  3. Im Setter weisen wir currentLevel neuen Wert zu. Dann erhalten wir den Index des aktuellen Niveaus. Schließlich durchlaufen wir alle Ebenen und aktivieren / deaktivieren die visuelle Anzeige abhängig von currentLevelIndex . Dies ist großartig, da das Sprite automatisch aktualisiert wird, wenn sich currentLevel ändert. Eigenschaften sind eine sehr bequeme Sache!

Fügen Sie die folgende OnEnable Implementierung hinzu:

 void OnEnable() { CurrentLevel = levels[0]; } 

Hier setzen wir CurrentLevel beim Platzieren. Dadurch wird sichergestellt, dass nur das gewünschte Sprite angezeigt wird.

Hinweis : Es ist wichtig, die Eigenschaft in OnEnable und nicht in OnStart zu initialisieren, da beim Erstellen vorgefertigter Instanzen die Ordnungsmethoden aufgerufen werden.

OnEnable wird sofort aufgerufen, wenn das Fertighaus erstellt wird (wenn das Fertighaus im aktivierten Zustand gespeichert wurde), OnStart erst aufgerufen, wenn das Objekt als Teil der Szene ausgeführt wird.

Wir müssen diese Daten überprüfen, bevor wir das Monster platzieren, also initialisieren wir sie auf OnEnable .

Speichern Sie die Datei und kehren Sie zu Unity zurück. Führe das Projekt aus und platziere die Monster. Sie zeigen jetzt die richtigen Sprites der untersten Ebene an.


Monster Upgrade


Kehren Sie zur IDE zurück und fügen Sie MonsterData die folgende Methode hinzu:

 public MonsterLevel GetNextLevel() { int currentLevelIndex = levels.IndexOf (currentLevel); int maxLevelIndex = levels.Count - 1; if (currentLevelIndex < maxLevelIndex) { return levels[currentLevelIndex+1]; } else { return null; } } 

In GetNextLevel wir den currentLevel Index und den Index der höchsten Ebene. Wenn das Monster das maximale Level nicht erreicht hat, kehrt das nächste Level zurück. Andernfalls wird null zurückgegeben.

Mit dieser Methode kannst du herausfinden, ob ein Monster-Upgrade möglich ist.

Füge die folgende Methode hinzu, um das Level des Monsters zu erhöhen:

 public void IncreaseLevel() { int currentLevelIndex = levels.IndexOf(currentLevel); if (currentLevelIndex < levels.Count - 1) { CurrentLevel = levels[currentLevelIndex + 1]; } } 

Hier erhalten wir den Index der aktuellen Ebene und stellen dann sicher, dass dies nicht die maximale Ebene ist. Überprüfen Sie, ob diese unter den Ebenen liegt. levels.Count - 1 . Wenn ja, CurrentLevel auf die nächste Stufe.

Überprüfen der Upgrade-Funktionalität


Speichern Sie die Datei und kehren Sie in der IDE zu PlaceMonster.cs zurück. Fügen Sie eine neue Methode hinzu:

 private bool CanUpgradeMonster() { if (monster != null) { MonsterData monsterData = monster.GetComponent<MonsterData>(); MonsterLevel nextLevel = monsterData.GetNextLevel(); if (nextLevel != null) { return true; } } return false; } 

Zuerst prüfen wir, ob es ein Monster gibt, das verbessert werden kann, indem wir die monster mit null . Wenn dies wahr ist, erhalten wir das aktuelle Monsterlevel aus seinen MonsterData .

Dann prüfen wir, ob die nächste Ebene verfügbar ist, GetNextLevel() ob GetNextLevel() nicht null GetNextLevel() . Wenn eine Pegelerhöhung möglich ist, geben wir true . Andernfalls wird false .

Wir implementieren Verbesserungen für Gold


OnMouseUp zum Aktivieren der Upgrade-Option den Zweig else if zu OnMouseUp :

 if (CanPlaceMonster()) { //      } else if (CanUpgradeMonster()) { monster.GetComponent<MonsterData>().IncreaseLevel(); AudioSource audioSource = gameObject.GetComponent<AudioSource>(); audioSource.PlayOneShot(audioSource.clip); // TODO:   } 

Wir prüfen die Möglichkeit eines Upgrades mit CanUpgradeMonster() . Wenn möglich, greifen wir mit GetComponent() auf die MonsterData Komponente zu und rufen IncreaseLevel() , wodurch sich das Level des Monsters erhöht. Schließlich starten wir die Monster AudioSource .

Speichern Sie die Datei und kehren Sie zu Unity zurück. Führe das Spiel aus, platziere und verbessere eine beliebige Anzahl von Monstern (aber vorerst).


Gold bezahlen - Game Manager


Wir können zwar sofort Monster bauen und verbessern, aber wird es im Spiel interessant sein?

Schauen wir uns das Thema Gold an. Das Problem beim Verfolgen ist, dass wir Informationen zwischen verschiedenen Spielobjekten übertragen müssen.

Die folgende Abbildung zeigt alle Objekte, die daran teilnehmen sollen.


Alle ausgewählten Spielobjekte müssen wissen, wie viel Gold ein Spieler hat.

Um diese Daten zu speichern, verwenden wir ein gemeinsames Objekt, auf das andere Objekte zugreifen können.

Klicken Sie mit der rechten Maustaste auf die Hierarchie und wählen Sie Leer erstellen . Benennen Sie das neue GameManager- Objekt.

Fügen Sie GameManager ein neues C # -Skript mit dem Namen GameManagerBehavior hinzu und öffnen Sie es in der IDE. Wir zeigen die Gesamtmenge an Spielergold auf dem Etikett an. Fügen Sie daher oben in der Datei die folgende Zeile hinzu:

 using UnityEngine.UI; 

Auf diese Weise können wir auf UI-Klassen wie Text zugreifen, die für Beschriftungen verwendet werden. Fügen Sie nun der Klasse die folgende Variable hinzu:

 public Text goldLabel; 

Es wird ein Link zur Text gespeichert, mit der die Goldmenge eines Spielers angezeigt wird.

GameManager wir den GameManager über das Etikett GameManager , wie synchronisieren wir die in der Variablen gespeicherte Goldmenge und den auf dem Etikett angezeigten Wert? Wir werden eine Immobilie erstellen.

Fügen Sie GameManagerBehavior den folgenden Code GameManagerBehavior :

 private int gold; public int Gold { get { return gold; } set { gold = value; goldLabel.GetComponent<Text>().text = "GOLD: " + gold; } } 

Kommt er mir bekannt vor? Der Code ähnelt CurrentLevel , den wir in Monster . Zuerst erstellen wir eine private Variable gold , um die aktuelle gold zu halten. Dann setzen wir die Gold Eigenschaft (unerwartet, richtig?) Und implementieren den Getter und Setter.

Der Getter gibt einfach den Wert von gold . Der Setter ist interessanter. Zusätzlich zum Festlegen des Werts der Variablen wird auch das text für goldLabel , um den neuen Goldwert anzuzeigen.

Wie großzügig werden wir sein? Fügen Sie Start() die folgende Zeile hinzu, um dem Spieler 1000 Gold oder weniger zu geben, wenn Ihnen das Geld leid tut:

 Gold = 1000; 

Zuweisen eines Beschriftungsobjekts zu einem Skript


Speichern Sie die Datei und kehren Sie zu Unity zurück. Wählen Sie in der Hierarchie GameManager aus . Klicken Sie im Inspektor auf den Kreis rechts neben dem Goldetikett . Wählen Sie im Dialogfeld Text auswählen die Registerkarte Szene und dann GoldLabel .


Führen Sie die Szene aus und auf dem Etikett wird Gold: 1000 angezeigt.


Überprüfen der "Brieftasche" des Players


Öffnen Sie das Skript PlaceMonster.cs in der IDE und fügen Sie die folgende Instanzvariable hinzu:

 private GameManagerBehavior gameManager; 

Wir werden gameManager um auf die GameManagerBehavior Komponente des GameManagerBehavior Objekts in der Szene GameManagerBehavior . Fügen Sie Start() Folgendes hinzu, um es anzugeben:

 gameManager = GameObject.Find("GameManager").GetComponent<GameManagerBehavior>(); 

Mit der Funktion GameObject.Find() wir ein GameObject namens GameManager, das das erste mit diesem Namen gefundene Spielobjekt zurückgibt. Dann holen wir uns die Komponente GameManagerBehavior und speichern sie für die Zukunft.

Hinweis : Sie können dies tun, indem Sie im Unity-Editor ein Feld GameManager oder GameManager eine statische Methode GameManager , die eine Instanz des Singletons GameManager , von dem wir GameManagerBehavior .

In dem oben gezeigten Codeblock gibt es jedoch ein dunkles Pferd: die Find Methode, die während der Anwendungsausführung langsamer arbeitet; aber es ist bequem und kann in Maßen verwendet werden.

Nimm mein Geld!


Wir haben Gold noch nicht subtrahiert, daher werden wir diese Zeile zweimal zu OnMouseUp() hinzufügen und jeden der Kommentare ersetzen // TODO: :

 gameManager.Gold -= monster.GetComponent<MonsterData>().CurrentLevel.cost; 

Speichern Sie die Datei und kehren Sie zu Unity zurück, aktualisieren Sie einige Monster und sehen Sie sich die Aktualisierung des Goldwerts an. Jetzt ziehen wir Gold ab, aber Spieler können Monster bauen, solange sie genug Platz haben. Sie leihen sich einfach Geld aus.


Unendlicher Kredit? Großartig! Aber wir können es nicht zulassen. Der Spieler muss in der Lage sein, Monster zu setzen, solange er genug Gold hat.

Goldcheck für Monster


Wechseln Sie in der IDE zu PlaceMonster.cs und ersetzen Sie den Inhalt von CanPlaceMonster() Folgendes:

 int cost = monsterPrefab.GetComponent<MonsterData>().levels[0].cost; return monster == null && gameManager.Gold >= cost; 

Wir MonsterData Monsterplatzierungspreis aus den levels in den MonsterData . Dann überprüfen wir, dass das monster nicht null ist und dass gameManager.Gold mehr als diesen Preis ist.

Die Aufgabe für Sie: CanUpgradeMonster() Sie CanUpgradeMonster() unabhängig CanUpgradeMonster() Überprüfung hinzu, ob der Spieler über genügend Gold verfügt.

Lösung im Inneren
Ersetzen Sie die Leitung:

 return true; 

dazu:

 return gameManager.Gold >= nextLevel.cost; 

Es wird geprüft, ob der Spieler mehr Gold als den Upgrade-Preis hat.

Speichern Sie die Szene und führen Sie sie in Unity aus. Versuchen Sie nun, Monster unbegrenzt hinzuzufügen!


Jetzt können wir nur eine begrenzte Anzahl von Monstern bauen.

Turmpolitik: Feinde, Wellen und Wegpunkte


Es ist Zeit, unseren Feinden den Weg zu ebnen. Feinde erscheinen am ersten Punkt der Route, gehen zum nächsten und wiederholen den Vorgang, bis sie den Cookie erreichen.

Sie können Feinde so bewegen:

  1. Stellen Sie die Straße ein, der die Feinde folgen sollen
  2. Bewegen Sie den Feind entlang der Straße
  3. Drehe den Feind so, dass er nach vorne schaut

Erstellen einer Straße aus Wegpunkten


Klicken Sie mit der rechten Maustaste auf die Hierarchie und wählen Sie "Leer erstellen", um ein neues leeres Spielobjekt zu erstellen. Nennen Sie es Straße und positionieren Sie es bei (0, 0, 0) .

Klicken Sie nun mit der rechten Maustaste auf Straße in der Hierarchie und erstellen Sie ein weiteres leeres Spielobjekt als Kind von Straße. Nennen Sie es Wegpunkt0 und platzieren Sie es am Punkt (-12, 2, 0) - von hier aus beginnen die Feinde ihre Bewegung.


Erstellen Sie auf ähnliche Weise fünf weitere Routenpunkte mit den folgenden Namen und Positionen:

  • Wegpunkt 1: (X: 7, Y: 2, Z: 0)
  • Wegpunkt 2: (X: 7, Y: -1, Z: 0)
  • Wegpunkt 3: (X: -7,3, Y: -1, Z: 0)
  • Wegpunkt 4: (X: -7,3, Y: -4,5, Z: 0)
  • Wegpunkt 5: (X: 7, Y: -4,5, Z: 0)

Der Screenshot unten zeigt die Routenpunkte und den resultierenden Pfad.


Feinde machen


Erstellen Sie nun einige Feinde, damit sie sich entlang der Straße bewegen können. Im Prefabs- Ordner befindet sich ein Enemy- Fertighaus. Die Position ist (-20, 0, 0) , sodass neue Instanzen außerhalb des Bildschirms erstellt werden.

Im Übrigen ist es fast genauso konfiguriert wie das Monster-Fertighaus, verfügt über AudioSource und ein Sprite Kind, und wir können dieses Sprite in Zukunft drehen, ohne die Gesundheitsleiste zu drehen.


Wir bewegen Feinde entlang der Straße


Fügen Sie dem Prefabs \ Enemy Prefab ein neues C # -Skript mit dem Namen MoveEnemy hinzu . Öffnen Sie das Skript in der IDE und fügen Sie die folgenden Variablen hinzu:

 [HideInInspector] public GameObject[] waypoints; private int currentWaypoint = 0; private float lastWaypointSwitchTime; public float speed = 1.0f; 

In waypoints wird eine Kopie der Routenpunkte im Array gespeichert, und die [HideIn inspector ] über den waypoints stellt sicher, dass wir dieses Feld im Inspector nicht versehentlich ändern können, aber weiterhin über andere Skripts darauf zugreifen können.

currentWaypoint verfolgt, woher die Route des Feindes zur aktuellen Zeit stammt, und lastWaypointSwitchTime speichert die Zeit, die der Feind durchlaufen hat. Außerdem speichern wir die speed Feindes.

Fügen Sie diese Zeile zu Start() :

 lastWaypointSwitchTime = Time.time; 

Also initialisieren wir lastWaypointSwitchTime mit dem Wert der aktuellen Zeit.

Update() den folgenden Code hinzu, damit sich der Feind entlang der Route bewegen kann:

 // 1 Vector3 startPosition = waypoints [currentWaypoint].transform.position; Vector3 endPosition = waypoints [currentWaypoint + 1].transform.position; // 2 float pathLength = Vector3.Distance (startPosition, endPosition); float totalTimeForPath = pathLength / speed; float currentTimeOnPath = Time.time - lastWaypointSwitchTime; gameObject.transform.position = Vector2.Lerp (startPosition, endPosition, currentTimeOnPath / totalTimeForPath); // 3 if (gameObject.transform.position.Equals(endPosition)) { if (currentWaypoint < waypoints.Length - 2) { // 3.a currentWaypoint++; lastWaypointSwitchTime = Time.time; // TODO:     } else { // 3.b Destroy(gameObject); AudioSource audioSource = gameObject.GetComponent<AudioSource>(); AudioSource.PlayClipAtPoint(audioSource.clip, transform.position); // TODO:   } } 

Lassen Sie uns den Code Schritt für Schritt analysieren:

  1. Aus dem Array von Routenpunkten erhalten wir die Start- und Endpositionen des aktuellen Routensegments.
  2. Wir berechnen die Zeit, die erforderlich ist, um die gesamte Strecke zurückzulegen, anhand der Formel Zeit = Entfernung / Geschwindigkeit und bestimmen dann die aktuelle Zeit auf der Route. Mit Vector2.Lerp interpolieren wir die aktuelle Position des Feindes zwischen dem exakten Start- und Endsegment.
  3. Überprüfen Sie, ob der Feind die endPosition erreicht endPosition . Wenn ja, verarbeiten wir zwei mögliche Szenarien:
    1. Der Feind hat den letzten Punkt der Route noch nicht erreicht. Erhöhen Sie daher den Wert von currentWaypoint und aktualisieren Sie lastWaypointSwitchTime . Später werden wir einen Code hinzufügen, um den Feind so zu drehen, dass er in die Richtung seiner Bewegung schaut.
    2. Der Feind hat den letzten Punkt der Route erreicht, dann zerstören wir ihn und starten den Soundeffekt. Später werden wir einen Code hinzufügen, der die health des Spielers verringert.

Speichern Sie die Datei und kehren Sie zu Unity zurück.

Wir informieren die Feinde über die Bewegungsrichtung


In ihrem aktuellen Zustand kennen die Feinde die Reihenfolge der Routenpunkte nicht.

Wählen Sie Straße in der Hierarchie aus und fügen Sie ein neues C # -Skript mit dem Namen SpawnEnemy hinzu . Öffnen Sie es in der IDE und fügen Sie die folgende Variable hinzu:

 public GameObject[] waypoints; 

Wir werden waypoints , um Verweise auf den Wegpunkt in der Szene in der gewünschten Reihenfolge zu speichern.

Speichern Sie die Datei und kehren Sie zu Unity zurück. Wählen Sie in der Hierarchie Straße aus und setzen Sie die Größe des Wegpunkt- Arrays auf 6 .

Ziehen Sie jedes der Road-Kinder in die Felder, indem Sie Waypoint0 in Element 0 , Waypoint1 in Element 1 usw. einfügen .


Jetzt haben wir ein Array, das die Routenpunkte in der richtigen Reihenfolge enthält - wohlgemerkt, die Feinde ziehen sich nie zurück, sie streben beharrlich nach einer süßen Belohnung.

Überprüfen Sie, wie alles funktioniert


Öffnen Sie SpawnEnemy in der IDE und fügen Sie die folgende Variable hinzu:

 public GameObject testEnemyPrefab; 

In testEnemyPrefab wird ein Verweis auf das Enemy- testEnemyPrefab .

Fügen Sie Start() den folgenden Code hinzu, um beim Ausführen des Skripts einen Feind zu erstellen:

 Instantiate(testEnemyPrefab).GetComponent<MoveEnemy>().waypoints = waypoints; 

Daher erstellen wir eine neue Kopie des in testEnemy gespeicherten testEnemy und weisen ihm eine Route zu.

Speichern Sie die Datei und kehren Sie zu Unity zurück. Wählen Sie das Road- Objekt in der Hierarchie aus und wählen Sie das Prefab Enemy für den Parameter Test Enemy aus .

Starten Sie das Projekt und sehen Sie, wie sich der Feind auf der Straße bewegt (in GIF wird die Geschwindigkeit aus Gründen der Übersichtlichkeit um das 20-fache erhöht).


Bemerkt, dass er nicht immer hinschaut, wohin er geht? Es ist lustig, aber wir versuchen ein professionelles Spiel zu machen. Daher werden wir im zweiten Teil des Tutorials den Feinden beibringen, nach vorne zu schauen.

Wohin als nächstes?


Wir haben bereits viel getan und sind schnell dabei, unser eigenes Tower Defense-Spiel zu entwickeln.

Spieler können eine begrenzte Anzahl von Monstern erschaffen, und der Feind rennt die Straße entlang und geht auf unseren Keks zu. Spieler haben Gold und können Monster verbessern.

Laden Sie das fertige Ergebnis hier herunter .

Im zweiten Teil werden wir die Entstehung riesiger Wellen von Feinden und deren Zerstörung betrachten. Bis dann!

Source: https://habr.com/ru/post/de413837/


All Articles