본문 바로가기
Hello, World!/Unity

유니티 3D 좀비 서바이벌 게임 만들자 (16)

by 27일 낮 2022. 6. 1.
728x90

코드 정렬 꿀팁

ctrl + a 를 눌러서 전체 선택한 다음

ctrl + k + f 을 누르면 c#에 맞춰서 정렬이 된다!!!!!!!!

 


 

Zombie.cs 작성

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

// 좀비 AI 구현
public class Zombie : LivingEntity
{
    public LayerMask whatIsTarget; // 추적 대상 레이어

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

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

    private Animator zombieAnimator; // 애니메이터 컴포넌트
    private AudioSource zombieAudioPlayer; // 오디오 소스 컴포넌트
    private Renderer zombieRenderer; // 렌더러 컴포넌트

    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()
    {
        // 초기화
        // 게임 오브젝트로부터 사용할 컴포넌트 가져오기
        navMeshAgent = GetComponent<NavMeshAgent>();
        zombieAnimator = GetComponent<Animator>();
        zombieAudioPlayer = GetComponent<AudioSource>();

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

    // 좀비 AI의 초기 스펙을 결정하는 셋업 메서드
    public void Setup(ZombieData zombieData)
    {
        // 체력 설정
        startingHealth = zombieData.health;
        health = zombieData.health;
        // 공격력 설정
        damage = zombieData.damage;
        // 내비메시 에이전트의 이동 속도 설정
        navMeshAgent.speed = zombieData.speed;
        // 렌더러가 사용 중인 머티리얼의 컬러를 변경, 외형 색이 변함
        zombieRenderer.material.color = zombieData.skinColor;
    }

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

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

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

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

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

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

                        // for 문 루프 즉시 정지
                        // 나중에 멀티플레이어가 됐을 때 가까운 1명의 플레이어만 검출하기 위해서
                        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);
            // Quaternion.LookRotation() 방향벡터를 입력받아 해당 방향을 바라보는 쿼터니언 회전값을 반환

            hitEffect.Play(); // 파티클!

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

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

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

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

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

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

    private void OnTriggerStay(Collider other)
    {
        // 트리거 충돌한 상대방 게임 오브젝트가 추적 대상이라면 공격 실행

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

            // 상대방의 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);
        }
    }
}
  • public LayerMask whatIsTarget; 
    • 레이어 마스크 (LayerMask) : 특정 레이어를 가진 게임 오브젝트에 물리 또는 그래픽 처리 등을 적용시킬 때 사용. 레이캐스트에서 많이 쓰인다!
    • 아래의 저 Layer를 말한다 (그리고 레이어는 8번 이후부터 써줌)

  •     private bool hasTarget
        {
            get
            { }           }
    읽기 전용 프로퍼티!

 

  • 일반적으로 무한 루프는 컴퓨터 자원을 과도하게 사용하여 프로그램을 망가뜨림.
    하지만 코루틴을 사용하면 루프 회차 사이에 적절한 휴식 시간을 삽입하여 에러 없는 무한 루프를 구현할 수 있음

 

  • 내비메시 에이전트 컴포넌트
    • isStopped 필드 : 이동 중단 여부
    • SetDestination() 메서드 : 목표 위치를 입력받아 이동 경로 계산

 

  • Pysics.OverlapSphere() 메서드 : 중심 위치와 반지름을 입력받아 가상의 구를 그리고, 구에 겹치는 모든 콜라이더 반환.
    세 번째 값으로 레이어 마스크를 입력하여 특정 레이어만 감지 (whatIsTarget)

 

 

  • Quaternion.LookRotation() 메서드 : 방향벡터를 입력받아 해당 방향을 바라보는 쿼터니언 회전값을 반환

 

  • NavMeshAgent.enabled = false;
    • 내비메시 에이전트들은 서로를 방해하지 않도록 경로를 계산하기 때문에 완전히 비활성화해준다

 


 

Layer 생성

8번에 Player

 

 

 

Player Character 프리팹

 

 

 

와 피가 닳는다

 

 

 

 

다른 좀비도 불러와오자

캐릭터, 애니메이션이 있는 사이트!

 

 

https://www.mixamo.com/#/?page=1&type=Character 

 

Mixamo

 

www.mixamo.com

 

 

좀비를 검색하고 다운로드를 눌러 준다.

로그인 해야 다운 가능!

 

 

유니티 에디터에 불러와보자

Humanoid로 바꾸고 Apply

 

 

Configure 눌렀을 때 이렇게 되면 휴머노이드로 잘 적용된 것

 

 

 

하얀색이라서 Material을 적용한다

근데..... 유실돼서 분홍색이 된다..............는 잘못한 것! 아래 사진 처럼 바로 Materials 에서 적용하면 안 된다

분홍분홍....

 

 

 

Materals에서 Textures, Materials

표시한 부분으로 불러와야 모델이 적용됨

 

 

근데 뭔가 반투명해보임

저걸로 바꿈

 

댓글