Faceți un shooter vector neon în XNA mai mult gameplay

În această serie de tutoriale, vă voi arăta cum să faceți un împușcătură cu stick-uri neon, cum ar fi Geometry Wars, în XNA. Scopul acestor tutoriale nu este să vă lase o replică exactă a războaielor Geometry, ci mai degrabă să treceți peste elementele necesare care vă vor permite să creați propria varianta de înaltă calitate.


Prezentare generală

În această parte vom construi pe tutorialul anterior adăugând dușmani, detectarea coliziunilor și notarea.

Iată ce vom avea până la sfârșit:

Avertizare: Tare!

Vom adăuga următoarele clase noi pentru a trata acest lucru:

  • Dusman
  • EnemySpawner: Responsabil pentru crearea de inamici si cresterea treptata a dificultatilor jocului.
  • PlayerStatus: Urmărește scorul jucătorului, scorul ridicat și viețile.

S-ar putea să fi observat că în videoclip există două tipuri de dușmani, dar există doar unul Dusman clasă. Am putea obține subclase din Dusman pentru fiecare tip de inamic. Cu toate acestea, prefer să evit ierarhiile de clasă profunde deoarece au unele dezavantaje:

  • Ei adaugă un cod de boilerplate mai mult.
  • Ele pot spori complexitatea codului și pot face mai greu de înțeles. Starea și funcționalitatea unui obiect se extind pe întregul său lanț de moștenire.
  • Ele nu sunt foarte flexibile. Nu puteți partaja bucăți de funcționalitate între diferite ramuri ale arborelui de moștenire, dacă această funcționalitate nu este în clasa de bază. De exemplu, luați în considerare luarea a două clase, Mamifer și Pasăre, care ambele derivă din Animal. Pasăre clasa are a A zbura() metodă. Apoi decideți să adăugați o Băţ clasa care derivă din Mamifer și poate zbura, de asemenea. Pentru a distribui această funcție folosind doar moștenire, va trebui să mutați A zbura() metoda pentru a Animal clasa în care nu aparține. În plus, nu puteți elimina metode din clasele derivate, așadar dacă ați făcut a Pinguin clasa care derivă din Pasăre, ar avea, de asemenea, a A zbura() metodă.

Pentru acest tutorial, vom favoriza compoziția asupra moștenirii pentru implementarea diferitelor tipuri de dușmani. Vom face acest lucru creând diverse comportamente reutilizabile pe care le putem adăuga inamicilor. Putem apoi să amestecăm cu ușurință comportamentele potrivite atunci când creăm noi tipuri de dușmani. De exemplu, dacă am fi avut deja FollowPlayer comportament și a DodgeBullet comportament, am putea face un nou dușman care să facă ambele pur și simplu prin adăugarea ambelor comportamente.

postări asemănatoare
  • Introducere în programarea orientată pe obiecte pentru dezvoltarea jocurilor
  • O abordare pragmatică a compoziției entităților

Inamici

Dușmanii vor avea câteva proprietăți suplimentare față de entități. Pentru a oferi jucătorului un timp pentru a reacționa, vom face ca inamicii să se estompeze treptat înainte de a deveni activi și periculoși.

Să codificăm structura de bază a Dusman clasă.

 clasa Enemy: Entitatea private int timeUntilStart = 60; boala publica 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;  

Acest cod va face inamicii să se estompeze pentru 60 de cadre și va permite viteza lor de a funcționa. Înmulțirea vitezei cu 0,8 aduce un efect de fricțiune. Dacă facem dușmanii să accelereze la o rată constantă, această frecare îi va face să se apropie de o viteză maximă. Îmi place simplitatea și netezirea acestui tip de frecare, dar poate doriți să utilizați o formulă diferită în funcție de efectul dorit.

A fost impuscat() metoda va fi chemată când inamicul va fi împușcat. Mai mult vom adăuga mai târziu în serie.

