Catégories
Autres

Unity3D: Les Stretch Goals du Projet Stealth 2/5

Suite de l’article précédent, on va cette fois s’attarder sur l’IA…

Unity Project Stealth
Unity Project Stealth - cliquez pour accéder au tutorial

Exercice 2: Refactoring du comportement de l’IA

Les fonctions de poursuite Chasing() et de patrouille Patrolling() de EnemyAI étant très similaires, est-il possible de refactorer le code pour qu’elles deviennent une seule, appelées 2 fois avec différents paramètres ?

On peut en effet créer une fonction void Moving(bool chasing) qui va combiner les fonctions Chasing() et Patrolling(). Si chasing est à False, l’IA va patrouiller normalement, sinon, elle va poursuivre l’avatar du joueur.

On va déclarer un booléen chasing qu’on initialise à False dans Awake()

[code lang= »csharp »]public class EnemyAI : MonoBehaviour
{
[…]
private bool chasing;
[…]
}
[/code]

[code lang= »csharp »]void Awake ()
{
[…]
chasing = false;
}
[/code]

Dans Update(), on met à jour la valeur de chasing avec l’algo qui déclenche la détection:

[code lang= »csharp »] void Update ()
{
[…]
chasing = enemySight.personalLastSighting != lastPlayerSighting.resetPosition && playerHealth.health > 0f;
[…]
}[/code]

Il nous reste à supprimer les références à Chasing(); et Patrolling(); puis on ajoute l’appel à notre nouvelle fonction Moving():

[code lang= »csharp »]
void Update ()
{
[…]
Moving(chasing);
}[/code]

On crée ensuite le stub de notre fonction void Moving(bool isChasing):

[code lang= »csharp »]
void Moving(bool isChasing)
{
}
[/code]

Il est temps de rentrer dans le vif du sujet. L’idée est de déclencher par défaut le comportement de patrouille, puis la poursuite si et seulement si chasing est à True.Pour cela, il faut comprendre comment fonctionnent les deux fonctions que nous allons fusionner.

  • Chasing() donne à l’IA une destination (dont les coordonnées sont établies par le lastPersonalSighting) et une vitesse, puis lui ordonne de s’arrêter durant un temps déterminé par chaseWaitTime si elle est suffisamment proche du lastPersonalSighting. Si le temps expire, l’IA abandonne la poursuite.
  • Patrolling() donne à l’IA une destination (dont les coordonnées sont établies par le point de patrouille courant dans la liste patrolWaypoints[]) et une vitesse, puis lui ordonne de s’arrêter durant un temps déterminé par patrolWaitTime si elle est suffisamment proche du point de patrouille. Si le temps expire, l’IA passe au point de patrouille suivant.

On va donc grouper la destination, la vitesse, le temps d’attente et notre chrono comme ceci:

[code lang= »csharp »]public class EnemyAI : MonoBehaviour
{
[…]
private float speed;
private float waitTime;
private Vector3 destination;
[…]
}
[/code]

[code lang= »csharp »]
void Moving(bool isChasing)
{
if (isChasing)
// On est en mode poursuite…
{
Vector3 sightingDeltaPos = enemySight.personalLastSighting – transform.position;

// si l’IA perd de vue l’avatar du joueur, on fait en sorte que la destination soit la dernière position connue.
if(sightingDeltaPos.sqrMagnitude > 4f)
destination = enemySight.personalLastSighting;

speed = chaseSpeed;
waitTime = chaseWaitTime;
}
else
{
// Sinon on est en mode patrouille…
destination = patrolWayPoints[wayPointIndex].position;
speed = patrolSpeed;
waitTime = patrolWaitTime;
}

// Quand l’IA atteint sa destination OU que le joueur s’est échappé…
if(nav.destination == lastPlayerSighting.resetPosition || nav.remainingDistance < nav.stoppingDistance)
{
timer += Time.deltaTime;

if(timer >= waitTime)
{
if (isChasing)
{
// On abandonne la poursuite…
lastPlayerSighting.position = lastPlayerSighting.resetPosition;
enemySight.personalLastSighting = lastPlayerSighting.resetPosition;
}
else
{
// Ou bien on passe au waypoint suivant
if(wayPointIndex == patrolWayPoints.Length – 1)
wayPointIndex = 0;
else
wayPointIndex++;
}
timer = 0f;
}

}
else
timer = 0f;

// On établit la nouvelle destination (avatar ou point de patrouille)
nav.destination = destination;
// avec la vitesse qui va bien (poursuite ou patrouille)
nav.speed = speed;

}
[/code]

Il reste à supprimer les références aux floats chaseTimer et patrolTimer qui ne sont plus utilisés dans notre code.

