Creare un Hockey gioco AI utilizzando comportamenti sterzo: meccanica del gioco
() translation by (you can also view the original English article)
In passato posti in questa serie, ci siamo concentrati sui concetti dietro l'intelligenza artificiale che abbiamo imparato circa. In questa parte, avvolgiamo avrai tutta l'implementazione in una partita di hockey interamente giocabile. Imparerete come aggiungere i pezzi mancanti necessari per trasformare questo in un gioco, come punteggio, power-up e un po' di game design.
Risultato finale
Di seguito è il gioco che sarà realizzato utilizzando tutti gli elementi descritti in questa esercitazione.
Design di gioco di pensiero
Le parti precedenti di questa serie si concentra sulla spiegazione di come funziona il gioco AI. Ogni parte dettagliata un particolare aspetto del gioco, come come si muovono gli atleti e modalità di implementazione di attacco e difesa. Erano basati su concetti come sterzo comportamenti e macchine a stati finiti basati sullo stack.
Al fine di rendere un gioco completamente giocabile, tuttavia, tutti questi aspetti devono essere avvolti in una meccanica di gioco di base. La scelta più ovvia sarebbe quella di implementare tutte le regole ufficiali di una partita di hockey ufficiale, ma che richiederebbe un sacco di tempo e lavoro. Prendiamo invece un approccio più semplice fantasia.
Tutte le regole di hockey saranno sostituite con uno solo: se si stanno portando il puck e sono toccato da un avversario, congelare e frantumarsi in 1 milione di pezzi! Renderà il gioco più semplice da giocare e divertente per entrambi i giocatori: quello che trasportano il puck e quello che sta cercando di recuperarlo.
Al fine di migliorare questo meccanico, aggiungeremo qualche power-up. Essi vi aiuterà il giocatore a segnare e rendere il gioco un po' più dinamico.
Aggiunta la possibilità di Punteggio
Cominciamo con il sistema di punteggio, responsabile di determinare chi vince o perde. Una squadra segna ogni volta che entra il puck avversaria.
Il modo più semplice per implementare questo è utilizzando due rettangoli sovrapposti:



Il rettangolo verde rappresenta l'area occupata dalla struttura obiettivo (telaio e rete). Funziona come un blocco solido, così il puck e gli atleti non sarà in grado di muoversi attraverso di essa; Essi si riprenderà.
Il rettangolo rosso rappresenta il "area di punteggio". Se il puck si sovrappone a questo rettangolo, vuol dire una squadra appena segnata.
Il rettangolo rosso è più piccolo di quello verde e posizionato di fronte ad essa, così se il puck tocca l'obiettivo su ogni lato, ma la parte anteriore, esso viene rispedito indietro e nessun punteggio verrà aggiunto:



Organizzare tutto dopo qualcuno segna
Dopo che una squadra segna, tutti gli atleti devono tornare alla loro posizione iniziale e il puck deve essere collocato al centro della pista nuovamente. Dopo questo processo, può continuare il match.
Lo spostamento di atleti alla loro posizione iniziale
Come spiegato nella prima parte di questa serie, tutti gli atleti hanno uno stato AI chiamato prepareForMatch
che spostarli verso la posizione iniziale e indurli a venire senza intoppi si fermano lì.
Quando il puck si sovrappone una delle zone"Punteggio", viene rimosso qualsiasi stato AI attualmente attivo di ogni atleta e prepareForMatch
è spinto nel cervello. Ovunque gli atleti sono, torneranno alla loro posizione iniziale dopo pochi secondi:
Spostando la fotocamera verso il centro della pista di pattinaggio
Poiché la telecamera segue sempre il puck, se è direttamente teletrasportato al centro della pista dopo qualcuno punteggi, la visualizzazione corrente cambierà bruscamente, che sarebbe brutto e confusionario.
Un modo migliore per farlo è quello di muovere il dischetto senza intoppi verso il centro della pista; Poiché la telecamera segue il puck, questo scivolerà la vista con garbo dall'obiettivo al centro della pista di pattinaggio.
Questo può essere ottenuto cambiando vettore velocità di puck dopo che colpisce qualsiasi area di meta. Il nuovo vettore di velocità deve "spingere" il disco verso il centro pista, così essa può essere calcolata come:
1 |
var c :Vector3D = getRinkCenter(); |
2 |
var p :Vector3D = puck.position; |
3 |
|
4 |
var v :Vector3D = c - p; |
5 |
v = normalize(v) * 100; |
6 |
|
7 |
puck.velocity = v; |
Sottraendo la posizione del centro pista dalla posizione attuale di puck, è possibile calcolare un vettore che punta direttamente verso il centro della pista di pattinaggio.
Dopo normalizzazione questo vettore, esso può essere scalata da qualsiasi valore, come 100
, che controlla quanto velocemente il puck si muove verso il centro della pista di pattinaggio.
Sotto è un'immagine con una rappresentazione del nuovo vettore velocità:



Questo vettore V
viene utilizzato come vettore di velocità di puck, pertanto il puck si sposterà verso il centro della pista, come previsto.
Per evitare qualsiasi comportamento strano mentre il puck si sta muovendo verso il centro della pista, come un'interazione con un atleta, il puck viene disattivato durante il processo. Di conseguenza, si smetta di interagire con gli atleti ed è segnato come invisibile. Il giocatore non vedrà il puck in movimento, ma la fotocamera ancora lo seguirà.
Al fine di decidere se il disco è già in posizione, la distanza tra esso e il centro pista viene calcolata durante il movimento. Se è inferiore a 10
, per esempio, il puck è abbastanza vicino per essere direttamente posto al centro della pista di pattinaggio e riattivato affinché possa continuare la partita.
Aggiunta di Power-up
L'idea alla base di power-up è quello di aiutare il giocatore a raggiungere l'obiettivo primario del gioco, che è quello di segnare portando il puck alla porta avversaria.
Per motivi di ambito, il nostro gioco avrà solo due power-up: fantasma aiutare e Puck la paura. L'ex aggiunge ulteriori tre atleti per la squadra del giocatore per un certo tempo, mentre quest'ultimo fa gli avversari fuggire il puck per pochi secondi.
Power-up vengono aggiunti ad entrambe le squadre quando qualcuno segna.
Attuare il "Fantasma guida" Power-up
Poiché tutti gli atleti aggiunti dal fantasma aiutare power-up sono temporanei, la classe di atleta
deve essere modificata per consentire un atleta deve essere contrassegnato come "fantasma". Se un atleta è un fantasma, rimuoverà sé dal gioco dopo pochi secondi.
Di seguito è la classe di atleta,
mettendo in evidenza solo le aggiunte fatte per accogliere la funzionalità ghost:
1 |
public class Athlete |
2 |
{
|
3 |
// (...)
|
4 |
private var mGhost :Boolean; // tells if the athlete is a ghost (a powerup that adds new athletes to help steal the puck). |
5 |
private var mGhostCounter :Number; // counts the time a ghost will remain active |
6 |
|
7 |
public function Athlete(thePosX :Number, thePosY :Number, theTotalMass :Number) { |
8 |
// (...)
|
9 |
mGhost = false; |
10 |
mGhostCounter = 0; |
11 |
|
12 |
// (...)
|
13 |
}
|
14 |
|
15 |
public function setGhost(theStatus :Boolean, theDuration :Number) :void { |
16 |
mGhost = theStatus; |
17 |
mGhostCounter = theDuration; |
18 |
}
|
19 |
|
20 |
public function amIAGhost() :Boolean { |
21 |
return mGhost; |
22 |
}
|
23 |
|
24 |
public function update() :void { |
25 |
// (...)
|
26 |
|
27 |
// Update powerup counters and stuff
|
28 |
updatePowerups(); |
29 |
|
30 |
// (...)
|
31 |
}
|
32 |
|
33 |
public function updatePowerups() :void { |
34 |
// TODO.
|
35 |
}
|
36 |
}
|
La proprietà mGhost
è un valore booleano che indica se l'atleta è un fantasma o no, mentre mGhostCounter
contiene la quantità di secondi che l'atleta deve attendere prima di rimuovere se stesso dal gioco.
Queste due proprietà sono utilizzati dal metodo updatePowerups():
1 |
private function updatePowerups():void { |
2 |
// If the athlete is a ghost, it has a counter that controls
|
3 |
// when it must be removed.
|
4 |
if (amIAGhost()) { |
5 |
mGhostCounter -= time_elapsed; |
6 |
|
7 |
if (mGhostCounter <= 2) { |
8 |
// Make athlete flicker when it is about to be removed.
|
9 |
flicker(0.5); |
10 |
}
|
11 |
|
12 |
if (mGhostCounter <= 0) { |
13 |
// Time to leave this world! (again)
|
14 |
kill(); |
15 |
}
|
16 |
}
|
17 |
}
|
Il metodo di updatePowerups()
, chiamato all'interno di routine di Update ()
dell'atleta, gestirà tutte le elaborazioni di accensione nell'atleta. Al momento tutto ciò che fa è controllare se l'atleta corrente è un fantasma o no. È, se la proprietà mGhostCounter
è decrementata la quantità di tempo trascorso dall'ultimo aggiornamento.
Quando il valore di mGhostCounter
arriva a zero, significa che l'atleta temporaneo è stato attivo per abbastanza a lungo, quindi si deve rimuovere dal gioco. Per rendere il lettore consapevole di che, l'atleta avrà inizio lo sfarfallio relativo ultimo a due secondi prima di scomparire.
Infine, è il momento di attuare il processo di aggiunta degli atleti temporanei quando l'accensione è attivata. Che viene eseguita nel metodo powerupGhostHelp()
, disponibile nella logica del gioco principale:
1 |
private function powerupGhostHelp() :void { |
2 |
var aAthlete :Athlete; |
3 |
|
4 |
for (var i:int = 0; i < 3; i++) { |
5 |
// Add the new athlete to the list of athletes
|
6 |
aAthlete = addAthlete(RINK_WIDTH / 2, RINK_HEIGHT - 100); |
7 |
|
8 |
// Mark the athlete as a ghost which will be removed after 10 seconds.
|
9 |
aAthlete.setGhost(true, 10); |
10 |
}
|
11 |
}
|
Questo metodo scorre un ciclo che corrisponde alla quantità di atleti temporanei viene aggiunto. Ogni atleta nuovo viene aggiunto alla parte inferiore della pista e contrassegnato come un fantasma.
Come precedentemente descritto, gli atleti fantasma si rimuoverà dal gioco.
Attuare il "Temere il Puck" Power-Up
La paura il Puck power-up fa tutti gli avversari fuggire il puck per pochi secondi.
Proprio come il fantasma aiutare power-up, la classe di atleta
deve essere modificata per supportare tale funzionalità:
1 |
public class Athlete |
2 |
{
|
3 |
// (...)
|
4 |
private var mFearCounter :Number; // counts the time the athlete should evade from puck (when fear powerup is active). |
5 |
|
6 |
|
7 |
public function Athlete(thePosX :Number, thePosY :Number, theTotalMass :Number) { |
8 |
// (...)
|
9 |
mFearCounter = 0; |
10 |
|
11 |
// (...)
|
12 |
}
|
13 |
|
14 |
public function fearPuck(theDuration: Number = 2) :void { |
15 |
mFearCounter = theDuration; |
16 |
}
|
17 |
|
18 |
// Returns true if the mFearCounter has a value and the athlete
|
19 |
// is not idle or preparing for a match.
|
20 |
private function shouldIEvadeFromPuck() :Boolean { |
21 |
return mFearCounter > 0 && mBrain.getCurrentState() != idle && mBrain.getCurrentState() != prepareForMatch; |
22 |
}
|
23 |
|
24 |
private function updatePowerups():void { |
25 |
if(mFearCounter > 0) { |
26 |
mFearCounter -= elapsed_time; |
27 |
}
|
28 |
|
29 |
// (...)
|
30 |
}
|
31 |
|
32 |
public function update() :void { |
33 |
// (...)
|
34 |
|
35 |
// Update powerup counters and stuff
|
36 |
updatePowerups(); |
37 |
|
38 |
// If the athlete is an AI-controlled opponent
|
39 |
if (amIAnAiControlledOpponent()) { |
40 |
// Check if "fear of the puck" power-up is active.
|
41 |
// If that's true, evade from puck.
|
42 |
if(shouldIEvadeFromPuck()) { |
43 |
evadeFromPuck(); |
44 |
}
|
45 |
}
|
46 |
|
47 |
// (...)
|
48 |
}
|
49 |
|
50 |
public function evadeFromPuck() :void { |
51 |
// TODO
|
52 |
}
|
53 |
}
|
In primo luogo è cambiato il metodo di updatePowerups()
per decrementare la proprietà mFearCounter
, che contiene la quantità di tempo che l'atleta dovrebbe evitare il puck. La proprietà mFearCounter
viene modificata ogni volta che viene chiamato il metodo fearPuck().
Nel metodo Update ()
dell'atleta
, è aggiunto un test per verificare se il power-up dovrebbe avvenire. Se l'atleta è un avversario controllato dall'intelligenza artificiale (amIAnAiControlledOpponent
() restituisce true
) e l'atleta deve eludere il puck (shouldIEvadeFromPuck
() restituisce true pure), viene richiamato il metodo evadeFromPuck
().
Il metodo evadeFromPuck()
utilizza il comportamento di eludere, che rende un'entità evitare qualsiasi oggetto e la sua traiettoria complessivamente:
1 |
private function evadeFromPuck() :void { |
2 |
mBoid.steering = mBoid.steering + mBoid.evade(getPuck().getBoid()); |
3 |
}
|
Tutto il metodo di evadeFromPuck()
non è di aggiungere una forza di eludere all'atleta corrente di sterzo forza. Lo rende eludere il puck senza trascurare le forze di sterzo già aggiunta, come quello creato dallo stato AI attualmente attivo.
Al fine di essere evadable, il puck deve comportarsi come un boide, come fanno tutti gli atleti (che nella prima parte della serie di ulteriori informazioni). Di conseguenza, una proprietà boid, che contiene la posizione attuale di puck e velocità, deve essere aggiunto alla classe Puck
:
1 |
class Puck { |
2 |
// (...)
|
3 |
private var mBoid :Boid; |
4 |
|
5 |
// (...)
|
6 |
|
7 |
public function update() { |
8 |
// (...)
|
9 |
mBoid.update(); |
10 |
}
|
11 |
|
12 |
public function getBoid() :Boid { |
13 |
return mBoid; |
14 |
}
|
15 |
|
16 |
// (...)
|
17 |
}
|
Infine, aggiorniamo il principale gioco di logica per rendere gli avversari temono il puck quando l'accensione è attivata:
1 |
private function powerupFearPuck() :void { |
2 |
var i :uint, |
3 |
athletes :Array = rightTeam.members, |
4 |
size :uint = athletes.length; |
5 |
|
6 |
for (i = 0; i < size; i++) { |
7 |
if (athletes[i] != null) { |
8 |
// Make athlete fear the puck for 3 seconds.
|
9 |
athletes[i].fearPuck(3); |
10 |
}
|
11 |
}
|
12 |
}
|
Il metodo scorre tutti gli atleti avversari (la squadra giusta, in questo caso), chiamando il metodo fearkPuck()
di ognuno di loro. Questo attiverà la logica che fa gli atleti a temere il puck durante alcuni secondi, come precedentemente spiegato.
Congelamento e la frantumazione
L'ultima aggiunta al gioco è la parte di congelamento e sconvolgente. Viene eseguita nella logica del gioco principale, dove una routine controlla se gli atleti della squadra sinistra sono sovrapposte con gli atleti della squadra giusta.
Il motore di gioco Flixel, che richiama un callback ogni volta che viene trovata una sovrapposizione esegue automaticamente questo controllo sovrapposto:
1 |
private function athletesOverlapped(theLeftAthlete :Athlete, theRightAthlete :Athlete) :void { |
2 |
// Does the puck have an owner?
|
3 |
if (mPuck.owner != null) { |
4 |
// Yes, it does.
|
5 |
if (mPuck.owner == theLeftAthlete) { |
6 |
//Puck's owner is the left athlete
|
7 |
theLeftAthlete.shatter(); |
8 |
mPuck.setOwner(theRightAthlete); |
9 |
|
10 |
} else if (mPuck.owner == theRightAthlete) { |
11 |
//Puck's owner is the right athlete
|
12 |
theRightAthlete.shatter(); |
13 |
mPuck.setOwner(theLeftAthlete); |
14 |
}
|
15 |
}
|
16 |
}
|
Questo callback riceve come parametri gli atleti di ogni squadra che sovrapposti. Un test controlla se proprietario di puck non è null, il che significa che è portato da qualcuno.
In tal caso, proprietario di puck è rispetto agli atleti che appena sovrappongono. Se uno di loro sta trasportando il puck (così egli è proprietario di puck), egli è in frantumi e proprietà di puck passa per l'altro atleta.
Il metodo shatter()
nella classe atleta segnerà l'atleta
come inattivo e posizionarlo nella parte inferiore della pista dopo pochi secondi. Emetterà anche parecchie particelle che rappresentano pezzi di ghiaccio, ma questo argomento sarà coperto 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. Intenzionalmente posto l'attenzione sui concetti dietro ciascuno di tali elementi, invece di come effettivamente implementarli nel motore di gioco X o Y.
L'approccio di freeze e frantumare utilizzato per il gioco potrebbe sembrare troppo fantastico, ma aiuta a mantenere il progetto gestibile. Regolamento sportivo è molto specifici, e loro attuazione può essere difficile.
Aggiungendo alcune schermate e alcuni elementi di HUD, è possibile creare il proprio gioco di hockey pieno da questa demo!
Riferimenti
- Pista: Stadio di hockey il GraphicRiver
- Sprite: Giocatori di Hockey di Taylor J Glidden
- Icone: Gioco-icone di Lorc
- Cursore del mouse: cursore di Iwan Gabovitch
- Tasti di istruzione: tastiera Pack da Nicolae Berbece
- Mirino: Mirino Pack da Bryan
- SFX/musica: frantumi di Michel Baradari, colpo di puck e tifo di gr8sfx, musica di DanoSongs.com