Vrem ca diferite tipuri de dușmani să se comporte diferit. Vom realiza acest lucru prin atribuirea de comportamente. Un comportament va folosi o funcție personalizată care rulează fiecare cadru pentru a controla inamicul. Vom implementa comportamentul folosind un iterator.

Iteratoarele (numite și generatoare) în C # sunt metode speciale care se pot opri în parte și apoi se reiau unde au rămas. Puteți face un iterator făcând o metodă cu un tip de returnare de IEnumerable <> și utilizând cuvântul cheie de randament în care doriți să se întoarcă și apoi să reiați. Iteratoarele din C # vă cer să vă întoarceți ceva când cedați. Nu avem nevoie să returnăm nimic, deci iteratorii noștri vor da doar zero.

Cel mai simplu comportament va fi FollowPlayer () comportamentul prezentat mai jos.

 IEnumerable FollowPlayer (accelerație flotantă = 1f) în timp ce (adevărat) Velocity + = (Poziția jucătorului - Poziție - Poziție) .ScaleTo (accelerare); dacă (Velocity! = Vector2.Zero) Orientation = Velocity.ToAngle (); randamentul randamentului 0; 

Acest lucru face pur și simplu inamicul accelera spre jucător la o rată constantă. Fricțiunea pe care am adăugat-o mai devreme va asigura că în cele din urmă depășește viteza maximă (5 pixeli pe cadru atunci când accelerația este 1 de la \ (0.8 \ ori 5 + 1 = 5 \)). Fiecare cadru, această metodă va rula până când va atinge declarația de randament și va relua apoi locul unde a rămas de pe următorul cadru.

S-ar putea să te întrebi de ce ne-am deranjat cu iteratori, de vreme ce am fi putut îndeplini aceeași sarcină mai ușor cu un simplu delegat. Utilizarea iteratorilor se face cu metode mai complexe care altfel ar necesita stocarea stării în variabilele membre din clasă.

De exemplu, mai jos este un comportament care face ca un inamic să se mute într-un model pătrat:

 IEnumerable MoveInASquare () const int framesPerSide = 30; în timp ce (adevărat) // muta dreapta pentru 30 de cadre pentru (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;   

Ce este frumos în acest sens este faptul că nu numai că ne salvează niște variabile de instanță, dar structură codul într-un mod foarte logic. Puteți vedea imediat că inamicul se va mișca drept, apoi în jos, apoi va pleca, apoi sus și apoi se va repeta. Dacă urmați să implementați această metodă ca mașină de stat, fluxul de control ar fi mai puțin evident.

Să adăugăm schela necesară pentru ca comportamentele să funcționeze. Vrăjitorii trebuie să-și păstreze comportamentele, așa că vom adăuga o variabilă la Dusman clasă.

 Lista privată> comportamente = Listă nouă> ();

Rețineți că un comportament are tipul IEnumerator, nu IEnumerable. Vă puteți gândi la IEnumerable ca șablon pentru comportament și IEnumerator ca exemplu de execuție. IEnumerator își amintește unde ne aflăm în comportament și ne vom ridica de unde sa oprit atunci când îl numiți MoveNext () metodă. Fiecare cadru va trece prin toate comportamentele pe care inamicul le are și le sună MoveNext () pe fiecare dintre ele. Dacă MoveNext () returnează fals, înseamnă că comportamentul sa terminat, așa că ar trebui să îl eliminăm din listă.

Vom adăuga următoarele metode la Dusman clasă:

 private void AddBehavior (IEnumerable comportament) behaviours.Add (comportament.GetEnumerator ());  void privat ApplyBehavior () pentru (int i = 0; i < behaviours.Count; i++)  if (!behaviours[i].MoveNext()) behaviours.RemoveAt(i--);  

Și o vom modifica Actualizați() metoda de a apela ApplyBehaviours ():

 dacă (timeUntilStart <= 0) ApplyBehaviours(); //… 

