[Unity][3D-Game] Tower Defense Game (26)

suhan0304·2023년 12월 31일
0
post-thumbnail

강의영상 (28)


개발

레벨 WIN UI 제작

레벨을 클리어 했을 시 나올 효과를 위해 UI를 제작한다. 일단 Level01에서 WIN UI를 제작하고 다른 레벨은 해당 레벨을 복사해서 만들어주자. Overlay Canvas에서 새로 만들지않고 GameOver를 그대로 복사해서 사용하자.

이름을 CompleteLevel로 바꿔주고 GameOver 스크립트 컴포넌트를 제거한다.

LevelWonText로 GameOverText의 이름을 바꿔주고 LEVEL WON!으로 바꿔준다.

Retry 버튼을 크게 크기를 늘리고 Menu 버튼을 하단의 작은 버튼으로 수정해서 옮겨준다. 그 다음에 Retry 버튼을 Continue 버튼으로 바꿔준다.

이 때 Menu 버튼의 경우 Image 범위를 클릭하는 것이 아니라 Text 범위를 클릭해야 클릭이 되도록 Button의 Target Graphic을 Text로 설정한다.

라운드 출력

RoundsSurvived의 Rounds에 Rounds Survived 스크립트를 만들어줘서 클리어 시 클리어한 라운드(=웨이브) 수가 출력되도록 한다. 이 때 기존의 GameOver 스크립트에서 roundsText를 가져와서 이제 RoundsSurvived에서 라운드 출력을 관리하도록 한다.

RoundsSurvived.cs

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

public class RoundsSurvived : MonoBehaviour
{
    public Text roundsText;

    void OnEnable() //게임 오버 UI가 Active 시 실행
    {
        StartCoroutine(AnimeText());
    }

    IEnumerator AnimeText()
    {
        roundsText.text = "0";
        int round = 0; 

        yield return new WaitForSeconds(.7f); //Fade 효과 대기시간

        while (round < PlayerStats.Rounds)
        {
            round++;
            roundsText.text = round.ToString();

            yield return new WaitForSeconds(.05f);
        }

    }
}

위와 같이 시간을 0.05초 간격을 두고 1씩 늘려주면서 text를 업데이트 시켜줘서 라운드 숫자가 올라가도록 보이게 효과를 줄 수 있다.

GameOver.cs

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

public class GameOver : MonoBehaviour
{

    public string menuSceneName = "MainMenu";
    public SceneFader sceneFader;

    public void Retry()
    {
        sceneFader.FadeTo(SceneManager.GetActiveScene().name); //현재 실행중인 Scene을 재로드
    }

    public void Menu()
    {
        sceneFader.FadeTo(menuSceneName);
    }
}

이제 인스펙터에서 Rounds를 연결시켜준다. GameOver에도 동일하게 RoundsSurvived 스크립트를 추가해주고 Rounds를 연결시켜준다.

CompleteLevel 스크립트

이제 각 버튼을 눌렀을시 실행될 기능을 ComleteLevel 오브젝트의 Complete Level 스크립트를 만들어서 해당 스크립트에 구현한다. 이 때 기존의 GameManager에서 WinLevel 함수와 해당 함수에 사용되는 변수도 모두 가져와서 WinLevel 기능을 CompleteLevel에서 담당하도록 하고 게임 매니저에서는 CompletLevel 오브젝트를 활성화시키는 기능만 수행하도록 수정한다.

CompleteLevel.cs

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

public class CompleteLevel : MonoBehaviour
{
    public string menuSceneName = "MainMenu";

    public string nextLevel = "Level02";
    public int levelToUnrock = 2;

    public SceneFader sceneFader;
    public void Continue()
    {
        PlayerPrefs.SetInt("levelReached", levelToUnrock);
        sceneFader.FadeTo(nextLevel);
    }

    public void Menu()
    {
        PlayerPrefs.SetInt("levelReached", levelToUnrock);
        sceneFader.FadeTo(menuSceneName);
    }
}

GameManager.cs

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

public class GameManager : MonoBehaviour
{
    public static bool GameIsOver = false;

    public GameObject gameOverUI;
    public GameObject completeLevelUI;

    private void Start()
    {
        GameIsOver = false; //게임 시작 시에 게임 오버를 False로 설정
    }

    void Update()
    {
        if (GameIsOver)
            return;

        if(Input.GetKeyDown("e"))
        {
            EndGame();
        }

        if(PlayerStats.Lives <= 0) //플레이어 체력이 0 이하
        {
            EndGame(); //게임 종료 함수
        }
    }

