Creare un gioco di hockey AI Utilizzo di comportamenti di sterzo Meccanica di gioco

Nei post precedenti di questa serie, ci siamo concentrati su concetti dietro l'intelligenza artificiale che stiamo imparando. In questa parte, avvolgeremo tutta l'implementazione in un gioco di hockey interamente giocabile. Imparerai come aggiungere i pezzi mancanti necessari per trasformare questo in un gioco, come punteggio, power-up e un po 'di design del gioco.

Risultato finale

Di seguito è riportato il gioco che verrà implementato utilizzando tutti gli elementi descritti in questo tutorial.

Design del gioco di pensiero

Le parti precedenti di questa serie si sono concentrate sulla spiegazione di come funziona l'intelligenza artificiale del gioco. Ogni parte ha dettagliato un aspetto particolare del gioco, come il modo in cui gli atleti si muovono e come vengono implementati attacco e difesa. Si basavano su concetti come i comportamenti di governo e le macchine a stati finiti basati sullo stack.

Tuttavia, per rendere un gioco completamente giocabile, tutti questi aspetti devono essere racchiusi in un nucleo meccanico di gioco. La scelta più ovvia sarebbe quella di implementare tutte le regole ufficiali di una partita di hockey ufficiale, ma ciò richiederebbe molto lavoro e tempo. Prendiamo invece un approccio fantasy più semplice.

Tutte le regole dell'hockey saranno sostituite con una sola: se porti il ​​disco e sei toccato da un avversario, ti blocchi e frantumi in un milione di pezzi! Rende il gioco più semplice e divertente per entrambi i giocatori: quello che porta il disco e quello che cerca di recuperarlo.

Per migliorare questa meccanica, aggiungeremo alcuni potenziamenti. Aiuteranno il giocatore a segnare e rendere il gioco un po 'più dinamico.

Aggiunta della capacità di segnare

Iniziamo con il sistema di punteggio, responsabile per determinare chi vince o perde. Una squadra segna ogni volta che il disco entra nella porta avversaria.

Il modo più semplice per implementare ciò è utilizzare due rettangoli sovrapposti:

Rettangoli sovrapposti che descrivono l'area obiettivo. Se il disco si scontra con il rettangolo rosso, la squadra segna.

Il rettangolo verde rappresenta l'area occupata dalla struttura dell'obiettivo (la cornice e la rete). Funziona come un blocco solido, quindi il disco e gli atleti non saranno in grado di muoversi attraverso di esso; si riprenderanno.

Il rettangolo rosso rappresenta la "area del punteggio". Se il disco si sovrappone a questo rettangolo, significa una squadra appena segnata.

Il rettangolo rosso è più piccolo di quello verde, e posto di fronte ad esso, quindi se il disco tocca l'obiettivo da qualsiasi lato, ma davanti, rimbalzerà e non verrà aggiunto alcun punteggio:

Alcuni esempi di come si comporterebbe il disco se toccava i rettangoli durante lo spostamento.

Organizzazione di tutto ciò dopo alcuni punteggi

Dopo un punteggio di squadra, tutti gli atleti devono tornare alla loro posizione iniziale e il disco deve essere nuovamente posizionato al centro della pista. Dopo questo processo, la partita può continuare.

Spostare gli atleti nella loro posizione iniziale

Come spiegato nella prima parte di questa serie, tutti gli atleti hanno uno stato AI chiamato prepareForMatch ciò li sposterà verso la posizione iniziale e li farà arrestare senza difficoltà.

Quando il disco si sovrappone a una delle "aree punteggio", viene rimosso qualsiasi stato AI attivo di tutti gli atleti e prepareForMatch è spinto nel cervello. Ovunque siano gli atleti, torneranno alla loro posizione iniziale dopo alcuni secondi:

Spostando la fotocamera verso il centro di pattinaggio

Poiché la fotocamera segue sempre il disco, se viene direttamente teletrasportata al centro della pista dopo che qualcuno ha segnato, la vista corrente cambierà bruscamente, il che sarebbe brutto e confuso.

Un modo migliore per farlo è spostare il disco verso il centro della pista; dal momento che la telecamera segue il disco, questo farà scorrere con grazia la vista dall'obiettivo al centro della pista. 

