Nous approchons de la fin de cet exercice. Il nous reste à finaliser le script de notre ennemi, et ce sera terminé ! Allez, courage !
L'IA et le choix des attaques
Commençons par le sujet le plus complexe : comment créer une intelligence artificielle. Heureusement, notre jeu est relativement simple, donc il n'y a pas énormément de choses à faire.
Lorsque c'est au tour de l'ennemi de jouer, il doit être capable de déterminer quelle attaque utiliser. Une intelligence artificielle repose principalement sur une série de conditions et de statistiques.
Dans notre cas, nous allons simplement générer un nombre au hasard et, en fonction de ce nombre, une action sera choisie.
Pour obtenir un nombre aléatoire, nous utiliserons la classe Random
suivie de la méthode Range
. Voici comment procéder :
using UnityEngine;
public class Ennemie : MonoBehaviour
{
public int vie;
[SerializeField] int force;
private GameObject cible;
[SerializeField] GameObject cercueil;
void Start()
{
cible = GameObject.Find("Joueur");
}
public void ChoisirUneAttaque()
{
int resultats = Random.Range(1,2);
}
void AttaqueNormal()
{
}
void AttaqueSurpuissante()
{
}
public void Mort()
{
if(vie <= 0)
{
gameObject.SetActive(false);
cercueil.SetActive(true);
}
}
}
Explication : Nous avons besoin temporairement de la variable resultats
, donc nous la déclarons dans la méthode ChoisirUneAttaque()
.
Vous remarquerez que la méthode Range
prend deux paramètres, qui définissent l'intervalle du nombre aléatoire. Dans notre cas, nous allons tirer un nombre entier entre 1 et 2, car nous avons seulement deux attaques disponibles.
Maintenant, ajoutons nos conditions :
using UnityEngine;
public class Ennemie : MonoBehaviour
{
public int vie;
[SerializeField] int force;
private GameObject cible;
[SerializeField] GameObject cercueil;
void Start()
{
cible = GameObject.Find("Joueur");
}
public void ChoisirUneAttaque()
{
int resultats = Random.Range(1,2);
if(resultats == 1) // fais une attaque normal
{
AttaqueNormal();
}
else if(resultats == 2)
{
AttaqueSurpuissante();
}
}
void AttaqueNormal()
{
}
void AttaqueSurpuissante()
{
}
public void Mort()
{
if(vie <= 0)
{
gameObject.SetActive(false);
cercueil.SetActive(true);
}
}
}
Comme vous pouvez le voir, en fonction du nombre généré dans la variable resultats
, soit 1 soit 2, nous exécutons une attaque différente.
Améliorations possibles
Si vous avez déjà joué à un jeu au tour par tour, vous savez qu'il est classique qu'un ennemi utilise de plus en plus souvent une attaque puissante lorsque sa vie est faible. Il est tout à fait possible de gérer ce cas avec ce que nous avons vu jusqu'à présent. Voici un exemple possible :
using UnityEngine;
public class Ennemie : MonoBehaviour
{
public int vie;
[SerializeField] int force;
private GameObject cible;
[SerializeField] GameObject cercueil;
void Start()
{
cible = GameObject.Find("Joueur");
}
public void ChoisirUneAttaque()
{
int resultats = Random.Range(1,10); // choisis un nombre entre 1 et 10
if(vie < 20) // la vie de l'ennemie est basse
{
if(resultats > 7) // fais une attaque normal
{
AttaqueNormal();
}
else // attaque surpuissante
{
AttaqueSurpuissante();
}
}
else
{
if(resultats > 2) // fais une attaque normal
{
AttaqueNormal();
}
else // attaque surpuissante
{
AttaqueSurpuissante();
}
}
}
void AttaqueNormal()
{
}
void AttaqueSurpuissante()
{
}
public void Mort()
{
if(vie <= 0)
{
gameObject.SetActive(false);
cercueil.SetActive(true);
}
}
}
Explication : Dans cet exemple, nous tirons un nombre aléatoire entre 1 et 10. Ensuite, nous vérifions si la vie de l'ennemi est inférieure à un seuil critique, ici 20. Lorsque la vie de l'ennemi est faible, il aura plus de chances d'effectuer une attaque surpuissante. Nous avons donc créé la condition if(resultats > 7)
, ce qui signifie que si le nombre tiré est supérieur à 7, l'ennemi effectue une attaque normale. Comme nous tirons un nombre entre 1 et 10, il est plus probable que le nombre soit inférieur à 7, donc l'ennemi effectuera une attaque surpuissante dans la majorité des cas.
Si la vie de l'ennemi n'est pas critique, la condition if(resultats > 2)
permettra à l'ennemi d'effectuer plus souvent une attaque normale, étant donné que cette condition sera activée si le nombre généré est l'un des suivants : 3, 4, 5, 6, 7, 8, 9, ou 10, tandis que 1 et 2 déclencheront une attaque surpuissante. Ainsi, une attaque normale sera plus fréquente. Bref, il s'agit de statistiques de base.
L'attaque normale
Passons maintenant à l'implémentation de l'attaque normale. Nous utiliserons la même logique que celle utilisée pour le joueur :
void AttaqueNormal() { cible.GetComponent<Joueur>().vie -= force; Debug.Log("L'ennemie à attaqué et à infligé " + force + " de dégats au joueur!"); cible.GetComponent<Joueur>().Mort(); }
Pas de nouveauté ici.
La difficulté résidera dans la gestion de l'esquive. Vous serez probablement d'accord qu'il serait injuste de permettre au joueur d'esquiver indéfiniment toutes les attaques simplement en appuyant sur un bouton esquive.
Nous allons donc obliger le joueur à n'esquiver que les attaques puissantes. Sinon, il subira davantage de dégâts lors d'une attaque normale.
Pour y parvenir, nous devons d'abord vérifier si l'esquive du joueur est activée :
if(cible.GetComponent<Joueur>().esquive) // si le bool esquive vaux true
{
}
else// si l'esquive n'est pas actif
{
cible.GetComponent<Joueur>().vie -= force;
Debug.Log("L'ennemie à attaqué et à infligé " + force + " de dégats au joueur!");
cible.GetComponent<Joueur>().Mort();
}
Étant donné que esquive
est un booléen, il suffit de vérifier si sa valeur est true
. Si c'est le cas, l'esquive est activée. Si ce n'est pas le cas, le joueur subira les dégâts normaux.
Ensuite, nous allons ajouter une "punition" en infligeant 5 points de dégâts supplémentaires si le joueur a essayer d'esquiver un attaque normale :
void AttaqueNormal()
{
if(cible.GetComponent<Joueur>().esquive) // si le bool esquive vaux true
{
int calculs = force + 5;
cible.GetComponent<Joueur>().vie -= calculs;
Debug.Log("Le joueur est supris par l'ennemie! Il subit " + calculs + " de dégats");
cible.GetComponent<Joueur>().Mort();
}
else// si l'esquive n'est pas actif
{
cible.GetComponent<Joueur>().vie -= force;
Debug.Log("L'ennemie à attaqué et à infligé " + force + " de dégats au joueur!");
cible.GetComponent<Joueur>().Mort();
}
}
Maintenant, prenez un moment pour réfléchir. Ce script présente un problème.
Vous l'avez trouvé ? Nous avons oublié de désactiver le booléen esquive
. En effet, si nous ne le faisons pas, l'esquive restera active pour les tours suivants. Nous devons donc ajouter une ligne pour la désactiver.
void AttaqueNormal()
{
if(cible.GetComponent<Joueur>().esquive) // si le bool esquive vaux true
{
int calculs = force + 5;
cible.GetComponent<Joueur>().vie -= calculs;
Debug.Log("Le joueur est supris par l'ennemie! Il subit " + calculs + " de dégats");
cible.GetComponent<Joueur>().esquive = false;
cible.GetComponent<Joueur>().Mort();
}
else// si l'esquive n'est pas actif
{
cible.GetComponent<Joueur>().vie -= force;
Debug.Log("L'ennemie à attaqué et à infligé " + force + " de dégats au joueur!");
cible.GetComponent<Joueur>().Mort();
}
}
Nous avons terminé l'attaque normale.
L'attaque surpuissante
L'objectif de cette attaque est de forcer le joueur à esquiver, sinon il subira d'énormes dégâts. Nous allons multiplier la force de l'attaque par 100 si le joueur n'a pas esquivé cette attaque. Si l'esquive est activée, nous désactiverons simplement l'esquive pour le prochain tour sans infliger de dégâts.
void AttaqueSurpuissante() { if(cible.GetComponent<Joueur>().esquive) // si le bool esquive vaux true { Debug.Log("Miracle! Le joueur esquive une attaque mortelle!"); cible.GetComponent<Joueur>().esquive = false; } else// si l'esquive n'est pas actif { int calculs = force * 100; cible.GetComponent<Joueur>().vie -= calculs; Debug.Log("Le joueur subit une attaque mortelle et subit " + calculs + " de dégats"); cible.GetComponent<Joueur>().Mort(); } }
Encore une fois, prenez le temps de réfléchir. Notre gameplay présente un problème... Vous pouvez essayer de jouer à votre jeu pour le trouver. Si vous testez, essayez en mettant la variable de vie à 500 pour le joueur et 200 pour votre ennemi afin d'avoir le temps de le constater.
Vous avez trouvé ?
Problème du gameplay :
Le jeu est actuellement injuste. L'ennemi choisit une attaque au hasard, ce qui rend impossible pour le joueur de savoir quand esquiver. Nous allons ajouter un tour de préparation avant que l'ennemi n'effectue une attaque surpuissante, afin que le joueur ait une chance de réagir.
Nous allons introduire une variable booléenne pour savoir si l'ennemi se prépare à une attaque surpuissante :
public class Ennemie : MonoBehaviour { public int vie; [SerializeField] int force; private GameObject cible; [SerializeField] GameObject cercueil;
private bool AttaqueSurpuissanteEnCours;
Ensuite, nous allons modifier notre méthode pour l'attaque surpuissante. Si c'est la première fois que l'attaque est lancée, nous avertissons le joueur que l'ennemi se prépare et activons notre variable booléenne.
void AttaqueSurpuissante() { if(AttaqueSurpuissanteEnCours) // si l'ennemie a eu le temps de se préparée { if(cible.GetComponent().esquive) // si le bool esquive vaux true { Debug.Log("Miracle! Le joueur esquive une attaque mortelle!"); cible.GetComponent().esquive = false; } else// si l'esquive n'est pas actif { int calculs = force * 100; cible.GetComponent().vie -= calculs; Debug.Log("Le joueur subit une attaque mortelle et subit " + calculs + " de dégats"); cible.GetComponent().Mort(); } AttaqueSurpuissanteEnCours = false; // après une attaque surpuissante on retire la préparation } else // si l'ennemie ne s'est pas préparer à attaquer { AttaqueSurpuissanteEnCours = true; Debug.Log("ATTENTION, L'ennemie se prépare à faire une attaque étrange"); } }
Modification de la méthode ChoisirUneAttaque
Enfin, nous devons adapter la méthode ChoisirUneAttaque()
pour que l'ennemi utilise systématiquement l'attaque surpuissante si l'attaque est en préparation. Sinon, il choisit une attaque au hasard comme précédemment.
public void ChoisirUneAttaque() { if(AttaqueSurpuissanteEnCours) { AttaqueSurpuissante(); } else { int resultats = Random.Range(1,10); // choisis un nombre entre 1 et 10 if(vie < 20) // la vie de l'ennemie est basse { if(resultats > 7) // fais une attaque normal { AttaqueNormal(); } else // attaque surpuissante { AttaqueSurpuissante(); } } else { if(resultats > 2) // fais une attaque normal { AttaqueNormal(); } else // attaque surpuissante { AttaqueSurpuissante(); } } } }
Conclusion : L'intelligence artificielle dans un jeu vidéo repose principalement sur l'utilisation de conditions et de probabilités. Plus vous maîtrisez les mathématiques, notamment les statistiques, plus vous pouvez rendre vos IA complexes et intéressantes, tout en gardant un bon équilibre pour que le jeu ne soit pas trop difficile ou injuste pour le joueur.
Félicitations !
Vous avez terminé l'exercice ! Votre jeu est maintenant prêt à être joué.
J'espère que vous avez aimé ce chapitre. N'hésitez surtout pas à venir faire un tour sur le Discord du site si vous avez des questions ou si vous rencontrez des problèmes. Vous pouvez aussi venir partager ce que vous avez réussi à créer !
Bien que cet exercice soit terminé, si vous souhaitez continuer à améliorer votre jeu, lisez la page suivante pour découvrir ce que vous pouvez ajouter avec vos connaissances actuelles.
À bientôt et merci d'avoir suivi ce cours !
Cette page ne contient pas de Fichier à Télécharger.
Les cours vous ont aidé et vous souhaitez à votre tour nous aider ?
- - - Vous pouvez partager le site avec vos connaissances, ainsi que le Discord. - - -
- - - Participer à la vie active du site et du Discord. - - -
- - - Faire un petit don pour nous aider à payer le serveur avec le lien ci-dessous. - - -