Einfacher Zombie-Shooter auf Unity

Hallo allerseits! In Kürze beginnt der Unterricht in der ersten Gruppe des Unity Games Developer- Kurses. Im Vorfeld des Kursbeginns wurde eine offene Lektion zum Erstellen eines Zombie-Shooters auf Unity abgehalten. Das Webinar wurde von Nikolai Zapolnov , Senior Game Developer von Rovio Entertainment Corporation, moderiert . Er hat auch einen ausführlichen Artikel geschrieben, auf den wir Sie aufmerksam machen.



In diesem Artikel möchte ich zeigen, wie einfach es ist, Spiele in Unity zu erstellen. Wenn Sie über grundlegende Programmierkenntnisse verfügen, können Sie schnell mit dieser Engine arbeiten und Ihr erstes Spiel erstellen.



Disclaimer # 1: Dieser Artikel ist für Anfänger. Wenn Sie in Unity einen Hund gefressen haben, kommt es Ihnen vielleicht langweilig vor.

Haftungsausschluss Nr. 2: Um diesen Artikel lesen zu können, benötigen Sie mindestens grundlegende Programmierkenntnisse. Zumindest die Wörter "Klasse" und "Methode" sollten Sie nicht erschrecken.

Achtung, Verkehr unter dem Schnitt!

Einführung in die Einheit


Wenn Sie mit dem Unity-Editor bereits vertraut sind, können Sie die Einführung überspringen und direkt zum Abschnitt „Erstellen einer Spielwelt“ wechseln.

Die grundlegende Struktureinheit in Unity ist die "Szene". Eine Szene ist normalerweise eine Ebene des Spiels, obwohl es in einigen Fällen mehrere Ebenen auf einmal in einer Szene geben kann, oder umgekehrt, eine große Ebene kann in mehrere dynamisch geladene Szenen unterteilt werden. Szenen sind mit Spielobjekten gefüllt und sie sind wiederum mit Komponenten gefüllt. Es sind die Komponenten, die verschiedene Spielfunktionen implementieren: Zeichnen von Objekten, Animation, Physik usw. Mit diesem Modell können Sie Funktionen aus einfachen Blöcken zusammensetzen, wie mit einem Spielzeug aus dem Lego-Konstruktor.

Sie können Komponenten mit der Programmiersprache C # selbst schreiben. So wird die Spiellogik geschrieben. Nachfolgend werden wir sehen, wie das gemacht wird, aber schauen wir uns jetzt den Motor selbst an.

Wenn Sie die Engine starten und ein neues Projekt erstellen, wird vor Ihnen ein Fenster angezeigt, in dem Sie vier Hauptelemente auswählen können:



In der oberen linken Ecke des Screenshots befindet sich das Fenster "Hierarchie". Hier sehen wir die Hierarchie der Spielobjekte in der aktuell geöffneten Szene. Unity hat zwei Spielobjekte für uns erstellt: eine Kamera („Hauptkamera“), durch die der Spieler unsere Spielwelt sehen kann, und ein „Richtungslicht“, das unsere Szene beleuchtet. Ohne sie würden wir nur ein schwarzes Quadrat sehen.

In der Mitte befindet sich das Szenenbearbeitungsfenster („Szene“). Hier sehen wir unsere Ebene und können sie visuell bearbeiten - bewegen und drehen Sie Objekte mit der Maus und sehen Sie, was passiert. In der Nähe sehen Sie die Registerkarte "Spiel", die derzeit inaktiv ist. Wenn Sie dorthin wechseln, können Sie von der Kamera aus sehen, wie das Spiel aussieht. Und wenn Sie das Spiel starten (über die Schaltfläche mit dem Wiedergabesymbol in der Symbolleiste), wechselt Unity zu dieser Registerkarte, auf der wir das gestartete Spiel spielen.

Im oberen rechten Teil befindet sich das Fenster „Inspector“. In diesem Fenster zeigt Unity die Parameter des ausgewählten Objekts an und wir können sie bearbeiten. Insbesondere können wir sehen, dass die ausgewählte Kamera zwei Komponenten aufweist: "Transformieren", mit der die Position der Kamera in der Spielwelt festgelegt wird, und "Kamera", mit der die Funktionalität der Kamera implementiert wird.

Übrigens ist die Transform-Komponente in allen Spielobjekten in Unity in der einen oder anderen Form.

Und schließlich befindet sich unten auf der Registerkarte „Projekt“ alle so genannten Assets, die sich in unserem Projekt befinden. Assets sind Datendateien wie Texturen, Sprites, 3D-Modelle, Animationen, Sounds und Musik sowie Konfigurationsdateien. Das sind alle Daten, mit denen wir Ebenen oder die Benutzeroberfläche erstellen können. Unity versteht eine Vielzahl von Standardformaten (z. B. PNG und JPG für Bilder oder FBX für 3D-Modelle), sodass das Laden von Daten in ein Projekt problemlos möglich ist. Und wenn Sie wie ich nicht wissen, wie man zeichnet, können Sie Assets aus dem Unity Asset Store herunterladen, der eine riesige Sammlung von Ressourcen aller Art enthält: sowohl kostenlos als auch für Geld.

Rechts neben der Registerkarte "Projekt" wird die inaktive Registerkarte "Konsole" angezeigt. Unity schreibt Warnungen und Fehlermeldungen an die Konsole. Überprüfen Sie sie daher regelmäßig. Vor allem, wenn etwas nicht funktioniert - höchstwahrscheinlich weist die Konsole auf die Ursache des Problems hin. Außerdem kann die Konsole Meldungen aus dem Spielcode zum Debuggen anzeigen.

Erstelle eine Spielwelt


Da ich Programmierer bin und schlechter zeichne als Hühnchenpfote, habe ich für die Grafiken ein paar kostenlose Assets aus dem Unity Asset Store genommen. Links dazu finden Sie am Ende dieses Artikels.

Aus diesen Assets habe ich ein einfaches Level zusammengestellt, mit dem wir arbeiten werden:



Keine Zauberei, ich habe einfach die gewünschten Objekte aus dem Projektfenster gezogen und sie mit der Maus nach Belieben angeordnet:



Übrigens: Mit Unity können Sie der Szene mit einem Klick Standardobjekte hinzufügen, z. B. einen Würfel, eine Kugel oder eine Ebene. Klicken Sie dazu einfach mit der rechten Maustaste in das Hierarchiefenster und wählen Sie beispielsweise 3D-Objekt - Ebene. Also wird der Asphalt in meinem Level nur aus einer Reihe von Ebenen zusammengesetzt, auf die ich eine Textur aus einer Reihe von Assets "gezogen" habe.