Questo può essere ottenuto cambiando il vettore di velocità del disco dopo aver colpito qualsiasi area obiettivo. Il nuovo vettore di velocità deve "spingere" il disco verso il centro della pista, quindi può essere calcolato come:

var c: Vector3D = getRinkCenter (); var p: Vector3D = puck.position; var v: Vector3D = c - p; v = normalize (v) * 100; puck.velocity = v;

Sottraendo la posizione del centro di pattinaggio dalla posizione attuale del disco, è possibile calcolare un vettore che punta direttamente verso il centro della pista.

Dopo aver normalizzato questo vettore, può essere ridimensionato di qualsiasi valore, come 100, che controlla la velocità con cui il disco si sposta verso il centro della pista.

Di seguito un'immagine con una rappresentazione del nuovo vettore di velocità:

Calcolo di un nuovo vettore di velocità che sposta il disco verso il centro della pista.

Questo vettore V è usato come il vettore di velocità del puck, quindi il disco si sposterà verso il centro della pista come previsto.

Per evitare qualsiasi comportamento strano mentre il disco si sta muovendo verso il centro della pista, come un'interazione con un atleta, il disco viene disattivato durante il processo. Di conseguenza, smette di interagire con gli atleti e viene contrassegnato come invisibile. Il giocatore non vedrà il disco muoversi, ma la telecamera continuerà a seguirlo.

Per decidere se il disco è già in posizione, la distanza tra esso e il centro della pista viene calcolata durante il movimento. Se è inferiore a 10, per esempio, il disco è abbastanza vicino per essere posizionato direttamente al centro della pista e riattivato in modo che la partita possa continuare.

Aggiunta di power-up

L'idea alla base dei potenziamenti è quella di aiutare il giocatore a raggiungere l'obiettivo primario del gioco, che è quello di segnare portando il disco all'obiettivo dell'avversario.

Per motivi di scopo, il nostro gioco avrà solo due potenziamenti: Aiuto fantasma e Fear The Puck. Il primo aggiunge altri atleti alla squadra del giocatore per qualche tempo, mentre il secondo fa fuggire gli avversari per pochi secondi.

I power-up vengono aggiunti a entrambe le squadre quando qualcuno segna.

Implementazione dell'accensione "Ghost Help"

Poiché tutti gli atleti aggiunti dal Aiuto fantasma l'accensione è temporanea, il Atleta la classe deve essere modificata per consentire ad un atleta di essere contrassegnato come un "fantasma". Se un atleta è un fantasma, si rimuoverà dal gioco dopo pochi secondi.

Di seguito è il Atleta classe, evidenziando solo le aggiunte fatte per accogliere la funzionalità ghost:

public class Athlete // (...) private var mGhost: Boolean; // racconta se l'atleta è un fantasma (un potenziamento che aggiunge nuovi atleti per aiutare a rubare il disco). private var mGhostCounter: Number; // conta il tempo in cui un ghost rimarrà attivo in funzione pubblica Athlete (thePosX: Number, thePosY: Number, theTotalMass: Number) // (...) mGhost = false; mGhostCounter = 0; // (...) public function setGhost (theStatus: Boolean, theDuration: Number): void mGhost = theStatus; mGhostCounter = theDuration;  public function amIAGhost (): Boolean return mGhost;  public function update (): void // (...) // Aggiorna i contatori di accensione e roba updatePowerups (); // (...) public function updatePowerups (): void // TODO. 

La proprietà mGhost è un booleano che dice se l'atleta è un fantasma o no, mentre mGhostCounter contiene la quantità di secondi che l'atleta dovrebbe aspettare prima di rimuoversi dal gioco.

Queste due proprietà sono usate dal updatePowerups () metodo:

funzione privata updatePowerups (): void // Se l'atleta è un fantasma, ha un contatore che controlla // quando deve essere rimosso. if (amIAGhost ()) mGhostCounter - = time_elapsed; if (mGhostCounter <= 2)  // Make athlete flicker when it is about to be removed. flicker(0.5);  if (mGhostCounter <= 0)  // Time to leave this world! (again) kill();   