Acum putem face o metodă statică pentru a crea căutarea dușmanilor. Tot ce trebuie să facem este să alegem imaginea pe care o dorim și să o adăugăm FollowPlayer () comportament.

 public static Enemy CreateSeeker (Poziție Vector2) var inamic = nou Enemy (Art.Seeker, position); enemy.AddBehaviour (enemy.FollowPlayer ()); intoarce inamicul; 

Pentru a face un inamic care se mișcă în mod aleatoriu, o să alegem o direcție și apoi să facem mici ajustări aleatorii în acea direcție. Cu toate acestea, dacă reglează direcția fiecărui cadru, mișcarea va fi nervoasă, așa că vom regla periodic numai direcția. Dacă vrăjmașul intră în marginea ecranului, îl vom alege să aleagă o nouă direcție aleatorie care se îndreaptă spre perete.

 IEnumerable MoveRandomly () float direcție = rand.NextFloat (0, MathHelper.TwoPi); în timp ce (true) direction + = rand.NextFloat (-0.1f, 0.1f); direcție = MathHelper.WrapAngle (direcție); pentru (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;   

Acum putem face o metodă de fabricație pentru crearea dușmanilor rătăciți, la fel ca și noi pentru căutător:

 statica publica Enemy CreateWanderer (pozitia Vector2) var inamic = nou Enemy (Art.Wanderer, position); enemy.AddBehaviour (enemy.MoveRandomly ()); intoarce inamicul; 

Detectarea coliziunii

Pentru detectarea coliziunilor, vom modela nava jucătorului, dușmanii și gloanțele ca cercuri. Detectarea coliziunii circulară este drăguță, deoarece este simplă, este rapidă și nu se schimbă atunci când obiectele se rotesc. Dacă vă amintiți, Entitate clasa are o rază și o poziție (poziția se referă la centrul entității). Asta e tot ce avem nevoie pentru detectarea coliziunii circulare.

Testarea fiecărei entități împotriva tuturor celorlalte entități care ar putea să se ciocnească poate fi foarte lentă dacă aveți un număr mare de entități. Există multe tehnici pe care le puteți utiliza pentru a accelera detectarea coliziunii în fază largă, cum ar fi quadtrees, mătură și prune, și copaci BSP. Cu toate acestea, pentru moment, vom avea doar cateva zeci de entitati pe ecran la un moment dat, asa ca nu ne vom face griji cu privire la aceste tehnici mai complexe. Le putem adăuga mai târziu, dacă avem nevoie de ele.

În Shape Blaster, nu fiecare entitate se poate ciocni cu orice alt tip de entitate. Gloanțele și nava jucătorului se pot ciocni doar cu dușmanii. Dușmanii se pot ciocni, de asemenea, cu alți dușmani, ceea ce le va împiedica să se suprapună.

Pentru a face față acestor tipuri diferite de coliziuni, vom adăuga două liste noi la EntityManager pentru a urmări gloanțele și dușmanii. Ori de câte ori adăugăm o entitate la EntityManager, vom dori să-l adăugăm la lista corespunzătoare, așa că vom face o privare AddEntity () metodă de a face acest lucru. Vom elimina, de asemenea, toate entitățile expirate din toate listele din fiecare cadru.

 Listă statică dușmani = lista nouă(); Listă statică gloanțe = Listă nouă(); privat static privat AddEntity (Entitate entitate) entities.Add (entitate); dacă (entitatea este Bullet) bullets.Add (entitate ca Bullet); altfel dacă (entitatea este Enemy) enemies.Add (entitate ca Enemy);  // ... // în Update () bullets = bullets.Where (x =>! X.IsExpired) .ToList (); inamici = inamici. Unde (x =>! x.IsExpired) .ToList ();

Înlocuiți apelurile la entity.Add () în EntityManager.Add () și EntityManager.Update () cu apeluri către AddEntity ().

Acum, să adăugăm o metodă care va determina dacă două entități se ciocnesc:

 boot static privat IsColliding (Entitatea a, Entitatea b) raza float = a.Radius + b.Radius; întoarcere! a.IsExpired &&! b.IsExpired && Vector2.DistanceSquared (a.Position, b.Position) < radius * radius; 

Pentru a determina dacă două cercuri se suprapun, verificați dacă distanța dintre ele este mai mică decât suma razei lor. Metoda noastră optimizează ușor acest lucru, verificând dacă pătratul distanței este mai mic decât pătratul sumei razei. Amintiți-vă că este puțin mai rapid să calculați distanța pătrată decât distanța reală.

Se vor întâmpla diferite lucruri în funcție de care două obiecte se ciocnesc. Dacă se ciocnesc doi dușmani, vrem ca ei să se împrăștie. Dacă un glonț lovește un dușman, glonțul și dușmanul ar trebui să fie distruse. Dacă jucătorul atinge un inamic, jucătorul ar trebui să moară și nivelul ar trebui să fie resetat.

Vom adăuga o HandleCollision () metoda pentru a Dusman clasă să se ocupe de coliziuni între dușmani:

 void publice HandleCollision (alt Enemy) var d = Poziție - other.Position; Viteza + = 10 * d / (d.LengthSquared () + 1); 

Această metodă va împinge inamicul actual de departe de celălalt inamic. Cu cât sunt mai aproape, cu atât mai greu va fi împins, deoarece magnitudinea lui (d / d.LengthSquared ()) este doar unul peste distanță.

Respingerea playerului

Apoi avem nevoie de o metodă pentru a face ca nava jucătorului să fie ucisă. Când se întâmplă acest lucru, nava jucătorului va dispărea pentru un timp scurt înainte de respawning.

Începem prin adăugarea a doi noi membri PlayerShip.

 int framesUntilRespawn = 0; boolul public IsDead get return framesUntilRespawn> 0; 

La începutul anului PlayerShip.Update (), adăugați următoarele:

 dacă (IsDead) framesUntilRespawn--; întoarcere; 

Și ne override A desena() așa cum se arată:

 public suprascrie void Draw (SpriteBatch spriteBatch) if (! IsDead) base.Draw (spriteBatch); 

În cele din urmă, adăugăm a Ucide() metoda de a PlayerShip.

 public void Kill () framesUntilRespawn = 60; 

Acum că toate piesele sunt în loc, vom adăuga o metodă la EntityManager care trece prin toate entitățile și verifică coliziunile.

 static void HandleCollisions () // manipula coliziuni între inamici pentru (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 ()); pauză; 

Apelați această metodă de la Actualizați() imediat după setare isUpdating la Adevărat.


Enemy Spawner

Ultimul lucru pe care trebuie să-l faceți este să faceți EnemySpawner clasa, care este responsabilă pentru crearea dușmanilor. Vrem ca jocul să înceapă ușor și să devină mai greu, așa că EnemySpawner va crea dușmani într-o rată tot mai mare cu progresul timpului. Când jucătorul moare, vom reseta EnemySpawner la dificultatea inițială.

 clasa statică EnemySpawner static Random rand = new Random (); static float inverseSpawnChance = 60; public static void Actualizare () 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;  privat static Vector2 GetSpawnPosition () Vector2 pos; do pos = nou Vector2 (rand.Next ((int) GameRoot.ScreenSize.X), rand.Next ((int) GameRoot.ScreenSize.Y));  în timp ce (Vector2.DistanceSquared (pos, PlayerShip.Instance.Position) < 250 * 250); return pos;  public static void Reset()  inverseSpawnChance = 60;  

Fiecare cadru, există unul în inverseSpawnChance de a genera fiecare tip de inamic. Șansa de a da naștere unui inamic crește treptat până când atinge maximum unu la douăzeci. Dușmanii sunt întotdeauna creați la cel puțin 250 de pixeli distanță de player.

Aveți grijă cu privire la bucla în timp ce vă aflați GetSpawnPosition (). Acesta va funcționa eficient, atâta timp cât zona în care dușmanii pot da naștere este mai mare decât zona în care nu pot să se înfulece. Cu toate acestea, dacă faceți zona prea interzisă, veți obține o buclă infinită.

Apel EnemySpawner.Update () din GameRoot.Update () și sunați EnemySpawner.Reset () când jucătorul este ucis.


Scor și trăiește

În Shape Blaster, vei începe cu patru vieți și vei câștiga o viață suplimentară la fiecare 2000 de puncte. Veți primi puncte pentru a distruge inamicii, cu diferite tipuri de dușmani în valoare de valori diferite de puncte. Fiecare inamic distrus mărește, de asemenea, multiplicatorul scorului dvs. cu unul. Dacă nu ucizi vrăjmașii într-o perioadă scurtă de timp, multiplicatorul tău va fi resetat. Suma totală a punctelor primite de la fiecare inamic pe care îl distrugeți este numărul de puncte pe care inamicul le valorează înmulțit cu multiplicatorul actual. Dacă îți pierzi toată viața, jocul se termină și începi un nou joc, cu resetarea scorului la zero.

Pentru a face față tuturor acestor lucruri, vom face o clasă statică numită PlayerStatus.

 static Stage PlayerStatus // timpul necesar, în câteva secunde, pentru expirarea unui multiplicator. private const float multiplicatorExpiryTime = 0.8f; const const int maxMultiplier = 20; public static int Viața get; set privat;  public static int Scor get; set privat;  public static int Multiplier get; set privat;  privat static float multiplicatorTimeLeft; // timpul până când multiplicatorul actual expiră static int private scoreForExtraLife; // scorul necesar pentru a obține o viață suplimentară // Constructor static static PlayerStatus () Reset ();  public static void Reset () Scor = 0; Multiplicator = 1; Viata = 4; scoreForExtraLife = 2000; multiplicatorTimeLeft = 0;  void public static Actualizare () if (Multiplicator> 1) // actualizați cronometrul multiplicatorului dacă ((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; Locuiește ++;  void public static IncreaseMultiplier () if (PlayerShip.Instance.IsDead) retur; multiplierTimeLeft = multiplicatorExpiryTime; dacă (Multiplicatorul < maxMultiplier) Multiplier++;  public static void ResetMultiplier()  Multiplier = 1;  public static void RemoveLife()  Lives--;  

Apel PlayerStatus.Update () din GameRoot.Update () când jocul nu este întrerupt.

În continuare vrem să afișăm scorul, viața și multiplicatorul pe ecran. Pentru a face acest lucru va trebui să adăugați o SpriteFont în Conţinut proiect și o variabilă corespunzătoare în Artă clasă, pe care o vom numi Font. Încărcați fontul în Art.Load () așa cum am făcut-o cu texturile.

Notă: Există un font numit Nova Square inclus în fișierele sursă Shape Blaster pe care le puteți utiliza. Pentru a utiliza fontul, trebuie să îl instalați mai întâi și să reporniți Visual Studio dacă a fost deschis. Apoi, puteți schimba numele fontului în fișierul cu font sprite în "Nova Square". Proiectul demo nu folosește acest font în mod implicit, deoarece va împiedica compilarea proiectului dacă fontul nu este instalat.

Modificați sfârșitul GameRoot.Draw () unde cursorul este desenat după cum se arată mai jos.

 spriteBatch.Begin (0, BlendState.Additiv); spriteBatch.DrawString (Art.Font, "Locuiește:" + PlayerStatus.Lives, new Vector2 (5), Color.White); DrawRightAlignedString ("Scor:" + PlayerStatus.Score, 5); DrawRightAlignedString ("Multiplicator:" + PlayerStatus.Multiplier, 35); // trageți cursorul mouse-ului personalizat spriteBatch.Draw (Art.Pointer, Input.MousePosition, Color.White); spriteBatch.End ();

DrawRightAlignedString () este o metodă de ajutor pentru desenarea textului aliniat în partea dreaptă a ecranului. Adăugați-o la GameRoot prin adăugarea codului de mai jos.

 void privat DrawRightAlignedString (text șir, float y) var textWidth = Art.Font.MeasureString (text) .X; spriteBatch.DrawString (Art.Font, text, nou Vector2 (ScreenSize.X - textWidth - 5, y), Color.White); 

Acum viata, scorul si multiplicatorul ar trebui sa apara pe ecran. Cu toate acestea, trebuie să modificăm aceste valori ca răspuns la evenimentele de joc. Adăugați o proprietate numită PointValue la Dusman clasă.

 public int PointValue get; set privat; 

Setați valoarea punctului pentru diferiți dușmani la ceva ce vă este potrivit. Am făcut dușmanii rătăciți în valoare de un punct, iar dușmanii căutători în valoare de două puncte.

Apoi, adăugați următoarele două linii la Enemy.WasShot () pentru a crește scorul și multiplicatorul jucătorului:

 PlayerStatus.AddPoints (PointValue); PlayerStatus.IncreaseMultiplier ();

Apel PlayerStatus.RemoveLife () în PlayerShip.Kill (). Dacă jucătorul își pierde toată viața, sunați PlayerStatus.Reset () pentru a-și restabili scorul și a trăit la începutul unui nou joc.

Scoruri mari

Să adăugăm abilitatea jocului de a urmări cel mai bun scor. Vrem ca acest scor să persiste în toate piesele, astfel încât să îl salvăm într-un fișier. O vom păstra foarte simplu și vom salva scorul mare ca un singur număr de text simplu într-un fișier din directorul curent de lucru (acesta va fi același director care conține jocul .executabil fişier).

Adăugați următoarele metode la PlayerStatus:

 privat const string highScoreFilename = "highscore.txt"; private static int LoadHighScore () // returnați scorul mare salvat dacă este posibil și returnați 0 în caz contrar; returnează File.Exists (highScoreFilename) && int.TryParse (File.ReadAllText (highScoreFilename), scor)? scor: 0;  privat static void SaveHighScore (sc de int) File.WriteAllText (highScoreFilename, score.ToString ()); 

LoadHighScore () metoda verifică mai întâi existența fișierului cu scor mare și apoi verifică dacă acesta conține un număr întreg valid. Cea de-a doua verificare nu va eșua, dacă nu utilizatorul modifică manual fișierul cu scoruri mari la ceva nevalid, dar este bine să fii prudent.

Vrem să încărcăm scorul mare când începe jocul și să îl salvăm când jucătorul obține un scor mare. Vom modifica constructorul static și Reset () metode în PlayerStatus să facă acest lucru. De asemenea, vom adăuga o proprietate ajutoare, IsGameOver pe care o vom folosi într-o clipă.

 public static bool IsGameOver get retur Lives == 0;  Static PlayerStatus () HighScore = LoadHighScore (); Reset ();  public static void Reset () if (Scor> HighScore) SaveHighScore (HighScore = Scor); Scor = 0; Multiplicator = 1; Viata = 4; scoreForExtraLife = 2000; multiplicatorTimeLeft = 0; 

Care se ocupă de urmărirea scorului ridicat. Acum trebuie să o afișăm. Adăugați următorul cod la GameRoot.Draw () în același SpriteBatch bloc unde este redactat celălalt text:

 dacă PlayerStatus.IsGameOver) string text = "Joc peste \ n" + "Scorul dvs.:" + PlayerStatus.Score + "\ n" + "Scor mare:" + PlayerStatus.HighScore; Vector2 textSize = Art.Font.MeasureString (text); spriteBatch.DrawString (Art.Font, text, ScreenSize / 2 - textSize / 2, Color.White); 

