본문 바로가기

수업 복습

Zombie - 좀비(Enemy) 스크립트, 게임오버 UI -

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<NavMeshAgent>();
        enemyAnimator = GetComponent<Animator>();
        enemyAudioPlayer = GetComponent<AudioSource>();
        enemyRenderer = GetComponent<Renderer>();
    }

    // 적 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)
            {
                pathFinder.isStopped = false;
                pathFinder.SetDestination(targetEntity.transform.position);
            }
            else
            {
                pathFinder.isStopped = true;
                Collider[] colliders =
                    Physics.OverlapSphere(transform.position, 20f, whatIsTarget);

                for(int i = 0; i < colliders.Length; i++)
                {
                    LivingEntity livingEntity =
                        colliders[i].GetComponent<LivingEntity>();
                    if(livingEntity != null && !livingEntity.dead)
                    {
                    	targetEntity = livingEntity;
                    	break;
                    }

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

    // 데미지를 입었을때 실행할 처리
    public override void OnDamage(float damage, Vector3 hitPoint, Vector3 hitNormal)
    {
        hitEffect.transform.position = hitPoint;
        hitEffect.transform.rotation = Quaternion.LookRotation(hitNormal);
        hitEffect.Play();

        enemyAudioPlayer.Play();

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

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

        Collider[] enemyColliders = GetComponents<Collider>();
        foreach(Collider collider in enemyColliders)
        {
            collider.enabled = false;
        }

        pathFinder.isStopped = true;
        pathFinder.enabled = false;
    }

    private void OnTriggerStay(Collider other)
    {
        // 트리거 충돌한 상대방 게임 오브젝트가 추적 대상이라면 공격 실행
        if(!dead && Time.time >= lastAttackTime + timeBetAttack)
        {
            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);
            }
        }
    }
}

IEnumerator UpdatePath()

{

       //살아있는동안 반복      

       while(!dead)

       {

             //추적할 대상이 존재하고, 대상이 사망하지 않았다면 (hasTarget : targetEntity != null && targetEntity.dead)

             if(hasTarget)

             {

                         //네비메쉬 사용

                         pathFinder.isStopped = false;

                         //네비메쉬의 도착지를 targetEntity의 포지션으로 정해줘

                         pathFinder.SetDestination(targetEntity.transfrom.position);

             }

             //추적할 대상이 없거나 사망했다면

             else

             {

                         //네비매쉬를 멈춰줘

                         pathFinder.isStopped = true;

                         //반지름 20의 구형 콜라이더를 내 위치에서 whatIsTarget쪽으로 발사해서

                         //닿은 콜라이더의 정보들을 Collider[]에 담아죠

                         Collider[] colliders =

                         Physics.OverlapSphere(transform.position, 20f, whatIsTarget)

 

                         for(int i = 0; i < colliders.Length; i++)

                         {

                                     //colliders[i]의 LivingEntity스크립트를 가지고 있는

                                     //colliders[i]의 컴포넌트를 참조해와서 livingEntity에  할당해

                                     LivingEntity livingEntity = 

                                     colliders[i].GetComponent<LivingEntity>();

                                     //구형콜라이더와 충돌한 livingEntity를 가지고 있는 녀석이 있고, 사망하지 않았다면

                                     if(livingEntity != null && !livingEntity.dead)

                                     {

                                                 //타겟을 구형콜라이더와 충돌한 녀석으로 지정

                                                 targetEntity = livingEntity;

                                                 break;

                                     }

                                     //0.25초 주기로 반복해서 실행

                                     yield return new WaitForSeconds(0.25f);

                         }

             }

       }

}

 

public override void OnDamage(float damage, Vector3 hitPoint, Vector3 hitNormal)

{

             hitEffect.transform.position = hitPoint;

             hitEffect.transform.rotation = Quaternion.LookRotation(hitNormal);

             hitEffect.Play();

 

             enemyAudioPlayer.Play();

             

             //LivingEntity의 Ondamage()를 실행하여 데미지 적용

             base.Ondamage(damage, hitPoint, hitNormal);

}

 