Il updatePowerups () metodo, chiamato all'interno dell'atleta aggiornare() routine, gestirà tutte le elaborazioni all'accensione nell'atleta. In questo momento tutto ciò che fa è controllare se l'atleta corrente è un fantasma o meno. Se lo è, allora il mGhostCounter la proprietà viene decrementata della quantità di tempo trascorso dall'ultimo aggiornamento.

Quando il valore di mGhostCounter raggiunge lo zero, significa che l'atleta temporaneo è rimasto attivo abbastanza a lungo, quindi deve rimuoversi dal gioco. Per rendere il giocatore consapevole di ciò, l'atleta inizierà a lampeggiare negli ultimi due secondi prima di sparire.

Infine, è il momento di implementare il processo di aggiunta degli atleti temporanei quando viene attivato l'accensione. Ciò viene eseguito nel powerupGhostHelp () metodo, disponibile nella logica di gioco principale:

funzione privata powerupGhostHelp (): void var aAthlete: Athlete; per (var i: int = 0; i < 3; i++)  // Add the new athlete to the list of athletes aAthlete = addAthlete(RINK_WIDTH / 2, RINK_HEIGHT - 100); // Mark the athlete as a ghost which will be removed after 10 seconds. aAthlete.setGhost(true, 10);  

Questo metodo itera su un ciclo che corrisponde alla quantità di atleti temporanei che vengono aggiunti. Ogni nuovo atleta viene aggiunto al fondo della pista e contrassegnato come un fantasma. 

Come precedentemente descritto, gli atleti fantasma si rimuoveranno dal gioco.

Implementazione dell'accensione "Fear The Puck"

Il Fear The Puck l'accensione fa fuggire tutti gli avversari per pochi secondi. 

Proprio come il Aiuto fantasma potenziamento, il Atleta la classe deve essere modificata per soddisfare tale funzionalità:

public class Athlete // (...) private var mFearCounter: Number; // conta il tempo in cui l'atleta deve schivare dal disco (quando è attivo il powerup della paura). public function Athlete (thePosX: Number, thePosY: Number, theTotalMass: Number) // (...) mFearCounter = 0; // (...) public function fearPuck (theDuration: Number = 2): void mFearCounter = theDuration;  // Restituisce true se mFearCounter ha un valore e l'atleta // non è inattivo o si sta preparando per una corrispondenza. funzione privata shouldIEvadeFromPuck (): Boolean return mFearCounter> 0 && mBrain.getCurrentState ()! = idle && mBrain.getCurrentState ()! = prepareForMatch;  funzione privata updatePowerups (): void if (mFearCounter> 0) mFearCounter - = elapsed_time;  // (...) public function update (): void // (...) // Aggiorna i contatori di accensione e roba updatePowerups (); // Se l'atleta è un avversario controllato dall'IA se (amIAnAiControlledOpponent ()) // Controlla se è attivo l'accensione "fear of the puck". // Se è vero, sfuggi al puck. if (shouldIEvadeFromPuck ()) evadeFromPuck ();  // (...) public function evadeFromPuck (): void // TODO

Prima il updatePowerups () il metodo è cambiato per decrementare mFearCounter proprietà, che contiene la quantità di tempo in cui l'atleta dovrebbe evitare il disco. Il mFearCounter la proprietà viene cambiata ogni volta che il metodo fearPuck () è chiamato.

Nel Atleta'S aggiornare() metodo, viene aggiunto un test per verificare se l'accensione deve aver luogo. Se l'atleta è un avversario controllato dall'IA (amIAnAiControlledOpponent () ritorna vero) e l'atleta dovrebbe sfuggire al disco (shouldIEvadeFromPuck () ritorna vero pure), il evadeFromPuck () il metodo è invocato.

Il evadeFromPuck () il metodo usa il comportamento di eludere, il che fa sì che un'entità eviti del tutto qualsiasi oggetto e la sua traiettoria:

funzione privata evadeFromPuck (): void mBoid.steering = mBoid.steering + mBoid.evade (getPuck (). getBoid ()); 

