[Unity]12.좀비 서바이버 따라하기 #6 - 게임 매니저와 포스트 프로세싱

안녕하세요 유랑입니다.



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

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




1. 좀비 서바이버



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

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

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



깃허브 사이트 => 깃허브

유튜브 사이트 => 유튜브







1-1) UI 다루기 - ㉠HUD Canvas



어느덧 좀비 서바이버 따라하기 마무리 단계입니다.

점수와 탄알, 게임에 관련된 UI를 추가하겠습니다.






HUD Canvas를 추가해 주세요.

HUD Canvas는 자식으로 다음과 같은 UI 오브젝트를 가집니다.


* Ammo Display : 남은 탄알을 표시하는 창

* Ammo Text : 탄알을 표시하는 텍스트

* Score Text : 점수를 표시하는 텍스트

* Enemy Wave Text : 현재 적 웨이브와 남은 적 수를 표시하는 텍스트

* Gameover UI : 게임오버 시 활성화되는 패널

* Gameover Text : 게임오버 안내 텍스트

* Restart Button : 게임 재시작 버튼

* Text : 버튼의 텍스트





해상도는 1280 x 720으로 설정합니다.

16:9 비율의 화면에 최적화 시켰습니다.






1-2) UI 다루기 - ㉡UIManager 스크립트



<UIManager>


UI 관련 구현을 모아두는 스크립트입니다.

탄알, 점수, 적 웨이브 등의 정보를 가지고 있어요ㅎㅎ




using UnityEngine;
using UnityEngine.SceneManagement; // 씬 관리자 관련 코드
using UnityEngine.UI; // UI 관련 코드

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

            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);
    }
}



스크립트 적용이 끝나셨으면 텍스트랑 UI가 잘 연결되어있는지 확인해 주세요^^

이미 적용되어 있을거에요.






1-3) UI 다루기 - ㉢재시작 버튼 설정



재시작 버튼을 클릭하였을 때 HUD Canvas스크립트의 재시작 기능을 사용하도록 

연결시켜 주겠습니다.

On Click () 리스트의 + 버튼을 클릭한 후 HUD Canvas를 드래그하고

UIManager.GameRestart 기능으로 설정해 주세요!!






Gameover UI는 캐릭터 사망시 활성화 되므로,

활성화 체크를 해제해 주겠습니다.






1-4) 게임 매니저



게임의 전반적인 규칙을 관리하는 게임 매니저를 만들어 보겠습니다.

빈 오브젝트 생성 후 이름을 Game Manger로 바꾸고 스크립트를 연결해 주세요.






<GameManger>


GameManger 스크립트는 점수 관리, 게임오버 상태 관리, 게임오버 UI 갱신 등을 관리합니다^^




using UnityEngine;

// 점수와 게임 오버 여부를 관리하는 게임 매니저
public class GameManager : MonoBehaviour {
    // 싱글톤 접근용 프로퍼티
    public static GameManager instance
    {
        get
        {
            // 만약 싱글톤 변수에 아직 오브젝트가 할당되지 않았다면
            if (m_instance == null)
            {
                // 씬에서 GameManager 오브젝트를 찾아 할당
                m_instance = FindObjectOfType();
            }

            // 싱글톤 오브젝트를 반환
            return m_instance;
        }
    }

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

    private int score = 0; // 현재 게임 점수
    public bool isGameover { get; private set; } // 게임 오버 상태

    private void Awake() {
        // 씬에 싱글톤 오브젝트가 된 다른 GameManager 오브젝트가 있다면
        if (instance != this)
        {
            // 자신을 파괴
            Destroy(gameObject);
        }
    }

    private void Start() {
        // 플레이어 캐릭터의 사망 이벤트 발생시 게임 오버
        FindObjectOfType().onDeath += EndGame;
    }

    // 점수를 추가하고 UI 갱신
    public void AddScore(int newScore) {
        // 게임 오버가 아닌 상태에서만 점수 증가 가능
        if (!isGameover)
        {
            // 점수 추가
            score += newScore;
            // 점수 UI 텍스트 갱신
            UIManager.instance.UpdateScoreText(score);
        }
    }

    // 게임 오버 처리
    public void EndGame() {
        // 게임 오버 상태를 참으로 변경
        isGameover = true;
        // 게임 오버 UI를 활성화
        UIManager.instance.SetActiveGameoverUI(true);
    }
}




1-5) 적 생성기 - ㉠생성 위치 추가



적을 실시간으로 생성해 줄 적 생성기를 만들어 보겠습니다.

우선 Spawn Points를 추가해 주세요.

적이 생성될 위치의 정보를 담고있습니다.







1-6) 적 생성기 - ㉡Enemy Spawner 스크립트



빈 오브젝트를 생성 후 Enemy Spawner라고 이름을 변경과 함께 스크립트를 추가해 주세요.






<EnemySpawner>


EnemySpawner 스크립트는 해당 웨이브에 적을 생성하도록 합니다.

적을 생성할 위치는 Spawn Points를 참고 합니다.



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

// 적 게임 오브젝트를 주기적으로 생성
public class EnemySpawner : MonoBehaviour
{
    public Enemy enemyPrefab; // 생성할 적 AI

    public Transform[] spawnPoints; // 적 AI를 소환할 위치들

    public float damageMax = 40f; // 최대 공격력
    public float damageMin = 20f; // 최소 공격력

    public float healthMax = 200f; // 최대 체력
    public float healthMin = 100f; // 최소 체력

    public float speedMax = 3f; // 최대 속도
    public float speedMin = 1f; // 최소 속도

    public Color strongEnemyColor = Color.red; // 강한 적 AI가 가지게 될 피부색

    private List enemies = new List(); // 생성된 적들을 담는 리스트
    private int wave; // 현재 웨이브

    private void Update()
    {
        // 게임 오버 상태일때는 생성하지 않음
        if (GameManager.instance != null && GameManager.instance.isGameover)
        {
            return;
        }

        // 적을 모두 물리친 경우 다음 스폰 실행
        if (enemies.Count <= 0)
        {
            SpawnWave();
        }

        // UI 갱신
        UpdateUI();
    }

    // 웨이브 정보를 UI로 표시
    private void UpdateUI()
    {
        // 현재 웨이브와 남은 적의 수 표시
        UIManager.instance.UpdateWaveText(wave, enemies.Count);
    }

    // 현재 웨이브에 맞춰 적을 생성
    private void SpawnWave()
    {
        // 웨이브 1 증가
        wave++;

        // 현재 웨이브 * 1.5에 반올림 한 개수 만큼 적을 생성
        int spawnCount = Mathf.RoundToInt(wave * 1.5f);

        // spawnCount 만큼 적을 생성
        for (int i = 0; i < spawnCount; i++)
        {
            // 적의 세기를 0%에서 100% 사이에서 랜덤 결정
            float enemyIntensity = Random.Range(0f, 1f);
            // 적 생성 처리 실행
            CreateEnemy(enemyIntensity);
        }
    }

    // 적을 생성하고 생성한 적에게 추적할 대상을 할당
    private void CreateEnemy(float intensity)
    {
        // intensity를 기반으로 적의 능력치 결정
        float health = Mathf.Lerp(healthMin, healthMax, intensity);
        float damage = Mathf.Lerp(damageMin, damageMax, intensity);
        float speed = Mathf.Lerp(speedMin, speedMax, intensity);

        // intensity를 기반으로 하얀색과 enemyStrength 사이에서 적의 피부색 결정
        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);

        // 적의 onDeath 이벤트에 익명 메서드 등록
        // 사망한 적을 리스트에서 제거
        enemy.onDeath += () => enemies.Remove(enemy);
        // 사망한 적을 10 초 뒤에 파괴
        enemy.onDeath += () => Destroy(enemy.gameObject, 10f);
        // 적 사망시 점수 상승
        enemy.onDeath += () => GameManager.instance.AddScore(100);
    }
} 


