(TowerDefence) 타워 디펜스 게임 마무리 및 코드 정리

게임 소개

게임 이름: PO!SON

장르: 타워 디펜스, 서바이벌

게임 목표: 마을이 몬스터들에게 습격당하고 독가스로 오염된 상황에서 주인공은 몬스터를 저지하고, 보스 몬스터를 잡고, 약초를 채취하고, 마을을 정화하는 정화가스를 생산하여 마을을 구하는 것입니다.

게임특징 : 기본적으로 타워 디펜스 게임이지만 지정된 공간에 타워를 설치하여 정해진 길을 따라가는 몬스터를 막는 타워 디펜스 게임이 아닙니다. 타워는 설치, 철거, 어디든지 이동할 수 있으며 뱀파이어 서바이벌과 유사하게 맵 가장자리에 몹이 무작위로 스폰되어 타워와 플레이어에게 접근합니다.

생산 기간: 2022년 10월 28일 ~ 2023년 3월 14일

창백한

아래 링크에서 플레이하실 수 있습니다.

https://wny0320.itch.io/poison

암호

다음은 동아리 프로젝트 내에서 직접 코드를 작성한 부분입니다.

이해하지 못하거나 수정하고 싶은 사항을 지적하는 것은 언제나 환영합니다.

Player.cs

여기에는 플레이어와 관련된 모든 움직임과 특성이 포함됩니다.

앞으로 모듈화를 좀 더 해주면 좋을 것 같습니다.

더보기

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

public class Player : MonoBehaviour
{
    //IMove 관련 필드
    (SerializeField) (Tooltip("이동속도")) float moveSpeed;
    (SerializeField) EnemySpawner enemySpawner;
    Vector3 inputDir;
    Vector3 moveDir;
    CharacterController controller;

    //Health 관련 필드
    Health health;
    int healthBuffer;
    (SerializeField) Slider hpBar;
    public bool playerRegenerating;
    (Header("체력 자연 재생 타이머"))
    (SerializeField) (Tooltip("맞지 않은 시간")) float healthTimer;
    (SerializeField) (Tooltip("자연 재생까지 맞지 않아야 하는 시간")) float healthTime;
    Coroutine healthUp;

    //Pick 관련 필드
    GameObject Hand = null;
    float standardDistance = 1.2f;

    //피격 관련 필드
    (Header(""))
    (Tooltip("플레이어가 피격 가능한지 유무"))
    public bool canDamaged = true;
    float unBeatTime = 1.0f;
    Material material;

    //Menu 관련 필드
    GameObject menuUI;
    GameObject selectUI;
    GameObject buildUI;
    GameObject manageUI;
    GameObject manageSelectUI;

    bool isEnabledMenuUI;
    /*bool isEnabledSelectUI;
    bool isEnabledBuildUI;
    bool isEnabledRepairUI;*/

    public int modeSelect;
    public int manageSelect;
    int manageSelectBuffer;
    public bool buildMode;
    public bool manageMode;
    enum towerList
    {
        BasicTower = 0,
        PunchTower = 1,
        RangeTower = 2,
        SlowTower = 3,
    }
    enum modeSelection
    {
        NonSelect = -1,
        BuildMode = 0,
        ManageMode = 1,
    }
    enum manageSelection
    {
        NonSelect = -1,
        RepairMode = 0,
        RemoveMode = 1,
        UpgradeMode = 2,
    }
    enum towerAttackType
    {
        기본 = 1,
        밀기 = 2,
        광역 = 3,
        슬로우 = 4,
    }
    (SerializeField)
    GameObject() towerObjList;
    public int choice;
    public int() buildCostAdder;

    //repair 관련 필드
    GameObject manageTarget;
    public bool manageOn;
    Text lv;
    Text attackType;
    Text maxHP;
    Text curHP;
    Text power;
    Text cost;
    int layerMask = (1 << 6);
    (SerializeField) Sprite() towerSpriteList;
    (SerializeField) Image towerImage;
    (SerializeField) AudioSource source;

    void Start()
    {
        //Move 필드 초기화
        controller = GetComponent<CharacterController>();

        //Health 필드 초기화
        health = GetComponent<Health>();
        healthBuffer = health.healthCheck;

        //UnBeat에 쓰일 필드 초기화
        material = GetComponent<Renderer>().material;

        //repair 필드 초기화
        manageTarget = null;
        manageMode = false;
        manageOn = false;
        manageSelectBuffer = (int)manageSelection.NonSelect;
        lv = GameObject.Find("Lv").GetComponent<Text>();
        attackType = GameObject.Find("AttackType").GetComponent<Text>();
        maxHP = GameObject.Find("Max").GetComponent<Text>();
        curHP = GameObject.Find("Cur").GetComponent<Text>();
        power = GameObject.Find("Power").GetComponent<Text>();
        cost = GameObject.Find("Cost").GetComponent<Text>();

        //Menu에 쓰일 필드 초기화
        menuUI = GameObject.Find("MenuUI");
        selectUI = GameObject.Find("SelectUI");
        buildUI = GameObject.Find("BuildUI");
        manageUI = GameObject.Find("ManageUI");
        manageSelectUI = GameObject.Find("ManageSelectUI");

        buildUI.SetActive(false);
        manageUI.SetActive(false);
        selectUI.SetActive(false);
        manageSelectUI.SetActive(false);
        menuUI.SetActive(false);

        isEnabledMenuUI = false;
        /*isEnabledSelectUI = true;
        isEnabledBuildUI = false;
        isEnabledRepairUI = false;*/

        modeSelect = -1;
        manageSelect = -1;
        buildMode = false;
        buildCostAdder = new int(4);        
    }
    void Update()
    {
        Move();
        OnPick();
        Menu();
        BuildMode();
        ManageMode();
        HealthRegenerate();
        Clipping();
    }