NB Wenn Sie sich fragen, warum ich viele Ebenen und keine mit großen Skalenwerten verwendet habe, ist die Antwort ganz einfach: Eine Ebene mit großem Maßstab hat eine stark vergrößerte Textur, die in Bezug auf andere Objekte in der Szene unnatürlich aussieht (dies kann mit den Parametern behoben werden) Material, aber wir versuchen alles so einfach wie möglich zu machen, oder?)

Zombies auf der Suche nach einem Weg


Wir haben also ein Spiellevel, aber es passiert noch nichts. In unserem Spiel jagen Zombies den Spieler und greifen ihn an. Dazu müssen sie sich auf den Spieler zubewegen und Hindernisse umgehen können.

Um dies zu implementieren, verwenden wir das Werkzeug „Navigationsnetz“. Basierend auf den Szenendaten berechnet dieses Tool die Bereiche, in denen Sie sich bewegen können, und generiert einen Datensatz, mit dem Sie während des Spiels nach der optimalen Route von einem beliebigen Punkt im Level zu einem anderen suchen können. Diese Daten werden im Asset gespeichert und können in Zukunft nicht mehr geändert werden - dieser Vorgang wird als "Backen" bezeichnet. Wenn Sie sich dynamisch ändernde Hindernisse benötigen, können Sie die NavMeshObstacle-Komponente verwenden, dies ist jedoch für unser Spiel nicht erforderlich.

Ein wichtiger Punkt: Damit Unity weiß, welche Objekte in die Berechnung einbezogen werden sollen, klicken Sie im Inspektor für jedes Objekt (Sie können alles auf einmal im Hierarchiefenster auswählen) auf den Abwärtspfeil neben der Option „Statisch“ und aktivieren Sie „Navigationsstatisch“:



Im Allgemeinen sind die verbleibenden Punkte auch nützlich und helfen Unity, das Rendern von Szenen zu optimieren. Wir werden uns heute nicht mit ihnen befassen, aber wenn Sie mit dem Erlernen der Grundlagen des Motors fertig sind, empfehle ich dringend, dass Sie sich auch mit anderen Parametern befassen. Manchmal kann ein einzelnes Häkchen die Bildrate erheblich erhöhen.

Nun verwenden wir den Menüpunkt Window⇨AI⇨Navigation und wählen im sich öffnenden Fenster die Registerkarte „Bake“. Hier bietet Unity an, Parameter wie die Höhe und den Radius des Zeichens, den maximalen Neigungswinkel der Erde, auf dem Sie noch gehen können, die maximale Höhe der Stufen usw. festzulegen. Wir werden noch nichts ändern und drücken Sie einfach die Taste "Backen".



Unity führt die notwendigen Berechnungen durch und zeigt uns das Ergebnis:



Hier kennzeichnet Blau den Bereich, in dem Sie gehen können. Wie Sie sehen, hat Unity eine kleine Seite um Hindernisse herum gelassen - die Breite dieser Seite hängt vom Radius des Charakters ab. Befindet sich die Mitte des Charakters in der blauen Zone, wird er nicht durch die Hindernisse "fallen".

Mit einem berechneten Navigationsraster können wir die NavMeshAgent-Komponente verwenden, um nach der Bewegungsroute zu suchen und die Bewegung von Spielobjekten auf unserer Ebene zu steuern.

Lassen Sie uns ein "Zombie" -Spielobjekt erstellen, ein 3D-Modell von Zombies aus Assets hinzufügen und auch die NavMeshAgent-Komponente:



Wenn Sie das Spiel jetzt starten, passiert nichts. Wir müssen der NavMeshAgent-Komponente mitteilen, wohin sie gehen soll. Dazu erstellen wir unsere erste Komponente in C #.

Wählen Sie im Projektfenster das Stammverzeichnis (es heißt "Assets") und klicken Sie in der Liste der Dateien mit der rechten Maustaste, um das Verzeichnis "Scripts" zu erstellen. Wir werden alle unsere Skripte darin speichern, damit das Projekt Ordnung hat. Jetzt erstellen wir in den "Skripten" ein "Zombie" -Skript und fügen es dem Zombie-Spielobjekt hinzu:



Ein Doppelklick auf das Skript öffnet es im Editor. Mal sehen, was die Einheit für uns geschaffen hat.

using System.Collections; using System.Collections.Generic; using UnityEngine; public class Zombie : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { } } 

Dies ist eine leere Standardkomponente. Wie wir sehen können, hat Unity die System.Collections- und System.Collections.Generic-Bibliotheken mit uns verbunden (jetzt werden sie nicht benötigt, aber sie werden häufig im Unity-Spielecode benötigt, sodass sie in der Standardvorlage enthalten sind) sowie die UnityEngine-Bibliothek, die alle enthält Core Engine API.

Außerdem hat Unity die Zombie-Klasse für uns erstellt (der Name stimmt mit dem Dateinamen überein; dies ist wichtig: Wenn sie nicht übereinstimmen, kann Unity das Skript nicht mit der Komponente in der Szene abgleichen). Die Klasse wird von MonoBehaviour geerbt - dies ist die Basisklasse für vom Benutzer erstellte Komponenten.

Innerhalb der Klasse hat Unity zwei Methoden für uns erstellt: Start und Update. Die Engine ruft diese Methoden selbst auf: Startet - unmittelbar nachdem die Szene geladen wurde, und Update - jeden Frame. In der Tat gibt es viele solcher Funktionen, die von der Engine aufgerufen werden, aber die meisten davon werden wir heute nicht mehr benötigen. Die vollständige Liste sowie die Reihenfolge der Aufrufe finden Sie immer in der Dokumentation: https://docs.unity3d.com/Manual/ExecutionOrder.html

Lassen Sie uns die Zombies auf der Karte bewegen!

Zuerst müssen wir die UnityEngine.AI-Bibliothek verbinden. Es enthält die NavMeshAgent-Klasse und andere Klassen, die sich auf das Navigationsraster beziehen. Fügen Sie dazu die using UnityEngine.AI-Direktive am Anfang der Datei hinzu.

