Dans cette partie, nous allons créer une mini Intelligence Artificielle, pour que le zombie se déplace vers le joueur.
Préparation
Nous allons utiliser NavMeshComponents. Vous pouvez le télécharger sur GitHub.
https://github.com/Unity-Technologies/NavMeshComponents
(Cliquez sur le Bouton vert « Code », puis sur Download Zip)
Dézippez, puis copiez le dossier Assets > NavMeshComponents dans le dossier Assets du projet.
Le NavMeshSurface
Dans la fenêtre Hierarchy :
Faites un clique droit, puis Créer Empty.
Renommez le nouveau GameObject en NavMeshSurface.
Faites Add Component pour ajouter le script NavMeshSurface.
Cliquez sur le bouton Bake dans NavMeshSurface. Cela permet de calculer la surface sur laquelle notre Intelligence Artificielle pourra se déplacer.
La surface bleue correspond à la zone où pourra marcher le zombie.
Player
Sélectionnez Player > MainCamera dans la fenêtre Hierarchy.
La caméra est un peu haute, on va la baisser :
Position y = 0,8
Le Zombie
Sélectionnez le zombie
Reculez un peu le zombie, par exemple :
Position z = 16
Puis on va le redimensionner :
Scale x,y,z = 1,2
NavMeshAgent
Faites Add Component pour ajouter NavMeshAgent.
Voici les paramètres pour le composant NavMeshAgent.
Le zombie va se déplacer, il faut donc déterminer sa vitesse de déplacement :
Speed 3,5
Et il faut aussi lui dire à quelle distance du joueur il devra s’arrêter. Plus tard c’est à cette distance qu’il attaquera !
StoppingDistance 1,5
Les animations
Ouvrez l’animator du zombie (Double cliquez sur Controller : Zombie):
Depuis la fenêtre Project :
Faites glisser l’animation Z_Run
Renommez-la en Walk
Ajoutez un paramètre de type bool : Walk
Créez la transition entre Stand et Walk
Condition : Walk = true
Créez une transition entre Walk et Damage
Condition : Damage = true
Et entre Walk et Defeated
Condition : Defeated = true
IMPORTANT
Pour toutes les transitions, veuillez décocher HasExitTime !
MonsterController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.AI;
public class MonsterController : MonoBehaviour
{
public GameObject player;
//Agent de Navigation
NavMeshAgent navMeshAgent;
//Composants
Animator animator;
//Actions possibles
//Stand ou Idle = attendre
const string STAND_STATE = "Stand";
//Reçoit des dommages
const string TAKE_DAMAGE_STATE = "Damage";
//Est vaincu
public const string DEFEATED_STATE = "Defeated";
//Est en train de marcher
public const string WALK_STATE = "Walk";
//Mémorise l'action actuelle
public string currentAction;
private void Awake()
{
//Au départ, la créature attend en restant debout
currentAction = STAND_STATE;
//Référence vers l'Animator
animator = GetComponent<Animator>();
//Référence NavMeshAgent
navMeshAgent = GetComponent<NavMeshAgent>();
//Référence de Player
player = FindObjectOfType<PlayerFPS>().gameObject;
}
private void Update()
{
//si la créature est défaite
//Elle ne peut rien faire d'autres
if (currentAction == DEFEATED_STATE)
{
navMeshAgent.ResetPath();
return;
}
//Si la créature reçoit des dommages:
//Elle ne peut rien faire d'autres.
//Cela servira quand on améliorera ce script.
if (currentAction == TAKE_DAMAGE_STATE)
{
navMeshAgent.ResetPath();
TakingDamage();
return;
}
if (player != null)
{
//Est-ce que l'IA se déplace vers le joueur ?
if (MovingToTarget())
{
//En train de marcher
return;
}
}
}
//La créature attend
private void Stand()
{
//Réinitialise les paramètres de l'animator
ResetAnimation();
//L'action est maintenant "Stand"
currentAction = STAND_STATE;
//Le paramètre "Stand" de l'animator = true
animator.SetBool("Stand", true);
}
public void TakeDamage()
{
//Réinitialise les paramètres de l'animator
ResetAnimation();
//L'action est maintenant "Damage"
currentAction = TAKE_DAMAGE_STATE;
//Le paramètre "Damage" de l'animator = true
animator.SetBool("Damage", true);
}
public void Defeated()
{
//Réinitialise les paramètres de l'animator
ResetAnimation();
//L'action est maintenant "Defeated"
currentAction = DEFEATED_STATE;
//Le paramètre "Defeated" de l'animator = true
animator.SetBool(DEFEATED_STATE, true);
}
//Permet de surveiller l'animation lorsque l'on prend un dégât
private void TakingDamage()
{
if (this.animator.GetCurrentAnimatorStateInfo(0).IsName(TAKE_DAMAGE_STATE))
{
//Compte le temps de l'animation
//normalizedTime : temps écoulé nomralisé (de 0 à 1).
//Si normalizedTime = 0 => C'est le début.
//Si normalizedTime = 0.5 => C'est la moitié.
//Si normalizedTime = 1 => C'est la fin.
float normalizedTime = this.animator.GetCurrentAnimatorStateInfo(0).normalizedTime;
//Fin de l'animation
if (normalizedTime > 1)
{
Stand();
}
}
}
private bool MovingToTarget()
{
//Assigne la destination : le joueur
navMeshAgent.SetDestination(player.transform.position);
// navMeshAgent.remainingDistance = distance restante pour atteindre la cible (Player)
// navMeshAgent.stoppingDistance = à quelle distance de la cible l'IA doit s'arrêter
// (exemple 2 m pour le corps à sorps)
if (navMeshAgent.remainingDistance > navMeshAgent.stoppingDistance)
{
if (currentAction != WALK_STATE)
Walk();
}
else
{
//Si arrivé à bonne distance, regarde vers le joueur
RotateToTarget(player.transform);
return false;
}
return true;
}
//Walk = Marcher
private void Walk()
{
//Réinitialise les paramètres de l'animator
ResetAnimation();
//L'action est maintenant "Walk"
currentAction = WALK_STATE;
//Le paramètre "Walk" de l'animator = true
animator.SetBool(WALK_STATE, true);
}
//Permet de tout le temps regarder en direction de la cible
private void RotateToTarget(Transform target)
{
Vector3 direction = (target.position - transform.position).normalized;
Quaternion lookRotation = Quaternion.LookRotation(new Vector3(direction.x, 0, direction.z));
transform.rotation = Quaternion.Slerp(transform.rotation, lookRotation, Time.deltaTime * 3f);
}
//Réinitialise les paramètres de l'animator
private void ResetAnimation()
{
animator.SetBool(STAND_STATE, false);
animator.SetBool(TAKE_DAMAGE_STATE, false);
animator.SetBool(DEFEATED_STATE, false);
animator.SetBool(WALK_STATE, false);
}
}
Ajouter des obstacles
Maintenant notre zombie, va foncer automatiquement vers le joueur !
Encore plus fort, si vous ajoutez des obstacles sur le sol, le zombie les évitera.
Ajoutez des cubes au hasard sur la scène, et sélectionnez NavMeshSurface.
Cliquez sur le bouton Bake dans NavMeshSurface. Cela permet de calculer la surface sur laquelle notre IA pourra se déplacer.
A chaque fois que la scène est modifiée, il faut recalculer la surface. Comme cela les changements de terrain sont pris en compte.