스크립트 적용이 끝났으면 좀비 프리팹과 적 생성 위치를 넣어주세요^^





이렇게 적이 몰려드니 무섭네요ㅎㄷㄷ






1-7) 아이템 생성기



난이도가 어렵다구요?

그러면 아이템을 이용해 쉽게 가보도록 하시죠.

빈 오브젝트를 생성 후 이름을 Item Spawner로 변경과 함께 스크립트를 적용해 주세요.






<ItemSpawner>


ItemSpawner 스크립트는 주기적으로 아이템 생성,

내비메시 위에서 랜덤한 한 점을 선택하여 아이템 생성 위치로 사용합니다.



using UnityEngine;
using UnityEngine.AI; // 내비메쉬 관련 코드

// 주기적으로 아이템을 플레이어 근처에 생성하는 스크립트
public class ItemSpawner : MonoBehaviour {
    public GameObject[] items; // 생성할 아이템들
    public Transform playerTransform; // 플레이어의 트랜스폼

    public float maxDistance = 5f; // 플레이어 위치로부터 아이템이 배치될 최대 반경

    public float timeBetSpawnMax = 7f; // 최대 시간 간격
    public float timeBetSpawnMin = 2f; // 최소 시간 간격
    private float timeBetSpawn; // 생성 간격

    private float lastSpawnTime; // 마지막 생성 시점

    private void Start() {
        // 생성 간격과 마지막 생성 시점 초기화
        timeBetSpawn = Random.Range(timeBetSpawnMin, timeBetSpawnMax);
        lastSpawnTime = 0;
    }

    // 주기적으로 아이템 생성 처리 실행
    private void Update() {
        // 현재 시점이 마지막 생성 시점에서 생성 주기 이상 지남
        // && 플레이어 캐릭터가 존재함
        if (Time.time >= lastSpawnTime + timeBetSpawn && playerTransform != null)
        {
            // 마지막 생성 시간 갱신
            lastSpawnTime = Time.time;
            // 생성 주기를 랜덤으로 변경
            timeBetSpawn = Random.Range(timeBetSpawnMin, timeBetSpawnMax);
            // 아이템 생성 실행
            Spawn();
        }
    }

    // 실제 아이템 생성 처리
    private void Spawn() {
        // 플레이어 근처에서 내비메시 위의 랜덤 위치 가져오기
        Vector3 spawnPosition =
            GetRandomPointOnNavMesh(playerTransform.position, maxDistance);
        // 바닥에서 0.5만큼 위로 올리기
        spawnPosition += Vector3.up * 0.5f;

        // 아이템 중 하나를 무작위로 골라 랜덤 위치에 생성
        GameObject selectedItem = items[Random.Range(0, items.Length)];
        GameObject item = Instantiate(selectedItem, spawnPosition, Quaternion.identity);

        // 생성된 아이템을 5초 뒤에 파괴
        Destroy(item, 5f);
    }

    // 내비메시 위의 랜덤한 위치를 반환하는 메서드
    // center를 중심으로 distance 반경 안에서 랜덤한 위치를 찾는다
    private Vector3 GetRandomPointOnNavMesh(Vector3 center, float distance) {
        // center를 중심으로 반지름이 maxDistance인 구 안에서의 랜덤한 위치 하나를 저장
        // Random.insideUnitSphere는 반지름이 1인 구 안에서의 랜덤한 한 점을 반환하는 프로퍼티
        Vector3 randomPos = Random.insideUnitSphere * distance + center;

        // 내비메시 샘플링의 결과 정보를 저장하는 변수
        NavMeshHit hit;

        // maxDistance 반경 안에서, randomPos에 가장 가까운 내비메시 위의 한 점을 찾음
        NavMesh.SamplePosition(randomPos, out hit, distance, NavMesh.AllAreas);

        // 찾은 점 반환
        return hit.position;
    }
}


스크립트 적용이 끝나셨으면 아이템들을 넣어주세요.

아이템 마다 콜라이더, 라이드, 해당 스크립트로 이루어져 있습니다.





