본문 바로가기

게임프로그래밍 개발 수업/유니티 수업

유니티 06 - 탄피 배출 파티클 이펙트 적용 하기, 스크립트로 애니메이션출력 해보기, Navigation 을 통해 NPC를 이동시켜 보기

03-22 수요일의 수업 내용입니다. 

 

어제 진행했었던 내용이 애니메이터의 플로우를 만든 거였죠?

Parameter 값으로 IsTrace , IsAttack, IsDie, IsHit 도 만들었습니다.

각각 Bool 자료형과, Trigger로 변수를 설정했었는데요.

 

자료형에는 어떤 차이가 있을까요?

Bool 자료형은 true값과 false값이 존재 하죠, On/Off 스위치 입니다. 한번 ture 로 설정 되면, false로 값을 바꿀 때 가지는 계속 true 예요, 반복되는 행위에 적합합니다. 제가 배우고 있는 프로젝트 내에서는 

'IsTrace'  이건 쫒아간다는거죠? 쫒아갈때 애니메이션이 한번만 나오지는 않죠? 팔다리를 계속 움직여 줘야 합니다.

'Is Attack'은 게임마다 다를 수 있겠으나, 연속공격이 되는 경우라면 Bool 자료형을 쓰는게 적합할것 같습니다.

 

반면 Trigger는 방아쇠입니다. 조정간은 단발이라고 치자구요, 한번 방아쇠를 당기면, 한번 실행됩니다.

'IsDie' 는 캐릭터가 죽는 애니메이션의 트리거인데, 죽는 애니메이션을 계속 재생한다면 뭔가 말이 안되죠?

'IsHit' 도 마찬가지, 거의 모든 상황에서 한번 맞았을 때 한번만 재생하면 됩니다.

애니메이터에서 Parameter의 자료형에 대해 잠깐 알아봤습니다.

 

 

 

오늘 수업의 시작은 탄피 배출이벤트 만들기 였습니다.

 

강사님이 준비해주신 탄피 배출 이벤트 프리팹을 설치 했습니다.

CartridgeEjectEffect 라는 프리팹입니다.

이 프리팹을 어제 총알이 발사되는것 처럼 동적 할당 시켜서 총을 쏠 때마다 나오게끔 만들어 줄거예요.

 

먼저 Hierachy상으로 옮깁니다.

FPSController의 하위항목으로 옮긴 후 좌표를 지정해 주었습니다.

 

Play를 눌러 재생해보면 탄피가 나오긴 나오는데 너무 작고, 방향도 반대로 나오네요.

 

Y Rotation 90을 주고

Inspector 항목에 StartSize를 3으로 해주었더니 볼만해졌습니다.

이제 스크립트로 탄피배출까지 만들어 줍니다.

 

FireCtrl 스크립트를 수정합니다.

 

public ParticleSystem cartridge;

전역변수 선언부에 해당 문장 추가

탄피배출 효과는 파티클 시스템이죠 자료형을 선언 해주고

컴포넌트가 생겼다는걸 확인 했습니다.

집어 넣었습니다.

위와 같이Fire 메소드에 재생 효과 추가

발사 중단시 이펙트에도 추가

 

나오긴 나오는데 탄피가 2개씩 나와요..

이건 한번 클릭 할 때, 단발 조건인 GetMouseButtonDown과 GetMouseBotton 2개가 짧은 간격으로

Update 함수가 2번 호출 되는 동안 각각 1번씩 나와서 그렇습니다.

한번에 두개가 아니라 엄청 빠른속도로 '따당' 하는거죠(프레임당 1번씩 총 2번 나온것임)

 

수정한 FireCtrl ( 탄피 효과를 추가 했다. )

using UnityEngine;

public class FireCtrl : MonoBehaviour
{
    public Animation animation;
    public ParticleSystem muzzleFlash;
    public AudioSource source;
    public AudioClip fireSound;
    public GameObject bulletPrefab; //총알 프리팹
    public Transform firePos;//총알 발사 위치
    public ParticleSystem cartridge;


    private float TimePrev;
    private float fireRate = 0.15f;
    private HandAni handAni;


