ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • FPS 연습 03. Zombie / Idle
    연습 프로젝트/FPS 연습

    안녕하세요.

     

    오늘은 총을 쏴서 맞으면 죽을 zombie를 제작해 보겠습니다.

     

    지금 머릿속에 다양한 좀비들이 생각나는데

     

    빠른 좀비, 맷집이 강하지만 느린 좀비, 파워가 세지만 누워서 다가오는 좀비 등등

     

    다양하게 각기 다른 스텟을 정해줘서 구현을 해보면 재밌을 것 같다는 생각을 했습니다.

    (뭔가 플레이어를 제작 할때보다 왜 생각에 집중이 되는 느낌적인 느낌 아닌 느낌..?)

     

    이번에 만들 좀비는

    1. 추적 범위에 들어오지 않는 이상은 필드를 활보하며 다닐 것입니다.

    2. 추적 범위에 들어오거나 혹은 공격에 맞은다면 빠른 속도로 Player에게 다가올 것입니다.

    3. 좀비를 잡게 되면 점수를 획득하며, Stage가 올라갈수록 다양한 좀비들이 나오게 됩니다.

     

    우선 구성은 이러합니다. 프로젝트를 제작하면서 

    수정과 변경 사항을 바로 실행하며 과정을 업로드하겠습니다.

     

    우선 빠른 좀비부터 구현을 해 보겠습니다.

     

    좀비!!!

    유니티 에셋 스토어에서 무료로 받아온 에셋입니다!

     

    바로 좀비에 입혀줄 스크립트 코드부터 보겠습니다.

    zombieState의 상태 값입니다.

     

    열거 형으로 제작하여 코드의 가독성이 높으며

     

    별도로 값을 지정해 주지 않더라도 0,1,2... 식으로 값이 자동 부여됩니다.

     

    상태 값을 보는데 제가 좀 헤맬 때가 있어서 public화 시켜서 인스펙터 창에서 보며 테스트가 가능하게 했습니다.

     

    Idle, Move, Attack, Damaged, Die로 총 5가지의 상태 값이 있습니다.

     

    또한 이 ZombieState의 상태 값을 제어하기 위해서 변수를 선언해주었습니다. ZombieState = zombieState;

     

    이 상태 값들은 Update문에서 Switch문으로 조건식을 실행하였습니다.

     

    if 문으로 사용해도 됩니다.

     

    Switch문은 크기 비교를 할 수 없어서 (< > =< => 등..) if문보다는 제한적이지만

     

    현재 제가 할 단순 비교 조건식의 경우에는 if~else 문 보다 코드 가독성도 좋고 알아본 결과

     

    Switch문이 if문 보다 코드 속도의 효율도 좋다고 합니다.

     

    여담이지만 Switch문으로도 불리고 Switch ~ case문 이라고도 한다고 합니다!


    처음으로 Zombie가 필드를 자유롭게 돌아다니는 모습을 연출해 보겠습니다.

     

    매 활성화 시 실행되는 OnEnable 함수를 볼 것이며 

     

    이와 반대로 비활성화될 시 사용되는 OnDisable의 함수가 있습니다.

     

    또한 Awake 함수나 Start 함수는 딱 한 번만 초기에 실행되는 함수임을 기억합시다..!

    (다 아시겠지만.. 제가 기억하기 위해서입니다 ㅎㅎ..)

    OnEnable 함수입니다.

    각 각 GetComponent를 이용해 ani / nav의 변수에 Animator / NavMeshAgent를 가져왔습니다.

     

    Player를 가져오는 데는 Find 함수를 사용했으며,?.로 널 연산자로 조건을 주어 봤습니다.

    씬 상에 Player라는 객체가 Null이 아니라면 transform을 가져오라는 의미며

     

    null일 시 에는 null을 가져옵니다.

     

    단순히 GameObject.Find("Player"). transform 만 실행해도 되지만.

     

    배운 것을 한번 써서 습관을 들이기 위해 null연산자를 써봤습니다.

    Vector3로 선언한 orizinPos 초기위치입니다.

     

    orizinPos = transform.position;

     

    좀비의 상태 구현 시 초기 위치와의 거리를 계산하기 위해 작성했습니다.

     

     

    StartCoroutine(EnemyAI()) : StartCorutine 함수로 코 루틴 함수 EnemyAI를 실행해 줍니다.

     

    StartCorutine은 문자열 ("EnemyAI")으로도 받을 수 있고

     

    (EnemyAI()) 로도 받을 수 있습니다.

     

    전 아직 골라서 쓰진 않고 손이 가는 대로? 쓰고 있었지만.. 함수명으로 써보려고 합니다.

     

     

    이게 솔직히 말씀드리면 아직 두 차이를 모르겠습니다.

     

    EnemyAI()처럼 함수 명으로 작성하는 게 나중에 찾기 더 쉽다

     

    라는 글을 본 적이 있는데, 왜 그런지 이유를 알게 되면 꼭 추가로 올리겠습니다.


    코 루틴 함수 EnemyAI를 보겠습니다.

     

    if (gameObject.tag!= "Dead") : 게임 오브젝트의 태그 (좀비)가 "Dead"가 아닐 시의 조건문입니다.

     

    ani.Play("Move") : Play함수로 "Move" 애니메이션을 동작하게 만들었습니다.

     

    이후 while 문으로 orizinDis와 targetDis를 검색해 줍니다.

     

    orizinDis = (orizinPos - transform.position). magnitude로 

     

    orizinPos(초기위치)에서 transfrom(좀비)가 얼마나 떨어져 있는지 계산을 하여

     

    magnitude: 벡터의 길이로 반환하여 orizinDis에 대입해 줍니다.


    targetDis는 (target - transform.position). magnitude로 

     

    target(목적지)에서 transform(좀비)가 얼마나 떨어져 있는지 계산을 하여

     

    magnitude: 벡터의 길이로 반환하여 targetDis에 대입해 줍니다.

     

    inCombat : 전투 중인지 확인할 불 변수

     

    if (! inCombat) : 전투 중이 아니라면

     

    Move_NonCombat 함수를 실행시킵니다.

     

    이후 yield return null으로 1 프레임의 제어의 권한을 넘겨줍니다.


    Move_NonCombat 함수

    OnMove : 움직이고 있는 중인지 체크할 불 변수

     

    if (targetDis <= 3) : 목적시에서의 거리가 3 이하 라면

     

    중복 체크 방지를 위해 onMove를 false로 변환시키며 

     

    그 아래 if문의 조건이 성립하게 해 줍니다.

     

     

    중복 체크 방지를 위해 onMove = true로 바꿔 주었고

     

    nav의 스피드는 = 전투 중이 아닐 시 스피드입니다.

     

    목적지를 new Vector3로 받아왔습니다.

     

    transform.position.x + Random.Range(-1 * notMoveDis, notMoveDis)

     

    현재 좀비의 x 값 + Random.Range로 움직일 수 있는 최소 거리와 최대 거리 사이를

     

    랜덤 값으로 받아왔습니다.

     

    y축은 사용하지 않을 거니 0

     

    transform.position.z + Random.Range(-1 * notMoveDis, notMoveDis)) 

     

    z값도 x값과 마찬가지로 최소거리와 최대 거리 사이를 랜덤 값으로 받아왔습니다.

     

     

    이후 nav.SetDestination(target)를 사용해 목적지를 target으로 정해 주었습니다.

     

     

    이렇게 좀비가 움직이다가, 자신이 움직일 수 있는 거리 이상을 벗어났다면?

    if (orizinDis >= notMoveDis)
            
                onMove = true

    움직 이는지 확인하는 onMove변수는 true


                target = orizinPos

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


                nav.SetDestination(target);

    그리고 SetDestination 함수를 활용해 목적지(target)를 정해 줍니다.
            

    우선 여기 부분의 작업물을 영상으로 보겠습니다.

     

     

    00 : 10초

     

    zonbie가 Idle 상태에서 Move상태로 넘어가는 Idle 함수 구현입니다.

     

    시작 시 1회 호출되는 Start함수에서 zombie의 상태 값은 Idle로 시작되게 하였죠?

     

    Idle함수에서

    (Vector3.Distance(transform.position, player.position) < findDis)

    거리를 측정하는데 쓰인 함수는 Distance 함수를 사용했습니다.

     

    거리를 구하는데 현재 제가 아는 것은

     Distance / magnitude / sqrMagnitude 세 가지가 있습니다.

     

     Distance와 magnitude는

     

    sqrMagnitude는 작거나 큰지 비교만 하고 싶을 때 사용하며

    거리의 제곱의 값을 리턴하기에 루트 연산을 하지 않아 연산속도가 더 빠르다고 합니다.

    sqrMagnitude를

     

    다시 돌아와서 (Vector3.Distance(transform.position, player.position) < findDis)

    transform.position (zombie의 위치)에서 player.position(player의 위치)가 

    findDis(추적범위) 보다 작다면 = 값이 findDis(추적범위) 안에 포함되어 있다면

     

    nav.speed = movSpeed 네비의 스피드를 빠른 스피드로 바꿔주고
                zombieState = ZombieState.Move 상태 값을 Move로 전환시켜

    swicth문에서 상태에 따른 함수 실행을 시켜줍니다.


                ani.SetTrigger("IdleToMove") 현재는 Move 상태이지만, 보다 더 확실히 하기 위해

    SetTrigger("IdleToMove")로 Move 애니메이션이 실행되게

    트랜지션의 방향을 확실하게 정해주었습니다.

     

    00 : 16초

     

     

    댓글

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