Advertisement
  1. Game Development
  2. Artificial Intelligence

Creare un Hockey gioco AI utilizzando comportamenti sterzo: meccanica del gioco

Scroll to top
Read Time: 13 min
This post is part of a series called Create AI for a Hockey Game Using Steering Behaviors.
Create a Hockey Game AI Using Steering Behaviors: Defense

() 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:

Overlapped rectangles describing the goal area If the puck collides with the red rectangle the team scoresOverlapped rectangles describing the goal area If the puck collides with the red rectangle the team scoresOverlapped rectangles describing the goal area If the puck collides with the red rectangle the team scores
Rettangoli sovrapposti che descrive l'area di obiettivo. Se il puck si scontra con il rettangolo rosso, i punteggi di squadra.

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:

A few examples of how the puck would behave if it touched the rectangles while movingA few examples of how the puck would behave if it touched the rectangles while movingA few examples of how the puck would behave if it touched the rectangles while moving
Alcuni esempi di come si comporterebbe il puck se toccato i rettangoli durante lo spostamento.

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à:

Calculation of a new velocity vector that will move the puck towards the rink centerCalculation of a new velocity vector that will move the puck towards the rink centerCalculation of a new velocity vector that will move the puck towards the rink center
Calcolo di un nuovo vettore di velocità che si muoverà il puck verso il centro della pista di pattinaggio.

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

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Game Development tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.