    void EndGame() 
    {
        GameIsOver = true;
        gameOverUI.SetActive(true); //게임 오버 UI 활성화
    }

    public void WinLevel()
    {
        GameIsOver = true;
        completeLevelUI.SetActive(true);
    }
}

이제 인스펙터에서 오버젝트들을 다 연결시켜준다.

이후에 Continue, Menu 버튼에 각 함수들을 연결시켜준다.

기타 오류 수정

에너미가 죽을 시에 에너미의 체력이 0보다 적게되면 Die함수를 호출하고 die에서는 deathEffect를 복사해서 생성해낸후에 파괴하고 객체를 Destroy 하는데 이 때 시간이 어느 정도 필요해서 해당 오브젝트를 어떤 터렛이 동일하게 또 공격하게되면 다시 한 번 Die함수가 실행되면서 돈이 중복 지급되는 버그가 있는데 이는 isDead라는 부울 변수로 한번만 Die가 실행되도록 아래와 같이 수정해서 보완했다.

Enemy.cs

private bool isDead = false;

public void TakeDamage(float amount)
{
    health -= amount; //amount 만큼 체력 감소

    healthBar.fillAmount = health / startHealth; //체력바를 퍼센트로 업데이트 [체력 100% = 1.00, 체력 1% = 0.01]

    if (health <= 0 && !isDead)
    {
        Die(); // 에너미 사망 메서드
    }
}

    void Die()
    {
        isDead = true;

        PlayerStats.Money += worth; //플레이어에게 돈 지급

        GameObject effect = (GameObject) Instantiate(deathEffect, transform.position, Quaternion.identity);
        Destroy(effect, 5f);

        WaveSpawner.EnemiesAlive--; //몬스터 개체 수 감소

        Destroy(gameObject);
    }

두 번째 버그는 레벨 클리어를 웨이브 숫자가 우리가 지정한 waves 배열의 사이즈에 도달하면 클리어라고 인식하게 되는데 이를 SpawnWave 코루틴에서 확인하는데 이를 Update 문에서 확인하도록 수정했다. 이렇게 해서 SpawnWave 코루틴이 끝나거나 실행이 안되도 정상적으로 레벨 클리어가 진행되도록 했다.

WaveSpawner.cs

private void Update()
{ 
    if (EnemiesAlive >0)
    {
        return;
    }

    if (waveIndex == waves.Length)
    {
        gameManager.WinLevel(); //레벨 클리어 
        this.enabled = false; //WaveSpawner 스크립트를 비활성화
    }

    if (countdown <= 0f) //카운트다운이 0 보다 작아지먄 Spawn Wave 실행
    {
        StartCoroutine(SpawnWave());
        countdown = timeBetweenWaves; //카운트 다운을 중간 시간으로 초기화
        return;
    }

    //deltaTime//마지막 프레임을 그린 후 경과한 시간
    countdown -= Time.deltaTime; //시간을 계속 줄인다.
        
    countdown = Mathf.Clamp(countdown, 0f, Mathf.Infinity); //카운트 다운이 0보다 낮아지지 않도록 설정

    waveCountdownText.text = string.Format("{0:00.00}", countdown); //출력 형식을 지정

    //waveCountdownText.text = Mathf.Round(countdown).ToString();
}

IEnumerator SpawnWave() //코루틴
{
    PlayerStats.Rounds++;

    Wave wave = waves[waveIndex];

    EnemiesAlive = wave.count;

    for (int i = 0; i < wave.count; i++)  //웨이브 레벨만큼 몬스터 소한
    {
        SpawnEnemy(wave.enemy);
        yield return new WaitForSeconds(1f / wave.rate);
    }

    waveIndex++;//웨이브가 올때마다 레벨업
}

최종 레벨 추가 작성

이제 수정된 Level01를 복사해서 Level02, Level03들을 만들어서 수정해나간다. 이 때 CompleteLevel 오브젝트의 스크립트에서 다음 레벨 장면을 지정해줄 수 있다.

최종본은 단순히 Level01을 그대로 재활용했다. 해당 Level 장면에서 더 많은 웨이브와 다양한 몬스터, 다양한 터렛 등, 등을 추가해서 레벨을 다양화 시키면서 쉽게 확장할 수 있다.


최종 결과물

profile
Be Honest, Be Harder, Be Stronger

0개의 댓글