Créer un FPS #7 – Champ de vision de l’ennemi IA améliorée – tuto unity en français

Actuellement le Zombie fonce droit vers le joueur, peu importe où il se situe dans la map. On va faire en sorte d’ajouter un peu de tactique. On va créer un scanner, si le joueur suffisamment près et s’il n’y a pas d’obstacle entre lui et le Zombie, alors le Zombie le détecte. Il fonce vers le joueur.

Le joueur pourra alors se cacher ou s’enfuir. Lorsque le Zombie aura perdu sa trace, au bout d’un certain délai, il abandonnera la poursuite.

L’obstacle

On va ajouter un obstacle à la scène.

  • Créez un Cube, renommez-le en « Wall ».
  • Position : x=0 y=1.5 z=20
  • Scale : x=10 y=3 z=3
  • Ajoutez un composant NavMeshObstacle.
    Ce composant est utile, car on n’est pas obligé de recalculer la NavMeshSurface.

Le Zombie

Nouvelles transitions

Ouvrez l’Animator du Zombie, nous allons créer deux nouvelle transitions.

  • De Attack vers Defeated
  • De Attack vers Walk

Le scanner

Nous allons créer un nouveau script TargetScanner, mais il ne sera pas dérivé de MonoBehaviour. Ce sera un script Serializable, il sera dans le script MonserController.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


//Serializable
[System.Serializable]
public class TargetScanner //IMPORTANT ne dérive pas de MonoBehaviour
{
    //Rayon de détection autour de la créature
    public float detectionRadius = 20;

    //Angle de détection, pour simuler le champ de vision
    [Range(0.0f, 360.0f)]
    public float detectionAngle = 270;

    //Obsacle qui bloque le champ de vision
    public LayerMask obstacleLayerMask;

    public bool Detect(Transform detector, GameObject target)
    {
        //Calcule de la distance entre detector et target
        Vector3 distanceToTarget = target.transform.position - detector.position;

        //Direction normalisée
        Vector3 directionToTarget = distanceToTarget.normalized;

        //Si le carré de la distance est plus grand que le carré du rayon :
        //target est trop loin
        //(sqrMagnitude est plus performant que magnitude)
        if (distanceToTarget.sqrMagnitude > detectionRadius * detectionRadius)
            return false;

        //Si target est dans le champ de vision
        if (Vector3.Dot(directionToTarget, detector.forward) >
                   Mathf.Cos(detectionAngle * 0.5f * Mathf.Deg2Rad))
        {
            //Dessine un trait bleu sur la Scene
            Debug.DrawRay(detector.position, distanceToTarget, Color.blue);

            //Si pas d'obstacle, target est détectée
            if (!Physics.Raycast(detector.position, directionToTarget, detectionRadius, obstacleLayerMask))
                return true;

        }

        //Défaut, rien n'est détecté
        return false;
    }
}

Mise à jour de MonsterController

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.AI;

public class MonsterController : MonoBehaviour
{
    public GameObject player;

    public MeleeWeapon meleeWeapon;

    //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";

    //Attaque
    public const string ATTACK_STATE = "Attack";

    //Mémorise l'action actuelle
    public string currentAction;

    //Scanner pour trouver des cibles
    public TargetScanner targetScanner;

    //Cible
    public GameObject currentTarget;

    //Temps avant de perdre la cible
    public float delayLostTarget = 10f;

    private float timeLostTarget = 0;


    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;
        }


        //Détection
        FindingTarget();

        //Si pas de cible, ne fait rien
        if (currentTarget == null)
        {
            //Defaut
            Stand();
            navMeshAgent.ResetPath();
            return;
        }


        //Est-ce que l'IA se déplace vers le joueur ?
        if (MovingToTarget())
        {
            //En train de marcher
            return;
        }


        //Attaque
        if (currentAction != ATTACK_STATE && currentAction != TAKE_DAMAGE_STATE)
        {
            Attack();
            return;
        }
        if (currentAction == ATTACK_STATE)
        {
            Attacking();
            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 void Attacking()
    {
        if (this.animator.GetCurrentAnimatorStateInfo(0).IsName(ATTACK_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 % 1;


            //Fin de l'animation
            if (normalizedTime > 1)
            {

                meleeWeapon.StopAttack();
                Stand();
                return;
            }

            meleeWeapon.StartAttack();

        }
    }


    private bool MovingToTarget()
    {

        //Assigne la destination : le joueur
        navMeshAgent.SetDestination(player.transform.position);

        //Si navMeshAgent n'est pas prêt
        if (navMeshAgent.remainingDistance == 0)
            return true;


        // 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(currentTarget.transform);

            return false;
        }

        return true;
    }

    //Cherche une cible
    private void FindingTarget()
    {
        //Si le joueur est détecté
        if (targetScanner.Detect(transform, player))
        {
            currentTarget = player;
            timeLostTarget = 0;
            return;
        }

        //Si le joueur était détecté
        //Calcule le temps avant d'abandonner
        if (currentTarget != null)
        {
            timeLostTarget += Time.deltaTime;

            if (timeLostTarget > delayLostTarget)
            {
                timeLostTarget = 0;
                currentTarget = null;
            }


            return;
        }


        currentTarget = null;

    }

    //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);
    }


    private void Attack()
    {
        //Réinitialise les paramètres de l'animator
        ResetAnimation();
        //L'action est maintenant "Attack"
        currentAction = ATTACK_STATE;
        //Le paramètre "Attack" de l'animator = true
        animator.SetBool(ATTACK_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);
        animator.SetBool(ATTACK_STATE, false);
    }

}

Recevez les dernières actus

Nous ne spammons pas ! Consultez notre politique de confidentialité pour plus d’informations.

Laisser un commentaire

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

Retour en haut