On pourrait certainement faire plus propre en passant speed, waitTime et destination directement en paramètre à partir du Update(), ceci allègerait notre fonction Moving() et nous sauverait quelques déclarations de variables, par contre on a quand même besoin de faire le distinguo entre arrêter la poursuite et changer de waypoint. On pourrait tout grouper de la sorte même si ça me parait un peu dégueulasse:

[code lang= »csharp »]
void Update ()
{
[…]
if(enemySight.personalLastSighting != lastPlayerSighting.resetPosition && playerHealth.health > 0f)
{
MovingImproved (chaseWaitTime, chaseSpeed, enemySight.personalLastSighting);
// J’ai coupé le "if(sightingDeltaPos.sqrMagnitude > 4f)"
// Pour une raison que j’ignore, calculer ou non la distance par rapport au
// personalLastSighting n’a aucune incidence sur le gameplay… Une idée ?
}
else
{
MovingImproved (patrolWaitTime, patrolSpeed, patrolWayPoints[wayPointIndex].position);
}
}
[/code]

[code lang= »csharp »]void MovingImproved(float fWaitTime, float fSpeed, Vector3 vDestination)
{
if(nav.destination == lastPlayerSighting.resetPosition || nav.remainingDistance < nav.stoppingDistance)
{
timer += Time.deltaTime;

if(timer >= fWaitTime)
{
// Grouper l’abandon de la poursuite avec le changement de waypoint n’est pas très très grave…
// Mais peut générer de la confusion pour celui qui va repasser derrière votre code.
// Seul effet de bord : en cas d’abandon de poursuite, l’IA va prendre le point de patrouille suivant plutôt que le courant.
lastPlayerSighting.position = lastPlayerSighting.resetPosition;
enemySight.personalLastSighting = lastPlayerSighting.resetPosition;

if(wayPointIndex == patrolWayPoints.Length – 1)
wayPointIndex = 0;
else
wayPointIndex++;

timer = 0f;
}

}
else
timer = 0f;

nav.destination = vDestination;
nav.speed = fSpeed;

}[/code]

Ceci conclut le second exercice. A bientôt pour la suite !

7 réponses sur « Unity3D: Les Stretch Goals du Projet Stealth 2/5 »

Très bon article. Deux points :
1) Je ne vois pas l’indentation du code avec ton syntax-highlighter, c’est pénible a lire du coup.
2) Pour l’effet de bord -> A chaque fois que l’ennemi perds le joueur de vue, il prends le waypoint suivant. Ne serait-il pas plus logique et plus simple de prendre le waypoint le plus proche à la place?
3) Pour le « if(sightingDeltaPos.sqrMagnitude > 4f) » as-tu tout simplement vérifié que le magic-number (4f) correspond a une valeur décente pour la distance a laquelle tu veux abandonner la poursuite? Je n’ai aucune idée des unités que tu utilise.

Hube:

J’ai corrigé l’indentation. Pour le waypoint le plus proche cela fait partie d’un des prochain stretch goals qui consiste à créer un comportement d’investigation. J’en profiterai pour le faire à ce moment-là.

Pour le Square Magnitude, là je t’avoue je ne suis pas assez calé en Maths pour comprendre exactement ce que ça génère comme valeur. C’est imposé tel quel dans le tutorial et je n’ai pas vraiment cherché à comprendre. Je creuserait le truc après mes vacances 🙂

channie a dit :
Hube:
Pour le Square Magnitude, là je t’avoue je ne suis pas assez calé en Maths pour comprendre exactement ce que ça génère comme valeur.

Il n’y a qu’a logger ce que tu trouves avec « sightingDeltaPos.sqrMagnitude > 4f » et la distance réelle quand tu arrête la poursuite. Ca te donnera une idée du problème.

Le squaremagnitude, ca définit le carré de la distance d’un vecteur. Admettons que tu veuilles une distance max de 2 unités pour la distance d’arrête de poursuite, tu compare cela avec 2f*2f = 4f. Ca évite d’avoir à faire une racine carré. C’est de l’optimisation.

le_poulet a dit :
Le squaremagnitude, ca définit le carré de la distance d’un vecteur. Admettons que tu veuilles une distance max de 2 unités pour la distance d’arrête de poursuite, tu compare cela avec 2f*2f = 4f. Ca évite d’avoir à faire une racine carré. C’est de l’optimisation.

Pour ajouter a cette explication concise:
Magnitude est le terme utilisé par Unity pour définir la taille d’un vecteur.

Selon le moteur utilisé, cette même valeur peut s’appeler Size, Length ou Len.

« Cool story, bro », comme on dit chez les jeunes.

Répondre à tranxen Annuler la réponse

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *