Se compiliamo il codice del gioco su un dispositivo, notiamo che le astronavi nemiche, ad un certo punto, emettono un colpo esattamente sulla traiettoria della nostra astronave. Si tratta di una semplice forma di intelligenza artificiale, incapsulata nella classe com.game.phantom.ai.SimpleReflexAgent. Quella classe rappresenta un agente che risponde ad una particolare percezione dell'ambiente.
Agenti, sensori e comportamenti
Gli agenti possono essere ovviamente molto più complessi, ma la forma più semplice è sufficiente per il nostro gioco. Un alieno prosegue per la sua traiettoria tramite un "sensore" che rileva le astronavi a lui nemiche, e che di fatto è in grado di restituire la posizione del nemico. A questa percezione, l'astronava aliena risponde con il fuoco verso la nostra direzione. Il sensore ha comunque un raggio di azione, il che implica che la capacità dell'alieno di rilevare l'astronava dipende dalla vicinanza di essa a quella del giocatore.
Esaminiamo il codice della classe SimpleReflexAgent
:
public class SimpleReflexAgent {
public enum Perception {
PLAYER_DETECTED, NONE
}
public enum Action {
FIRE, NONE
}
private HashMap<Perception, Action> actuator;
private PhEnemyShip enemy;
private PhSpaceShip ship;
public SimpleReflexAgent() {
actuator = new HashMap<Perception,Action>();
actuator.put(Perception.PLAYER_DETECTED, Action.FIRE);
actuator.put(Perception.NONE, Action.NONE);
}
private Perception sensor() {
if (enemy==null || ship==null) {
return Perception.NONE;
}
if (enemy.distanceTo(ship) <= 150 && !enemy.isFired()) {
return Perception.PLAYER_DETECTED;
}
return Perception.NONE;
}
public Action activateAi(PhEnemyShip enemy,PhSpaceShip ship) {
this.enemy=enemy;
this.ship=ship;
Perception perception = sensor();
return actuator.get(perception);
}
}
Definiamo con due enumerazioni le possibili percezioni ed azioni dell'agente. L'alieno può solo rilevare la nostra astronave, oppure non rilevare nulla. Le azioni che può compiere sono solo di fuoco verso il giocatore. Il mapping tra percezioni e azioni è realizzato da una semplice HashMap
, chiamata actuator
.
Per collegare l'agente ad un nemico e attivare questa semplice forma di intelligenza artificiale, si utilizza il metodo activateAi()
, che accetta come parametri un'astronave aliena e l'astronave del giocatore.
Da notare il funzionamento del sensore: la classe PhEnemyShip
è dotata di un metodo in grado di calcolare la distanza tra 2 oggetti, nel nostro applicata alle due astronavi. Se essa è minore o uguale a 150 pixel (il raggio d'azione di cui si parlava in precedenza) e l'astronave non ha già sparato, la percezione PLAYER_DETECTED
si attiva.
Nel metodo activateAi
viene quindi recuperata l'azione corrispondente. Per capire come viene processata l'azione, diamo nuovamente uno sguardo alla classe GameLevels
ed al metodo updateEnemyAndExplosions()
. Individuiamo la seguente porzione di codice:
for (PhEnemyShip enemy : activeEnemyShips) {
if (enemy.isActive()) {
enemy.update(deltaTime);
SimpleReflexAgent.Action action = simpleReflexAgent.activateAi(enemy, ship);
if(action==SimpleReflexAgent.Action.FIRE){
PhEnemyShipFire fire = (PhEnemyShipFire) enemyShipsFire
.getObject();
fire.setX(enemy.getX());
fire.setY(enemy.getY());
fire.setX2(ship.getX());
fire.setY2(ship.getY());
fire.setActive(true);
enemy.setFired(true);
activeEnemyShipsFire.add(fire);
}
} else {
enemy.setActive(false);
mustEnemyRemove.add(enemy);
}
}
In questo ciclo iteriamo su tutte le astronavi nemiche attive, colleghiamo a ciascuna l'agente e ne testiamo la risposta. Se l'agente risponde con l'azione FIRE
, significa che l'astronave del giocatore è nel raggio di azione del sensore, e che quindi l'astronave aliena dovrà sparare.
Richiediamo dal pool degli oggetti PhEnemyShipFire
, un oggetto disponibile, ed impostiamo su esso le coordinate di partenza (quelle dell'astronave nemica) e quelle di arrivo (quelle dell'astronave del giocatore) della traiettoria che dovrà compiere il proiettile. Al suo interno, la classe PhEnemyShipFire
farà seguire al proiettile la traiettoria definita dall'equazione di una retta che passa per 2 punti, quelli appunto occupati dall'alieno e dall'astronave del giocatore.