아이템 먹고 점수를 높이자구요ㅎㅎ






1-8) 포스트 프로세싱 - ㉠포스트 프로세싱 스택



게임 구성은 거의 끝났습니다.

포스트 프로세싱을 이용해 뛰어난 영상미를 보여줄 차례입니다.

포스트 프로세싱은 카메라 앱의 필터와 비슷하다고 보시면 됩니다.







1-9) 포스트 프로세싱 - ㉡카메라 렌더 설정



포스트 프로세싱을 적용하기 전에 카메라의 렌더 설정을 디퍼트 셰이딩으로 변경하겠습니다.

최상의 품질을 얻기 위해서는 꼭 필요하다고 하네요.


* 포워드 렌더링 : 성능이 가볍지만 라이팅 표현이 실제보다 간략화되고 왜곡됩니다.

* 디퍼트 셰이딩 : 라이팅 연산을 마뤄서 실행하는 방식. 개수 제한 없이 광원 표현이 가능.

(MSAA 지원 X)







1-10) 포스트 프로세싱 - ㉢포스트 프로세스 레이어



이제 포스트 프로세싱을 적용하겠습니다.

카메라에 포스트 프로세스 레이어 컴포넌트를 적용해 주세요.

포스트 프로세스 레이어는 포스트 프로세싱 볼륨을 감지하고 

포스트 프로세싱 볼륨으로부터 설정을 얻어와 카메라에 적용됩니다.

이 때 특정 레이어만 감지하도록 레이어 설정을 해주겠습니다.


* FXAA : 전반적인 품질은 높지 않지만 성능 저하가 가장 적고 연산이 빠른 방식







1-11) 포스트 프로세싱 - ㉣포스트 프로세스 볼륨



마지막으로 포스트 프로세스 볼륨을 추가해 주겠습니다.

Create => 3D Object => Post-process Volume을 클릭해 생성해 주세요.






포스트 프로세스 볼륨은 트리거 콜라이더와 함께 사용합니다.

Is Global을 체크하면 카메라의 위치와 상관없이 효과를 전역으로 사용가능 합니다.

레이어도 꼭 설정해 주세요.





포스트 프로세스 프로파일은 사용할 효과 목록을 기록하는 파일입니다.

New 버튼을 클릭해 새로운 파일 생성이 가능하지만,

저는 예제에 있는 걸로 적용하였습니다.

기존 목록에 다른 효과도 적용 가능하답니다^^


* 모션 블러 : 빠르게 움직이는 물체에 대한 잔상

* 블룸 : 밝은 물체의 경계에서 빛이 산란되는 효과(뽀샤시)

* 컬러 그레이딩 : 최정 컬러, 대비, 감마 등을 조정(사진 필터)

* 색 수차 : 이미지의 경계가 번지고 삼원색이 분리되는 효과(방사능 중독 효과)

* 비네트 : 화면 가장자리의 채도와 명도를 낮추는 효과

* 그레인 : 화면에 입자 노이즈 추가








1-12) 마무리 - ㉠배경음 추가



마무리 단계입니다.

배경음악 추가 후 빌드를 진행하겠습니다.

게임 매니저 오브젝트에 오디오 소스 컴포넌트 추가 후 오디오 클립을 적용해 주세요.

Loop를 체크해주면 무한으로 음악이 실행됩니다.







1-13) 마무리 - ㉡빌드하기



상단 메뉴 File => Build Settings를 클릭하면 빌드 창이 보입니다.

현재 씬을 빌드 목록에 등록한 후 Build and Run을 클릭하여 빌드를 진행해 주세요.






우와 드디어 게임을 완성했네요.

물론 책의 내용을 따라한거지만,

여기까지 한 것도 대단합니다!!






2. 마무리



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

좀비 서바이버를 따라하면서 게임 매니저와 포스트 프로세싱을 만들어 보았습니다.

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

감사합니다.




수업자료: 좀비 서바이버 따라하기 #6 - 게임 매니저와 포스트 프로세싱



댓글

Designed by JB FACTORY