Unity ajoute des ennemis à une plate-forme 2D

Créer un Platformer dans Unity est relativement facile, mais ajouter des ennemis fonctionnels peut ne pas être aussi simple.

Dans cet article, je montrerai comment créer un jeu de plateforme 2D avec une IA ennemie.

Généralement, dans les plateformes 2D, le joueur ne peut que marcher d'avant en arrière, sauter et, dans certains cas, monter/descendre sur l'échelle, si la carte est à plusieurs niveaux. Sachant qu'on pourrait utiliser une approche modulaire où le même contrôleur est partagé entre le joueur et l'IA.

Étape 1: Créer les scripts

Commençons par créer tous les scripts nécessaires. Vérifiez le code source ci-dessous:

Ladder2D.cs

//Copyright @2018 sharpcoderblog.com
//You are free to use this script in free or commercial projects
//Selling the source code of this script is not allowed

using UnityEngine;

public class Ladder2D : MonoBehaviour
{
    Collider2D ladderCollider;
    [HideInInspector]
    public Vector3 boundsCenter;

    void Start()
    {
        //t = transform;
        ladderCollider = GetComponent<Collider2D>();
        if (ladderCollider)
        {
            ladderCollider.isTrigger = true;
            ladderCollider.gameObject.layer = 2; //Set ladder collider layer to IgnoreRaycast
        }
    }

    void OnTriggerEnter2D(Collider2D other)
    {
        if (other.CompareTag("Player"))
        {
            if (ladderCollider)
            {
                boundsCenter = ladderCollider.bounds.center;
            }
            other.SendMessage("AssignLadder", this, SendMessageOptions.DontRequireReceiver);
        }
    }

    void OnTriggerExit2D(Collider2D other)
    {
        if (other.CompareTag("Player"))
        {
            other.SendMessage("RemoveLadder", this, SendMessageOptions.DontRequireReceiver);
        }
    }
}

PlayerController2D.cs

//Copyright @2018 sharpcoderblog.com
//You are free to use this script in free or commercial projects
//Selling the source code of this script is not allowed

using System.Collections.Generic;
using UnityEngine;

public class PlayerController2D : MonoBehaviour
{
    //Move player in 2D space
    public float maxSpeed = 2.57f;
    public float jumpHeight = 6.47f;
    public float playerHP = 100;

    [HideInInspector]
    public bool facingRight = true;
    [HideInInspector]
    public float moveDirection = 0;
    [HideInInspector]
    public Rigidbody2D r2d;
    [HideInInspector]
    public Collider2D mainCollider;
    [HideInInspector]
    public Vector2 playerDimensions;
    [HideInInspector]
    public bool isGrounded = false;
    //Check every collider except Player and Ignore Raycast
    LayerMask layerMask = ~(1 << 2 | 1 << 8); //Make sure our player has Layer 8

    [HideInInspector]
    public Ladder2D currentLadder;
    List<Ladder2D> allLadders = new List<Ladder2D>();
    float moveDirectionY = 0;
    float distanceFromLadder;
    [HideInInspector]
    public bool isAttachedToLadder = false;
    bool ladderGoingDown = false;
    //bool isMovingOnLadder = false;
    [HideInInspector]
    public bool canGoDownOnLadder = false;
    [HideInInspector]
    public bool canClimbLadder = false;

    //Bot movement directions
    [HideInInspector]
    public bool isBot = false;
    [HideInInspector]
    public float botMovement = 0;
    [HideInInspector]
    public float botVerticalMovement = 0;
    [HideInInspector]
    public bool botJump = false;
    [HideInInspector]
    public Transform t;
    [HideInInspector]
    public int selectedWeaponTmp = 0;

    float gravityScale;

    // Use this for initialization
    void Start()
    {
        r2d = GetComponent<Rigidbody2D>();
        r2d.freezeRotation = true;
        mainCollider = GetComponent<Collider2D>();
        t = transform;

        gravityScale = r2d.gravityScale;
        selectedWeaponTmp = -100;

        facingRight = t.localScale.x > 0;

        //sr = GetComponent<SpriteRenderer>();
        playerDimensions = BotController2D.ColliderDimensions(GetComponent<Collider2D>());
    }

    void OnDisable()
    {
        r2d.bodyType = RigidbodyType2D.Static;
        r2d.velocity = Vector3.zero;
    }