    void LateUpdate()
    {
        PlayerHpCheck();
        PlayerDead();
    }

    void Pick()
    {
        //플레이어 안에 게임 오브젝트를 하나 둠
        //주울 수 있는 오브젝트 List인 PickList 안에 있는 물체에서 제일 가까운 물체의 거리와 Target 오브젝트를 찾음
        float closeDistance = float.PositiveInfinity;
        GameObject Target = null;

        //손에 들고 있지 않은 경우
        if (Hand == null)
        {
            /*foreach (GameObject i in GameManager.PickList)
            //distance에 값을 두기
            {
                //PickList에 있는 물체들로 거리 측정 반복문 
                Vector3 targetPos = i.transform.position;
                Vector3 playerPos = this.transform.position;
                float nowDistacne = Vector3.Magnitude(playerPos - targetPos);
                if (closeDistance > nowDistacne)
                {
                    closeDistance = nowDistacne;
                    Target = i;
                }
            }*/

            //PickList 사용 안한 충돌 물체 구하기
            //기준 거리 내에 있는 Collider 추출해서 목록에 넣기
            Collider() collider = Physics.OverlapSphere(this.transform.position, standardDistance);
            foreach (Collider i in collider)
            {
                Vector3 targetPos = i.gameObject.transform.position;
                Vector3 playerPos = this.transform.position;
                float nowDistacne = Vector3.Magnitude(playerPos - targetPos);
                if (closeDistance > nowDistacne && (i.gameObject.tag == "Tower" || i.gameObject.tag == "Resource" || i.gameObject.tag == "Herb"))
                {
                    closeDistance = nowDistacne;
                    Target = i.gameObject;
                }
            }
            //collider가 IsTrigger든 아니든 줍고 내려놓을 수 있음, 단 플레이어의 위치에 내려놓는다면
            //Trigger가 아닐시 플레이어가 움직일시 밀릴 수 있음

            //기준 거리 안에 있다면(주울 수 있는 거리라면)
            if (closeDistance <= standardDistance)
            {
                //Target의 이름별로 실행
                if (Target.tag == "Tower")
                {
                    Hand = Target;
                    Hand.transform.SetParent(this.transform);
                    Hand.SetActive(false);
                    //renderer가 자식한테도 있어서 타워가 포신이 안지워짐(그거까지 넣기)
                    /*TowerAttackType HandScript = Hand.GetComponentInChildren<TowerAttackType>();
                    TowerManager HandScript2 = Hand.GetComponentInChildren<TowerManager>();
                    HandScript.enabled = false;
                    HandScript2.enabled = false;
                    Renderer Handrenderer = Hand.GetComponent<Renderer>();
                    Handrenderer.enabled = false;*/
                    //오브젝트 콜라이더 제외 비활성화
                    //상속
                }
                if (Target.tag == "Resource")
                {
                    GameManager.resource++;
                    //GameManager.PickList.Remove(Target);
                    Destroy(Target);
                    //자원 추가
                }
                if (Target.tag == "Herb")
                {
                    GameManager.herb++;
                    Destroy(Target);
                }
            }
        }
        else
        {
            //물체를 들고 있는 경우(들 수 있는 물체가 타워만 우선 구현)
            //타워 내려놓기, 설치방법에 따라 함수호출
            //임시로 플레이어 위치에 내려놓기로 설정
            Hand.transform.position = this.transform.position;
            Hand.SetActive(true);
            /*TowerAttackType HandScript = Hand.GetComponentInChildren<TowerAttackType>();
            TowerManager HandScript2 = Hand.GetComponentInChildren<TowerManager>();
            HandScript.enabled = true;
            HandScript2.enabled = true;
            Renderer Handrenderer = Hand.GetComponent<Renderer>();
            Handrenderer.enabled = true;*/
            Hand.transform.SetParent(null);
            Hand = null;
        }
    }

    void OnPick()
    {
        //space가 눌리면 Pick 함수 실행
        if (Input.GetKeyDown(KeyCode.Space))
        {
            Pick();
        }

    }

    public void ManageHP(int amount)
    {
        health.healthCheck += amount;
        // amount는 음수, 양수로 받아 계산
    }


    //무적 코루틴
    IEnumerator UnBeat()
    {
        canDamaged = false;

        int blinkTime = (int)(unBeatTime / 0.25f);
        for (int i = 0; i < blinkTime; i++)
        {
            if (i % 2 == 0)
            {
                material.color = new Color32(255, 100, 0, 255);
            }
            else
            {
                material.color = new Color32(255, 255, 255, 255);
            }
            yield return new WaitForSeconds(0.25f);
        }
        material.color = new Color32(255, 255, 255, 255);

        canDamaged = true;

        yield return null;
    }