Acest lucru va face să afișeze scorul dvs. și scorul ridicat pe joc peste, centrat pe ecran.

Ca o ajustare finală, vom mări timpul înainte ca nava să respawns în timpul jocului pentru a da jucătorului timp pentru a-și vedea scorul. Modifica PlayerShip.Kill () prin setarea timpului respawn la 300 de cadre (cinci secunde) dacă jucătorul nu mai are viață.

 // în PlayerShip.Kill () PlayerStatus.RemoveLife (); framesUntilRespawn = PlayerStatus.IsGameOver? 300: 120;

Jocul este acum gata pentru a juca. Poate nu arata mult, dar are toate mecanismele de baza implementate. În tutorialele viitoare vom adăuga un filtru de bloom și efecte de particule pentru a-i condimenta. Dar chiar acum, să adăugăm rapid un sunet și o muzică mai interesantă.


Sunet și muzică

Redarea sunetului și a muzicii este ușor în XNA. Mai întâi, adăugăm efecte sonore și muzică la conductele de conținut. În Proprietăți , asigurați-vă că procesorul de conținut este setat la Cântec pentru muzică și muzică Efect sonor pentru sunete.

Apoi, facem o clasă ajutoare statice pentru sunete.

 clasa statică Sunet public Static Song Music get; set privat;  private static readonly Random rand = nou Random (); exploziile private statice SoundEffect []; // returnează o explozie aleatorie de sunet public static SoundEffect Explozie get return explozii [rand.Next (explosions.Length)];  fotografii statice private SoundEffect []; statică publică SoundEffect Shot get retur fotografii [rand.Next (shots.Length)];  static privat SoundEffect [] spawns; public static SoundEffect Spawn a se întoarce în spawns [rand.Next (spawns.Length)];  Încărcare void statică publică (conținut ContentManager) Music = content.Load( "Sound / Muzică"); // Aceste expresii linq sunt doar o modalitate fantezie de încărcare a tuturor sunetelor din fiecare categorie într-o matrice. explozii = Enumerable.Range (1, 8) .Selectați (x => content.Load("Sunet / explozie-0" + x)) ToArray (); shots = Enumerable.Range (1, 4). Selectați (x => content.Load("Sunet / trage-0" + x)) ToArray (); spawns = enumerable.Range (1, 8) .Selectați (x => content.Load("Sunet / spawn-0" + x)) ToArray (); 