    // Update is called once per frame
    void Update()
    {
        if (!isBot)
        {
            if ((Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.D)) &&
                (isGrounded || r2d.velocity.x > 0.01f || isAttachedToLadder))
            {
                moveDirection = Input.GetKey(KeyCode.A) ? -1 : 1;
            }
            else
            {
                if (isGrounded || r2d.velocity.magnitude < 0.01f)
                    moveDirection = 0;
            }
        }
        else
        {
            if (botMovement != 0 && (isGrounded || r2d.velocity.x > 0.01f))
            {
                moveDirection = botMovement < 0 ? -1 : 1;
            }
            else
            {
                if (isGrounded || r2d.velocity.magnitude < 0.01f)
                    moveDirection = 0;
            }
        }

        //Change facing position
        if (moveDirection != 0)
        {
            if (moveDirection > 0 && !facingRight)
            {
                facingRight = true;
            }
            if (moveDirection < 0 && facingRight)
            {
                facingRight = false;
            }
        }

        if (facingRight)
        {
            if (t.localScale.x < 0)
            {
                t.localScale = new Vector3(Mathf.Abs(t.localScale.x), t.localScale.y, transform.localScale.z);
            }
        }
        else
        {
            if (t.localScale.x > 0)
            {
                t.localScale = new Vector3(-Mathf.Abs(t.localScale.x), t.localScale.y, t.localScale.z);
            }
        }

        //Vector2 velocityTmp = r2d.velocity;
        bool canGoDownTmp = false;

        // LADDER CONTROL START
        if (currentLadder)
        {
            distanceFromLadder = Mathf.Abs(currentLadder.boundsCenter.x - t.position.x);
            canClimbLadder = distanceFromLadder < 0.34f;
            if (!isAttachedToLadder)
            {
                if (canClimbLadder)
                {
                    if (currentLadder.boundsCenter.y > t.position.y)
                    {
                        if (!isBot)
                        {
                            if (Input.GetKey(KeyCode.W))
                            {
                                isAttachedToLadder = true;
                            }
                        }
                        else
                        {
                            if (botVerticalMovement > 0)
                            {
                                isAttachedToLadder = true;
                            }
                        }
                    }
                    if (currentLadder.boundsCenter.y < t.position.y)
                    {
                        if (!isBot)
                        {
                            if (Input.GetKey(KeyCode.S))
                            {
                                isAttachedToLadder = true;
                            }
                        }
                        else
                        {
                            if (botVerticalMovement < 0)
                            {
                                isAttachedToLadder = true;
                            }
                        }

                        canGoDownTmp = true;
                    }
                }

                if (isAttachedToLadder)
                {
                    r2d.gravityScale = 0;
                    moveDirection = 0;
                    moveDirectionY = 0;
                }
            }
            else
            {
                //Make our collider trigger if we stand on top of the ladder (To prevent collision with the ground while going down)
                mainCollider.isTrigger = currentLadder.boundsCenter.y < t.position.y; 

                //Ladder movement
                if ((!isBot && Input.GetKey(KeyCode.W)) || (isBot && botVerticalMovement > 0))
                {
                    moveDirectionY = 3.97f;
                    ladderGoingDown = false;
                    //For sound controller
                    //isMovingOnLadder = true;
                }
                else if ((!isBot && Input.GetKey(KeyCode.S)) || (isBot && botVerticalMovement < 0))
                {
                    moveDirectionY = -3.97f;
                    ladderGoingDown = true;
                    if (!mainCollider.isTrigger && isGrounded)
                    {
                        //RemoveLadder(currentLadder);
                        isAttachedToLadder = false;
                        mainCollider.isTrigger = false;
                        r2d.gravityScale = gravityScale;
                        moveDirectionY = 0;
                    }
                    //For sound controller
                    //isMovingOnLadder = true;
                }
                else
                {
                    //isMovingOnLadder = false;
                    moveDirectionY = 0;
                }
            }

            if (distanceFromLadder > playerDimensions.x * 2)
            {
                RemoveLadder(currentLadder);
            }
        }
        canGoDownOnLadder = canGoDownTmp;
        // LADDER CONTROL END

        if (!isBot)
        {
            //Jumping
            if (Input.GetKeyDown(KeyCode.W))
            {
                Jump();
            }
        }
        else
        {
            if (botJump)
            {
                botJump = false;
                Jump();
            }
        }

