[Unity]11.좀비 서바이버 따라하기 #5 - 내비게이션 시스템과 좀비 준비

안녕하세요 유랑입니다.



실력향상을 위해서 오늘도 책을 따라하면서 공부하겠습니다.

궁금하신점 있으시면 댓글로 남겨주세요^^




1. 좀비 서바이버



이번 강의는 레트로님께서 만든 예제이며,

교재를 구입하시면 자세한 내용을 배우실 수 있습니다.

저는 책을 구입하였고, 스킬업을 위해서 복습겸 글을 올리겠습니다.



깃허브 사이트 => 깃허브

유튜브 사이트 => 유튜브







1-1) 내비게이션 시스템 - ㉠내비게이션



좀비가 플레이어를 따라가는 인공지능을 만들어 주겠습니다.

그러기 위해선 유니티에서 제공하는 내비게이션 시스템을 사용해야 합니다.

A* 길찾기 알고리즘으로써 비전공자도 쉽게 사용할 수 있습니다.


* 내비메시(NavMesh) : 에이전트가 걸어 다닐 수 있는 표면

* 내비메시 에이전트(NavMesh Agent)  : 내비메시 위에서 경로를 계산하고 이동하는 캐릭터

* 내비메시 장애물(NavMesh Obstacle) : 에이전트의 경로를 막는 장애물

* 오프메시 링크(Off Mesh Linkg) : 끊어진 내비메시 영역 사이를 잇는 연결 지점

 






1-2) 내비게이션 시스템 - ㉡내비메시 굽기



내비메시는 내비메시 에이전트가 걸어 다닐 표면입니다.

내비메시는 정적 게임 오브젝트를 대상으로 미리 구워 생성할 수 있습니다.

Level Art는 이미 정적으로 체크되어 있기 때문에 따로 설정하지는 않겠습니다.






상단 메뉴바 Window => AI => Navigation을 실행시켜 주세요.





Bake를 선택 후 에이전트의 범위를 지정,

그리고 구워주시면 됩니다!!!





구워진 내비메시는 파란색으로 표시됩니다.

파란색으로 표시된 부분은 내비메시 에이전트가 이동 가능한 영역입니다.






1-3) 좀비 준비 - ㉠좀비 오브젝트



내비메시도 생성했으니 적 캐릭터 좀비를 추가하겠습니다.

위치와 애니메이터를 각각 설정해 주세요.






좀비 애니메이터의 구성은 다음과 같이 되어 있습니다.

따로 설정하지는 않겠습니다ㅎㅎ






1-4) 좀비 준비 - ㉡콜라이더



콜라이더는 두 개 준비해 줄텐데요.

Capsule은 장애물과 물리 충돌처리를 위해서,

Box는 캐릭터에게 데미지를 줄 때 사용합니다.

Box만 트리거를 체크해 주겠습니다.







1-5) 좀비 준비 - ㉢오디오 소스



좀비도 오디오 소스를 추가해 주겠습니다.







1-6) 좀비 준비 - ㉣AI 기능



좀비 인공지능 구성을 위해서 NavMeshAgent 컴포넌트와 Enemy 스크립트를 추가해 주세요.

NavMeshAgent는 내비메시를 바탕으로 움직이도록 도와줍니다.







1-7) 좀비 준비 - ㉤피탄 효과와 레이어 설정



좀비가 총알에 맞았을 때의 피탄 효과를 위해서 BloodSprayEffect를 추가해 주시고






좀비가 플레이어를 쫒아가도록 Player 레이어 설정을 해주겠습니다.

이 때 자식은 바꿔주지 마세요ㅎㅎ






1-8) 좀비 준비 - ㉥Enemy 스크립트



<Enemy>


Enemy 스크립트는 플레이어를 찾아 공격하고 사망 시 추적을 중단하는 내용을 담고 있습니다.

외부에서 Enemy의 초기 능력치 셋업도 가능하답니다^^




using System.Collections;
using UnityEngine;
using UnityEngine.AI; // AI, 내비게이션 시스템 관련 코드를 가져오기

// 적 AI를 구현한다
public class Enemy : LivingEntity
{
    public LayerMask whatIsTarget; // 추적 대상 레이어

    private LivingEntity targetEntity; // 추적할 대상
    private NavMeshAgent pathFinder; // 경로계산 AI 에이전트

    public ParticleSystem hitEffect; // 피격시 재생할 파티클 효과
    public AudioClip deathSound; // 사망시 재생할 소리
    public AudioClip hitSound; // 피격시 재생할 소리

    private Animator enemyAnimator; // 애니메이터 컴포넌트
    private AudioSource enemyAudioPlayer; // 오디오 소스 컴포넌트
    private Renderer enemyRenderer; // 렌더러 컴포넌트

    public float damage = 20f; // 공격력
    public float timeBetAttack = 0.5f; // 공격 간격
    private float lastAttackTime; // 마지막 공격 시점

    // 추적할 대상이 존재하는지 알려주는 프로퍼티
    private bool hasTarget
    {
        get
        {
            // 추적할 대상이 존재하고, 대상이 사망하지 않았다면 true
            if (targetEntity != null && !targetEntity.dead)
            {
                return true;
            }

            // 그렇지 않다면 false
            return false;
        }
    }

    private void Awake()
    {
        // 게임 오브젝트로부터 사용할 컴포넌트들을 가져오기
        pathFinder = GetComponent();
        enemyAnimator = GetComponent();
        enemyAudioPlayer = GetComponent();

        // 렌더러 컴포넌트는 자식 게임 오브젝트에게 있으므로
        // GetComponentInChildren() 메서드를 사용
        enemyRenderer = GetComponentInChildren();
    }

