Costruisci un gioco di puzzle basato sulla griglia come Minesweeper in Unity Winning

Nella parte finale di questa serie, abbiamo dato il tocco finale al nostro gioco di puzzle Unity basato su una griglia e lo abbiamo reso giocabile. Alla fine di questa parte, il giocatore sarà in grado di vincere o perdere la partita.

Ora che hai completato le esercitazioni precedenti, il nostro gioco può creare un campo di tessere e assegnarle in modo casuale. Abbiamo anche un buon effetto di illuminazione quando il giocatore passa sopra una tessera con il mouse, ed è possibile posizionare e rimuovere le bandiere.

Internamente, ogni tessera conosce anche le tessere vicine e può già calcolare quante mine si trovano nelle vicinanze.

Scoprire le piastrelle

Abbiamo già aggiunto la possibilità di posizionare le bandiere con un clic destro. Ora, aggiungiamo la possibilità di scoprire le tessere con un clic sinistro.

Nel OnMouseOver () funzione, dove abbiamo il codice di riconoscimento dei clic, dobbiamo riconoscere un clic sinistro. Adatta la funzione in modo che appaia così:

function OnMouseOver () if (state == "idle") renderer.material = materialLightup; if (Input.GetMouseButtonDown (0)) UncoverTile (); if (Input.GetMouseButtonDown (1)) SetFlag ();  else if (state == "flagged") renderer.material = materialLightup; if (Input.GetMouseButtonDown (1)) SetFlag (); 

Quando si preme il tasto sinistro del mouse, il UncoverTile () la funzione verrà chiamata. Assicurati di creare questa funzione, in modo che questo non causi un bug!

function UncoverTile () if (! isMined) state = "scoperto"; displayText.renderer.enabled = true; renderer.material = materialUncovered;  else Explode (); 

Perché ciò funzioni, dobbiamo introdurre un nuovo materiale.

public var materialSncovered: Material;

Crea qualcosa che ha un colore diverso rispetto alle tessere di base, quindi se le tue tessere di base sono blu, puoi scegliere il verde per il scoperto stato. Ma non usare il rosso; ne avremo bisogno in seguito per dimostrare che abbiamo innescato una mina.

Quando chiami quella funzione, accade quanto segue: 

  • Per prima cosa, controlliamo se il riquadro è effettivamente estratto. 
  • Altrimenti, impostiamo lo stato su scoperto, attivare la visualizzazione del testo che ci mostra il numero di mine vicine e impostare il materiale su scoperto Materiale. 
  • Successivamente, la tessera non può essere cliccata di nuovo, e anche non si accenderà di nuovo, il che significa che il feedback passivo delle tessere che reagiscono al cursore del mouse avverrà solo sulle tessere che possiamo effettivamente fare clic.

Prima di provare questo, dobbiamo assicurarci che il materiale non sia cambiato quando il cursore del mouse uscite la tessera. Per questo, adattare il OnMouseExit () funzione in questo modo:

function OnMouseExit () if (state == "idle" || state == "flagged") renderer.material = materialIdle; 

In questo modo, il colore si spegne solo se la tessera non è stata ancora scoperta.

Provalo! Dovresti essere in grado di scoprire le tessere. Se una tessera viene estratta, però, non accadrà nulla al momento.

Rendere le piastrelle vuote scoperte a vicenda

Questo sarà un po 'complicato. In Campo minato, quando si scopre una tessera con nonelle vicinanze, scoprirà tutte le tessere adiacenti ad essa che non hanno mine e le tessere adiacenti loro che non hanno mine e così via.

Considera questo campo:

In realtà non vediamo i numeri o le mine, solo le tessere regolari.

Quando una tessera con zero le miniere vicine sono scoperte, anche tutte le tessere adiacenti dovrebbero essere automaticamente scoperte. La tessera scoperta poi scopre tutti i vicini.

Queste tessere scoperte controlleranno anche i loro vicini e, se non ci sono mine, scoprirle pure.

Questo si propagherà attraverso il campo fino a raggiungere le tessere che in realtà hanno mine adiacenti a loro, dove si fermerà.

Questo crea il aree vuote possiamo vedere in Minesweeper.

Per far funzionare questo, abbiamo bisogno di altre due funzioni, UncoverAdjacentTiles () e UncoverTileExternal ():

funzione privata UncoverAdjacentTiles () for (var currentTile: Tile in adjacentTiles) // individua tutti i nodi adiacenti con 0 mine adiacenti se (! currentTile.isMined && currentTile.state == "idle" && currentTile.adjacentMines == 0) currentTile .UncoverTile (); // scopri tutti i nodi adiacenti con più di 1 mina adiacente, quindi smetti di scoprire altro se (! currentTile.isMined && currentTile.state == "idle" && currentTile.adjacentMines> 0) currentTile.UncoverTileExternal ();  public function UncoverTileExternal () state = "scoperto"; displayText.renderer.enabled = true; renderer.material = materialUncovered; 