        if (!isBot)
        {
            //Weapon firing
            if (Input.GetKeyDown(KeyCode.LeftControl))
            {
                Attack();
            }
        }
    }

    void FixedUpdate()
    {
        Bounds colliderBounds = mainCollider.bounds;
        Vector3 groundCheckPos = colliderBounds.min + new Vector3(colliderBounds.size.x * 0.5f, 0.1f, 0);
        //Check if player is grounded
        isGrounded = Physics2D.OverlapCircle(groundCheckPos, 0.25f, layerMask);

        Debug.DrawLine(groundCheckPos, groundCheckPos - new Vector3(0, 0.25f, 0), isGrounded ? Color.green : Color.red);

        //Apply player velocity
        r2d.velocity = new Vector2((moveDirection) * maxSpeed, isAttachedToLadder ? moveDirectionY : r2d.velocity.y);
    }

    void AssignLadder(Ladder2D ladderTmp)
    {
        currentLadder = ladderTmp;
        allLadders.Add(ladderTmp);
    }

    void RemoveLadder(Ladder2D ladderTmp)
    {
        //print("On trigger out");
        allLadders.Remove(ladderTmp);
        if (currentLadder == ladderTmp)
        {
            currentLadder = null;

            if (allLadders.Count > 0)
            {
                currentLadder = allLadders[allLadders.Count - 1];
            }
        }

        if (isAttachedToLadder && !currentLadder)
        {
            isAttachedToLadder = false;
            //r2d.bodyType = RigidbodyType2D.Dynamic;
            mainCollider.isTrigger = false;

            r2d.gravityScale = gravityScale;
            r2d.velocity = Vector3.zero;

            if (!ladderGoingDown)
            {
                r2d.velocity = new Vector2(r2d.velocity.x, 1.47f);
            }
            ladderGoingDown = false;
        }
    }

    public void Jump()
    {
        if (isGrounded && !isAttachedToLadder)
        {
            r2d.velocity = new Vector2(r2d.velocity.x, jumpHeight);
            //Tip: Play jump sound here
        }
    }

    public void Attack()
    {
        print(gameObject.name + " is Attacking");

        //Tip: Write your attack function here (ex. Raycast toward the enemy to inflict the damage)
    }
}

CameraFollow2D.cs

//Copyright @2018 sharpcoderblog.com
//You are free to use this script in free or commercial projects
//Selling the source code of this script is not allowed

using UnityEngine;

public class CameraFollow2D : MonoBehaviour
{
    public Transform target;
    public Vector3 offset = new Vector3(0, 2.8f, 0);
    public bool smoothFollow = true;

    Vector2 moveToPos;
    bool beginMove = false;
    float distanceTmp = 0f;

    // Update is called once per frame
    void LateUpdate()
    {
        if (!target)
            return;

        if (smoothFollow)
        {
            distanceTmp = ((target.position + offset) - transform.position).sqrMagnitude;
            if (beginMove)
            {
                moveToPos = Vector3.Lerp(moveToPos, target.position + offset, Time.fixedDeltaTime * 7.75f);
                transform.position = new Vector3(moveToPos.x, moveToPos.y, -10);

                if (distanceTmp < 0.05f * 0.05f)
                {
                    beginMove = false;
                }
            }
            else
            {
                if (distanceTmp > 0.5f * 0.5f)
                {
                    beginMove = true;
                }
            }
        }
        else
        {
            transform.position = new Vector3(target.position.x, target.position.y, -10);
        }

    }

    void StopFollowing()
    {
        beginMove = false;
    }
}

BotController2D.cs

//Copyright @2018 sharpcoderblog.com
//You are free to use this script in free or commercial projects
//Selling the source code of this script is not allowed

using System.Collections;
using UnityEngine;

public class BotController2D : MonoBehaviour
{
    //This script will handle bot control
    public enum BotType { Enemy, Friendly }
    public BotType botType = BotType.Enemy;
    public enum BotDifficulty { Easy, Medium, Hard }
    public BotDifficulty botDifficulty = BotDifficulty.Medium;
    public enum InitialState { Idle, Explore }
    public InitialState initialState = InitialState.Idle; //Should the Bot stand in place until approached or begin exploring the level right away
    public bool canJump = true; //Can this bot jump?

    public enum CurrentState { Idle, MovingLeft, MovingRight, GoinUPLadder, GoingDownLadder, Attack }
    CurrentState currentState;
    InitialState appliedState;
    PlayerController2D pc2d;
    RaycastHit2D hitLeft;
    RaycastHit2D hitRight;
    RaycastHit2D groundHit;
    Vector3 leftOrigin;
    Vector3 rightOrigin;
    float distanceLeft = -1;
    float distanceRight = -1;