    void Menu()
    {
        if (Input.GetKeyDown(KeyCode.B))
        {
            if (isEnabledMenuUI)
            {
                isEnabledMenuUI = false;
                buildUI.SetActive(false);
                manageUI.SetActive(false);
                selectUI.SetActive(false);
                manageSelectUI.SetActive(false);
                menuUI.SetActive(false);
                manageTarget = null;
                manageMode = false;
                modeSelect = (int)modeSelection.NonSelect;
                manageSelectBuffer = (int)manageSelection.NonSelect;
                TutorialUIOff();
            }
            else
            {
                isEnabledMenuUI = true;
                menuUI.SetActive(true);
                selectUI.SetActive(true);
            }
        }
        if (modeSelect == (int)modeSelection.BuildMode)
        {
            buildUI.SetActive(true);
            selectUI.SetActive(false);
            modeSelect = (int)modeSelection.NonSelect;
            TutorialUIOff();
        }
        if (modeSelect == (int)modeSelection.ManageMode)
        {
            selectUI.SetActive(false);
            manageSelectUI.SetActive(true);
            if (manageSelect != (int)manageSelection.NonSelect)
            {
                manageSelectUI.SetActive(false);
                manageUI.SetActive(true);
                manageMode = true;
                modeSelect = (int)modeSelection.NonSelect;
                manageSelectBuffer = manageSelect;
                manageSelect = (int)manageSelection.NonSelect;
                TutorialUIOff();
            }
            TutorialUIManageOff();
        }
    }

    void TutorialUIOff()
    {
        if (SceneManager.GetActiveScene().name == "Tutorial")
        {
            MouseEvent ME = FindObjectOfType<MouseEvent>().GetComponent<MouseEvent>();
            foreach (GameObject i in ME.eventList)
            {
                i.SetActive(false);
            }
        }
    }
    void TutorialUIManageOff()
    {
        if (SceneManager.GetActiveScene().name == "Tutorial")
        {
            MouseEvent ME = FindObjectOfType<MouseEvent>().GetComponent<MouseEvent>();
            ME.eventList(6).SetActive(false);
        }
    }
    void BuildMode()
    {
        if (buildMode)
        {
            switch (choice)
            {
                case (int)towerList.BasicTower:
                    buildMode = false;
                    if (GameManager.resource >= buildCostAdder(choice) + 6)
                    {
                        Instantiate(towerObjList((int)towerList.BasicTower), transform.position, new Quaternion(0, 0, 0, 0));
                        GameManager.resource -= buildCostAdder(choice) + 6;
                        buildCostAdder(choice) += 3;
                    }
                    choice = -1;
                    break;

                case (int)towerList.PunchTower:
                    buildMode = false;
                    if (GameManager.resource >= buildCostAdder(choice) + 15)
                    {
                        Instantiate(towerObjList((int)towerList.PunchTower), transform.position, new Quaternion(0, 0, 0, 0));
                        GameManager.resource -= buildCostAdder(choice) + 15;
                        buildCostAdder(choice) += 5;
                    }
                    choice = -1;
                    break;

                case (int)towerList.RangeTower:
                    buildMode = false;
                    if (GameManager.resource >= buildCostAdder(choice) + 9)
                    {
                        Instantiate(towerObjList((int)towerList.RangeTower), transform.position, new Quaternion(0, 0, 0, 0));
                        GameManager.resource -= buildCostAdder(choice) + 9;
                        buildCostAdder(choice) += 4;
                    }
                    choice = -1;
                    break;

                case (int)towerList.SlowTower:
                    buildMode = false;
                    if (GameManager.resource >= buildCostAdder(choice) + 18)
                    {
                        Instantiate(towerObjList((int)towerList.SlowTower), transform.position, new Quaternion(0, 0, 0, 0));
                        GameManager.resource -= buildCostAdder(choice) + 18;
                        buildCostAdder(choice) += 6;
                    }
                    choice = -1;
                    break;
            }
            source.Play();
        }
    }

    void VisualizeStatus(int select)
    {
        TowerManager targetTowerManager = manageTarget.GetComponent<TowerManager>();
        int targetCurHP = targetTowerManager.hp.health;
        int targetMaxHP = targetTowerManager.hp.healthCheck;
        int targetPower = targetTowerManager.damage;
        int targetAttackType = targetTowerManager.damageType;
        int targetLv = targetTowerManager.towerLevel;
        int targetCost = 0;
        lv.text = targetLv.ToString();
        curHP.text = targetCurHP.ToString();
        maxHP.text = targetMaxHP.ToString();
        power.text = targetPower.ToString();
        switch (targetAttackType)
        {
            case (int)towerAttackType.기본:
                attackType.text = "기본";
                towerImage.sprite = towerSpriteList(0);
                if (select == (int)manageSelection.RemoveMode)
                    targetCost = (int)((buildCostAdder(targetAttackType - 1) + 6) * 0.7);
                if (select == (int)manageSelection.RepairMode)
                    targetCost = 10;
                if (select == (int)manageSelection.UpgradeMode)
                    targetCost = targetTowerManager.upgradeCost(Mathf.Clamp(targetLv - 1, 0, 3));
                break;

            case (int)towerAttackType.밀기:
                attackType.text = "밀기";
                towerImage.sprite = towerSpriteList(1);
                if (select == (int)manageSelection.RemoveMode)
                    targetCost = (int)((buildCostAdder(targetAttackType - 1) + 9) * 0.7);
                if (select == (int)manageSelection.RepairMode)
                    targetCost = 12;
                if (select == (int)manageSelection.UpgradeMode)
                    targetCost = targetTowerManager.upgradeCost(Mathf.Clamp(targetLv - 1, 0, 3));
                break;

            case (int)towerAttackType.광역:
                attackType.text = "광역";
                towerImage.sprite = towerSpriteList(2);
                if (select == (int)manageSelection.RemoveMode)
                    targetCost = (int)((buildCostAdder(targetAttackType - 1) + 18) * 0.7);
                if (select == (int)manageSelection.RepairMode)
                    targetCost = 14;
                if (select == (int)manageSelection.UpgradeMode)
                    targetCost = targetTowerManager.upgradeCost(Mathf.Clamp(targetLv - 1, 0, 3));
                break;

            case (int)towerAttackType.슬로우:
                attackType.text = "슬로우";
                towerImage.sprite = towerSpriteList(3);
                if (select == (int)manageSelection.RemoveMode)
                    targetCost = (int)((buildCostAdder(targetAttackType - 1) + 15) * 0.7);
                if (select == (int)manageSelection.RepairMode)
                    targetCost = 16;
                if (select == (int)manageSelection.UpgradeMode)
                    targetCost = targetTowerManager.upgradeCost(Mathf.Clamp(targetLv - 1, 0, 3));
                break;
        }
        cost.text = targetCost.ToString();
    }
    void ManageMode()
    {
        if (manageMode)
        {
            if (manageTarget != null && manageTarget.tag == "Tower")
            {
                StartCoroutine("LoopViusalizeStatus");
            }
            if (Input.GetMouseButton(0))
            {
                Vector3 mousePos = Input.mousePosition;
                Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
                RaycastHit hit;
                if (Physics.Raycast(ray, out hit, 100f, layerMask))
                {
                    manageTarget = hit.transform.gameObject;
                }
                if (manageTarget != null && manageTarget.tag == "Tower")
                {
                    VisualizeStatus(manageSelectBuffer);
                    manageOn = false;
                }
            }
            if (manageOn && manageTarget.tag == "Tower")
            {
                if (manageSelectBuffer == (int)manageSelection.RepairMode)
                {
                    TowerManager targetTowerManager = manageTarget.GetComponent<TowerManager>();
                    int targetAttackType = manageTarget.GetComponent<TowerManager>().damageType;
                    switch (targetAttackType)
                    {
                        case (int)towerAttackType.기본:
                            if (GameManager.resource >= 10)
                            {
                                targetTowerManager.hp.healthCheck = targetTowerManager.hp.health;
                                GameManager.resource -= 10;
                            }
                            break;

                        case (int)towerAttackType.밀기:
                            if (GameManager.resource >= 12)
                            {
                                targetTowerManager.hp.healthCheck = targetTowerManager.hp.health;
                                GameManager.resource -= 12;
                            }
                            break;

                        case (int)towerAttackType.광역:
                            if (GameManager.resource >= 14)
                            {
                                targetTowerManager.hp.healthCheck = targetTowerManager.hp.health;
                                GameManager.resource -= 14;
                            }
                            break;

                        case (int)towerAttackType.슬로우:
                            if (GameManager.resource >= 16)
                            {
                                targetTowerManager.hp.healthCheck = targetTowerManager.hp.health;
                                GameManager.resource -= 16;
                            }
                            break;
                    }
                    //자원 감소 코드
                    //타워 매니저 데미지 타입으로 설정하면 될듯
                }
                if (manageSelectBuffer == (int)manageSelection.RemoveMode)
                {
                    int targetAttackType = manageTarget.GetComponent<TowerManager>().damageType;
                    switch (targetAttackType)
                    {
                        case (int)towerAttackType.기본:
                            GameManager.resource += (int)((buildCostAdder(targetAttackType - 1) + 6) * 0.7);
                            break;

                        case (int)towerAttackType.밀기:
                            GameManager.resource += (int)((buildCostAdder(targetAttackType - 1) + 9) * 0.7);
                            break;

                        case (int)towerAttackType.광역:
                            GameManager.resource += (int)((buildCostAdder(targetAttackType - 1) + 18) * 0.7);
                            break;

                        case (int)towerAttackType.슬로우:
                            GameManager.resource += (int)((buildCostAdder(targetAttackType - 1) + 15) * 0.7);
                            break;
                    }
                    GameObject.Destroy(manageTarget);
                }
                if (manageSelectBuffer == (int)manageSelection.UpgradeMode)
                {
                    manageTarget.GetComponent<TowerManager>().UpgradeTower();
                    //int targetAttackType = manageTarget.GetComponent<TowerManager>().damageType;
                    //int targetLv = manageTarget.GetComponent<TowerManager>().towerLevel;
                    //int cost = 0;
                    //switch (targetAttackType)
                    //{
                    //    case (int)towerAttackType.기본:
                    //        cost = 4 + targetLv * 4;
                    //        if (GameManager.resource >= cost && targetLv < 5)
                    //        {
                    //            manageTarget.GetComponent<TowerManager>().towerLevel += 1;
                    //            GameManager.resource -= cost;
                    //        }
                    //        break;

                    //    case (int)towerAttackType.밀기:
                    //        cost = 7 + targetLv * 4;
                    //        if (GameManager.resource >= cost && targetLv < 5)
                    //        {
                    //            manageTarget.GetComponent<TowerManager>().towerLevel += 1;
                    //            GameManager.resource -= cost;
                    //        }
                    //        break;

                    //    case (int)towerAttackType.광역:
                    //        cost = 16 + targetLv * 4;
                    //        if (GameManager.resource >= cost && targetLv < 5)
                    //        {
                    //            manageTarget.GetComponent<TowerManager>().towerLevel += 1;
                    //            GameManager.resource -= cost;
                    //        }
                    //        break;

                    //    case (int)towerAttackType.슬로우:
                    //        cost = 13 + targetLv * 4;
                    //        if (GameManager.resource >= cost && targetLv < 5)
                    //        {
                    //            manageTarget.GetComponent<TowerManager>().towerLevel += 1;
                    //            GameManager.resource -= cost;
                    //        }
                    //        break;
                    //}
                }
                manageOn = false;
            }
        }
    }
    IEnumerator LoopViusalizeStatus()
    {
        VisualizeStatus(manageSelectBuffer);
        yield return 0;
    }

    void Move()
    {
        inputDir = Vector3.zero;
        if (Input.GetKey(KeyCode.A)) inputDir += Vector3.left;
        if (Input.GetKey(KeyCode.D)) inputDir += Vector3.right;
        if (Input.GetKey(KeyCode.S)) inputDir += Vector3.back;
        if (Input.GetKey(KeyCode.W)) inputDir += Vector3.forward;
        inputDir.Normalize();

        moveDir = Vector3.MoveTowards(moveDir, inputDir, Time.deltaTime * 5);
        controller.Move(moveDir * moveSpeed);
        if (inputDir != Vector3.zero)
            transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(inputDir), Time.deltaTime * 5);
    }

    void PlayerDead()
    {
        if (health.healthCheck <= 0)
        {
            gameObject.SetActive(false);
        }
    }
    void PlayerHpCheck()
    {
        //체력이 변했는지 체크
        if (healthBuffer != health.healthCheck)
        {
            if (healthBuffer > health.healthCheck)
            {
                //체력이 줄음
                StartCoroutine(UnBeat());
            }
            //체력 변환 체크값을 현재 체력으로 바꿔줌
            healthBuffer = health.healthCheck;
        }
    }

    void HealthRegenerate()
    {
        healthTimer += Time.deltaTime;
        if (!canDamaged)
        {
            healthTimer = 0f;
        }
        if (healthTimer > healthTime)
        {
            if (health.healthCheck < health.health && healthUp == null)
            {
                healthUp = StartCoroutine(HealthUp());
                hpBar.gameObject.SetActive(true);
                playerRegenerating = true;
            }
            if (health.healthCheck == health.health)
            {
                hpBar.gameObject.SetActive(false);
                playerRegenerating = false;
            }
        }
    }
    IEnumerator HealthUp()
    {
        health.healthCheck += 1;
        yield return new WaitForSeconds(1f);
        healthUp = null;
    }

    void Clipping()
    {
        transform.position = new Vector3(Mathf.Clamp(transform.position.x, -enemySpawner.spawnPos(0) / 2, enemySpawner.spawnPos(0) / 2), transform.position.y,
            Mathf.Clamp(transform.position.z, -enemySpawner.spawnPos(1) / 2, enemySpawner.spawnPos(1) / 2));
    }
}

WaveManager.cs

웨이브 시간이 되면 웨이브를 소환하는 스크립트

더보기

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

public class WaveManager : MonoBehaviour
{
    //웨이브 시간 관련 필드
    (Header("웨이브 대기 시간"))
    (SerializeField)(Tooltip("현재 웨이브 남은 대기 시간")) float waveTime;
    (SerializeField)(Tooltip("웨이브 기본 대기 시간")) float waveTimer;
    (SerializeField)(Tooltip("웨이브 대기 시간 추가 가중치")) int timeAdder;
    GameObject enemyAlive;

    //현재 웨이브
    public int wave;
    public bool bossWave;

    //코드 참조
    WaveSystem waveSystem;

    //UI
    GameObject timer;

    //정화 웨이브
    (SerializeField) LabUI labUI;


    void Start()
    {
        bossWave = false;
        waveTime = 10f;
        waveTimer = 10f;
        timeAdder = 5;
        timeAdder = 0;
        wave = 0;
        waveSystem = GameObject.Find("waveSystem").GetComponent<WaveSystem>();
        timer = GameObject.Find("Timer");
    }
    void TimeAdd()
    {
        enemyAlive = GameObject.FindWithTag("Enemy");
        //몹이 전부 죽은 경우 wave 카운트 다운
        if (enemyAlive == null)
        {
            timer.SetActive(true);
            waveTime -= Time.deltaTime;
        }
        else
        {
            timer.SetActive(false);
        }

    }
    void waveStarter()
    {
        if (labUI.purifyBt)
        {
            wave = 100;
            Debug.Log("정화 웨이브 시작");
            waveTime += 30f;
        }
        if (waveTime < 0f)
        {
            if ((wave == 3 || wave == 5 || wave == 7 || wave == 9 || wave == 10) && bossWave)
            {
                bossWave = false;
                waveSystem.bossStage = true;
            }
            else if (wave < 10)
            {
                wave += 1;
                if (wave == 3 || wave == 5 || wave == 7 || wave == 9 || wave == 10) bossWave = true;
                waveSystem.bossStage = false;
            }
            waveSystem.waveStage = wave;
            Debug.Log(wave + " 웨이브 시작");
            waveTime = waveTimer + (timeAdder * waveSystem.waveStage);
            if (bossWave) waveTime -= timeAdder;
        }
    }
    void VisualizeTimer()
    {
        if (!bossWave) timer.GetComponent<Text>().text = "다음 웨이브까지 " + (int)waveTime + "초";
        else timer.GetComponent<Text>().text = "보스 등장까지 " + (int)waveTime + "초";
    }

    void Update()
    {
        TimeAdd();
        waveStarter();
        VisualizeTimer();
    }
}

EnemySpawner.cs

정사각형 지도 범위 밖의 임의의 위치에 몬스터를 생성하는 생성기 스크립트입니다.

더보기

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

public class EnemySpawner : MonoBehaviour
{
    //적 프리팹 리스트
    public GameObject() enemyPrefab;

    //적 스폰 타임
    //public float maxSpawnTime;
    //public float curSpawnTime;

    //맵 크기 좌표 x,z
    public int() spawnPos = new int(2);
    public float spawnRange;

    public int selection;
    private void Start()
    {

    }


    // spawnPos = 1024 x 1080
    // 1024 / 2 = -512 ~ 512
    // 1080 / 2 = -540 ~ 540

    // Random 0 1
    // 0 : -pos
    // float posX = Random.Range(-700f, -512f);
    // 1 : +pos
    // float posX = Random.Range(+512f, 700f);

    public void EnemySpawn()
    {
        //정해진 적 선택 후 생성
        GameObject selectPrefab = enemyPrefab(selection);

        //소환할 좌표
        float posX;
        float posZ;

        int pos = Random.Range(0, 4);
        if (pos == 0)
        {
            posX = Random.Range((-spawnPos(0) / 2) - spawnRange, 0);
            if(posX < -spawnPos(0) / 2)
            {
                posZ = Random.Range(0, spawnPos(1) / 2 + spawnRange);
            }
            else
            {
                posZ = Random.Range(spawnPos(1) / 2, spawnPos(1) / 2 + spawnRange);
            }
        }
        else if (pos == 1)
        {
            posX = Random.Range(0, spawnPos(0) / 2 + spawnRange);
            if (posX > spawnPos(0) / 2)
            {
                posZ = Random.Range(0, spawnPos(1) / 2 + spawnRange);
            }
            else
            {
                posZ = Random.Range(spawnPos(1) / 2, spawnPos(1) / 2 + spawnRange);
            }
        }
        else if (pos == 2)
        {
            posX = Random.Range(0, spawnPos(0) / 2 + spawnRange);
            if (posX > spawnPos(0) / 2)
            {
                posZ = Random.Range(-(spawnPos(1) / 2 + spawnRange), 0);
            }
            else
            {
                posZ = Random.Range(-(spawnPos(1) / 2 + spawnRange), -(spawnPos(1) / 2));
            }
        }
        else
        {
            posX = Random.Range((-spawnPos(0) / 2) - spawnRange, 0);
            if (posX < -spawnPos(0) / 2)
            {
                posZ = Random.Range(-(spawnPos(1) / 2 + spawnRange), 0);
            }
            else
            {
                posZ = Random.Range(-(spawnPos(1) / 2 + spawnRange), -(spawnPos(1) / 2));
            }
        }
        Instantiate(selectPrefab, new Vector3(posX, 0.5f, posZ), Quaternion.identity);

    }
    public void Update()
    {

    }
}

WaveSystem.cs

웨이브를 관리하는 스크립트와 몬스터의 구성 방식

더보기

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

public class WaveSystem : MonoBehaviour
{
    EnemySpawner spawner;
    GameObject poison;
    public int waveStage;
    public bool bossStage = false;
    int() SpawnNum = new int(5);
    enum enemySelection
    {
        basic = 0,
        rush = 1,
        interrupt = 2,
        tanker = 3,
        healer = 4,
    }
    private void Start()
    {
        spawner = GameObject.Find("spawner").GetComponent<EnemySpawner>();
        poison = GameObject.Find("Poison");
        poison.SetActive(false);
        waveStage = 0;
    }

