본문 바로가기
유니티 게임 개발

[유니티 강좌] 2D RPG 게임 만들기 - 11 / 간단한 적 AI

by 호일이 2021. 3. 21.

범위 안에 들어온 적을 쫓아가고, 공격 사거리에 들어왔을 때 공격을 하는 간단한 AI를 만들어보겠습니다.

 

실행 흐름 의사 코드

while True

    if 시야 범위 안에 들어왔을 때 then

        타깃 방향 쳐다보기

        if 공격 범위 안에 들어왔을 때  then

            공격

        else 

            추적

        end if

    endif

endwhile

 

EnemyAI

이 구조를 토대로 EnemyAI 스크립트를 만들겠습니다.

public class EnemyAI : MonoBehaviour
{
    public Transform target;
    float attackDelay;

    Enemy enemy;
    Animator enemyAnimator;
    void Start()
    {
        enemy = GetComponent<Enemy>();
        enemyAnimator = enemy.enemyAnimator;
    }

    void Update()
    {
        attackDelay -= Time.deltaTime;
        if (attackDelay < 0) attackDelay = 0;

        float distance = Vector3.Distance(transform.position, target.position);

        if (attackDelay == 0 && distance <= enemy.fieldOfVision)
        {
            FaceTarget();

            if (distance <= enemy.atkRange)
            {
                AttackTarget();
            }
            else
            {
                if (!enemyAnimator.GetCurrentAnimatorStateInfo(0).IsName("Attack"))
                {
                    MoveToTarget();
                }
            }
        }
        else
        {
            enemyAnimator.SetBool("moving", false);
        }
    }

    void MoveToTarget()
    {
        float dir = target.position.x - transform.position.x;
        dir = (dir < 0) ? -1 : 1;
        transform.Translate(new Vector2(dir, 0) * enemy.moveSpeed * Time.deltaTime);
        enemyAnimator.SetBool("moving", true);
    }

    void FaceTarget()
    {
        if (target.position.x - transform.position.x < 0) // 타겟이 왼쪽에 있을 때
        {
            transform.localScale = new Vector3(-1, 1, 1);
        }
        else // 타겟이 오른쪽에 있을 때
        {
            transform.localScale = new Vector3(1, 1, 1);
        }
    }

    void AttackTarget()
    {
        target.GetComponent<Sword_Man>().nowHp -= enemy.atkDmg;
        enemyAnimator.SetTrigger("attack"); // 공격 애니메이션 실행
        attackDelay = enemy.atkSpeed; // 딜레이 충전
    }
}

 

코드 설명

쓰다 보니 좀 많아졌는데요

다른 거 다 떼고 Update문만 보면 편합니다.

void Update()
{
    attackDelay -= Time.deltaTime;
    if (attackDelay < 0) attackDelay = 0;

	// 타겟과 자신의 거리를 확인
    float distance = Vector3.Distance(transform.position, target.position);

	// 공격 딜레이(쿨타임)가 0일 때, 시야 범위안에 들어올 때
    if (attackDelay == 0 && distance <= enemy.fieldOfVision)
    {
        FaceTarget(); // 타겟 바라보기

		// 공격 범위안에 들어올 때 공격
        if (distance <= enemy.atkRange)
        {
            AttackTarget();
        }
        else // 공격 애니메이션 실행 중이 아닐 때 추적
        {
            if (!enemyAnimator.GetCurrentAnimatorStateInfo(0).IsName("Attack"))
            {
                MoveToTarget();
            }
        }
    }
    else // 시야 범위 밖에 있을 때 Idle 애니메이션으로 전환
    {
        enemyAnimator.SetBool("moving", false);
    }
}

공격 딜레이를 줘서 공격 딜레이가 있을 때는 실행이 안되게 만든 것 제외하고 어느 정도 위의 의사 코드와 비슷하게 만들어졌습니다.

함수들은 예전에 설명했던 것들 응용이라서 넘어가겠습니다.

 

Enemy.cs 수정

EnemyAI 코드를 보면 Enemy 클래스를 가져와 사용하는데 기존 Enemy.cs에 없는 변수를 사용해서 수정, 추가가 필요합니다.