    private void Start()
    {
        //본인 게임오브젝트 컴포넌트의 HandAni에 접근
        handAni = this.gameObject.GetComponent<HandAni>();
        TimePrev = Time.time; // 현재 시를 대입한다.
        muzzleFlash.Stop(); 
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            if(handAni.isRun == false)
                Fire();
        }
        else if (Input.GetMouseButton(0)) // 연사
        {
            if(Time.time - TimePrev > fireRate) 
                //현재시간 - 과거시간 = 흘러간 시간이 0.15초를 초과 할 경우
            {
                if (handAni.isRun == false)
                    Fire();
                TimePrev = Time.time;
                Debug.Log("TimePrev 초기화");
            }
            
        }
        else if (Input.GetMouseButtonUp(0))
        {
            muzzleFlash.Stop();
            cartridge.Stop();
        }
        
        
    }

    private void Fire()
    {
        animation.Play("fire");
        cartridge.Play();
        muzzleFlash.Play();
        Instantiate(bulletPrefab, firePos.position, firePos.rotation);
        //프리팹 생성 함수 Instantiate(무엇What을? 어디Where에? 어떻How rotate게?)
        source.PlayOneShot(fireSound, 0.5f);
    }
}

 

 

 


 

다음으로는 적을 담당하고 있는 좀비의 움직임을 담당하는 스크립트를 만들어 보았습니다.

 

Q: 이 스크립트는 무엇을 담당할까요?

A: 좀비의 이동, 공격, 아이들모션, 죽음까지 움직임에 대한 전반적인 것을 담당할 예정입니다.

 

Q: 그렇다면 필요한 요소들이 무엇이 있을 까요?

A: 이동을 시키려면 좀비 자기자신의 위치와 플레이어의 위치가 필요하겠습니다.

공격도 마찬가지 이겠고요, 죽음을 담당하도록 만들려면 HP값도 필요 하겠으나 HP는 따로 클래스를 만들어서 관리하도록 할 예정입니다. 

아무튼, 위에서 언급한 움직임들이 이루어지기 위한 조건들로 필요한 요소들을 선언 하는것 부터 시작하겠습니다.

 

먼저 플레이어와 좀비간의 위치 정보값을 실시간으로 업데이트받는 동작을 Update 함수에 구현 했습니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ZombieCtrl : MonoBehaviour
{
    public Transform zombieTr;  //좀비 트랜스폼 정보
    public Transform playerTr;  //플레이어 트랜스폼 정보
    
    //플레이어를 추적하며 사거리내에 들어오면 공격을 한다.
    void Start()
    {
        zombieTr = this.transform;  //자기자신의 위치정보로 초기화
        playerTr = GameObject.FindWithTag("Player").GetComponent<Transform>(); //Player 라는 태그를 찾아 그 위치정보값으로 초기화
        
    }

    // Update is called once per frame
    void Update()
    {
        float dist = Vector3.Distance(playerTr.position, zombieTr.position);    //Vector3.Distance는 거리를 구하는 함수로, 아주 중요하니 외워 두자
        Debug.Log("거리: " + dist);
       
    }

}

이렇게 설정하면 좀비와 플레이어간의 거리가 실시간으로 콘솔창에 표시되게 됩니다.

 

>>>>  Vector3.Distance 함수  <<<< 
Vector3.Distance(A좌표, B좌표); 이런식으로 Transform 자료형을 가진 2개의 매개변수를 필요로 하는데
자동으로 2개의 점 사이의 거리를 구해 줍니다.
중요하니까 꼭 외우도록 하겠습니다.

콘솔창에 거리가 실시간으로 달라집니다.


 

이제 NPC의 움직임을 구현할 때 필요한 Nav Mesh Agent라는 컴포넌트를 Zombie 오브젝트에 추가 하겠습니다.

플레이어의 위치를 추척하는 내비게이션 역할을 하는 컴포넌트라고 합니다.

 

추가하고, 스크립트 상에서 이용하기 위해서 맨위에 지시문에 아래와 같은 문장을 추가해줍니다.

using UnityEngine.AI;

스크립트가 위와 같은 모양이 됩니다.

추가 했다면 사용할 수 있게 스크립트 상에서도 추가 해줍니다.

 

Start와 동시에 자기자신의 컴포넌트로 초기화 되게 됩니다.

 

추가로 공격에 필요한 공격 사거리, 추적에 필요한 추적 거리도 변수로 선언 했습니다.

애니메이션 호출에 필요한 애니메이터 자료형의 변수도 선언 했습니다.

 