    int encounteredLadders = 0;
    int encounteredLaddersCacche = 0;
    Ladder2D previousLadder;
    Ladder2D lastAttachedLadder;
    bool previousCanGoDownOnLadder = false;
    bool previousCanClimbLadder = false;

    //Everything except "Player" and "IgnoreRaycast" layers
    LayerMask layerMask = ~(1 << 2 | 1 << 8);
    //Only "Player" layer
    LayerMask playerLayerMask = 1 << 8;

    float timeMotionless = 0.0f;
    bool statePause = false;

    float trVelocity;
    Vector3 previousPos;

    Collider2D[] detectedPlayers = new Collider2D[0];
    Collider2D[] previousDetectedPlayers = new Collider2D[0];
    PlayerController2D enemyToFollow;
    int followPriority = 0; //0 = Easy, 1 = Medium (This player inflicted the damage)

    [HideInInspector]
    public Transform t;

    bool checkingTotalEnemies = false;
    bool runAway = false;

    int attackingFromLeft = 0;
    int attackingFromRight = 0;

    //Limit attack rate for easy bots
    float attackTimer = 0;
    float nextAttackTime = 0;

    Camera mainCamera;
    float cameraWidth; //Horizontal size of camera view

    // Use this for initialization
    void Start()
    {
        pc2d = GetComponent<PlayerController2D>();
        pc2d.isBot = true;
        t = transform;
        appliedState = initialState;

        if (Random.Range(-10, 10) > 0)
        {
            StartCoroutine(StatePause(CurrentState.Idle, true));
        }
    }

