ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • RPG 액션 연습 03. 기본 Enemy
    연습 프로젝트/RPG 연습

    안녕하세요

     

    날씨가 뒤숭숭합니다. 다들 환절기 감기 조심하세요. 더불어 코로나도.. ㅎㅎ

    얼마 전에 이모티콘이 있는 걸 알아서 사용해봤는데

    무척 귀여운것 같습니다.

     

    오늘은 기본 Enemy를 만들어 보겠습니다.

     

    화려한 이펙트 들은 없지만, 기본 동작 구현에 맞게 움직이며

     

    hit 모션damage 모션을 적용하여 player와 상호 작용을 연습해 보겠습니다.

     

    이러한 관계를 시각적으로 보이기 위해서 UI작업도 함께 해야될것 같습니다.

     

    그럼 바로 시작해 보겠습니다.

     

    책에서 보았을땐 프로토 타이핑을 통해 간단한 큐브나 캡슐 등 기본 도형을 활용해 구현을 하여 시간을 단축시키고 기획을 검증하여 각 직군별로 동시 작업을 가능하게 하는 효율적인 방법이 있다고 합니다.

     

    하지만, 아직 제 역량으로는 기본 도형이나 에셋에서 다운로드하고 난 후 작업이나 별반 차이가 없는 것 같습니다.

     

    그렇기에 어김없이 오늘도 몬스터를 유니티 에셋 스토어에서 다운받아 와 보도록 하겠습니다.

    기본 Enemy로 쓰일 녀석 입니다.

    상태 구현은

    단순하게 Idle(대기), Move(이동), Attack(공격), Return(복귀), Damaged(대미지), Die(죽음)

    으로 총 6가지가 있습니다.

    또한 선언 변수들로

    GolemState의 상태 값을 받아오기 위해 golemState를 지어 주었고

    Enemy에 붙은 NavMeshAgent를 받아왔습니다.

    (이 Enemy의 이름을 Golem으로 만들겠습니다)

     

    moveDistance : 움직일 수 있는 거리

    findDistance : 플레이어를 찾을 수 있는 거리

    atkDistance : 공격 범위

     

    그리고 각 스피드, 파워, 체력의 stat을 부여해 주었습니다.

     

    Slider를 사용하기 위해 using UnityEngine.UI의 네임스페이스를 선언하고

    golem의 체력으로 사용할 Slider를 받아왔습니다.

    골렘의 Update 부분입니다.

    첫 번째로 hpSlider의 0.0f - 1.0f로 조절 가능한 value 값을 

    현재 hp에서 최대 hp로 나누어 0.0f - 1.0f의 값을 동기화해 주었습니다.

     

    이후 switch문을 통해 golemState의 상태에 따른 함수 실행을 구현하였습니다.

    우선 골렘의 Idle 값입니다. 거리를 보기 위해서 Vector3.Distance함수를 사용하였습니다.

    오브젝트 간의 거리를 체크하고 싶을 때는 우선 제가 아는 세 가지 방법이 있는데

    1) Vector3.Distance

    2) Vector3.magnitude

    3) Vector3.sqrMagnitude

    로 1,2 번은 정확한 거리를 계산하고

    3번은 계산된 거리의 제곱 값을 리턴하여 루트 연산을 하지 않아 연산 속도가 더 빠르다고 합니다.

    단지 크거나 작은 것을 비교할 때는 3번을 사용하며, 1.2번의 결과 및 계산 방식은 동일하다고 합니다.

     

    이 전 프로젝트를 진행하면서 몇 번 설명했던 기억이 있지만, 제가 다시 보고... 또한 직접 적으면서 기억을 더듬기 위해서 설명을 이어나갔습니다.


    이어서 코드를 보자면

    if(Vector3.Distance(transform.position, player.position) < findDistance)

    로 골렘의 위치와 플레이어의 위치를 계산합니다.

    findDistance는 골렘의 위치에서 8f의 값을 주어졌기에

    8f보다 작으면 플레이어가 범위 내에 있다고 간주하고 

    골램의 상태가 Move로 바뀝니다.

     

    또한 애니메이터의 SetTrigger("IdleToMove")를 통해 Idle ->Move의 트랜지션 방향으로 전환됩니다.


    초기 위치와 초기 회전값 입니다.

    Start 함수에서

    orizinPos = transform.position
    orizinRot = transform.rotation

    로 받아와 초기 위치 값과 초기 회전 값을 받아왔습니다.

    골렘의 Move함수입니다.

    Vector3.Distance를 통해 골렘의 위치가 초기 위치로부터 얼마나 떨어져 있는지 계산을 하여

    moveDistance : 움직일 수 있는 최대 거리보다 크다면

    golemState = GolemState.Return을 통해 상태 값을 Return으로 변환시켜 줍니다.

     

    위 조건이 아닌 움직일 수 있는 최대 거리보다 작은 상태에서

    else if(Vector3.Distance(transform.position, player.position) > atkDistance)

    조건문이 실행됩니다.

    이번엔 골렘의 위치가 플레이어로부터 떨어져 있는 거리가 공격 범위보다 크다면?

     

    nav.isStopped를 true로 바꿔주어 네비의 기능을 멈춰 주고

    이후 ResetPath()를 이용해 목적지 경로를 초기화시켜줍니다.

     

    골렘의 공격 모션 도중 플레이어가 거리를 벗어나게 된다면 골렘이 미끄러지며 이동하는 현상을 방지했습니다.

     

    nav.stoppingDistance = atkDistance로 멈추는 거리를 공격 범위 끝으로 값을 할당해주고

    nav.destination = player.position으로 목적지를 player.position으로 정해 주었습니다.

     

    nav.destination = player.position 와 nav.SetDestinatin(player.position) 은 같습니다.

     

    만일 모든 조건이 아니라면?

    골렘은 atkDistance범위 안에 있는 것으로 간주하여

    골렘의 상태를 GolemState.Attack로 변경하여주고

     

    공격 딜레이를 담당할 float currentTime의 값을 공격딜레의 최대 값으로 정해 주어

    즉시 공격을 미리 준비하여줍니다.

     

    이후 SetTrigger를 이용해 ("MoveToAttackDelay")의 트리거를 체크해 주어서

    트랜지션 중 이 조건을 선택한 트랜지션은 방향 전환을 하게 됩니다.

     

    여기서! 또 자주 까먹는 게 하나가 나왔는데

    바로 Has Exit Time입니다.

    이 Has Exit Time을 체크해 두면, 트리거로 체크가 되어 있다 하더라도

    전 애니메이션의 동작을 모두 완료하고 나서 전환이 됩니다.

    반대로 Has Exit Time를 언체크 해 두면 즉시 방향 전환이 됩니다.


    그다음 살펴볼 부분은 Return함수입니다.

     

    여기서도 거리를 체크하는 데 있어서 Vector3.Distance 함수를 사용하였습니다.

     

    Distance 함수를 사용하여 골렘의 위치가 초기 위치로부터 얼마나 떨어져 있는가를 계산하여

    0.1f보다 크다면, destination = orizinPos를 이용해 목적지를 초기 위치로 정해주었으며

     

    멈출 수 있는 거리인 stoppingDistance는 0으로 지정해 주어

    멈추는 거리 값인 stoppingDistance  큰 오차가 발생하지 않도록 지정해 주었습니다.

     

    Idle / Move / Return 함수 구현

     


    바로 이어서 Attack 함수를 보겠습니다.

    골렘의 상태가 Attack일시 Update문에서 실행되는 Attack 함수입니다.

     

    if(Vector3.Distance(transform.position, player.position) < atkDistance)로 

    골렘의 위치가 플레이어의 위치로부터 얼마나 떨어져 있는지를 검색하여

    공격 범위의 안에 있다면(공격 범위보다 작다면)

     

    currentTime += Time.deltaTime를 이용해 1초씩 더해 줍니다.

    이후 currentTime이 지정해둔 float값 atkDelay보다 크다면

    공격을 실행합니다.

     

    공격을 실행 후 currentTime의 값은 0으로 초기화해주어서

    항시 조건문 실행을 방지하였습니다.

     

    이후 anim.SetTrigger("StartAttack")를 이용해 Attack 애니메이션 모션을 취합니다.

     

    반대로 공격 범위의 안에 있지 않다면, 다시 플레이어가 벗어난 것으로 간주하여

    골렘의 상태를 Move로 바꿔주고, 현재 진행되고 있던 currentTime을 0으로 초기화해 줍니다.

    이후 anim.SetTrigger("AttackToMove")로 Move 애니메이션을 실행하게 합니다.

    테스트는 OnDrawGizmos를 활용해 시각적인 부분을 활용하여 보다 쉽게 할 수 있었습니다.

    그림과 같이 외곽선을 이용한 WireCube, WireSphere 의 함수도 있고

    외각선 없이 전체 색이 구현되는 도형을 나타내 주는 Gizmos.DrawCube나 Gizmos.DrawSphere도 존재하며

    컬러 지정도 선택이 가능하기에, 시각적으로 테스트 등 씬 뷰에서 시각적으로 보는데 좋은 함수인 것 같습니다.

     

    참고로 씬 뷰에서만 시각적이게 보이며 게임 뷰의 영향과는 아무런 관계가 없습니다.

     

     이제 Damage를 입는 부분과 Die인 죽음 부분만 완성하면 될 것 같습니다.

     

    우선 골렘이 대미지를 입었을 때의 함수 구현 부분부터 살펴보겠습니다.

    플레이어가 공격하면 실행시킬 함수입니다.

    이 함수의 실행은 플레이어에서 실행시키기 위해 Public으로 선언하였습니다.

    int hitPower로 플레이어의 공격 대미지 값을 받아왔습니다.

     

    우선 첫 번째 조건문으로 골렘의 상태가 Damaged 이거나 Die상태 이거나 Return 상태이라면

    return을 이용해 함수 구문을 빠져나가게 했습니다.

     

    이후 hp -= hitPower로 플레이어의 공격력만큼 hp를 빼 주고

    공격을 맞으면서 움직이는 것을 방지하기 위해 (실로 Hit 모션을 취하면서 미끄러집니다)

    nav.isStopped = true;
    nav.ResetPath();

    네비를 멈춰 주고 목적지를 초기화해 주며

    hp가 0보다 크다면 Damaged() 함수를 실행합니다.

     

     

    Damged 함수 구현 부분입니다.

    DamageProcess 코 루틴 함수부터 보겠습니다.

    골렘의 hit 모션을 기다리기 위해 yield return new WaitForSeconds(0.2f)를 통해 0.2f초의 대기시간을 걸어 주고 

    골렘 상태를 Move 상태로 바꿔주는 기능입니다.

     

    이 코 루틴 함수는 Damaged 함수에서 StartCorutine으로 실행합니다.

     

    반대로 HitEnemy의 함수에서 hp가 0보다 작을 시 실행되는 else문에서는

    골렘의 상태를 Die로 바꿔주고 SetTrigger를 사용해 "Die" 모션을 취해주며

    Die() 함수를 실행해 줍니다.

     

    Die() 함수의 구분은.

    StopCorutine 이 아닌 StopAllCorutiens()을 사용하여 모든 코 루틴 함수를 중지시킵니다.

    이후 죽었을 때 이동을 하는 것을 방지하기 위해서 nav의 enabled = false로 컴포넌트를 언체크 해 비활성화해 주고

    yield return new WaitForSeconds(2f) 대기시간 (Die모션 시간)을 기다린 뒤

    Destiry로 오브젝트를 파괴시켜 줍니다.


    한 가지 빼먹은 것이 있는데 테스트를 해보니 골렘이 공격을 했을 시 player가 받는 hit 모션을 빼먹었습니다.

     

    우선 플레이어에게 Hit 받을 시 함수를 구현하겠습니다.

    플레이어의 대미지 받는 함수입니다.

    int값으로 damage (골렘의 공격력을 넣을 부분)을 받아와 주었고

    hp -= damage(골렘의 공격력)만큼 빼줍니다

     

    아직 플레이어가 죽는 것은 설정하지 않아서

    단순히 hp가 0보다 크다는 조건의 구문만 작성하였습니다.

     

    Manager.instance.manager_SE.audioSource.PlayOneShot(Manager.instance.manager_SE.Hit_Clip)

    사운드를 PlayOneShot으로 재생하는 소스입니다.

    반대로 Play로 사운드 재생을 하면 소리가 중첩되는 현상이 발생합니다.

     

    StartCoroutine(PlayerHitEffect()) : Hit를 당했을 시 시각적인 효과를 주기 위한 코 루틴 함수 구현입니다.

    바로 아래에 설명하겠습니다.

     

    sturn_Fx.SetActive(true) : Hit를 당했을 시 머리 위에 떠 있는 이펙트입니다.

     

    Combo.instance.playerAnim.Play("stun") : player의 Animator로 접근하여

    "Sturn" 애니메이션을 재생시켜 주는 소스 코드입니다.

     

    HitBox_Player.instance.message.gameObject.SetActive(true)

    시각적인 효과를 나타내 주기 위하여 STURN이라는 텍스트를 

    SetActive로 활용해 활성화해주는 코드입니다.


    우선 StartCoroutine(PlayerHitEffect()) 부분부터 보겠습니다.   

    PlayerHitEffect() 코 루틴 함수에서 SetActive로 활성화시켰을 시 모습입니다.

    이후 yield return new WaitForSeconds(0.3f) 0.3초 후 

    hitEffect.SetActive(false) 다시 비활성화를 해 주었고

     

    공격을 받을 시 시각적인 효과로 0.3초 동안 붉은 화면이 켜졌다가 꺼지는 것을 구현했습니다.

    STURN의 텍스트와 Sturn 효과 이펙트입니다.


    이후 Enemy의 스크립트로 다시 돌아와서

    public으로 선언한 AttackAction 함수에 

    player에 붙은 DamageAction(골렘 공격력)을 실행시켜 주는 함수입니다.

     

    이 함수를, 공격 모션에 맞게 골렘이 공격하는 지점에서 실행시켜 주겠습니다.

    골렘의 공격 함수인 AttackAction을 새로운 스크립트에서 public으로 선언한 모습입니다. 

    마지막으로 이 함수를 

    애니메이션 Events항목을 통하여 골렘이 뿔을 들이받는 동작에 Function에 PlayerHit 함수를 적용해 주는 모습입니다.

     

    댓글

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