//상속받은 메소드

public override void Die()

{

             //LivingEntity의 Die()를 실행하여 기본 사망 처리 실행

             base.Die()

             

             //사망했다면, enemyColliders에 사망한 녀석의 Collider의 컴포넌트를 가져와

             Collider[] enemyColliders = GetComponents<Collider>();

             foreach(Collider collider in enemyColliders)

             {

                         //사망한 오브젝트는 사용하지 않겠음

                         collider.enabled = false;

             }

             pathFinder.isStopped = true;

             pathFinder.enabled = false;

}

 

//트리거 콜라이더에 닿아있는 동안에

private void OnTriggerStay(Collider other)

{

             //트리거 충돌한 상대방 게임 오브젝트가 추적 대상이라면 공격 실행

             if(!dead && Time.time >= lastAttackTime + timeBetAttack)

             {

                         //트리거 충돌했을 때 LivingEntity를 가지고 있는 other의

                         //LivingEntity스크립트의 컴포넌트를 참조해와 

                         LivingEntity attackTarget = other.GetComponent<LivingEntity>();

                         //LivingEntity를 가진 녀석과 충돌했고, 그게 타겟이라면(플레이어라면)

                         if(attackTarget != null && attackTarget == targetEntity)

                         {

                                     //쿨타임 초기화

                                     lastAttackTime = Time.time;

                                     

                                     //맞은곳은 좀비 포지션과 맞은녀석의 가까운 곳

                                     Vector3 hitPoint = 

                                     other.ClosestPoint(transform.position);

                                     //맞은 방향은 좀비의 포지션에서 맞은녀석쪽으로

                                     Vector hitNormal =

                                     transform.position - other.transform.position;

 

                                     attackTarget.OnDamage(damage, hitPoint, hitNormal);

                         }

             }

}

 

#EnemySpawner

    // 적을 생성하고 생성한 적에게 추적할 대상을 할당
    private void CreateEnemy(float intensity)
    {
        //★Lerp : 최소값과 최대값을 정해놓고 중간값이 무엇인지 구하는것
        float health = Mathf.Lerp(healthMin, healthMax, intensity);
        //
        float damage = Mathf.Lerp(damageMin, damageMax, intensity);
        //
        float speed = Mathf.Lerp(speedMin, speedMax, intensity);
        //Color.Lerp : strongEnemyColor(Red)
        //흰색 : RGB(255, 255, 255), 빨강 : RGB(255, 0, 0) 이 사이의 중간점.
        //0에 가까울수록 흰색에 가깝고 1에 가까울수록 빨강에 가까워짐
        Color skinColor = Color.Lerp(
            Color.white, strongEnemyColor, intensity);
        Transform spawnPoint = spawnPoints[Random.Range(0, spawnPoints.Length)];

        //좀비를 생성해서 좀비를 참조함
        Enemy enemy = Instantiate(enemyPrefab, spawnPoint.position, spawnPoint.rotation);

        enemy.Setup(health, damage, speed, skinColor);
        enemies.Add(enemy);

        //익명 메소드(람다식) : void 형태로 메소드로 추가해준 것
        //enemy.onDeath += () => enemies.Remove(enemy);
        //enemy.onDeath += () => Destroy(enemy.gameObject,10f);
        //enemy.onDeath += () => GameManager.instance.AddScore(100);
        enemy.onDeath += () =>
        {
            enemies.Remove(enemy);
            Destroy(enemy.gameObject, 10f);
            GameManager.instance.AddScore(100);
        };
    }
}

RGB같은 경우 흰색은 (255, 255, 255), 빨간색은 (255, 0, 0)이기 때문에 G,B값에서 0~255사이의 값으로 조율이 될것이다.

 

#UI Manager

using UnityEngine;
using UnityEngine.SceneManagement; // 씬 관리자 관련 코드
using UnityEngine.UI; // UI 관련 코드, Text를 쓸 때 사용