    // Update is called once per frame
    void FixedUpdate()
    {
        //Draw rays back and forth

        if (!mainCamera)
        {
            mainCamera = Camera.main;
            cameraWidth = mainCamera.aspect * mainCamera.orthographicSize;
        }

        rightOrigin = t.position + t.right * (pc2d.playerDimensions.x / 2f);
        hitRight = Physics2D.Raycast(rightOrigin, t.right, cameraWidth, layerMask);
        if (hitRight)
        {
            Debug.DrawLine(rightOrigin, hitRight.point, Color.red);
            distanceRight = hitRight.distance;
        }
        else
        {
            Debug.DrawLine(rightOrigin, rightOrigin + t.right * cameraWidth, Color.cyan);
            distanceRight = -1;
        }

        leftOrigin = t.position - t.right * (pc2d.playerDimensions.x / 2f);
        hitLeft = Physics2D.Raycast(leftOrigin, -t.right, cameraWidth, layerMask);
        if (hitLeft)
        {
            Debug.DrawLine(leftOrigin, hitLeft.point, Color.red);
            distanceLeft = hitLeft.distance;
        }
        else
        {
            Debug.DrawLine(leftOrigin, leftOrigin - t.right * cameraWidth, Color.cyan);
            distanceLeft = -1;
        }

        if (appliedState == InitialState.Explore)
        {
            if (currentState == CurrentState.Idle)
            {
                if (!statePause)
                {
                    //Decide which direction to move
                    if (distanceRight == -1 && distanceLeft == -1)
                    {
                        //Decide random direaction
                        currentState = Random.Range(-10, 10) > 0 ? CurrentState.MovingRight : CurrentState.MovingLeft;
                    }
                    else if (distanceRight == -1 && distanceLeft >= 0)
                    {
                        currentState = CurrentState.MovingRight;
                    }
                    else if (distanceRight >= 0 && distanceLeft == -1)
                    {
                        currentState = CurrentState.MovingLeft;
                    }
                    else if (distanceRight > distanceLeft)
                    {
                        currentState = CurrentState.MovingRight;
                    }
                    else if (distanceRight < distanceLeft)
                    {
                        currentState = CurrentState.MovingLeft;
                    }
                }
            }
            else if (currentState == CurrentState.MovingLeft)
            {
                if (!statePause && pc2d.isGrounded)
                {
                    pc2d.botMovement = -1;
                    float jumpHeightTmp = pc2d.jumpHeight * 0.25f;

                    if (distanceLeft > 0 && distanceLeft < pc2d.playerDimensions.x)
                    {
                        if (hitLeft && canJump &&
                            !Physics2D.Linecast(t.position + t.up * jumpHeightTmp, (t.position + t.up * jumpHeightTmp) - t.right * pc2d.playerDimensions.x * 2, layerMask) &&
                            Random.Range(-2, 10) > 0
                        )
                        {
                            StartCoroutine(DoJump());
                        }
                        else
                        {
                            if (!enemyToFollow)
                            {
                                StartCoroutine(StatePause(CurrentState.Idle, true));
                            }
                        }
                    }

                    /*if(!Physics2D.Linecast(t.position + t.up * jumpHeightTmp, (t.position + t.up * jumpHeightTmp) - t.right * pc2d.playerDimensions.x * 2)){
                        Debug.DrawLine(t.position + t.up * jumpHeightTmp, (t.position + t.up * jumpHeightTmp) - t.right * pc2d.playerDimensions.x * 2, Color.yellow);
                    }
                    else
                    {
                        Debug.DrawLine(t.position + t.up * jumpHeightTmp, (t.position + t.up * jumpHeightTmp) - t.right * pc2d.playerDimensions.x * 2, Color.red);
                    }*/

                    //Jump if there is no groun in front
                    groundHit = Physics2D.Raycast(leftOrigin, -t.up, pc2d.playerDimensions.y * 2.1f, layerMask);
                    if (groundHit)
                    {
                        Debug.DrawLine(leftOrigin, groundHit.point, Color.red);
                    }
                    else
                    {
                        Debug.DrawLine(leftOrigin, leftOrigin - t.up * (pc2d.playerDimensions.y * 2.1f), Color.blue);
                        if (canJump)
                        {
                            StartCoroutine(DoJump());
                        }
                        else
                        {
                            //StartCoroutine(StatePause(CurrentState.MovingRight, true));
                            StartCoroutine(CheckEnemiesEnumerator(1, 0, false));
                        }
                    }
                }
            }
            else if (currentState == CurrentState.MovingRight)
            {
                if (!statePause && pc2d.isGrounded)
                {
                    pc2d.botMovement = 1;
                    float jumpHeightTmp = pc2d.jumpHeight * 0.25f;

                    if (distanceRight > 0 && distanceRight < pc2d.playerDimensions.x)
                    {
                        if (hitRight && canJump &&
                            !Physics2D.Linecast(t.position + t.up * jumpHeightTmp, (t.position + t.up * jumpHeightTmp) + t.right * pc2d.playerDimensions.x * 2, layerMask) &&
                            Random.Range(-2, 10) > 0
                        )
                        {
                            StartCoroutine(DoJump());
                        }
                        else
                        {
                            if (!enemyToFollow)
                            {
                                StartCoroutine(StatePause(CurrentState.Idle, true));
                            }
                        }
                    }

                    /*if (!Physics2D.Linecast(t.position + t.up * jumpHeightTmp, (t.position + t.up * jumpHeightTmp) + t.right * pc2d.playerDimensions.x * 2))
                    {
                        Debug.DrawLine(t.position + t.up * jumpHeightTmp, (t.position + t.up * jumpHeightTmp) + t.right * pc2d.playerDimensions.x * 2, Color.yellow);
                    }
                    else
                    {
                        Debug.DrawLine(t.position + t.up * jumpHeightTmp, (t.position + t.up * jumpHeightTmp) + t.right * pc2d.playerDimensions.x * 2, Color.red);
                    }*/

                    //Jump if there is no groun in front
                    groundHit = Physics2D.Raycast(rightOrigin, -t.up, pc2d.playerDimensions.y * 2.1f, layerMask);
                    if (groundHit)
                    {
                        Debug.DrawLine(rightOrigin, groundHit.point, Color.red);
                    }
                    else
                    {
                        Debug.DrawLine(rightOrigin, rightOrigin - t.up * (pc2d.playerDimensions.y * 2.1f), Color.blue);
                        if (canJump)
                        {
                            StartCoroutine(DoJump());
                        }
                        else
                        {
                            //StartCoroutine(StatePause(CurrentState.MovingLeft, true));
                            StartCoroutine(CheckEnemiesEnumerator(0, 1, false));
                        }
                    }
                }
            }
            else if (currentState == CurrentState.GoinUPLadder)
            {
                if (!statePause)
                {
                    pc2d.botVerticalMovement = 1;
                    if (!pc2d.currentLadder)
                    {
                        StartCoroutine(StatePause(CurrentState.Idle, true));
                    }
                }
            }
            else if (currentState == CurrentState.GoingDownLadder)
            {
                if (!statePause)
                {
                    pc2d.botVerticalMovement = -1;
                    if (!pc2d.currentLadder)
                    {
                        StartCoroutine(StatePause(CurrentState.Idle, true));
                    }
                }
            }
            else if (currentState == CurrentState.Attack)
            {
                if (!statePause)
                {
                    if (!enemyToFollow)
                    {
                        StartCoroutine(StatePause(CurrentState.Idle, true));
                    }
                    else
                    {
                        //Firing weapon
                        if (attackTimer >= nextAttackTime)
                        {
                            //Check if player is above us and jump
                            if (enemyToFollow.t.position.y > t.position.y && enemyToFollow.t.position.y - t.position.y > pc2d.playerDimensions.y * 0.95f)
                            {
                                if (Random.Range(-5, 10) > 0)
                                {
                                    StartCoroutine(DoJump());
                                }
                            }
                            //

                            pc2d.Attack();

                            if (botDifficulty == BotDifficulty.Easy)
                            {
                                attackTimer = 0;
                                nextAttackTime = Random.Range(0.25f, 0.95f);
                            }
                            if (botDifficulty == BotDifficulty.Medium)
                            {
                                attackTimer = 0;
                                nextAttackTime = Random.Range(0.01f, 0.37f);
                            }
                            if (botDifficulty == BotDifficulty.Hard)
                            {
                                attackTimer = 0;
                                nextAttackTime = Random.Range(0.01f, 0.24f);
                            }
                        }
                        else
                        {
                            attackTimer += Time.deltaTime;
                        }


                        if (enemyToFollow && !checkingTotalEnemies)
                        {
                            if (enemyToFollow.t.position.x > t.position.x && !pc2d.facingRight)
                            {
                                pc2d.facingRight = true;
                            }
                            if (enemyToFollow.t.position.x < t.position.x && pc2d.facingRight)
                            {
                                pc2d.facingRight = false;
                            }

                            attackingFromLeft = 0;
                            attackingFromRight = 0;

                            //Check if there too many player attacking us and run away
                            for (int i = 0; i < detectedPlayers.Length; i++)
                            {
                                if (detectedPlayers[i])
                                {
                                    BotController2D bcTmp = detectedPlayers[i].GetComponent<BotController2D>();
                                    if (bcTmp && bcTmp.botType != botType && bcTmp.enemyToFollow == pc2d && bcTmp.currentState == CurrentState.Attack)
                                    {
                                        if (bcTmp.t.position.x > t.position.x)
                                        {
                                            attackingFromRight++;
                                        }
                                        else
                                        {
                                            attackingFromLeft++;
                                        }
                                    }
                                }
                            }

                            //If the value playerHP from PlayerController2D get too low, and the bot is being attacked, increase the probability to run away
                            if (attackingFromRight >= 2 || attackingFromLeft >= 2 || (pc2d.playerHP < 70 && botDifficulty == BotDifficulty.Hard && (attackingFromRight > 0 || attackingFromLeft > 0)) || (pc2d.playerHP < 40 && botDifficulty == BotDifficulty.Medium && (attackingFromRight > 0 || attackingFromLeft > 0)))
                            {
                                StartCoroutine(CheckEnemiesEnumerator(attackingFromLeft, attackingFromRight, false));
                            }
                        }
                    }
                }
            }
        }

        if (pc2d.currentLadder && (previousLadder != pc2d.currentLadder || previousCanGoDownOnLadder != pc2d.canGoDownOnLadder || previousCanClimbLadder != pc2d.canClimbLadder))
        {
            previousLadder = pc2d.currentLadder;
            previousCanGoDownOnLadder = pc2d.canGoDownOnLadder;
            previousCanClimbLadder = pc2d.canClimbLadder;

            if (!pc2d.isAttachedToLadder)
            {
                if (pc2d.canClimbLadder)
                {
                    encounteredLadders++;

                    if ((lastAttachedLadder != pc2d.currentLadder || encounteredLadders > 1) && !statePause)
                    {
                        if (Random.Range(-10, 10) > 0)
                        {
                            if (pc2d.canGoDownOnLadder)
                            {
                                StartCoroutine(StatePause(CurrentState.GoingDownLadder, true));
                            }
                            else
                            {
                                StartCoroutine(StatePause(CurrentState.GoinUPLadder, true));
                            }
                        }
                    }
                }
            }
            else
            {
                encounteredLadders = 0;
                lastAttachedLadder = pc2d.currentLadder;
            }
        }

        trVelocity = ((t.position - previousPos).magnitude) / Time.deltaTime;
        previousPos = t.position;

        if (trVelocity < 0.01f && !statePause)
        {
            timeMotionless += Time.deltaTime;

            if (timeMotionless > 0.5f)
            {
                StartCoroutine(StatePause(CurrentState.Idle, true));
            }
        }
        else
        {
            timeMotionless = 0;
        }

        //Detect and attack enemy players
        detectedPlayers = Physics2D.OverlapCircleAll(t.position, cameraWidth, playerLayerMask);

        if (!enemyToFollow)
        {
            if (!runAway)
            {
                if (previousDetectedPlayers.Length != detectedPlayers.Length || (previousDetectedPlayers.Length > 0 && detectedPlayers.Length > 0 && previousDetectedPlayers[0] != detectedPlayers[0]))
                {
                    previousDetectedPlayers = detectedPlayers;

                    for (int i = 0; i < detectedPlayers.Length; i++)
                    {
                        BotController2D bcTmp = detectedPlayers[i].GetComponent<BotController2D>();
                        PlayerController2D pc2dTmp = null;
                        if (!bcTmp)
                        {
                            pc2dTmp = detectedPlayers[i].GetComponent<PlayerController2D>();
                        }
                        if ((pc2dTmp && botType == BotType.Enemy) || (bcTmp && bcTmp.botType != botType))
                        {
                            Vector3 enemyPos = bcTmp ? bcTmp.t.position : pc2dTmp.t.position;
                            float yDistance = Mathf.Abs(enemyPos.y - t.position.y);
                            if (yDistance < pc2d.playerDimensions.y * 2)
                            {
                                if (!enemyToFollow || Mathf.Abs(enemyPos.x - t.position.x) < Mathf.Abs(enemyToFollow.t.position.x - t.position.x))
                                {
                                    enemyToFollow = bcTmp ? bcTmp.pc2d : pc2dTmp;
                                    appliedState = InitialState.Explore;
                                }
                            }

                        }
                    }
                }
            }
        }
        else
        {
            float yDistance = enemyToFollow.t.position.y - t.position.y;
            float xDistance = enemyToFollow.t.position.x - t.position.x;
            if (Mathf.Abs(yDistance) >= pc2d.playerDimensions.y * 2 || Mathf.Abs(xDistance) > cameraWidth || !enemyToFollow.enabled)
            {
                enemyToFollow = null;
            }
            else
            {
                if (Mathf.Abs(xDistance) > pc2d.playerDimensions.x * 1.45f)
                {
                    if (!statePause && pc2d.botVerticalMovement == 0)
                    {
                        if (xDistance > 0)
                        {
                            if (currentState != CurrentState.MovingRight)
                            {
                                statePauseCoroutine = StartCoroutine(StatePause(CurrentState.MovingRight, false));
                            }
                        }
                        else if (xDistance < 0)
                        {
                            if (currentState != CurrentState.MovingLeft)
                            {
                                statePauseCoroutine = StartCoroutine(StatePause(CurrentState.MovingLeft, false));
                            }
                        }
                    }
                    else
                    {
                        StopPauseCoroutine();
                    }
                }
                else
                {
                    if (pc2d.botVerticalMovement == 0)
                    {
                        if (currentState != CurrentState.Attack)
                        {
                            if (!statePause)
                            {
                                statePauseCoroutine = StartCoroutine(StatePause(CurrentState.Attack, true));
                            }
                        }
                        else
                        {
                            if (Mathf.Abs(xDistance) < pc2d.playerDimensions.x / 3 && !checkingTotalEnemies)
                            {
                                //print("Enemies are too close!!!");
                                StartCoroutine(CheckEnemiesEnumerator(attackingFromLeft, attackingFromRight, true));
                            }
                        }
                    }
                }
            }
        }
    }