Deoarece avem mai multe variante ale fiecărui sunet, Explozie, Lovitură, și Icre proprietățile vor alege un sunete aleatoriu între variante.

Apel Sound.Load () în GameRoot.LoadContent (). Pentru a reda muzica, adăugați următoarele două linii la sfârșitul paginii GameRoot.Initialize ().

 MediaPlayer.IsRepeating = true; MediaPlayer.Play (Sound.Music);

Pentru a reda sunete în XNA, puteți apela pur și simplu Joaca() metoda pe Efect sonor. Această metodă oferă, de asemenea, o supraîncărcare care vă permite să reglați volumul, pasul și tigaia sunetului. Un truc pentru a face sunetele noastre mai variate este de a regla aceste cantitati la fiecare joc.

Pentru a declanșa efectul de sunet pentru fotografiere, adăugați următoarea linie PlayerShip.Update (), în interiorul declarației if-unde sunt create gloanțele. Rețineți că schimbăm aleatoriu pitch-ul în sus sau în jos, până la o cincime dintr-o octavă, pentru a face sunetele mai puțin repetitive.

 Sound.Shot.Play (0.2f, rand.NextFloat (-0.2f, 0.2f), 0);

De asemenea, declanșați un efect de zgomot de explozie de fiecare dată când un inamic este distrus prin adăugarea următoarelor Enemy.WasShot ().

 Sound.Explosion.Play (0.5f, rand.NextFloat (-0.2f, 0.2f), 0);

Acum ai un sunet și o muzică în jocul tău. Ușor, nu-i așa??


Concluzie

Acest lucru împachetează mecanica de bază a gameplay-ului. În tutorialul următor, vom adăuga un filtru înflorit pentru a face lumina neonului să strălucească.