// 필요한 UI에 즉시 접근하고 변경할 수 있도록 허용하는 UI 매니저
public class UIManager : MonoBehaviour {
    // 싱글톤 접근용 프로퍼티
    public static UIManager instance
    {
        get
        {
            if (m_instance == null)
            {
                m_instance = FindObjectOfType<UIManager>();
            }

            return m_instance;
        }
    }

    private static UIManager m_instance; // 싱글톤이 할당될 변수

    public Text ammoText; // 탄약 표시용 텍스트
    public Text scoreText; // 점수 표시용 텍스트
    public Text waveText; // 적 웨이브 표시용 텍스트
    public GameObject gameoverUI; // 게임 오버시 활성화할 UI 

    // 탄약 텍스트 갱신
    public void UpdateAmmoText(int magAmmo, int remainAmmo) {
        ammoText.text = magAmmo + "/" + remainAmmo;
    }

    // 점수 텍스트 갱신
    public void UpdateScoreText(int newScore) {
        scoreText.text = "Score : " + newScore;
    }

    // 적 웨이브 텍스트 갱신
    public void UpdateWaveText(int waves, int count) {
        waveText.text = "Wave : " + waves + "\nEnemy Left : " + count;
    }

    // 게임 오버 UI 활성화
    public void SetActiveGameoverUI(bool active) {
        gameoverUI.SetActive(active);
    }

    // 게임 재시작
    public void GameRestart() {
        SceneManager.LoadScene(SceneManager.GetActiveScene().name);
    }
}

Scale With Screen Size : UI의 크기를 스크린 사이즈로 설정

Interactable : 버튼과 상호작용 하겠음 (★버튼 사용시 체크)

On Click() : 버튼을 클릭했을 때 사용할 기능

 > +로 추가해 준 후 어느 상황에서 실행할건지 설정, 오브젝트를 넣으면 그 안에 포함된 스크립트의 메소드들을 사용할 수 있음.

 

다음으로는 화면에 효과를 적용할건데,

우선 Window > PackageManager > Packages : In Project > Post Precessing이 있는지 확인. 없다면 In Project를 Unity Registry로 변경하여 검색 후 다운.

Post Processing
Main Cam > Rendering Path > Use Graphics Settings 를 Deferred로 변경
forward : 가볍게 표현하다보니 생략되거나 대충 표현되는 경우가 많음
Deferred : 빛에의한 연산을 나중으로 미룸. 세팅한것들을 거의 온전하게 표현
MSAA(Anti 얼라이어싱) : 계단현상. 그래픽 성능이 떨어져서 그림자가 각져서
나오는 그런 현상들을 완화시켜주는 역할.
Deferred를 키면 정상작동을 안할 뿐더러 Deferred를 사용하면 굳이 안써도 됨.
Add Component > Rendering > Post-process Layer
Layer는 인스펙터에 있는 Layer. > PostProcessing으로 체크.
AA(Anti-aliasing) : Fast Approximate(FXAA)>기능은 높지 않지만
빨리빨리 처리해줌

오브젝트 생성(하이어아키 3D Object > Post-process Volume)

Layer : PostProcessing으로 변경
Is Global : 게임 안의 전체에 적용시키겠다.
체크 안하면 오브젝트에만 적용
Profile에 
Motion Blur : 모션 잔상
Bloom : 뽀샤시 효과
Color Grading : 인스타그램 사진필터 ㅋㅋ 색상 대비, 감마값 변경
Chromatic Aberration : 색수차? 방사능, 중독효과사용할때 보통 사용
 이미지의 경계가 번지고 3원색이 분리가 됨
Vignette : 화면 모서리의 채도랑 명도를 조절
Grain : 화면에 입자효과

효과를 적용시켜주면 화면이 이렇게 변한다.

<적용 후>
<적용 전>

 

'수업 복습' 카테고리의 다른 글

2D RogueLike  (1) 2023.09.06
Zombie - MultiPlay -  (0) 2023.08.30
Zombie 복습2 - 플레이어 스크립트 -  (2) 2023.08.27
Zombie - 좀비, 플레이어 UI -  (0) 2023.08.22
Zombie 복습 - 총 세팅, 플레이어 애니메이션 세팅 -  (0) 2023.08.22