    Coroutine statePauseCoroutine = null;

    void StopPauseCoroutine()
    {
        if (statePauseCoroutine != null)
        {
            StopCoroutine(statePauseCoroutine);
            statePauseCoroutine = null;
            statePause = false;
        }
    }

    IEnumerator StatePause(CurrentState newState, bool stopMovement)
    {
        //print("State pause");
        statePause = true;
        if (stopMovement)
        {
            pc2d.botMovement = 0;
            pc2d.botVerticalMovement = 0;
        }
        currentState = newState;

        if (newState == CurrentState.Attack && botDifficulty == BotDifficulty.Hard)
        {
            yield return new WaitForSeconds(Random.Range(0.15f, 0.45f));
        }
        else
        {
            yield return new WaitForSeconds(Random.Range(0.45f, 0.75f));
        }


        statePause = false;
    }

    IEnumerator DoJump()
    {
        //print("Do jump");
        statePause = true;
        pc2d.botJump = true;

        yield return new WaitForSeconds(0.65f);

        statePause = false;
    }

    IEnumerator CheckEnemiesEnumerator(int attackingFromLeft, int attackingFromRight, bool doNotRunAway)
    {
        checkingTotalEnemies = true;

        //print("CHECKING FOR TOTAL ENEMIES");

        yield return new WaitForSeconds(Random.Range(0.27f, 0.75f));

        if (Random.Range(-10, 10) > 0)
        {
            runAway = true;
            enemyToFollow = null;

            if (attackingFromLeft > attackingFromRight)
            {
                currentState = CurrentState.MovingRight;
            }
            else
            {
                currentState = CurrentState.MovingLeft;
            }
        }

        checkingTotalEnemies = false;

        if (runAway)
        {
            if (doNotRunAway)
            {
                //Simply walk away a bit
                yield return new WaitForSeconds(Random.Range(0.37f, 0.75f));
            }
            else
            {
                //Run away
                yield return new WaitForSeconds(Random.Range(1.57f, 2.45f));
            }


            runAway = false;
        }
    }

