ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • RPG 듀토리얼 08. Monster(Enemy) AI & Attack
    유니티 프로젝트/RPG 듀토리얼

    안녕하세요.

     

    이번 글에서는 Enemy의 이동 & 공격 기능을 구현한 코드를 알아보겠습니다.

     

    우선 간략하게 Enemy가 동작할 구현 목록부터 보겠습니다.

     

    현재 제작할 Slime Duo는 선제 공격을 하는 Enemy가 아니기에

     

    공격을 받기 전 까지는 필드를 자유롭게 돌아다닐것 입니다.

     

    이후 Player에게 공격을 받게 된다면 Enemy는 Player를 추적하게 되며

     

    공격 범위 안에 들어왔을 시 공격을 하게 됩니다.

     

    또한 Enemy에게 최대 움직일 수 있는 Traceing이라는 최대 거리를 제한을 해 두어

     

    player가 계속 멀어져 Enemy가 일정 범위 이상 갔을 시 

     

    전투를 종료하고 hp를 초기화하게 됩니다.


    크게 보면 이동 / 공격 두 가지로 나누어집니다.

     

     

    Enemy의 동작 구현 코드는 Enemy_AI 스크립트를 새로 생성하여 작성했습니다.


    speed_NomCombat : Enemy의 비 전투 시 스피드입니다.

    speed_Combat : Enemy의 전투 상태 시 스피드입니다.

    (비 전투 중 / 전투중 의 스피드를 나눴습니다.)

     

    aniSpeed : Animation의 속도를 제어하려고 선언했었습니다.

     

    moveRange : Enemy가 활동할 범위입니다.

    (비 전투 상태일 시 이 활동 범위를 이용해 움직입니다.)

     

    traceing : 플레이어를 추적할 수 있는 범위입니다.

    (이 추적 거리를 벗어나면 복귀합니다.)

     

    inCombat / inAtk : 각 전투 중 / 공격 중 인지 체크할 불 변수입니다.

     

    nav / ani / 각 NavMeshAgent / Animator 컴포넌트를 담을 변수들입니다.

     

    onMove : 현재 Enemy가 이동 중인지 체크할 불 변수입니다.

     

    orizinPos : Vector3로 받을 초기 위치입니다.

    orizinDis : 초기(시작) 위치부터의 거리입니다.

    target : Vector3로 받아온 목표 지점입니다.

    targetDis : 목표 지점 과의 거리입니다.

     

    playerHit : Enemy가 공격하면 실행시킬 함수가 담겨 있는 playerHit입니다.

    (playerHit에는 player가 공격을 받을 시 실행시킬 함수가 구현이 되어있습니다.)


    활성화 시 매 호출되는 OnEnable함수입니다.

    Start 함수와 Awake 함수는 활성화될 시 최초로 딱 1회만 생성됩니다.

     

     

    OnEnable 함수가 나올 때마다 대부분 적은 거 같아서 안 적으려다가

    바늘 가는데 실 간다고.. 안 적으면 섭섭하기에 적어줬습니다!

     

    변수를 보자면 orizinPos(초기 위치) = transform.position(활성화될 시 Enemy의 위치)를 받아왔습니다.

    NavMeshAgent / Animator의 컴포넌트를 nav / ani의 변수에 할당시키고

     

    dead = false로 죽지 않음을 나타내 주었습니다.

     

    그리고 ResetAI를 활성화될 시 매 실행시켜주었습니다.

    우선 ResetAI 함수를 보겠습니다.

     

    매우 간단한 코드이지만,

    Enemy의 초기 상태를 구현해 줄 중요한 함수입니다.

     

    활성화될 시 매 실행시켜주는 OnEnable 함수에서 실행되기에,

     

    이번 글에선 작성하지 않을 예정이지만 

    Enemy가 죽어 비활성화된 후 

    일정 시간 후 리스폰되어 Enemy가 활성화가 된다면 OnEnable 함수로 이 코드가 실행됩니다.

     

    우선 Enemy의 BoxCollider를 enabled를 이용해 ture(활성화)를 시켜줍니다.

    (이 BoxCollider는 Enemy가 Die 했을 시 충돌 감지를 사용 안 하기 위하여 enabled를 false로 꺼주게 됩니다.)

    nav의 기능도 위와 마찬가지로 Die시 꺼준 컴포넌트인데 enabled를 true로 이용하여 다시 활성화를 시켜줍니다.

     

    inCombat / onMove / inAtk

    전투 중인지 / 이동 중인지 / 공격 중인지

    체크해줄 불입니다.

     

    Enemy가 죽기 전 상태 값을 그대로 받아 온다면 예기치 못한 상황이 발생할 수 있습니다.

    그렇기에 초기 상태로 Reset을 해주려고 flase로 "아니"라고 선언해 주었습니다.

    이 기능은 Enemy가 Die 했을 시 선언해도 되지만. 

    한 함수의 구현으로 실행이 되기에 복잡하게 하지 않았습니다.

     

    gameObject의 태그를 "Enemy"로 바꿔줍니다.

    Enemy가 죽었을 시 에는 태그가 "Dead"로 바뀝니다.

    이를 조건으로 걸어 Enemy의 태그가 Dead가 아닐 시에는?

    이라는 조건을 걸어 주어 코드를 작성할 수 있습니다.

     

    //

    transform.GetChild(0). gameObject.SetActive(false)

    Eenemy의 첫 번째 자식은 Player의 스킬을 맞았을 시 뜨게 되는 오브젝트입니다.

    이 오브젝트를 띄울 Player의 스킬은 지속시간을 갖고 있으며

    지속시간 동안 오브젝트가 Enemy의 머리 위에 떠있게 됩니다.

    이 오브젝트가 활성화되어 있을 때 Enemy가 죽게 된다면 리스폰이 되어서도 머리 위에 

    이 오브젝트가 떠있게 되기 때문에, Reset 기능에서 SetActive(false)로 비활성화를 해 주었습니다.

    1. transform.GetChild(0). gameObject입니다.

    2. transform.GetChild(0) 이 붙어 있는 인스펙터 창 모습입니다.

    //

     

    StartCoroutine("EnemyAI");

    StartCorutine으로 코 루틴 함수 EnemyAI를 실행시켜 줍니다.

    EnemyAI 에선 orizinDis(시작 위치로부터의 거리) / targetDis (타깃 과의 거리)

    를 검사를 해 주며

     

    전투 중 / 비 전투중 의 상태로 함수를 나뉘어 주는 코 루틴 함수입니다.

     

    EnemyAI의 코 루틴 함수부터 들어가 보시죠!


    EnemyAI 코루틴 함수

    OnEnable에서 활성화 시 실행될 코 루틴 함수 EnemyAI입니다.

    처음 시작하게 되면 Enemy가 Play함수로 인해 "Move" 애니메이션을 실행하게 됩니다.

     

    또한 Move 애니메이션의 Speed값을 float값으로 받아 랜덤으로 부여를 해주었습니다.

     

    Enemy가 초기 위치로부터 떨어져 있는 거리인 orizinDis와

    Enemy가 타깃으로부터 떨어져 있는 거리인 targetDis를

    Wile문을 통해 magnitude로 길이를 받으며 할당받고

     

    inCombat : 전투 중인지 아닌지 체크할 불 변수입니다.

    inCombat의 true / false의 조건에 맞게

    Move_NonCombat (비 전투 중) / Move_Combat (전투 중)의 함수를 실행합니다.

     


    비 전투 중일 때 (inCombat == false) 실행되는

    Move_NonCombat 함수입니다.

     

    첫 번째 조건문으로 if (nav.enabled == true)를 걸어 줬습니다.

    이 는 Enemy가 죽지 않은 상태를 판단할 경우도 있겠지만

    제가 사용한 이유는 Player의 3번째 스킬에 Enemy가 스턴이 걸리는 효과를 주었습니다.

    이 효과를 맞으면 nav.enabled를 false로 움직임을 멈춰 주었는데요

    player의 3번째 스킬에 맞았을 시 에는 동작을 안 하게 만들었습니다.

     

    NavMeshAgent 컴포넌트가 활성화되어 있는 상태라면

    조건이 성립해 if (targetDis <= 3)로 들어갑니다.

    Enemy가 움직일 목적지인 target 과의 거리(targetDis)가 3의 크기보다 작을 경우

    OnMove를 false로 움직임을 멈춰 줍니다.

     

    이호 중복 방지를 위해 if (! onMove) 조건문을 걸어 주며

    onMove = true로 바꿔 주었습니다.

     

    nav의 스피드는 비 전투 중인 스피드(speed_NonCombat)로 바꿔 주고

     

    Enemy가 움직일 target(목적지)를 정해 줍니다.

     

    target(목적지)는 new Vector3로 받아

    x, z : enemy의 움직일 거리 최소 , enemy의 움직일 거리 최대로 랜덤 값을 받았고

    y : 0으로 선언해 주어

     

    x : 랜덤 / y : 0 / z : 랜덤

    의 자유로운 랜덤의 이동이 가능해졌습니다!

    이후 SetDestination으로 목적지를 target으로 정해 주었습니다.


    이렇게 움직이다가 Enemy가 움직일 수 있는 최대 거리를 벗어나면 

    target를 orizinPos(초기위치)로 대입해

    SetDestination targtet(초기위치)로 변경해 주었습니다.

    위와 같은 조건문이 없다면, Enemy의 계획했던 활동범위에서 한참 벗어난 곳에서 돌아다닐 수 있습니다.

     

    else if (nav.enabled == false && onMove) 

    NavMeshAgent 컴포넌트가 비활성화되어있는데 onMove의 상태는 true라면?

    위에 말한 Player의 3번째 스킬의 효과인 상태가 되는 것입니다.

     

    이럴 경우엔 움직임을 체크하는 불 변수 onMove를 false로 만들어

    Restart 코 루틴 함수를 실행시켜 줍니다.

     

    코루틴 함수 ReStart 함수에서는 yield return new WaitForSeconds(11)를 이용해 11초를 기다리게 만들어 줍니다.

    이 11초는 Player의 3번째 스킬을 맞았을 시 Enemy가 그 자리에서 기다릴 시간입니다.

    이후 StartCoruoutine 함수로 인해 다시 EnemyAI를 실행해 주면

     

    비 전투 중 끊기지 않는 한 사이클의 동작이 완성되었습니다.

    아래 주석 처리는 IdleToMove로 애니메이터 트랜지션의 조건을 성립하게 해 주려고 했는데 

    EnemyAI에서 ani.Play("Move")로 Move동작을 처리 하기에 주석 처리했습니다.


    inCombat(전투 중인지 체크할 불변수)가 ture 일 시 실행되는

    Move_Combat 함수를 보겠습니다.

     

    비 전투 중일 시 와 다르게 전투 중에 돌입을 하면 target은 player로 전환해 줍니다.

    player의 접근은 하나의 싱글톤 패턴인 Manager로 인해 타 스크립트로 접근하였습니다.

    //

    현재 씬 내에 하나인 싱글톤 패턴 Manager 스크립트

    //

    Move_Combat 함수는 대체적으로 Move_NonCombat(비전투 중)의 함수와 비슷합니다.

    if(! inAtk)의 조건으로 공격 중이 아니라면

    nav의 스피드는 speed_Combat(전투 중 스피드)로 정해주며

    SetDestination을 이용해 목적지를 target(palyer)로 정해줍니다.

     

    만일 타깃과의 거리 (targetDis)가 2 이하 라면

    inAtak(공격 중인지 체크할 불 변수)는 true로 바꿔주며

    Idle 애니메이션을 실행합니다.

     

    Attack 애니메이션이 아닌 Idle 애니메이션을 실행하는 이유는

     

    Idle 애니메이션의 Event에 Attack 기능의 함수를 추가시켜 

    움직임 중 갑자기 공격 모션을 취하는 상황을 방지하기 위함입니다.


    이 Attack 함수는. 공격을 동작하는 함수로

    타깃과의 거리 (targetDis)가 2 이하라면 

    LookAt 함수로 (target(player)) 를 바라보게 회전을 시켜 주었고

    SetTrigger를 이용해 Move에서 Attack으로 향하는 트랜지션의 조건을 맞게 해 주었습니다.

     

    반대로 타깃과의 거리 (targetDis)가 2 보다 크라면 공격 범위에 벗어난 것으로 판단하여

    Play함수를 이용해 "Move"애니메이션을 실행시켜 줍니다.


    Move_Combat (전투 중) 함수의 마지막 조건문입니다.

     

    이는 초기 위치와의 거리가 멀어졌을 시 리턴되는 기능입니다.

     

    전투가 종료된 것으로 판단하여

    inCombat (전투 중인지 체크하는 불 변수)는 false

    onMove (움직임을 체크하는 불 변수) 는 true

    target (목적지)는 초기위치인 orizinPos

    nav의 SetDestination(target(목적지)) 를 정해 주어 초기 위치로 되돌아갑니다.

    마지막으로 체력을 초기화시켜주는 구현까지 

    리턴의 기능을 구현했습니다.

     

    00 : 30초

     

    댓글

김효겸 / Tel. 010-7735-0580 / E-mail. dollzzang2@hanmail.net