    void ModifyWaveInfo()
    {        
        if (waveStage == 1) SpawnNum = new int() { 8, 3, 2, 0, 0 };
        else if(waveStage == 2) SpawnNum = new int() { 10, 3, 2, 1, 0 };
        else if (waveStage == 3 && bossStage) SpawnNum = new int() { 1, 0, 0, 0, 0 };
        else if (waveStage == 3) SpawnNum = new int() { 10, 4, 4, 2, 1 };
        else if (waveStage == 4) SpawnNum = new int() { 12, 5, 4, 2, 2 };
        else if (waveStage == 5 && bossStage) SpawnNum = new int() { 0, 1, 0, 0, 0 };
        else if (waveStage == 5) SpawnNum = new int() { 15, 5, 5, 3, 2 };
        else if (waveStage == 6) SpawnNum = new int() { 15, 6, 6, 3, 3 };
        else if (waveStage == 7 && bossStage) SpawnNum = new int() { 0, 0, 1, 0, 0 };
        else if (waveStage == 7) SpawnNum = new int() { 17, 7, 6, 4, 3 };
        else if (waveStage == 8) SpawnNum = new int() { 18, 7, 7, 4, 4 };
        else if (waveStage == 9 && bossStage) SpawnNum = new int() { 0, 0, 0, 1, 0 };
        else if (waveStage == 9) SpawnNum = new int() { 21, 8, 7, 5, 4 };
        else if (waveStage == 10 && bossStage) SpawnNum = new int() { 0, 0, 0, 0, 1 };
        else if (waveStage == 10) SpawnNum = new int() { 23, 8, 8, 5, 5 };
        else if (waveStage == 100) SpawnNum = new int() { 25, 10, 8, 7, 5 };
		//개발 시간때문에 하드 코딩을 했지만 나중엔 하드코딩을 고쳐보아야한다.
        SpawnWave(SpawnNum);
    }
    
    
    void SpawnWave(int() roopTime)
    {
        for(int i = 0; i < 5; i++)
        {
            spawner.selection = i;
            if (bossStage) spawner.selection += 5;
            for(int j = 0; j < roopTime(i); j++)
            {
                spawner.EnemySpawn();
            }
        }
        waveStage = 0;
        bossStage = false;
        poison.SetActive(true);
    }

    private void Update()
    {
        if(waveStage != 0) ModifyWaveInfo();
    }
}

Posion.cs

독가스가 자라고 플레이어를 따라가는 스크립트

더보기

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

public class Poison : MonoBehaviour
{
    GameObject objPlayer;
    Player player;
    (SerializeField)
    float moveSpeed;
    (SerializeField)
    float growSpeed;
    (SerializeField)
    EnemySpawner spawner;

    int posionDamage = -5;
    // Start is called before the first frame update
    void Start()
    {
        objPlayer = GameObject.Find("Player");
        player = objPlayer.GetComponent<Player>();
        transform.position = Vector3.zero;
        PoisonSpawn();
    }

    void PoisonSpawn()
    {
        Vector3 direction = Vector3.zero;
        direction.x = Random.Range(-spawner.spawnPos(0) / 2, spawner.spawnPos(0) / 2);
        direction.z = Random.Range(-spawner.spawnPos(1) / 2, spawner.spawnPos(1) / 2);
        transform.position = direction;
    }
    void PoisonMove()
    {
        Vector3 playerPos = new Vector3(objPlayer.transform.position.x, 1, objPlayer.transform.position.z);
        transform.position = Vector3.MoveTowards(transform.position, playerPos, Time.deltaTime * moveSpeed);
    }

    void PoisonGrow()
    {
        if(transform.localScale.x < spawner.spawnPos(0) / 2)
            transform.localScale = new Vector3(transform.localScale.x + growSpeed, 1, transform.localScale.z + growSpeed);
    }

    private void OnTriggerStay(Collider other)
    {
        if(other.tag == "Player" && player.canDamaged)
        {
            player.canDamaged = false;
            player.ManageHP(posionDamage);
        }
    }

    // Update is called once per frame
    void Update()
    {
        PoisonMove();
        PoisonGrow();
    }
}

GameManager.cs

리소스 등의 부분을 담당하는 스크립트

GameManager보다 ResourceManager에 더 가깝습니다.

더보기

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

public class GameManager : MonoBehaviour
{
    (Header("자원"))
    (Tooltip("타워에 관련된 자원"))
    (SerializeField) int reveal_resource;
    public static int resource;
    (Tooltip("클리어 정화가스"))
    (SerializeField) int reveal_purifyRs;
    (Tooltip("클리어 제작재료"))
    (SerializeField) int reveal_herb;
    public static int herb;
    public static int purifyRs;

    //public static List<GameObject> PickList = new List<GameObject>();

    void Start()
    {
        ResourceSetting();
    }
    private void Awake()
    {
        Application.targetFrameRate = 50;
    }

    
    void Update()
    {
        SynchronizeRs();
    }
    void ResourceSetting()
    {
        if (SceneManager.GetActiveScene().name == "MainScene")
        {
            resource = 30;
            purifyRs = 0;
            herb = 0;
        }
        else if(SceneManager.GetActiveScene().name == "Tutorial" || SceneManager.GetActiveScene().name == "ScenePSW")
        {
            resource = int.MaxValue;
        }
    }
    void SynchronizeRs()
    {
        reveal_purifyRs = purifyRs;
        reveal_resource = resource;
        reveal_herb = herb;
    }
}

PurifyLab.cs

정화 가스를 생산하는 작업대용 스크립트