Als nächstes müssen wir auf die NavMeshAgent-Komponente zugreifen. Dazu können wir die Standardmethode GetComponent verwenden. Hiermit können Sie eine Verknüpfung zu jeder Komponente im selben Spielobjekt herstellen, in der sich die Komponente befindet, von der aus wir diese Methode aufrufen (in unserem Fall das Spielobjekt „Zombie“). Lassen Sie uns das Feld NavMeshAgent navMeshAgent in der Klasse abrufen. In der Start-Methode erhalten wir einen Link zu NavMeshAgent und fordern Sie auf, zum Punkt (0, 0, 0) zu wechseln. Wir sollten dieses Skript bekommen:

 using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; public class Zombie : MonoBehaviour { NavMeshAgent navMeshAgent; // Start is called before the first frame update void Start() { navMeshAgent = GetComponent<NavMeshAgent>(); navMeshAgent.SetDestination(Vector3.zero); } // Update is called once per frame void Update() { } } 

Wenn Sie das Spiel starten, werden Sie sehen, wie sich der Zombie in die Mitte der Karte bewegt:



Zombies jagen ein Opfer


Großartig Aber unsere Zombies sind gelangweilt und einsam. Lassen Sie uns für ihn das Opfer eines Spielers in das Spiel aufnehmen.

In Analogie zu Zombies erstellen wir ein "Spieler" -Spielobjekt (dieses Mal wählen wir ein 3D-Modell eines Polizeibeamten aus). Außerdem fügen wir die NavMeshAgent-Komponente und das neu erstellte Spielerskript hinzu. Wir werden den Inhalt des Player-Skripts noch nicht berühren, müssen jedoch Änderungen am Zombie-Skript vornehmen. Außerdem empfehle ich, den Priority-Eigenschaftswert des Players in der NavMeshAgent-Komponente auf 10 zu setzen (oder einen anderen Wert, der unter dem Standardwert von 50 liegt, dh dem Player eine höhere Priorität zuzuweisen). In diesem Fall können die Zombies den Spieler nicht bewegen, wenn sich der Spieler und die Zombies auf der Karte treffen, während der Spieler die Zombies hinausschieben kann.

Um einen Spieler zu jagen, muss ein Zombie seine Position kennen. Dazu müssen wir in unserer Zombie-Klasse einen Link mit der Standardmethode FindObjectOfType erstellen. Nachdem wir uns an den Link erinnert haben, können wir uns der Transformationskomponente des Spielers zuwenden und ihn nach dem Positionswert fragen. Damit der Zombie den Spieler immer und nicht erst zu Beginn des Spiels verfolgt, legen wir in der Update-Methode ein Ziel für NavMeshAgent fest. Sie erhalten folgendes Skript:

 using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; public class Zombie : MonoBehaviour { NavMeshAgent navMeshAgent; Player player; // Start is called before the first frame update void Start() { navMeshAgent = GetComponent<NavMeshAgent>(); player = FindObjectOfType<Player>(); } // Update is called once per frame void Update() { navMeshAgent.SetDestination(player.transform.position); } } 

Starte das Spiel und stelle sicher, dass der Zombie sein Opfer gefunden hat:



Flucht Flucht


Unser Spieler steht wie ein Idol. Dies wird ihm eindeutig nicht helfen, in einer so aggressiven Welt zu überleben. Sie müssen ihm also beibringen, sich auf der Karte zu bewegen.

Dazu benötigen wir Informationen zu den von Unity gedrückten Tasten. Die GetKey-Methode der Standard-Input-Klasse liefert nur solche Informationen!

NB Im Allgemeinen ist diese Art der Eingabe nicht ganz kanonisch. Es ist besser, Input.GetAxis zu verwenden und über Project Settings (Projekteinstellungen) Input Manager zu binden. Besser noch, neues Eingabesystem . Dieser Artikel erwies sich jedoch als zu lang. Machen wir es uns also so einfach wie möglich.

Öffnen Sie das Player-Skript und ändern Sie es wie folgt:

 using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; public class Player : MonoBehaviour { NavMeshAgent navMeshAgent; public float moveSpeed; // Start is called before the first frame update void Start() { navMeshAgent = GetComponent<NavMeshAgent>(); } // Update is called once per frame void Update() { Vector3 dir = Vector3.zero; if (Input.GetKey(KeyCode.LeftArrow)) dir.z = -1.0f; if (Input.GetKey(KeyCode.RightArrow)) dir.z = 1.0f; if (Input.GetKey(KeyCode.UpArrow)) dir.x = -1.0f; if (Input.GetKey(KeyCode.DownArrow)) dir.x = 1.0f; navMeshAgent.velocity = dir.normalized * moveSpeed; } } 

Wie bei Zombies erhalten wir bei der Start-Methode einen Link zur NavMeshAgent-Komponente des Players und speichern sie im Klassenfeld. Jetzt haben wir aber auch das moveSpeed-Feld hinzugefügt.
Da dieses Feld öffentlich ist, kann sein Wert direkt im Inspector in Unity bearbeitet werden! Wenn Sie einen Spieledesigner in Ihrem Team haben, wird er sich sehr freuen, dass er nicht in den Code gehen muss, um die Parameter des Spielers zu bearbeiten.

Setze 10 als Geschwindigkeit:



Bei der Update-Methode überprüfen wir mit Input.GetKey, ob einer der Pfeile auf der Tastatur gedrückt ist, und bilden einen Richtungsvektor für den Player. Beachten Sie, dass wir die X- und Z-Koordinaten verwenden, da in Unity die Y-Achse in den Himmel schaut und sich die Erde in der XZ-Ebene befindet.

Nachdem wir einen Richtungsvektor für die Bewegungsrichtung gebildet haben, normalisieren wir ihn (andernfalls ist der Vektor etwas länger als ein einzelner, wenn der Spieler sich diagonal bewegen möchte, und diese Bewegung ist schneller als eine direkte Bewegung) und multiplizieren sie mit der angegebenen Bewegungsgeschwindigkeit. Das Ergebnis wird an navMeshAgent.velocity übergeben und der Agent erledigt den Rest.

Durch das Starten des Spiels können wir endlich versuchen, den Zombies an einen sicheren Ort zu entkommen:



Um die Kamera mit dem Player zu bewegen, schreiben wir ein weiteres einfaches Skript. Nennen wir es "PlayerCamera":

 using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerCamera : MonoBehaviour { Player player; Vector3 offset; // Start is called before the first frame update void Start() { player = FindObjectOfType<Player>(); offset = transform.position - player.transform.position; } // Update is called once per frame void LateUpdate() { transform.position = player.transform.position + offset; } } 

Die Bedeutung dieses Skripts sollte weitgehend verstanden werden. Von Funktionen - hier anstelle von Update verwenden wir LateUpdate. Diese Methode ähnelt Update, wird jedoch immer strikt nach Abschluss von Update für alle Skripte in der Szene aufgerufen. In diesem Fall verwenden wir LateUpdate, da es für uns wichtig ist, dass NavMeshAgent die neue Position des Players berechnet, bevor wir die Kamera bewegen. Andernfalls kann ein unangenehmer "Ruck" -Effekt auftreten.

Wenn Sie diese Komponente jetzt an das Spielobjekt "Hauptkamera" anhängen und das Spiel starten, steht der Charakter des Spielers immer im Rampenlicht!

Moment der Animation


Für einen Moment schweifen wir von den Problemen des Überlebens unter den Bedingungen einer Zombie-Apokalypse ab und denken über das Ewige nach - über Kunst. Unsere Figuren sehen jetzt aus wie animierte Statuen, die von einer unbekannten Kraft in Bewegung gesetzt werden (möglicherweise Magnete unter dem Asphalt). Und ich möchte, dass sie wie echte, lebende (und nicht sehr lebende) Menschen aussehen - sie bewegten ihre Arme und Beine. Die Animator-Komponente und ein Tool namens Animator Controller helfen uns dabei.

Animator Controller ist eine Zustandsmaschine, in der wir bestimmte Zustände festlegen (der Charakter steht, der Charakter ist an, der Charakter stirbt usw.), ihnen Animationen hinzufügen und die Regeln für den Übergang von einem Zustand in einen anderen festlegen. Unity wechselt automatisch von einer Animation zur nächsten, sobald die entsprechende Regel funktioniert.

Erstellen wir einen Animator-Controller für Zombies. Erstellen Sie dazu das Animationsverzeichnis im Projekt (merken Sie sich die Reihenfolge im Projekt) und dort - mit der rechten Maustaste - Animator Controller. Und nennen wir ihn "Zombie". Doppelklick - und der Editor erscheint vor uns:



Bisher gibt es hier keine Bundesstaaten, aber es gibt zwei Einstiegspunkte („Entry“ und „Any State“) und einen Ausstiegspunkt („Exit“). Ziehen Sie einige Animationen aus den Assets:



Wie Sie sehen, hat Unity die erste Animation beim Ziehen automatisch an den Entry-Einstiegspunkt gebunden. Dies ist die sogenannte Standardanimation. Es wird sofort nach dem Start des Levels gespielt.

Um in einen anderen Status zu wechseln (und eine andere Animation abzuspielen), müssen Übergangsregeln erstellt werden. Dazu müssen wir zunächst einen Parameter hinzufügen, den wir aus dem Code zum Verwalten von Animationen festlegen.

In der oberen linken Ecke des Editorfensters befinden sich zwei Schaltflächen: „Ebenen“ und „Parameter“. Standardmäßig ist die Registerkarte „Ebenen“ ausgewählt, wir müssen jedoch zu „Parameter“ wechseln. Jetzt können wir mit der Schaltfläche „+“ einen neuen Parameter vom Typ float hinzufügen. Nennen wir es "Geschwindigkeit":



Jetzt müssen wir Unity mitteilen, dass die Animation "Z_run" abgespielt werden soll, wenn die Geschwindigkeit größer als 0 ist, und "Z_idle_A", wenn die Geschwindigkeit Null ist. Dazu müssen wir zwei Übergänge erstellen: einen von "Z_idle_A" nach "Z_run" und den anderen in die entgegengesetzte Richtung.

Beginnen wir mit dem Übergang vom Leerlauf zum Betrieb. Klicken Sie mit der rechten Maustaste auf das Rechteck "Z_idle_A" und wählen Sie "Make Transition". Es erscheint ein Pfeil, auf den Sie die Parameter konfigurieren können. Zuerst müssen Sie das Häkchen bei "Hat Austrittszeit" entfernen. Ist dies nicht der Fall, wird die Animation nicht gemäß unserer Bedingung umgeschaltet, sondern erst, wenn die vorherige Wiedergabe beendet ist. Wir brauchen das überhaupt nicht, also deaktivieren wir es. Zweitens müssen Sie unten in der Liste der Bedingungen („Conditions“) auf „+“ klicken, und Unity fügt uns eine Bedingung hinzu. Die Standardwerte in diesem Fall sind genau das, was wir brauchen: Der Parameter "Geschwindigkeit" muss größer als Null sein, um vom Leerlauf in den Betrieb zu wechseln.



In Analogie erzeugen wir einen Übergang in die entgegengesetzte Richtung, aber als Bedingung geben wir jetzt "Geschwindigkeit" kleiner als 0,0001 an. Es gibt keine Gleichheitsprüfungen für Parameter vom Typ float, sie können nur für mehr / weniger verglichen werden:



Nun müssen Sie den Controller an das Spielobjekt binden. Wir wählen das 3D-Modell des Zombies in der Szene aus (dies ist ein Kind des "Zombie" -Objekts) und ziehen den Controller mit der Maus in das entsprechende Feld in der Animator-Komponente:



Es muss nur noch ein Skript geschrieben werden, das den Geschwindigkeitsparameter steuert!

Erstellen Sie das MovementAnimator-Skript mit den folgenden Inhalten:

 using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; public class MovementAnimator : MonoBehaviour { NavMeshAgent navMeshAgent; Animator animator; // Start is called before the first frame update void Start() { navMeshAgent = GetComponent<NavMeshAgent>(); animator = GetComponentInChildren<Animator>(); } // Update is called once per frame void Update() { animator.SetFloat("speed", navMeshAgent.velocity.magnitude); } } 

Hier erhalten wir wie in anderen Skripten in der Start-Methode Zugriff auf NavMeshAgent. Wir erhalten auch Zugriff auf die Animator-Komponente. Da wir jedoch die MovementAnimator-Komponente an das Zombie-Spielobjekt anhängen und der Animator sich im untergeordneten Objekt befindet, müssen wir anstelle von GetComponent die Standardmethode GetComponentInChildren verwenden.

Bei der Update-Methode fragen wir NavMeshAgent nach seinem Geschwindigkeitsvektor, berechnen seine Länge und übergeben ihn als Geschwindigkeitsparameter an den Animator. Keine Magie, alles in der Wissenschaft!

Fügen Sie nun die MovementAnimator-Komponente zum Zombie-Spielobjekt hinzu und wenn das Spiel startet, sehen wir, dass die Zombies jetzt animiert sind:



Da wir den Animator-Steuercode in einer separaten MovementAnimation-Komponente platziert haben, kann er problemlos für den Player hinzugefügt werden. Es muss nicht einmal ein Controller von Grund auf neu erstellt werden. Sie können einen Zombie-Controller kopieren (indem Sie die Datei "Zombie" auswählen und Strg + D drücken) und die Animationen in den Statusrechtecken durch "m_idle_" und "m_run" ersetzen. Alles andere ist wie ein Zombie. Ich überlasse Ihnen dies als Übung (oder laden Sie den Code am Ende des Artikels herunter).

Eine kleine Ergänzung, die nützlich ist, ist das Hinzufügen der folgenden Zeilen zur Zombie-Klasse:

In der Start-Methode:

 navMeshAgent.updateRotation = false; 

In der Update-Methode:

 transform.rotation = Quaternion.LookRotation(navMeshAgent.velocity.normalized); 

In der ersten Zeile wird NavMeshAgent mitgeteilt, dass er die Drehung des Zeichens nicht steuern soll, wir werden es selbst tun. In der zweiten Zeile wird der Zug des Charakters in dieselbe Richtung gesetzt, in die seine Bewegung gerichtet ist. NavMeshAgent interpoliert standardmäßig den Drehwinkel des Zeichens und dies sieht nicht besonders gut aus (der Zombie dreht sich langsamer als die Bewegungsrichtung ändert). Durch Hinzufügen dieser Zeilen wird dieser Effekt entfernt.

NB Wir verwenden das Quaternion, um die Rotation zu spezifizieren. In dreidimensionalen Grafiken sind die Hauptmethoden zum Angeben der Drehung eines Objekts Euler-Winkel, Drehmatrizen und Quaternionen. Die ersten beiden sind nicht immer bequem zu bedienen und unterliegen auch so einem unangenehmen Effekt wie „Gimbal Lock“. Quaternionen haben diesen Nachteil nicht mehr und werden heute fast universell eingesetzt. Unity bietet praktische Werkzeuge für die Arbeit mit Quaternionen (sowie mit Matrizen und Euler-Winkeln), mit denen Sie nicht auf Details des Geräts dieses mathematischen Geräts eingehen können.

Ich sehe das Ziel


Großartig, jetzt können wir den Zombies entkommen. Aber das ist nicht genug, früher oder später wird ein zweiter Zombie auftauchen, dann ein dritter, fünfter, zehnter ... aber du kannst nicht einfach vor der Menge davonlaufen. Um zu überleben, musst du töten. Außerdem hat der Spieler bereits eine Waffe in der Hand.

Damit der Spieler schießen kann, müssen Sie ihm die Möglichkeit geben, ein Ziel auszuwählen. Dazu platzieren Sie den mausgesteuerten Cursor auf dem Boden.

Auf dem Bildschirm bewegt sich der Mauszeiger im zweidimensionalen Raum - der Oberfläche des Monitors. Gleichzeitig ist unsere Spielszene dreidimensional. Der Betrachter sieht die Szene durch sein Auge, wo alle Lichtstrahlen an einem Punkt zusammenlaufen. Wenn wir all diese Strahlen kombinieren, erhalten wir eine Pyramide der Sichtbarkeit:



Das Auge des Betrachters sieht nur, was in diese Pyramide fällt. Darüber hinaus schneidet der Motor diese Pyramide gezielt von zwei Seiten ab: Erstens befindet sich auf der Seite des Betrachters ein Monitorbildschirm, die sogenannte „Nahe Ebene“ (in der Abbildung ist sie gelb gestrichen). Der Monitor kann keine Objekte anzeigen, die näher als der Bildschirm sind, sodass der Motor sie abschneidet. Zweitens kann die Engine, da der Computer über eine begrenzte Anzahl von Ressourcen verfügt, die Strahlen nicht bis ins Unendliche ausdehnen (zum Beispiel muss für den Tiefenpuffer ein bestimmter Bereich möglicher Werte festgelegt werden; je breiter er ist, desto geringer ist die Genauigkeit), sodass die Pyramide hinter der sogenannten abgeschnitten wird "Weites Flugzeug".

Da sich der Mauszeiger entlang der nahen Ebene bewegt, können wir den Strahl an der Stelle auslösen, an der er sich tief in der Szene befindet. Das erste Objekt, mit dem es sich schneidet, ist das Objekt, auf das der Mauszeiger aus Sicht des Betrachters zeigt.



Um einen solchen Strahl zu erstellen und seine Schnittmenge mit Objekten in der Szene zu finden, können Sie die Raycast-Standardmethode aus der Physikklasse verwenden. Wenn wir diese Methode verwenden, wird der Schnittpunkt mit allen Objekten in der Szene gefunden - Erde, Mauern, Zombies ... Wir möchten jedoch, dass sich der Cursor nur auf dem Boden bewegt. Daher müssen wir Unity irgendwie erklären, dass die Suche nach Schnittpunkten nur begrenzt sein soll eine gegebene Menge von Objekten (in unserem Fall nur die Ebenen der Erde).

Wenn Sie ein Spielobjekt in der Szene auswählen, wird im oberen Bereich des Inspektors die Dropdown-Liste „Ebene“ angezeigt. Standardmäßig gibt es den Wert "Standard". Wenn Sie die Dropdown-Liste öffnen, finden Sie darin das Element "Ebene hinzufügen ...", wodurch das Ebeneneditorfenster geöffnet wird. Im Editor müssen Sie eine neue Ebene hinzufügen (nennen wir es "Ground"):



Jetzt können Sie alle Grundebenen in der Szene auswählen und diese Dropdown-Liste verwenden, um ihnen die Grundebene zuzuweisen. Auf diese Weise können wir der Physics.Raycast-Methode im Skript mitteilen, dass nur mit diesen Objekten der Schnittpunkt des Strahls überprüft werden muss.

Ziehen wir nun das Cursor-Sprite aus den Assets in die Szene (ich verwende Spags Assets⇨Textures⇨Demo⇨white_hip⇨white_hip_14):



Ich habe dem Cursor eine 90-Grad-Drehung um die X-Achse hinzugefügt, damit er horizontal auf dem Boden liegt. Stellen Sie die Skala auf 0,25 ein, damit sie nicht so groß ist, und legen Sie die Y-Koordinate auf 0,01 fest. Letzteres ist wichtig, damit es keinen Effekt gibt, der als "Z-Fighting" bezeichnet wird. Die Grafikkarte ermittelt anhand von Gleitkommaberechnungen, welche Objekte näher an der Kamera liegen. Wenn Sie den Cursor auf 0 setzen (d. H. Den gleichen Wert wie die Grundebene), entscheidet die Grafikkarte aufgrund von Fehlern bei diesen Berechnungen für einige Pixel, dass der Cursor näher und für andere die Erde ist. Darüber hinaus sind die Pixelmengen in verschiedenen Frames unterschiedlich, was zu einem unangenehmen Effekt führt, bei dem Teile des Cursors durch den Boden leuchten und flackern, wenn er sich bewegt. Der Wert 0,01 ist groß genug, um die Fehler bei der Berechnung der Grafikkarte auszugleichen, aber nicht so groß, dass das Auge bemerkt, dass der Cursor in der Luft hängt.

Benenne nun das Spielobjekt in Cursor um und erstelle ein gleichnamiges Skript mit folgendem Inhalt:

 using System.Collections; using System.Collections.Generic; using UnityEngine; public class Cursor : MonoBehaviour { SpriteRenderer spriteRenderer; int layerMask; // Start is called before the first frame update void Start() { spriteRenderer = GetComponent<SpriteRenderer>(); layerMask = LayerMask.GetMask("Ground"); } // Update is called once per frame void Update() { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; if (!Physics.Raycast(ray, out hit, 1000, layerMask)) spriteRenderer.enabled = false; else { transform.position = new Vector3(hit.point.x, transform.position.y, hit.point.z); spriteRenderer.enabled = true; } } } 

Da der Cursor ein Sprite ist (zweidimensionale Zeichnung), verwendet Unity die SpriteRenderer-Komponente zum Rendern. Wir erhalten eine Verknüpfung zu dieser Komponente in der Start-Methode, um sie bei Bedarf ein- und ausschalten zu können.

Auch bei der Start-Methode konvertieren wir den Namen der zuvor erstellten "Ground" -Ebene in eine Bitmaske. Unity verwendet bitweise Operationen zum Filtern von Objekten, wenn nach Schnittpunkten gesucht wird, und die LayerMask.GetMask-Methode gibt die der angegebenen Ebene entsprechende Bitmaske zurück.

Bei der Update-Methode greifen wir mit Camera.main auf die Hauptkamera der Szene zu und fordern sie auf, die zweidimensionalen Koordinaten der Maus (erhalten mit Input.mousePosition) in einen dreidimensionalen Strahl umzurechnen. Als Nächstes übergeben wir diesen Strahl an die Physics.Raycast-Methode und prüfen, ob er sich mit einem Objekt in der Szene schneidet. Ein Wert von 1000 ist die maximale Entfernung. In der Mathematik sind die Strahlen endlos, die Rechenressourcen und der Speicher eines Computers jedoch nicht. Aus diesem Grund bittet uns Unity, eine angemessene maximale Entfernung zu bestimmen.

Wenn es keine Kreuzung gab, schalten wir den SpriteRenderer aus und das Cursorbild verschwindet vom Bildschirm. Wenn der Schnittpunkt gefunden wurde, bewegen wir den Cursor zum Schnittpunkt.Bitte beachten Sie, dass wir die Y-Koordinate nicht ändern, da der Schnittpunkt des Strahls mit dem Boden Y gleich Null hat. Wenn wir ihn unserem Cursor zuweisen, erhalten wir erneut den Z-Kampf-Effekt, den wir oben loswerden wollten. Daher nehmen wir nur die X- und Z-Koordinaten vom Schnittpunkt und Y bleibt gleich.

Fügen Sie die Cursor-Komponente zum Cursor-Spielobjekt hinzu.

Lassen Sie uns nun das Player-Skript fertigstellen: Fügen Sie zuerst das Cursor-Cursor-Feld hinzu. Fügen Sie dann in der Start-Methode die folgenden Zeilen hinzu:

 cursor = FindObjectOfType<Cursor>(); navMeshAgent.updateRotation = false; 

Und schließlich, damit sich der Player immer zum Cursor dreht, fügen Sie in der Update-Methode Folgendes hinzu:

 Vector3 forward = cursor.transform.position - transform.position; transform.rotation = Quaternion.LookRotation(new Vector3(forward.x, 0, forward.z)); 

Auch hier berücksichtigen wir die Y-Koordinate nicht.

Schießen Sie, um zu überleben


Die bloße Drehung in Richtung des Cursors schützt uns nicht vor Zombies, sondern entlastet nur den Charakter des Spielers von Überraschungseffekten - jetzt können Sie sich nicht hinter ihn heranschleichen. Damit er in den harten Realitäten unseres Spiels wirklich überleben kann, musst du ihm das Schießen beibringen. Und was ist das für eine Einstellung, wenn sie nicht sichtbar ist? Jeder weiß, dass jeder seriöse Schütze immer Markierungskugeln abschießt.

Erstellen Sie ein Shot-Spielobjekt und fügen Sie die Standard-LineRenderer-Komponente hinzu. Geben Sie im Editor im Feld „Breite“ eine kleine Breite ein, z. B. 0,04. Wie wir sehen können, malt Unity es mit einer hellvioletten Farbe - auf diese Weise werden Objekte ohne Material hervorgehoben.

Materialien sind ein wichtiges Element jeder dreidimensionalen Maschine. Die Verwendung von Materialien beschreibt das Erscheinungsbild des Objekts. Alle Lichtparameter, Texturen, Shader - all dies wird durch das Material beschrieben.

Lassen Sie uns das Materialverzeichnis im Projekt erstellen und darin das Material als Gelb bezeichnen. Wählen Sie als Shader Unlit / Color. Dieser Standard-Shader enthält keine Beleuchtung, sodass unsere Kugel auch im Dunkeln sichtbar ist. Wählen Sie die gelbe Farbe:



Nachdem das Material erstellt wurde, können Sie es LineRenderer zuweisen:



Erstellen Sie ein Shot-Skript:

 using System.Collections; using System.Collections.Generic; using UnityEngine; public class Shot : MonoBehaviour { LineRenderer lineRenderer; bool visible; // Start is called before the first frame update void Start() { lineRenderer = GetComponent<LineRenderer>(); } // Update is called once per frame void FixedUpdate() { if (visible) visible = false; else gameObject.SetActive(false); } public void Show(Vector3 from, Vector3 to) { lineRenderer.SetPositions(new Vector3[]{ from, to }); visible = true; gameObject.SetActive(true); } } 

Dieses Skript muss, wie Sie wahrscheinlich bereits erraten haben, zum Shot-Spielobjekt hinzugefügt werden.

Hier habe ich einen kleinen Trick benutzt, um eine Aufnahme für genau ein Bild mit einem Minimum an Code auf dem Bildschirm anzuzeigen. Zuerst benutze ich FixedUpdate anstelle von Update. Die FixedUpdate-Methode wird mit der angegebenen Häufigkeit aufgerufen (standardmäßig 60 Bilder pro Sekunde), auch wenn die tatsächliche Bildrate instabil ist. Zweitens setze ich die Variable sichtbar, die ich auf wahr gesetzt habe, wenn ich die Aufnahme auf dem Bildschirm anzeige. Beim nächsten FixedUpdate setze ich es auf false zurück und schalte erst im nächsten Frame das Spielobjekt des Schusses aus. Im Wesentlichen verwende ich eine boolesche Variable als Zähler von 1 bis 0.

Die Methode gameObject.SetActive aktiviert oder deaktiviert das gesamte Spielobjekt, auf dem sich unsere Komponente befindet. Deaktivierte Spielobjekte werden nicht auf dem Bildschirm angezeigt und ihre Komponenten rufen keine Update-, FixedUpdate-Methoden usw. auf. Mit dieser Methode können Sie den Schuss unsichtbar machen, wenn der Player nicht schießt.

Es gibt auch eine öffentliche Show-Methode im Skript, die wir im Player-Skript verwenden, um das Aufzählungszeichen beim Abfeuern tatsächlich anzuzeigen.

Aber zuerst müssen Sie in der Lage sein, die Koordinaten des Waffenrohrs zu ermitteln, damit der Schuss aus dem richtigen Loch kommt. Suchen Sie dazu im 3D-Modell des Spielers das Objekt Bip001⇨Bip001 Bip001 Pelvis⇨Bip001 Spine⇨Bip001 R Clavicle⇨Bip001 R UpperArm⇨Bip001 R Forearm⇨Bip001 R Hand⇨R_hand_container⇨w_handgun und fügen Sie das untergeordnete GunBarrel-Objekt hinzu. Platzieren Sie es so, dass es direkt neben dem Gewehrlauf liegt:



Fügen Sie nun im Player-Skript die Felder hinzu:

 Shot shot; public Transform gunBarrel; 


Fügen Sie der Start-Methode des Player-Skripts Folgendes hinzu:

 shot = FindObjectOfType<Shot>(); 

Und in der Update-Methode:

 if (Input.GetMouseButtonDown(0)) { var from = gunBarrel.position; var target = cursor.transform.position; var to = new Vector3(target.x, from.y, target.z); shot.Show(from, to); } 

Wie Sie sich vorstellen können, ist das hinzugefügte öffentliche Feld gunBarrel wie moveSpeed ​​früher im Inspector verfügbar.



Weisen wir ihm das eigentliche Spielobjekt zu, das wir erstellt haben: Wenn wir jetzt das Spiel starten, können wir endlich die Zombies erschießen!



Hier stimmt etwas nicht! Es scheint, dass die Schüsse keine Zombies töten, sondern einfach durchfliegen!

Wenn Sie sich unseren Schusscode ansehen, können wir natürlich nicht nachvollziehen, ob unser Schuss den Feind getroffen hat oder nicht. Zeichnen Sie einfach eine Linie zum Cursor.

Dies ist ziemlich einfach zu beheben. Fügen Sie im Code für die Verarbeitung von Mausklicks in der Player-Klasse nach der Zeile var to = ... und vor der Zeile shot.Show (...) die folgenden Zeilen hinzu:

 var direction = (to - from).normalized; RaycastHit hit; if (Physics.Raycast(from, to - from, out hit, 100)) to = new Vector3(hit.point.x, from.y, hit.point.z); else to = from + direction * 100; 

Hier verwenden wir den bekannten Physics.Raycast, um den Strahl aus dem Lauf einer Waffe herauszulassen und festzustellen, ob er sich mit einem Spielobjekt schneidet.

Hier gibt es jedoch eine Einschränkung: Die Kugel wird immer noch durch die Zombies fliegen. Tatsache ist, dass der Autor des Assets den Objekten des Levels (Gebäuden, Kisten usw.) einen Collider hinzugefügt hat. Und der Autor des Assets mit den Charakteren nicht. Beheben wir dieses ärgerliche Missverständnis.

Ein Collider ist eine Komponente, mit der die Physik-Engine Kollisionen zwischen Objekten ermittelt. Gewöhnlich werden einfache geometrische Formen als Kollider verwendet - Würfel, Kugeln usw. Obwohl dieser Ansatz weniger genaue Kollisionen liefert, sind die Schnittformeln zwischen solchen Objekten recht einfach und erfordern keine großen Rechenressourcen. Wenn Sie maximale Genauigkeit benötigen, können Sie natürlich immer die Leistung opfern und den MeshCollider verwenden. Da wir jedoch keine hohe Genauigkeit benötigen, verwenden wir die CapsuleCollider-Komponente:



Jetzt fliegt die Kugel nicht mehr durch die Zombies. Die Zombies sind jedoch immer noch unsterblich.

Zombies - Zombietod!


Fügen wir dem Zombie Animation Controller zunächst eine Todesanimation hinzu. Ziehen Sie dazu die Animation AssetPacks⇨ToonyTinyPeople⇨TT_demo⇨animation⇨zombie⇨Z_death_A hinein. Um es zu aktivieren, legen Sie einen neuen Parameter an, der mit dem Triggertyp abgestorben ist. Im Gegensatz zu anderen Parametern (bool, float usw.) erinnern sich Trigger nicht an ihren Status und ähneln eher einem Funktionsaufruf: Sie haben einen Trigger aktiviert - der Übergang funktionierte und der Trigger wurde zurückgesetzt. Und da ein Zombie in jedem Zustand sterben kann - und wenn er stillsteht und läuft, fügen wir den Übergang vom Zustand "Jeder Zustand" hinzu:



Fügen Sie dem Zombie-Skript die folgenden Felder hinzu:

 CapsuleCollider capsuleCollider; Animator animator; MovementAnimator movementAnimator; bool dead; 

Fügen Sie in die Start-Methode der Zombie-Klasse Folgendes ein:

 capsuleCollider = GetComponent<CapsuleCollider>(); animator = GetComponentInChildren<Animator>(); movementAnimator = GetComponent<MovementAnimator>(); 

Ganz am Anfang der Update-Methode müssen Sie eine Prüfung hinzufügen:

 if (dead) return; 

Und schließlich fügen Sie der Zombie-Klasse die öffentliche Methode Kill hinzu:

 public void Kill() { if (!dead) { dead = true; Destroy(capsuleCollider); Destroy(movementAnimator); Destroy(navMeshAgent); animator.SetTrigger("died"); } } 

Die Zuordnung neuer Felder ist meiner Meinung nach ganz offensichtlich. Bei der Kill-Methode setzen wir (wenn wir nicht tot sind) das Zombietod-Flag und entfernen die CapsuleCollider-, MovementAnimator- und NavMeshAgent-Komponenten von unserem Spielobjekt. Danach aktivieren wir die Wiedergabe der Todesanimation vom Animations-Controller.

Warum Komponenten entfernen? Sobald ein Zombie stirbt, hört er auf, sich auf der Karte zu bewegen, und ist kein Hindernis mehr für Kugeln. Im Endeffekt müssen Sie den Körper nach dem Abspielen der Todesanimation noch irgendwie auf schöne Weise loswerden. Andernfalls fressen tote Zombies weiterhin Ressourcen, und wenn zu viele Leichen vorhanden sind, verlangsamt sich das Spiel merklich. Am einfachsten fügen Sie hier den Destroy-Aufruf (gameObject, 3) hinzu. Dies bewirkt, dass Unity dieses Spielobjekt 3 Sekunden nach diesem Aufruf löscht.

Damit dies alles endlich funktionierte, blieb die letzte Berührung. In der Player-Klasse fügen wir in der Update-Methode, in der wir Physics.Raycast aufrufen, in der Verzweigung für den Fall, dass eine Kreuzung gefunden wurde, eine Überprüfung hinzu:

 if (hit.transform != null) { var zombie = hit.transform.GetComponent<Zombie>(); if (zombie != null) zombie.Kill(); } 

Physics.Raycast ruft die Schnittpunktinformationen in der Treffervariable auf. Insbesondere im Transformationsfeld gibt es eine Verknüpfung zur Transformationskomponente des Spielobjekts, mit der sich der Strahl geschnitten hat. Wenn dieses Spielobjekt eine Zombie-Komponente hat, dann ist es ein Zombie und wir töten es. Grundlegend!

Damit der Tod des Feindes spektakulär aussieht, fügen wir den Zombies ein einfaches Partikelsystem hinzu.

Mit Partikelsystemen können Sie eine große Anzahl kleiner Objekte (normalerweise Sprites) nach einem physikalischen Gesetz oder einer mathematischen Formel steuern. Zum Beispiel können Sie sie auseinander fliegen lassen oder mit einer bestimmten Geschwindigkeit direkt nach unten fliegen. Mit Hilfe von Partikelsystemen in Spielen werden alle möglichen Effekte erzielt: Feuer, Rauch, Funken, Regen, Schnee, Schmutz unter den Rädern usw. Wir werden ein Partikelsystem verwenden, damit zum Zeitpunkt des Todes Blut von einem Zombie spritzt.

Fügen Sie dem Zombie-Spielobjekt ein Partikelsystem hinzu (klicken Sie mit der rechten Maustaste darauf und wählen Sie Effekte - Partikelsystem):

Ich schlage folgende Optionen vor:
Transformieren:

  • Position: Y 0,5
  • Drehung: X -90

Partikelsystem
  • Dauer: 0.2
  • Schleifen: falsch
  • Startlebensdauer: 0.8
  • Startgröße: 0,5
  • Startfarbe: grün
  • Schwerkraftmodifikator: 1
  • Wach spielen: falsch
  • Emission:
  • Rate über Zeit: 100
  • Form:
  • Radius: 0,25

Es sollte ungefähr so ​​aussehen: Es muss



noch in der Kill-Methode der Zombie-Klasse aktiviert werden:

 GetComponentInChildren<ParticleSystem>().Play(); 

Und jetzt eine ganz andere Sache!



Zombies greifen in der Menge an


In der Tat ist es langweilig, gegen einen einzelnen Zombie zu kämpfen. Du hast ihn getötet und das wars. Wo ist das Drama? Wo ist die Angst, jung zu sterben? Um eine wahre Atmosphäre der Apokalypse und Hoffnungslosigkeit zu schaffen, sollte es viele Zombies geben.

Zum Glück ist das ziemlich einfach. Wie Sie vielleicht erraten haben, benötigen wir ein anderes Skript. Nennen Sie es EnemySpawner und füllen Sie es mit folgendem Inhalt:

 using System.Collections; using System.Collections.Generic; using UnityEngine; public class EnemySpawner : MonoBehaviour { public float Period; public GameObject Enemy; float TimeUntilNextSpawn; // Start is called before the first frame update void Start() { TimeUntilNextSpawn = Random.Range(0, Period); } // Update is called once per frame void Update() { TimeUntilNextSpawn -= Time.deltaTime; if (TimeUntilNextSpawn <= 0.0f) { TimeUntilNextSpawn = Period; Instantiate(Enemy, transform.position, transform.rotation); } } } 

Über das öffentliche Feld „Periode“ kann der Spieledesigner im Inspektor festlegen, wie oft ein neuer Feind erstellt werden muss. Im Feld "Feind" geben wir an, welcher Feind erstellt werden soll (bisher haben wir nur einen Feind, aber in Zukunft können wir weitere hinzufügen). Nun, dann ist alles ganz einfach - mit TimeUntilNextSpawn zählen wir, wie viel Zeit bis zum nächsten Auftauchen des Feindes verbleibt, und fügen der Szene nach der Standardmethode Instantiate einen neuen Zombie hinzu. Oh ja, bei der Start-Methode weisen wir dem TimeUntilNextSpawn-Feld einen zufälligen Wert zu, sodass, wenn sich mehrere Spawner mit den gleichen Verzögerungen in der Ebene befinden, sie nicht gleichzeitig Zombies hinzufügen.

Eine Frage bleibt - wie man den Feind im feindlichen Feld fragt? Dazu verwenden wir ein Unity-Tool wie "Prefabs". Tatsächlich ist ein Fertighaus ein Teil der Szene, der in einer separaten Datei gespeichert ist. Dann können wir diese Datei in andere Szenen (oder in dieselbe) einfügen und müssen sie nicht jedes Mal wieder von Stücken sammeln. Zum Beispiel haben wir aus den Gegenständen von Wänden, Boden, Decke, Fenstern und Türen ein schönes Haus gesammelt und es als Fertighaus gespeichert. Jetzt können Sie dieses Haus mit einem Handgriff in andere Karten stecken. Wenn Sie die vorgefertigte Datei bearbeiten (z. B. eine Hintertür zum Haus hinzufügen), ändert sich das Objekt gleichzeitig in allen Szenen. Manchmal ist es sehr bequem. Wir können auch Prefabs als Vorlagen für Instantiate verwenden - und wir werden diese Gelegenheit jetzt nutzen.

Um ein Fertighaus zu erstellen, ziehen Sie das Spielobjekt einfach aus dem Hierarchiefenster in das Projektfenster. Den Rest erledigt Unity. Erstellen wir ein Fertighaus aus Zombies und fügen der Szene dann einen feindlichen Spawner



hinzu : Ich habe dem Projekt zur Abwechslung drei weitere Spawner hinzugefügt (also habe ich am Ende vier davon). Und so, was ist passiert:



Hier! Es sieht schon aus wie eine Zombie-Apokalypse!

Fazit


Dies ist natürlich alles andere als ein vollständiges Spiel. Wir haben nicht viele Probleme berücksichtigt, wie das Erstellen einer Benutzeroberfläche, Geräusche, Leben und Tod eines Spielers - all dies wird in diesem Artikel nicht behandelt. Mir scheint jedoch, dass dieser Artikel eine würdige Einführung in Unity für diejenigen sein wird, die mit diesem Tool nicht vertraut sind. Oder kann jemand mit Erfahrung einen Trick daraus ziehen?

Im Allgemeinen, Freunde, hoffe ich, dass Ihnen mein Artikel gefallen hat. Schreiben Sie Ihre Fragen in die Kommentare, ich werde versuchen zu beantworten. Der Quellcode des Projekts kann auf den Github heruntergeladen werden: https://github.com/zapolnov/otus_zombies . Sie benötigen Unity 2019.3.0f3 oder höher, es kann komplett kostenlos und ohne SMS von der offiziellen Website heruntergeladen werden: https://store.unity.com/download .

Links zu Assets, die im Artikel verwendet werden:

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


All Articles