    // 적 AI의 초기 스펙을 결정하는 셋업 메서드
    public void Setup(float newHealth, float newDamage, float newSpeed, Color skinColor)
    {
        // 체력 설정
        startingHealth = newHealth;
        // 공격력 설정
        damage = newDamage;
        // 내비메쉬 에이전트의 이동 속도 설정
        pathFinder.speed = newSpeed;
        // 렌더러가 사용중인 머테리얼의 컬러를 변경, 외형 색이 변함
        enemyRenderer.material.color = skinColor;
    }

    private void Start()
    {
        // 게임 오브젝트 활성화와 동시에 AI의 추적 루틴 시작
        StartCoroutine(UpdatePath());
    }

    private void Update()
    {
        // 추적 대상의 존재 여부에 따라 다른 애니메이션을 재생
        enemyAnimator.SetBool("HasTarget", hasTarget);
    }

    // 주기적으로 추적할 대상의 위치를 찾아 경로를 갱신
    private IEnumerator UpdatePath()
    {
        // 살아있는 동안 무한 루프
        while (!dead)
        {
            if (hasTarget)
            {
                // 추적 대상 존재 : 경로를 갱신하고 AI 이동을 계속 진행
                pathFinder.isStopped = false;
                pathFinder.SetDestination(
                    targetEntity.transform.position);
            }
            else
            {
                // 추적 대상 없음 : AI 이동 중지
                pathFinder.isStopped = true;

                // 20 유닛의 반지름을 가진 가상의 구를 그렸을때, 구와 겹치는 모든 콜라이더를 가져옴
                // 단, targetLayers에 해당하는 레이어를 가진 콜라이더만 가져오도록 필터링
                Collider[] colliders =
                    Physics.OverlapSphere(transform.position, 20f, whatIsTarget);

                // 모든 콜라이더들을 순회하면서, 살아있는 플레이어를 찾기
                for (int i = 0; i < colliders.Length; i++)
                {
                    // 콜라이더로부터 LivingEntity 컴포넌트 가져오기
                    LivingEntity livingEntity = colliders[i].GetComponent();

                    // LivingEntity 컴포넌트가 존재하며, 해당 LivingEntity가 살아있다면,
                    if (livingEntity != null && !livingEntity.dead)
                    {
                        // 추적 대상을 해당 LivingEntity로 설정
                        targetEntity = livingEntity;

                        // for문 루프 즉시 정지
                        break;
                    }
                }
            }

            // 0.25초 주기로 처리 반복
            yield return new WaitForSeconds(0.25f);
        }
    }

    // 데미지를 입었을때 실행할 처리
    public override void OnDamage(float damage,
        Vector3 hitPoint, Vector3 hitNormal)
    {
        // 아직 사망하지 않은 경우에만 피격 효과 재생
        if (!dead)
        {
            // 공격 받은 지점과 방향으로 파티클 효과를 재생
            hitEffect.transform.position = hitPoint;
            hitEffect.transform.rotation
                = Quaternion.LookRotation(hitNormal);
            hitEffect.Play();

            // 피격 효과음 재생
            enemyAudioPlayer.PlayOneShot(hitSound);
        }

        // LivingEntity의 OnDamage()를 실행하여 데미지 적용
        base.OnDamage(damage, hitPoint, hitNormal);
    }

    // 사망 처리
    public override void Die()
    {
        // LivingEntity의 Die()를 실행하여 기본 사망 처리 실행
        base.Die();

        // 다른 AI들을 방해하지 않도록 자신의 모든 콜라이더들을 비활성화
        Collider[] enemyColliders = GetComponents();
        for (int i = 0; i < enemyColliders.Length; i++)
        {
            enemyColliders[i].enabled = false;
        }

        // AI 추적을 중지하고 내비메쉬 컴포넌트를 비활성화
        pathFinder.isStopped = true;
        pathFinder.enabled = false;

        // 사망 애니메이션 재생
        enemyAnimator.SetTrigger("Die");
        // 사망 효과음 재생
        enemyAudioPlayer.PlayOneShot(deathSound);
    }

    private void OnTriggerStay(Collider other)
    {
        // 자신이 사망하지 않았으며,
        // 최근 공격 시점에서 timeBetAttack 이상 시간이 지났다면 공격 가능
        if (!dead && Time.time >= lastAttackTime + timeBetAttack)
        {
            // 상대방으로부터 LivingEntity 타입을 가져오기 시도
            LivingEntity attackTarget
                = other.GetComponent();

            // 상대방의 LivingEntity가 자신의 추적 대상이라면 공격 실행
            if (attackTarget != null && attackTarget == targetEntity)
            {
                // 최근 공격 시간을 갱신
                lastAttackTime = Time.time;

                // 상대방의 피격 위치와 피격 방향을 근삿값으로 계산
                Vector3 hitPoint
                    = other.ClosestPoint(transform.position);
                Vector3 hitNormal
                    = transform.position - other.transform.position;

                // 공격 실행
                attackTarget.OnDamage(damage, hitPoint, hitNormal);
            }
        }
    }
} 



스크립트 적용이 끝났으면 타겟 설정과 이펙트 그리고 좀비 사운드를 넣어주겠습니다~~






1-9) 프리팹화



만들어진 좀비는 프리팹화를 통해서 나중에 또 쓸수 있도록 만들어 줄게요.






우와 좀비가 빠르게 달려오네요ㅎㄷㄷ






2. 마무리



오늘 강의는 여기까지입니다.

좀비 서바이버를 따라하면서 내비게이션 시스템과 좀비를 만들어 보았습니다.

책이랑 순서나 내용이 다를 수 있습니다.

감사합니다.




수업자료: 좀비 서바이버 따라하기 #5 - 내비게이션 시스템과 좀비 준비




댓글

Designed by JB FACTORY