게임 시작 시 임의의 위치에서 산란에 대한 정보를 포함합니다.

더보기

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

public class PurifyLab : MonoBehaviour
{
    (SerializeField) EnemySpawner enemySpawner;
    public bool IsNearLab = false;
    Vector3 spawnloca = Vector3.zero;
    
    private void Start()
    {
        LabSpawn();
    }
    void LabSpawn()
    {
        spawnloca.x = Random.Range(-enemySpawner.spawnPos(0) / 2, enemySpawner.spawnPos(0) / 2);
        spawnloca.z = Random.Range(-enemySpawner.spawnPos(1) / 2, enemySpawner.spawnPos(1) / 2);
        spawnloca.y = 0.5f;
        transform.position = spawnloca;
    }
    private void OnTriggerStay(Collider other)
    {
        if(other.CompareTag("Player"))
        {
            IsNearLab = true;
        }
    }
    private void OnTriggerExit(Collider other)
    {
        if(other.CompareTag("Player"))
        {
            IsNearLab = false;
        }
    }
}

LabUI.cs

근처에 가면 UI가 활성화되고 없으면 UI를 끄는 기능 가스 생산과 관련된 게이지 바에 대한 기능이 포함되어 있습니다.

더보기

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

public class LabUI : MonoBehaviour
{
    Camera cam;
    (SerializeField) Slider gauge;
    (SerializeField) float timer;
    (SerializeField) float purifyTime;
    public bool purifyBt = false;
    (SerializeField) PurifyLab purifyLab;
    (SerializeField) GameObject UI;
    bool manufactComple = false;
    // Start is called before the first frame update
    void Start()
    {
        cam = Camera.main;
        gauge.value = 0;
        gauge.enabled = false;
        UI.SetActive(false);
    }

    // Update is called once per frame
    void Update()
    {
        Purify();
        VisualizeUI();
        //Debug.Log(Time.deltaTime);
    }
    IEnumerator GaugeIncrease()
    {
        gauge.value = 0;
        while(timer < purifyTime)
        {
            transform.rotation = Camera.main.transform.rotation;
            gauge.value = timer;
            yield return new WaitForSeconds(1f);
            timer += 1f;
        }
        manufactComple = true;
        gauge.value = 0;
        yield return null;
    }
    void Purify()
    {
        if(purifyBt)
        {
            if (GameManager.herb >= 5)
            {
                StartCoroutine(GaugeIncrease());
                GameManager.herb -= 5;
                purifyBt = false;
            }
            else
                purifyBt = false;
        }
    }
    public void PurifyButton()
    {
        if (GameManager.herb >= 5) purifyBt = true;
        //Debug.Log("Pressed");
    }
    void VisualizeUI()
    {
        if (purifyLab.IsNearLab)
        {
            UI.SetActive(true);
            if(manufactComple)
            {
                GameManager.purifyRs += 1;
                manufactComple = false;
                AlarmMessage.ins.Message("정화 가스를 획득했습니다.");
            }
        }
        else
        {
            UI.SetActive(false);
        }
    }
}

AlarmMessage.cs

화면에 메시지를 표시하는 UI

코루틴을 사용하여 중복 표시를 방지하고 싱글톤에서 함수 처리를 사용하여 메시지를 편리하게 표시하도록 설계되었습니다.

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

public class AlarmMessage : MonoBehaviour
{
    static AlarmMessage instance;
    public static Coroutine MessageCo;
    Image image;
    Text text;
    private void Awake()
    {
        image = GetComponentInChildren<Image>();
        text = GetComponentInChildren<Text>();
        image.gameObject.SetActive(false);
    }
    public static AlarmMessage ins
    {
        get
        {
            if(instance == null)
            {
                instance = FindObjectOfType<AlarmMessage>();
                if(ins == null)
                {
                    GameObject go = new GameObject();
                    instance = go.AddComponent<AlarmMessage>();
                    go.name = typeof(AlarmMessage).Name;
                }
            }
            return instance;
        }
    }
    public void Message(string contents)
    {
        if(MessageCo == null)
            MessageCo = StartCoroutine(ShowMessage(contents));
    }
    
    IEnumerator ShowMessage(string contents)
    {
        Debug.Log("show");
        text.text = contents;
        StartCoroutine(BlinkMessage());
        yield return new WaitForSeconds(1f);
    }

    IEnumerator BlinkMessage()
    {
        Debug.Log("blink");
        float timer = 0f;
        float blinkTime = 2f;
        float imageA = 0.6f;
        float textA = 1f;
        image.gameObject.SetActive(true);
        image.color = new Color(image.color.r, image.color.g, image.color.b, imageA);
        text.color = new Color(text.color.r, text.color.g, text.color.b, textA);
        while (timer < blinkTime)
        {
            timer += Time.deltaTime;
            imageA -= Time.deltaTime / 4;
            textA -= Time.deltaTime / 4;
            image.color = new Color(image.color.r, image.color.g, image.color.b, imageA);
            text.color = new Color(text.color.r, text.color.g, text.color.b, textA);
            yield return new WaitForSeconds(0.1f);
        }
        image.gameObject.SetActive(false);
        MessageCo = null;
        yield return null;
    }
}