Abbiamo anche bisogno di apportare questa modifica al UncoverTile () funzione:

function UncoverTile () if (! isMined) state = "scoperto"; displayText.renderer.enabled = true; renderer.material = materialUncovered; if (adjacentMines == 0) UncoverAdjacentTiles (); 

Quando scopriamo una tessera e non ci sono mine accanto, chiamiamo il UncoverAdjacentTiles () funzione. Questo poi controlla ogni tessera attigua per vedere se ha o meno mine. pure. Se non ce ne sono, scopre anche questa tessera e avvia un altro giro di controllo. Se ci sono mine nelle vicinanze, scopre solo la tessera in cui si trova attualmente.

Ora provalo. Per avere buone probabilità di apparire un campo vuoto, crea un campo piuttosto grande con alcune miniere, ad esempio 81 tessere, con nove tessere per fila e 10 mine in totale.

Ora puoi giocare a questo gioco, tranne che non puoi ancora attivare mine. Aggiungeremo quella funzione in seguito.

Triggering Mines

Quando scopriamo una tessera che viene estratta, il gioco si ferma e il giocatore perde. Inoltre, tutte le altre tessere estratte diventano visibili. Perché ciò accada, abbiamo bisogno di un altro materiale, per le tessere mine esplosive:

public var materialDetonated: Material;

Suggerisco di usare qualcosa di rosso per questo.

Inoltre, abbiamo bisogno di aggiungere altre due funzioni per gestire l'esplosione di tutte le miniere:

function Explode () state = "detonated"; renderer.material = materialDetonated; for (var currentTile: Tile in Grid.tilesMined) currentTile.ExplodeExternal ();  function ExplodeExternal () state = "detonated"; renderer.material = materialDetonated; 

Noi attiviamo questi metodi nel UncoverTile () funzione:

function UncoverTile () if (! isMined) state = "scoperto"; displayText.renderer.enabled = true; renderer.material = materialUncovered; if (adjacentMines == 0) UncoverAdjacentTiles ();  else Explode (); 

Se una tessera viene estratta, la tessera esplode. Il Esplodere() funzione quindi invia un comando "esploda" a tutte le altre tessere con mine, rivelandole tutte.

Vincere il gioco

Il gioco viene vinto una volta che tutte le tessere con mine sono state contrassegnate correttamente. A questo punto, vengono scoperti anche tutti i riquadri non scoperti. Quindi, come possiamo tenerne traccia?

Iniziamo aggiungendo a stato variabile al Griglia classe, in modo da poter tenere traccia della parte del gioco in cui ci troviamo attualmente (ancora giocando, perso o vinto).

statico var: String = "inGame";

Mentre ci siamo, possiamo anche iniziare ad aggiungere una semplice interfaccia grafica, in modo da poter visualizzare le informazioni necessarie sullo schermo. Unity è dotato di un proprio sistema GUI che useremo per questo.

funzione OnGUI () GUI.Box (Rect (10,10,100,50), stato); 

Questo ci mostrerà in quale stato ci troviamo attualmente. Chiameremo questi stati in gioco, gioco finito, e gameWon.

Possiamo anche aggiungere assegni alla Tessera, per assicurarci di poter interagire solo con le tessere mentre lo stato attuale del gioco è in gioco.

Nel OnMouseOver () e OnMouseExit funzioni, sposta tutto il codice esistente in un Se blocco che controlla se Grid.state è attualmente in gioco, così:

function OnMouseOver () if (Grid.state == "inGame") if (state == "idle") renderer.material = materialLightup; if (Input.GetMouseButtonDown (0)) UncoverTile (); if (Input.GetMouseButtonDown (1)) SetFlag ();  else if (state == "flagged") renderer.material = materialLightup; if (Input.GetMouseButtonDown (1)) SetFlag ();  function OnMouseExit () if (Grid.state == "inGame") if (state == "idle" || state == "flagged") renderer.material = materialIdle; 

Ci sono in realtà due modi per verificare se il gioco è stato vinto: possiamo contare quante mine sono state marcate correttamente, o possiamo verificare se tutte le tessere che non sono mine sono state scoperte. Per questo, abbiamo bisogno delle seguenti variabili; aggiungili al Griglia classe:

statico var minesMarkedCorrectly: int = 0; static var tilesUncovered: int = 0; var vares staticiRemaining: int = 0;

Non dimenticare di impostare minesRemainingnel Inizio() funzione a numberOfMines, e le altre variabili a 0. Il Inizio() la funzione dovrebbe ora assomigliare a questa:

function Start () CreateTiles (); minesRemaining = numberOfMines; minesMarkedCorrectly = 0; tilesUncovered = 0; state = "inGame"; 

L'ultima riga imposta lo stato per il gioco. (Questo sarà importante quando vogliamo introdurre una funzione di "riavvio" in seguito).

Quindi controlliamo le nostre condizioni di gioco finale nel Aggiornare() funzione:

function Update () if (state == "inGame") if ((minesRemaining == 0 && minesMarkedCorrectly == numberOfMines) || (tilesUncovered == numberOfTiles - numberOfMines)) FinishGame (); 

Finiamo il gioco impostando lo stato su gameWon, scoprire tutte le tessere rimanenti e contrassegnare tutte le miniere rimanenti:

function FinishGame () state = "gameWon"; // scopre i campi rimanenti se tutti i nodi sono stati posizionati per (var currentTile: Tile in tilesAll) if (currentTile.state == "idle" &&! currentTile.isMined) currentTile.UncoverTileExternal (); // contrassegna le rimanenti mine se sono stati scoperti tutti i nodi eccetto le mine (var currentTile: Tile in Grid.tilesMined) if (currentTile.state! = "flagged") currentTile.SetFlag (); 

Perché tutto funzioni effettivamente, abbiamo bisogno di incrementare le variabili che tracciano i nostri progressi nei punti giusti. Adattare il UncoverTile () funzione per farlo:

function UncoverTile () if (! isMined) state = "scoperto"; displayText.renderer.enabled = true; renderer.material = materialUncovered; Grid.tilesUncovered + = 1; if (adjacentMines == 0) UncoverAdjacentTiles ();  else Explode (); 

… così come il UncoverTileExternal () funzione:

function UncoverTileExternal () state = "scoperto"; displayText.renderer.enabled = true; renderer.material = materialUncovered; Grid.tilesUncovered + = 1; 

Abbiamo anche bisogno di incrementare e decrementare il minesMarkedCorrectly e minesRemaining variabili a seconda che sia stato impostato un flag:

function SetFlag () if (state == "idle") state = "flagged"; displayFlag.renderer.enabled = true; Grid.minesRemaining - = 1; if (isMined) Grid.minesMarkedCorrectly + = 1;  else if (state == "flagged") state = "idle"; displayFlag.renderer.enabled = false; Grid.minesRemaining + = 1; if (isMined) Grid.minesMarkedCorrectly - = 1; 

Perdere il gioco

Allo stesso modo, dobbiamo rendere possibile perdere una partita. Lo realizziamo tramite il Esplodere() funzione all'interno della tessera. 

Basta aggiungere questa linea al Esplodere() funzione:

Grid.state = "gameOver";

Una volta che quella linea è stata lanciata, lo stato del gioco è cambiato gioco finito, e le tessere non possono più essere interagite con.

Aggiunta di una GUI più funzionale

Abbiamo usato la GUI Unity pochi passi fa per dire al giocatore qual è lo stato di gioco in cui si trovano attualmente. Ora lo estenderemo per mostrare alcuni messaggi effettivi.

Il framework per questo è simile al seguente:

function OnGUI () if (state == "inGame")  else if (state == "gameOver")  else if (state == "gameWon") 

A seconda dello stato, nella GUI vengono visualizzati diversi messaggi. Se la partita è persa o vinta, ad esempio, possiamo visualizzare messaggi che dicono così:

function OnGUI () if (state == "inGame")  else if (state == "gameOver") GUI.Box (Rect (10,10,200,50), "You lose");  else if (state == "gameWon") GUI.Box (Rect (10,10,200,50), "You rock!"); 

Possiamo anche mostrare il numero di mine trovate fino ad ora, o aggiungere un pulsante che ricarica il livello una volta che il gioco è finito:

function OnGUI () if (state == "inGame") GUI.Box (Rect (10,10,200,50), "Mine left:" + minesRemaining);  else if (state == "gameOver") GUI.Box (Rect (10,10,200,50), "You lose"); if (GUI.Button (Rect (10,70,200,50), "Restart")) Restart ();  else if (state == "gameWon") GUI.Box (Rect (10,10,200,50), "You rock!"); if (GUI.Button (Rect (10,70,200,50), "Restart")) Restart ();  function Restart () state = "loading"; Application.LoadLevel (Application.loadedLevel); 

Puoi provare tutto in questa build finale!

Conclusione

Questo è tutto! Abbiamo creato un semplice rompicapo con Unity, che puoi utilizzare come base per crearne uno tuo. Spero ti sia piaciuta questa serie; per favore fai tutte le domande che hai nei commenti!