선언부만 엄청 길어졌는데 아래와 같습니다.

    public Transform zombieTr;	//좀비 위치
    public Transform playerTr;	//플레이어 위치
    public NavMeshAgent agent;	//NPC가 플레이어를 추적하기 위한 내비게이션
    public Animator animator;	//애니메이션 사용을 위해 선언 
    public float attackDist = 2.5f;	//공격 사거리
    public float traceDist = 20.0f;	//추적 사거리

 

 

전체적인 코드는 아래와 같습니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;



public class ZombieCtrl : MonoBehaviour
{
    public Transform zombieTr;  //좀비 트랜스폼 정보
    public Transform playerTr;  //플레이어 트랜스폼 정보
    public NavMeshAgent agent;  //플레이어 추적을 위한 에이전트 정보
    public Animator animator;   //애니메이션 사용을 위해 선언
    public float attackDist = 2.5f; //공격 사거리
    public float traceDist = 20.0f; //추적거리
    //플레이어를 추적하며 사거리내에 들어오면 공격을 한다.
    void Start()
    {
        agent = gameObject.GetComponent<NavMeshAgent>();
        zombieTr = this.transform;  //자기자신의 위치정보로 초기화
        playerTr = GameObject.FindWithTag("Player").GetComponent<Transform>();
        //Player 라는 태그를 찾아 그 위치정보값으로 초기화
        
    }

    // Update is called once per frame
    void Update()
    {
        float dist = Vector3.Distance(playerTr.position, zombieTr.position);
        Debug.Log("거리: " + dist);
        if (dist <= attackDist)
        {
            agent.isStopped = true; //네비메시 에이전트 추적 중지
            animator.SetBool("IsAttack", true);
        }
        else if (dist <= traceDist)
        {
            agent.isStopped = false; //네비메시 에이전트 추적 실행
            agent.destination = playerTr.position;
            animator.SetBool("IsAttack", false);    //공격을 중지하라
            animator.SetBool("IsTrace", true);      //추적을 시작하라
        }
        else
        {
            agent.isStopped = true;
            animator.SetBool("IsTrace", false); //idle 애니메이션 실행
        }



    }

}

처음 보는 컴포넌트들이 등장했습니다.

agent.isStopped

추적을 멈출지 여부를 말합니다. 이 값이 true 면 더이상 추적하지 않습니다.

flase값이 주어지면 추적을 다시 시작합니다.

agent.destination

NPC의 목적지를 말합니다.

좌표값을 이곳에 대입하면 그곳을 향해 자동으로 움직이게 됩니다.

 

 

아직은 좀비가 움직이지 않습니다.

NPC가 원할하게 움직일 수 있도록 땅을 구워줘야 합니다.

Navigation 창을 열면 inspector 창옆에 생성됩니다. 

 

지형오브젝트인 mainLevelMesh를 선택하고

우측 끝에있는 Static 박스를 체크 해줍니다.

Static 체크!

바로 우측에 Navigation 창으로 이동하면

bake 버튼이 있습니다.

땅을 구워주면

 

 

다좋은데 계단 부분이 끊겼습니다.

계단의 모델이 별로좋지 않아서 큐브로 계단을 보조했었죠,

다시 계단 오브젝트를 선택하고 똑같이 Static을 활성화 해줍니다.

Mesh Renderer를 체크해서 가시성을 확보하고, 길이 끊어지지 않게 맞춰줍니다.

 

이후 다시 Bake 해서 길이 이어질때까지 트라이 합니다.

어렵게 이었는데 이정도로 만족하도록 하죠

 

땅을 다 구웠다면 이제 실행시켜보겠습니다.

 

추적과 공격을 잘 하고 있습니다.

 

 

오늘 배운것들

탄피를 배출하는 파티클 이펙트를 적용해서 발사 메소드에 추가시켜 보았습니다.

Animation Tree에 각 Parameter값을 설정하여 스크립트로 불러오는 과정을 배웠습니다.

Vector3.Distance 함수의 역할과 기능을 익혔습니다.

using UnityEngine.AI; 

를 통해 사용 가능한

agent를 활용한 동작들을 실습해 보았습니다.

agent.isStopped

agent.destination

두가지 지능을 직접 써봤습니다.

 

Navigation창에서 지형을 굽고 AI가 이동할 수 있게 만들어 보았습니다.