Tutti i evadeFromPuck () il metodo consiste nell'aggiungere una forza di eludere alla forza di governo dell'attuale atleta. Lo fa sfuggire al disco senza ignorare le forze di guida già aggiunte, come quella creata dallo stato AI attualmente attivo.

Per essere evadibile, il disco deve comportarsi come un boid, come fanno tutti gli atleti (maggiori informazioni a riguardo nella prima parte della serie). Di conseguenza, una proprietà boid, che contiene la posizione corrente e la velocità del puck, deve essere aggiunta al Disco classe:

class Puck // (...) private var mBoid: Boid; // (...) public function update () // (...) mBoid.update ();  public function getBoid (): Boid return mBoid;  // (...)

Infine, aggiorniamo la logica di gioco principale per fare in modo che gli avversari temano il disco quando viene attivato l'accensione:

funzione privata powerupFearPuck (): void var i: uint, athletes: Array = rightTeam.members, size: uint = athletes.length; per (i = 0; i < size; i++)  if (athletes[i] != null)  // Make athlete fear the puck for 3 seconds. athletes[i].fearPuck(3);   

Il metodo itera su tutti gli atleti avversari (la squadra giusta, in questo caso), chiamando il fearkPuck () metodo di ognuno di essi. Questo innescherà la logica che fa temere agli atleti il ​​disco durante alcuni secondi, come spiegato in precedenza.

Congelamento e frantumazione

L'ultima aggiunta al gioco è la parte congelante e sconvolgente. Viene eseguito nella logica di gioco principale, in cui una routine verifica se gli atleti della squadra di sinistra si sovrappongono agli atleti della squadra giusta.

Questo controllo sovrapposto viene eseguito automaticamente dal motore di gioco Flixel, che richiama una richiamata ogni volta che viene rilevata una sovrapposizione:

atleti di funzione privataOverlapped (theLeftAthlete: Athlete, theRightAthlete: Athlete): void // Il disco ha un proprietario? if (mPuck.owner! = null) // Sì, lo fa. if (mPuck.owner == theLeftAthlete) // Il proprietario di Puck è l'atleta di sinistra theLeftAthlete.shatter (); mPuck.setOwner (theRightAthlete);  else if (mPuck.owner == theRightAthlete) // Il proprietario di Puck è l'atleta giusto theRightAthlete.shatter (); mPuck.setOwner (theLeftAthlete); 

Questa richiamata riceve come parametri gli atleti di ogni squadra che si sono sovrapposti. Un test verifica se il proprietario del puck non è nullo, il che significa che viene trasportato da qualcuno.

In tal caso, il proprietario del disco viene confrontato con gli atleti che si sono appena sovrapposti. Se uno di loro porta il disco (quindi è il proprietario del disco), viene distrutto e la proprietà del disco passa all'altro atleta.

Il frantumi () metodo nel Atleta la classe segnerà l'atleta come inattivo e lo metterà in fondo alla pista dopo pochi secondi. Emetterà anche diverse particelle che rappresentano pezzi di ghiaccio, ma questo argomento sarà trattato in un altro post.

Conclusione

In questo tutorial, abbiamo implementato alcuni elementi necessari per trasformare il nostro prototipo di hockey in un gioco completamente giocabile. Ho intenzionalmente posto l'accento sui concetti alla base di ognuno di questi elementi, invece di come implementarli effettivamente nel motore di gioco X o Y.

L'approccio di congelamento e frantumazione utilizzato per il gioco potrebbe sembrare troppo fantastico, ma aiuta a mantenere il progetto gestibile. Le regole sportive sono molto specifiche e la loro implementazione può essere complicata.

Aggiungendo alcune schermate e alcuni elementi HUD, è possibile creare il proprio gioco di hockey completo da questa demo!

Riferimenti

  • Pista: Hockey Stadium su GraphicRiver
  • Sprites: Hockey Players di Taylor J Glidden
  • Icons: Game-Icons di Lorc
  • Cursore del mouse: Cursor di Iwan Gabovitch
  • Tasti di istruzioni: Keyboard Pack di Nicolae Berbece
  • Crosshair: Crosshairs Pack di Bryan
  • SFX / Musica: esplosione di Michel Baradari, puck hit e allegria di gr8sfx, musica di DanoSongs.com