public class Enemy : MonoBehaviour
{
    public string enemyName;
    public int maxHp;
    public int nowHp;
    public int atkDmg;
    public float atkSpeed;
    public float moveSpeed;
    public float atkRange;
    public float fieldOfVision;

    private void SetEnemyStatus(string _enemyName, int _maxHp, int _atkDmg, float _atkSpeed, float _moveSpeed, float _atkRange, float _fieldOfVision)
    {
        enemyName = _enemyName;
        maxHp = _maxHp;
        nowHp = _maxHp;
        atkDmg = _atkDmg;
        atkSpeed = _atkSpeed;
        moveSpeed = _moveSpeed;
        atkRange = _atkRange;
        fieldOfVision = _fieldOfVision;
    }

    public GameObject prfHpBar;
    public GameObject canvas;
    RectTransform hpBar;
    public float height = 1.7f;

    public Sword_Man sword_man;
    Image nowHpbar;
    public Animator enemyAnimator;

    void Start()
    {
        hpBar = Instantiate(prfHpBar, canvas.transform).GetComponent<RectTransform>();
        if (name.Equals("Enemy1"))
        {
            SetEnemyStatus("Enemy1", 100, 10, 1.5f, 2, 1.5f, 7f);
        }
        nowHpbar = hpBar.transform.GetChild(0).GetComponent<Image>();

        SetAttackSpeed(atkSpeed);
    }

    void Update()
    {
        Vector3 _hpBarPos = Camera.main.WorldToScreenPoint
            (new Vector3(transform.position.x, transform.position.y + height, 0));
        hpBar.position = _hpBarPos;
        nowHpbar.fillAmount = (float)nowHp / (float)maxHp;
    }
    private void OnTriggerEnter2D(Collider2D col)
    {
        if (col.CompareTag("Player"))
        {
            if (sword_man.attacked)
            {
                nowHp -= sword_man.atkDmg;
                sword_man.attacked = false;
                if (nowHp <= 0) // 적 사망
                {
                    Die();
                }
            }
        }
    }

    void Die()
    {
        enemyAnimator.SetTrigger("die");            // die 애니메이션 실행
        GetComponent<EnemyAI>().enabled = false;    // 추적 비활성화
        GetComponent<Collider2D>().enabled = false; // 충돌체 비활성화
        Destroy(GetComponent<Rigidbody2D>());       // 중력 비활성화
        Destroy(gameObject, 3);                     // 3초후 제거
        Destroy(hpBar.gameObject, 3);               // 3초후 체력바 제거
    }

    void SetAttackSpeed(float speed)
    {
        enemyAnimator.SetFloat("attackSpeed", speed);
    }
}

SetEnemyStatus 함수의 매개변수가 너무 많아져버렸네요.. 보기 좋지 않지만 일단 진행했습니다.

 

수정된 변수  

public int atkSpeed; -> public float atkSpeed

 

추가된 부분
public float moveSpeed;
public float atkRange;
public float fieldOfVision;

public Animator enemyAnimator;

Die() 함수

SetAttackSpeed(float speed) 함수

 

Enemy 애니메이션 추가

 

우측 하단 슬라이드를 왼쪽으로 밀면 파일이 선택하기 쉽게 됩니다.

Idle 애니메이션은 있으니 attack, die, run을 Scene으로 드래그해서 애니메이션을 만듭니다.

 

Attack 애니메이션 만드는 예시

Controller 하나만 남기고 나머진 다 삭제합니다.

 

애니메이션을 드래그해서 가져올 수 있습니다. Transition을 위와 같이 설정해주고, 파라미터도 만들어줍니다.

 

 

이제 그림과 같이 설정해주시면 됩니다.

Any State -> Die
Any State -> Attack
Attack -> Idle
Attack -> Run
Run -> Idle
Idle -> Run

Conditions를 제외한 설정에 정답은 없으니 자유롭게 조절하면 됩니다.

 

추가 설정

attackSpeed Parameter 추가
Project창에서 Die 클릭 -> Loop Time 체크 해제
enemyAnimator 설정, EnemyAI 추가 후 Target 설정
실행 화면

반응형

댓글