In dieser Serie von Tutorials zeige ich Ihnen, wie Sie einen Neon-Twin-Shooter wie Geometry Wars in XNA erstellen. Das Ziel dieser Tutorials ist es nicht, Ihnen eine exakte Nachbildung von Geometry Wars zu geben, sondern die notwendigen Elemente durchzugehen, mit denen Sie eine qualitativ hochwertige Variante erstellen können.
In diesem Teil bauen wir auf dem vorherigen Tutorial auf, indem wir Feinde, Kollisionserkennung und Treffer hinzufügen.
Folgendes haben wir am Ende:
Warnung: Laut!Wir werden die folgenden neuen Klassen hinzufügen, um dies zu handhaben:
Feind
EnemySpawner
: Verantwortlich dafür, Feinde zu schaffen und den Schwierigkeitsgrad des Spiels schrittweise zu erhöhen.PlayerStatus
: Erfasst die Punktzahl, die Höchstpunktzahl und das Leben des Spielers.Sie haben vielleicht bemerkt, dass es zwei Arten von Feinden im Video gibt, aber es gibt nur einen Feind
Klasse. Wir könnten Unterklassen ableiten Feind
für jeden Feindtyp. Ich ziehe es jedoch vor, tiefe Klassenhierarchien zu vermeiden, weil sie einige Nachteile haben:
Säugetier
und Vogel
, die beide von ableiten Tier
. Das Vogel
Klasse hat eine Fliegen()
Methode. Dann entscheiden Sie sich, eine Fledermaus
Klasse, die von abgeleitet ist Säugetier
und kann auch fliegen. Um diese Funktionalität nur mit Vererbung zu teilen, müssen Sie das verschieben Fliegen()
Methode zum Tier
Klasse, wo es nicht hingehört. Außerdem können Sie keine Methoden aus abgeleiteten Klassen entfernen, wenn Sie also eine erstellt haben Pinguin
Klasse, von der abgeleitet wurde Vogel
, es hätte auch eine Fliegen()
Methode.In diesem Tutorial bevorzugen wir die Komposition gegenüber der Vererbung, um die verschiedenen Arten von Feinden zu implementieren. Wir werden dies tun, indem wir verschiedene, wiederverwendbare Verhaltensweisen erstellen, die wir Feinden hinzufügen können. Wir können dann auf einfache Weise Verhalten mischen und anpassen, wenn wir neue Arten von Feinden erstellen. Zum Beispiel, wenn wir schon eine hatten FollowPlayer
Verhalten und a DodgeBullet
Verhalten, könnten wir einen neuen Feind machen, der beides durch einfaches Hinzufügen beider Verhaltensweisen tut.
Gegner haben einige zusätzliche Eigenschaften gegenüber Entitäten. Um dem Spieler etwas Zeit zum Reagieren zu geben, lassen wir die Feinde allmählich einblenden, bevor sie aktiv und gefährlich werden.
Lassen Sie uns die Grundstruktur des programmieren Feind
Klasse.
Klasse Feind: Entität private int timeUntilStart = 60; public bool IsActive get return timeUntilStart <= 0; public Enemy(Texture2D image, Vector2 position) this.image = image; Position = position; Radius = image.Width / 2f; color = Color.Transparent; public override void Update() if (timeUntilStart <= 0) // enemy behaviour logic goes here. else timeUntilStart--; color = Color.White * (1 - timeUntilStart / 60f); Position += Velocity; Position = Vector2.Clamp(Position, Size / 2, GameRoot.ScreenSize - Size / 2); Velocity *= 0.8f; public void WasShot() IsExpired = true;
Dieser Code lässt die Feinde 60 Bilder lang einblenden und ihre Geschwindigkeit funktioniert. Die Multiplikation der Geschwindigkeit mit 0,8 führt zu einem reibungsartigen Effekt. Wenn wir Gegner dazu bringen, mit konstanter Geschwindigkeit zu beschleunigen, wird diese Reibung dazu führen, dass sie sich sanft einer Höchstgeschwindigkeit annähern. Ich mag die Einfachheit und Glätte dieser Art von Reibung, aber Sie möchten möglicherweise eine andere Formel verwenden, je nachdem, welche Wirkung Sie erzielen möchten.
Das Wurde erschossen()
Methode wird aufgerufen, wenn der Feind erschossen wird. Wir werden später in der Serie mehr hinzufügen.
Wir möchten, dass sich verschiedene Arten von Feinden unterschiedlich verhalten. Wir werden dies durch das Zuweisen von Verhalten erreichen. Ein Verhalten verwendet eine benutzerdefinierte Funktion, die jeden Frame zur Kontrolle des Feindes ausführt. Wir implementieren das Verhalten mit einem Iterator.
Iteratoren (auch Generatoren genannt) in C # sind spezielle Methoden, die vorübergehend anhalten und später dort weitermachen können, wo sie aufgehört haben. Sie können einen Iterator erstellen, indem Sie eine Methode mit dem Rückgabetyp von erstellen IEnumerable <>
und verwenden Sie das Ertragsschlüsselwort, wo Sie es zurückgeben und später fortsetzen möchten. Iteratoren in C # erfordern, dass Sie etwas zurückgeben, wenn Sie nachgeben. Wir müssen wirklich nichts zurückgeben, so dass unsere Iteratoren einfach Null ergeben.
Unser einfachstes Verhalten wird das sein FollowPlayer ()
Verhalten unten gezeigt.
IEnumerableFollowPlayer (Float-Beschleunigung = 1f) while (true) Velocity + = (PlayerShip.Instance.Position - Position) .ScaleTo (Beschleunigung); if (Velocity! = Vector2.Zero) Orientierung = Velocity.ToAngle (); Rendite Rendite 0;
Dadurch beschleunigt der Feind mit konstanter Geschwindigkeit auf den Spieler zu. Die zuvor hinzugefügte Reibung stellt sicher, dass sie bei maximaler Geschwindigkeit (5 Pixel pro Bild bei Beschleunigung 1 seit \ (0,8 \ mal 5 + 1 = 5 \)) erreicht wird. Bei jedem Frame wird diese Methode ausgeführt, bis sie die Ertragsanweisung trifft, und setzt dann dort fort, wo der nächste Frame abgebrochen wurde.
Sie fragen sich vielleicht, warum wir uns überhaupt mit Iteratoren beschäftigt haben, da wir die gleiche Aufgabe mit einem einfachen Delegierten einfacher hätten erledigen können. Die Verwendung von Iteratoren zahlt sich aus mit komplexeren Methoden, die andernfalls erfordern, dass der Status in Member-Variablen in der Klasse gespeichert wird.
Unten ist beispielsweise ein Verhalten, das einen Feind in einem quadratischen Muster bewegt:
IEnumerableMoveInASquare () const int framesPerSide = 30; while (wahr) // für 30 Bilder nach rechts bewegen für (int i = 0; i < framesPerSide; i++) Velocity = Vector2.UnitX; yield return 0; // move down for (int i = 0; i < framesPerSide; i++) Velocity = Vector2.UnitY; yield return 0; // move left for (int i = 0; i < framesPerSide; i++) Velocity = -Vector2.UnitX; yield return 0; // move up for (int i = 0; i < framesPerSide; i++) Velocity = -Vector2.UnitY; yield return 0;
Das Schöne daran ist, dass es uns nicht nur einige Instanzvariablen erspart, sondern auch den Code sehr logisch strukturiert. Sie können sofort sehen, dass sich der Feind nach rechts bewegt, dann nach unten, dann nach links, dann nach oben und dann wiederholt. Wenn Sie diese Methode stattdessen als Zustandsmaschine implementieren würden, wäre der Steuerungsfluss weniger offensichtlich.
Fügen wir das Gerüst hinzu, das zum Verhalten benötigt wird. Feinde müssen ihr Verhalten speichern, also fügen wir eine Variable hinzu Feind
Klasse.
private Liste> Verhalten = neue Liste > ();
Beachten Sie, dass ein Verhalten den Typ hat IEnumerator
, nicht IEnumerable
. Sie können an das denken IEnumerable
als Vorlage für das Verhalten und die IEnumerator
als laufende Instanz. Das IEnumerator
merkt sich, wo wir uns im Verhalten befinden, und nimmt dort auf, wo es aufgehört hat, wenn Sie es anrufen MoveNext ()
Methode. Bei jedem Frame gehen wir alle Verhaltensweisen durch, die der Feind hat, und rufen ihn auf MoveNext ()
auf jedem von ihnen. Ob MoveNext ()
Gibt false zurück. Dies bedeutet, dass das Verhalten abgeschlossen ist. Daher sollten wir es aus der Liste entfernen.
Wir werden die folgenden Methoden hinzufügen Feind
Klasse:
private void AddBehaviour (IEnumerableVerhalten) Verhalten.Add (Verhalten.GetEnumerator ()); private void ApplyBehaviours () für (int i = 0; i < behaviours.Count; i++) if (!behaviours[i].MoveNext()) behaviours.RemoveAt(i--);
Und wir werden das ändern Aktualisieren()
Methode aufzurufen ApplyBehaviours ()
:
if (timeUntilStart <= 0) ApplyBehaviours(); //…
Jetzt können wir eine statische Methode erstellen, um suchende Feinde zu erzeugen. Wir müssen nur das gewünschte Bild auswählen und das Bild hinzufügen FollowPlayer ()
Verhalten.
public static Enemy CreateSeeker (Position Vector2) var enemy = new Enemy (Art.Seeker, Position); Feind.AddBehaviour (Feind.FollowPlayer ()); Feind zurückkehren;
Damit sich ein Feind zufällig bewegt, müssen wir eine Richtung wählen und dann kleine zufällige Anpassungen in dieser Richtung vornehmen. Wenn wir jedoch die Richtung bei jedem Bild anpassen, wird die Bewegung unruhig, so dass wir die Richtung nur periodisch anpassen. Wenn der Feind auf den Bildschirmrand trifft, wird er eine neue zufällige Richtung auswählen, die von der Wand weg zeigt.
IEnumerableMoveRandomly () float direction = rand.NextFloat (0, MathHelper.TwoPi); while (wahr) Richtung + = rand.NextFloat (-0.1f, 0.1f); direction = MathHelper.WrapAngle (direction); für (int i = 0; i < 6; i++) Velocity += MathUtil.FromPolar(direction, 0.4f); Orientation -= 0.05f; var bounds = GameRoot.Viewport.Bounds; bounds.Inflate(-image.Width, -image.Height); // if the enemy is outside the bounds, make it move away from the edge if (!bounds.Contains(Position.ToPoint())) direction = (GameRoot.ScreenSize / 2 - Position).ToAngle() + rand.NextFloat(-MathHelper.PiOver2, MathHelper.PiOver2); yield return 0;
Wir können jetzt eine Fabrikmethode zum Erstellen von wandernden Feinden erstellen, ähnlich wie wir es für den Sucher getan haben:
public static Enemy CreateWanderer (Position Vector2) var enemy = neuer Feind (Art.Wanderer, Position); Feind.AddBehaviour (enemy.MoveRandomly ()); Feind zurückkehren;
Für die Kollisionserkennung modellieren wir das Schiff des Spielers, die Feinde und die Kugeln als Kreise. Die Erkennung von kreisförmigen Kollisionen ist einfach, weil sie einfach und schnell ist und sich nicht ändert, wenn sich die Objekte drehen. Wenn Sie sich erinnern, die Entität
Klasse hat einen Radius und eine Position (die Position bezieht sich auf die Mitte der Entität). Dies ist alles, was wir für die Erkennung von Umlaufkollisionen benötigen.
Das Testen jeder Entität gegen alle anderen Entitäten, die möglicherweise kollidieren könnten, kann bei einer großen Anzahl von Entitäten sehr langsam sein. Es gibt viele Techniken, mit denen Sie die Kollisionserkennung für breite Phasen beschleunigen können, wie Quadtrees, Sweep und Prune sowie BSP-Bäume. Momentan werden jedoch nur ein paar Dutzend Objekte auf dem Bildschirm angezeigt, sodass wir uns nicht um diese komplexeren Techniken kümmern müssen. Wir können sie später jederzeit hinzufügen, wenn wir sie brauchen.
In Shape Blaster kann nicht jede Entität mit jedem anderen Entitätstyp kollidieren. Kugeln und das Schiff des Spielers können nur mit Feinden kollidieren. Feinde können auch mit anderen Feinden kollidieren - dies verhindert, dass sie sich überschneiden.
Um mit diesen unterschiedlichen Arten von Kollisionen fertig zu werden, fügen wir der Liste zwei neue Listen hinzu EntityManager
um Kugeln und Feinde im Auge zu behalten. Wann immer wir eine Entität zum hinzufügen EntityManager
, Wir wollen es der entsprechenden Liste hinzufügen, also machen wir eine private AddEntity ()
Methode, um dies zu tun. Wir werden auch sicher sein, dass alle abgelaufenen Objekte aus allen Listen in jedem Frame entfernt werden.
statische ListeFeinde = neue Liste (); statische Liste Aufzählungszeichen = neue Liste (); privates statisches void AddEntity (Entity-Entität) Entities.Add (Entity); if (Entität ist Bullet) bullets.Add (Entität als Bullet); else if (Entität ist Feind) Feinde. Hinzufügen (Entität als Feind); //… // in Update () bullets = bullets.Where (x =>! X.IsExpired) .ToList (); Feinde = Feinde. Wo (x =>! x.IsExpired) .ToList ();
Ersetzen Sie die Anrufe an entity.Add ()
im EntityManager.Add ()
und EntityManager.Update ()
mit anrufen zu AddEntity ()
.
Fügen wir nun eine Methode hinzu, die bestimmt, ob zwei Entitäten kollidieren:
private static bool IsColliding (Entity a, Entity b) float radius = a.Radius + b.Radius; return! a.IsExpired &&! b.IsExpired && Vector2.DistanceSquared (a.Position, b.Position) < radius * radius;
Um festzustellen, ob sich zwei Kreise überlappen, prüfen Sie einfach, ob der Abstand zwischen ihnen kleiner als die Summe ihrer Radien ist. Unsere Methode optimiert dies geringfügig, indem geprüft wird, ob das Quadrat der Entfernung kleiner als das Quadrat der Summe der Radien ist. Denken Sie daran, dass die Entfernung im Quadrat etwas schneller berechnet werden kann als die tatsächliche Entfernung.
Abhängig davon, welche zwei Objekte kollidieren, werden verschiedene Dinge passieren. Wenn zwei Feinde zusammenstoßen, wollen wir, dass sie sich gegenseitig wegschieben. Wenn eine Kugel einen Feind trifft, sollten sowohl die Kugel als auch der Feind zerstört werden. Wenn der Spieler einen Feind berührt, sollte der Spieler sterben und das Level zurückgesetzt werden.
Wir werden ein hinzufügen HandleCollision ()
Methode zum Feind
Klasse, um Kollisionen zwischen Feinden zu handhaben:
public void HandleCollision (Andere Gegner) var d = Position - andere.Position; Geschwindigkeit + = 10 · d / (d.LengthSquared () + 1);
Diese Methode drückt den aktuellen Feind vom anderen Feind weg. Je näher sie sind, desto schwieriger wird es geschoben, weil die Größenordnung von (d / d.LengthSquared ())
ist nur einer über die Entfernung.
Als nächstes brauchen wir eine Methode, um das Schiff des Spielers zu erledigen. In diesem Fall verschwindet das Schiff des Spielers für kurze Zeit, bevor es erneut startet.
Wir fangen damit an, zwei neue Mitglieder hinzuzufügen SpielerSchiff
.
int framesUntilRespawn = 0; public bool IsDead get return framesUntilRespawn> 0;
Ganz am Anfang von PlayerShip.Update ()
, Folgendes hinzufügen:
if (IsDead) framesUntilRespawn--; Rückkehr;
Und wir überschreiben Zeichnen()
wie gezeigt:
public override void Draw (SpriteBatch spriteBatch) if (! IsDead) base.Draw (spriteBatch);
Zum Schluss fügen wir ein Töten()
Methode zu SpielerSchiff
.
public void Kill () framesUntilRespawn = 60;
Nun, da alle Teile vorhanden sind, fügen wir dem eine Methode hinzu EntityManager
Das geht durch alle Entitäten und prüft auf Kollisionen.
statisch void HandleCollisions () // behandelt Kollisionen zwischen Gegnern für (int i = 0; i < enemies.Count; i++) for (int j = i + 1; j < enemies.Count; j++) if (IsColliding(enemies[i], enemies[j])) enemies[i].HandleCollision(enemies[j]); enemies[j].HandleCollision(enemies[i]); // handle collisions between bullets and enemies for (int i = 0; i < enemies.Count; i++) for (int j = 0; j < bullets.Count; j++) if (IsColliding(enemies[i], bullets[j])) enemies[i].WasShot(); bullets[j].IsExpired = true; // handle collisions between the player and enemies for (int i = 0; i < enemies.Count; i++) if (enemies[i].IsActive && IsColliding(PlayerShip.Instance, enemies[i])) PlayerShip.Instance.Kill(); enemies.ForEach(x => x.WasShot ()); brechen;
Rufen Sie diese Methode von auf Aktualisieren()
unmittelbar nach der Einstellung isUpdating
zu wahr
.
Das Letzte, was Sie tun müssen, ist das EnemySpawner
Klasse, die für die Schaffung von Feinden verantwortlich ist. Wir möchten, dass das Spiel einfach beginnt und härter wird, so der EnemySpawner
wird im Laufe der Zeit immer schneller Feinde schaffen. Wenn der Spieler stirbt, setzen wir das zurück EnemySpawner
zu seiner anfänglichen Schwierigkeit.
statische Klasse EnemySpawner statisch Random rand = new Random (); statischer Float inverseSpawnChance = 60; public static void Update () if (! PlayerShip.Instance.IsDead && EntityManager.Count.) < 200) if (rand.Next((int)inverseSpawnChance) == 0) EntityManager.Add(Enemy.CreateSeeker(GetSpawnPosition())); if (rand.Next((int)inverseSpawnChance) == 0) EntityManager.Add(Enemy.CreateWanderer(GetSpawnPosition())); // slowly increase the spawn rate as time progresses if (inverseSpawnChance > 20) inverseSpawnChance - = 0,005f; private static Vector2 GetSpawnPosition () Vector2 pos; do pos = new Vector2 (rand.Next ((int) GameRoot.ScreenSize.X), rand.Next ((int) GameRoot.ScreenSize.Y)); while (Vector2.DistanceSquared (pos, PlayerShip.Instance.Position) < 250 * 250); return pos; public static void Reset() inverseSpawnChance = 60;
Jeder Frame enthält eine Eins inverseSpawnChance
jede Art von Feind zu erzeugen. Die Chance, einen Feind zu spawnen, steigt allmählich, bis er maximal ein Zwanzigstel erreicht. Gegner werden immer mindestens 250 Pixel vom Player entfernt erstellt.
Seien Sie vorsichtig bei der while-Schleife GetSpawnPosition ()
. Es funktioniert effizient, solange der Bereich, in dem Feinde spawn können, größer ist als der Bereich, in dem sie nicht laichen können. Wenn Sie jedoch den verbotenen Bereich zu groß machen, erhalten Sie eine Endlosschleife.
Anruf EnemySpawner.Update ()
von GameRoot.Update ()
und Ruf an EnemySpawner.Reset ()
wenn der Spieler getötet wird.
In Shape Blaster beginnen Sie mit vier Leben und erhalten alle 2000 Punkte ein zusätzliches Leben. Sie erhalten Punkte für die Zerstörung von Feinden, wobei verschiedene Arten von Gegnern unterschiedliche Punktzahlen wert sind. Jeder zerstörte Gegner erhöht außerdem den Punktemultiplikator um eins. Wenn Sie innerhalb kurzer Zeit keine Gegner töten, wird Ihr Multiplikator zurückgesetzt. Die Gesamtanzahl an Punkten, die von jedem Gegner, den Sie zerstören, erhalten wird, ist die Anzahl der Punkte, die der Gegner wert ist, multipliziert mit Ihrem aktuellen Multiplikator. Wenn Sie alle Ihre Leben verlieren, ist das Spiel vorbei und Sie beginnen ein neues Spiel, wobei Ihre Punktzahl auf Null zurückgesetzt wird.
Um all dies zu bewältigen, erstellen wir eine statische Klasse PlayerStatus
.
statische Klasse PlayerStatus // Zeit, in der ein Multiplikator abläuft, in Sekunden. private const float multiplierExpiryTime = 0.8f; private const int maxMultiplier = 20; public static int Lives get; privates Set; public static int Score get; privates Set; public static int Multiplikator get; privates Set; private static float multiplierTimeLeft; // Zeit bis der aktuelle Multiplikator abläuft private static int scoreForExtraLife; // Punktzahl erforderlich, um ein zusätzliches Leben zu erhalten // Statischer Konstruktor static PlayerStatus () Reset (); public static void Reset () Score = 0; Multiplikator = 1; Leben = 4; scoreForExtraLife = 2000; multiplierTimeLeft = 0; public static void Update () if (Multiplier> 1) // Aktualisiere den Multiplikator-Timer if ((multiplierTimeLeft - = (float) GameRoot.GameTime.ElapsedGameTime.TotalSeconds) <= 0) multiplierTimeLeft = multiplierExpiryTime; ResetMultiplier(); public static void AddPoints(int basePoints) if (PlayerShip.Instance.IsDead) return; Score += basePoints * Multiplier; while (Score >= scoreForExtraLife) scoreForExtraLife + = 2000; Lebt ++; public static void IncreaseMultiplier () if (PlayerShip.Instance.IsDead) return; multiplierTimeLeft = multiplierExpiryTime; if (Multiplikator < maxMultiplier) Multiplier++; public static void ResetMultiplier() Multiplier = 1; public static void RemoveLife() Lives--;
Anruf PlayerStatus.Update ()
von GameRoot.Update ()
wenn das Spiel nicht pausiert ist.
Als Nächstes möchten wir Ihre Punktzahl, Ihr Leben und Ihren Multiplikator auf dem Bildschirm anzeigen. Dazu müssen wir eine hinzufügen SpriteFont
in dem Inhalt
Projekt und eine entsprechende Variable in der Kunst
Klasse, die wir nennen werden Schriftart
. Laden Sie die Schrift in Art.Load ()
wie wir es mit den Texturen gemacht haben.
Ändern Sie das Ende von GameRoot.Draw ()
wo der Cursor wie unten dargestellt gezeichnet wird.
spriteBatch.Begin (0, BlendState.Additive); spriteBatch.DrawString (Art.Font, "Lives:" + PlayerStatus.Lives, neuer Vector2 (5), Color.White); DrawRightAlignedString ("Score:" + PlayerStatus.Score, 5); DrawRightAlignedString ("Multiplier:" + PlayerStatus.Multiplier, 35); // den benutzerdefinierten Mauszeiger zeichnen spriteBatch.Draw (Art.Pointer, Input.MousePosition, Color.White); spriteBatch.End ();
DrawRightAlignedString ()
ist eine Hilfsmethode zum Zeichnen von Text, der auf der rechten Seite des Bildschirms ausgerichtet ist. Fügen Sie es zu hinzu GameRoot
indem Sie den folgenden Code hinzufügen.
private void DrawRightAlignedString (Zeichenfolgentext, Gleitkommazahl y) var textWidth = Art.Font.MeasureString (text) .X; spriteBatch.DrawString (Art.Font, text, new Vector2 (ScreenSize.X - textWidth - 5, y), Color.White);
Jetzt sollten Ihre Leben, Punkte und Multiplikatoren auf dem Bildschirm angezeigt werden. Diese Werte müssen jedoch noch in Reaktion auf Spielereignisse geändert werden. Fügen Sie eine Eigenschaft hinzu Punktwert
zum Feind
Klasse.
public int PointValue get; privates Set;
Stellen Sie den Punktwert für verschiedene Feinde auf einen Wert, der Ihrer Meinung nach angemessen ist. Ich machte die wandernden Feinde um einen Punkt und die suchenden Feinde um zwei Punkte.
Fügen Sie als nächstes die folgenden zwei Zeilen hinzu Feind.WasShot ()
um die Punktzahl und den Multiplikator des Spielers zu erhöhen:
PlayerStatus.AddPoints (PointValue); PlayerStatus.IncreaseMultiplier ();
Anruf PlayerStatus.RemoveLife ()
im PlayerShip.Kill ()
. Wenn der Spieler sein ganzes Leben verliert, rufen Sie an PlayerStatus.Reset ()
ihre Punktzahl und Leben am Anfang eines neuen Spiels zurücksetzen.
Fügen wir dem Spiel die Möglichkeit hinzu, Ihre beste Punktzahl zu ermitteln. Wir möchten, dass diese Partitur über alle Spiele hinweg bestehen bleibt, also speichern wir sie in einer Datei. Wir halten es wirklich einfach und speichern den Highscore als eine einzige Klartextnummer in einer Datei im aktuellen Arbeitsverzeichnis (dies ist das gleiche Verzeichnis, in dem sich das Spiel befindet.) .exe
Datei).
Fügen Sie die folgenden Methoden hinzu PlayerStatus
:
private const Zeichenfolge highScoreFilename = "highscore.txt"; private static int LoadHighScore () // wenn möglich den gespeicherten Highscore zurückgeben und ansonsten 0 zurückgeben; return File.Exists (highScoreFilename) && int.TryParse (File.ReadAllText (highScoreFilename), out score)? Bewertung: 0; private static void SaveHighScore (int score) File.WriteAllText (highScoreFilename, score.ToString ());
Das LoadHighScore ()
Die Methode prüft zuerst, ob die Datei mit hoher Punktzahl vorhanden ist, und prüft dann, ob sie eine gültige ganze Zahl enthält. Die zweite Prüfung schlägt höchstwahrscheinlich niemals fehl, es sei denn, der Benutzer bearbeitet die Datei mit hoher Punktzahl manuell in etwas Ungültiges, aber es ist gut, vorsichtig zu sein.
Wir möchten den Highscore laden, wenn das Spiel beginnt, und speichern, wenn der Spieler einen neuen Highscore erhält. Wir modifizieren den statischen Konstruktor und Zurücksetzen ()
Methoden in PlayerStatus
zu tun Wir fügen auch eine Helfer-Eigenschaft hinzu, IsGameOver
das werden wir gleich nutzen.
public static bool IsGameOver get return Lives == 0; static PlayerStatus () HighScore = LoadHighScore (); Zurücksetzen (); public static void Reset () if (Score> HighScore) SaveHighScore (HighScore = Ergebnis); Score = 0; Multiplikator = 1; Leben = 4; scoreForExtraLife = 2000; multiplierTimeLeft = 0;
Das sorgt dafür, dass die hohe Punktzahl verfolgt wird. Jetzt müssen wir es anzeigen. Fügen Sie den folgenden Code hinzu GameRoot.Draw ()
im gleichen SpriteBatch
Block, wo der andere Text gezeichnet wird:
if (PlayerStatus.IsGameOver) string text = "Spiel vorbei \ n" + "Ihre Punktzahl:" + PlayerStatus.Score + "\ n" + "High Score:" + PlayerStatus.HighScore; Vector2 textSize = Art.Font.MeasureString (Text); spriteBatch.DrawString (Art.Font, text, ScreenSize / 2 - textSize / 2, Color.White);
Dadurch wird Ihre Punktzahl und Höchstpunktzahl auf dem Spielende angezeigt, zentriert auf dem Bildschirm.
Als letzte Anpassung erhöhen wir die Zeit, bevor das Schiff beim Spielende erneut erscheint, um dem Spieler Zeit zu geben, seine Punktzahl zu sehen. Ändern PlayerShip.Kill ()
Setzen Sie die Respawn-Zeit auf 300 Frames (fünf Sekunden), wenn der Spieler keine Leben mehr hat.
// in PlayerShip.Kill () PlayerStatus.RemoveLife (); framesUntilRespawn = PlayerStatus.IsGameOver? 300: 120;
Das Spiel ist jetzt spielbereit. Es sieht vielleicht nicht nach viel aus, hat aber alle grundlegenden Mechanismen implementiert. In zukünftigen Tutorials werden wir einen Bloom-Filter und Partikeleffekte hinzufügen, um es aufzufrischen. Aber jetzt, lasst uns schnell etwas Sound und Musik hinzufügen, um es interessanter zu machen.
Das Abspielen von Ton und Musik ist in XNA einfach. Zuerst fügen wir unsere Soundeffekte und Musik zur Inhaltspipeline hinzu. In dem Eigenschaften Stellen Sie sicher, dass der Inhaltsprozessor auf eingestellt ist Lied
für die Musik und Sound-Effekt
für die Töne.
Als Nächstes erstellen wir eine statische Hilfsklasse für die Sounds.
statische Klasse Sound public static Song Music get; privates Set; private static readonly Random rand = new Random (); private statische SoundEffect [] Explosionen; // eine zufällige Explosion zurückgeben Sound public static SoundEffect Explosion get return explosions [rand.Next (explosions.Length)]; private statische SoundEffect [] - Aufnahmen; public static SoundEffect Shot get return shots [rand.Next (Aufnahmen.Länge)]; private statische SoundEffect [] -Programme; public static SoundEffect Spawn get return spawns [rand.Next (spawns.Length)]; public static void Load (ContentManager-Inhalt) Music = content.Load("Ton / Musik"); // Diese Linq-Ausdrücke sind nur ein fantastischer Weg, um alle Sounds jeder Kategorie in ein Array zu laden. explosions = Enumerable.Range (1, 8) .Wählen Sie (x => content.Load ("Sound / explosion-0" + x)). ToArray (); Shots = Enumerable.Range (1, 4) .Wählen Sie (x => content.Load.) ("Sound / shoot-0" + x)). ToArray (); Spawns = Enumerable.Range (1, 8) .Wählen Sie (x => content.Load ("Sound / Spawn-0" + x)). ToArray ();
Da wir von jedem Sound mehrere Variationen haben, wird der Explosion
, Schuss
, und Laichen
Eigenschaften wählen einen zufälligen Ton unter den Varianten aus.
Anruf Sound.Load ()
im GameRoot.LoadContent ()
. Um die Musik abzuspielen, fügen Sie am Ende von die folgenden zwei Zeilen ein GameRoot.Initialize ()
.
MediaPlayer.IsRepeating = true; MediaPlayer.Play (Sound.Music);
Um Sounds in XNA abzuspielen, rufen Sie einfach die Abspielen()
Methode auf einem Sound-Effekt
. Diese Methode bietet auch eine Überladung, mit der Sie die Lautstärke, die Tonhöhe und das Panorama des Sounds einstellen können. Ein Trick, um unsere Sounds abwechslungsreicher zu gestalten, besteht darin, diese Mengen für jedes Spiel anzupassen.
Um den Soundeffekt für die Aufnahme auszulösen, fügen Sie die folgende Zeile hinzu PlayerShip.Update ()
, in der if-Anweisung, wo die Aufzählungszeichen erstellt werden. Beachten Sie, dass wir die Tonhöhe zufällig bis zu einer Fünftel einer Oktave nach oben oder unten verschieben, um die Klänge weniger zu wiederholen.
Sound.Shot.Play (0.2f, rand.NextFloat (-0.2f, 0.2f), 0);
Triggern Sie jedes Mal einen Explosionseffekt aus, wenn ein Gegner zerstört wird, indem Sie Folgendes hinzufügen Feind.WasShot ()
.
Sound.Explosion.Play (0.5f, rand.NextFloat (-0.2f, 0.2f), 0);
Sie haben jetzt Sound und Musik in Ihrem Spiel. Leicht ist es nicht?
Damit sind die grundlegenden Spielmechanismen abgeschlossen. Im nächsten Tutorial fügen wir einen Blütenfilter hinzu, um die Neonlichter zum Leuchten zu bringen.