    public static Vector2 ColliderDimensions(Collider2D sp)
    {
        return new Vector2(sp.bounds.max.x - sp.bounds.min.x, sp.bounds.max.y - sp.bounds.min.y);
    }
}

Étape 2: Configurez le joueur et les ennemis

Il est maintenant temps de configurer notre joueur et l'IA ennemie en utilisant les scripts ci-dessus.

Configurer notre instance Player

  • Créez un nouveau GameObject et nommez-le "Player"
  • Changez la balise de cet objet en "Player"
  • Changez le calque de l'objet en 8 (s'il n'y a pas de calque 8 dans la sélection, ajoutez-en un en cliquant sur Ajouter un calque..., nommez-le "Player")
  • Créez un autre GameObject, appelez-le "Body" et ajoutez un composant SpriteRenderer
  • Affectez votre joueur Sprite à un "Body" et déplacez-le à l'intérieur de l'objet "Player"
  • Sélectionnez l'objet "Player" et ajoutez les composants CapsuleCollider2D, Rigidbody2D et PlayerController2D
  • Mettez à l'échelle le CapsuleCollider2D jusqu'à ce qu'il s'adapte au lecteur Sprite

Comme vous pouvez le voir, PlayerController2D a quelques variables, la plupart d'entre elles sont explicites, cependant, l'une d'entre elles nécessite un peu d'explication:

PlayerHP - cette valeur est utilisée dans BotController2D pour décider si l'IA doit s'enfuir lorsque ses HP sont trop bas.

Utilisez la variable PlayerHP lors de l'implémentation de la fonction d'attaque (Cochez void Attack() à la fin du script PlayerController2D.cs).

Configuration de la caméra du lecteur

  • Sélectionnez la caméra principale et ajoutez le composant CameraFollow2D
  • Affectez le joueur à la variable cible
  • En option, vous pouvez modifier la variable Offset (si vous ne voulez pas que la caméra soit centrée exactement au milieu)

Mise en place d'une échelle

PlayerController2D prend également en charge les échelles grimpables.

La mise en place d'une nouvelle échelle est très simple:

  • Créez un nouveau GameObject et appelez-le "Ladder"
  • Changez son calque en "IgnoreRaycast"
  • Créez un autre objet de jeu avec un SpriteRenderer et attribuez un sprite de votre échelle et déplacez-le à l'intérieur de l'objet Ladder
  • Ajoutez les composants BoxCollider2D et Ladder2D à l'objet "Ladder"
  • Mettez à l'échelle les dimensions du collisionneur pour qu'elles correspondent à l'échelle Sprite et marquez-la comme déclencheur

Configuration de l'IA ennemie

  • Tout d'abord, allez dans le panneau Physics2D et désactivez une collision entre la couche Player, afin que les bots et les joueurs ne soient pas coincés entre eux.

  • Dupliquer l'instance Player
  • Ajouter un composant BotController2D
  • Le robot est maintenant prêt

Propriétés de l'IA du robot 2D

Regardez la vidéo ci-dessous pour voir l'IA ennemie en action (l'instance blanche est notre lecteur, les instances rouges sont contrôlées par l'IA):

Sharp Coder Lecteur vidéo

Source
📁2DEnemyAI.unitypackage40.49 KB
Articles suggérés
Implémentation de l'IA d'un ennemi dans Unity
Comment créer un FPS avec le support de l'IA dans Unity
Créer un PNJ qui suit le joueur dans Unity
Comment faire une IA d'un cerf dans Unity
Travailler avec NavMeshAgent dans Unity
Examen du package Unity Asset Store - Zombie AI System
Créer un jeu de plateforme simple dans Unity