Unity

[타워 디펜스] 플레이어 만들기

wny0320 2022. 12. 30. 18:08

만들기 시작한지는 좀 되었지만 이제와서 윤곽이 잡혀서 올림

 

만들고자 하는 타워 디펜스의 아이디어들

굳이 비슷한 게임을 찾자면 몬스터 아웃브레이크와 비슷하다고 생각함

 

팀 프로젝트로 맡은 부분이 플레이어 부분이기 때문에 플레이어에 대해서만 다룰 것임

 

플레이어의 기능은 위의 기능과 이동이 있음

 

아래는 여태껏 구현해온 코드임

 

//Player.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour
{
    //IMove 관련 필드
    float moveSpeed;
    Vector3 inputDir;
    Vector3 moveDir;
    CharacterController controller;

    //Health 관련 필드
    Health health;
    int healthBuffer;

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

    //피격 관련 필드
    bool canDamaged = true;
    float unBeatTime = 1.0f;
    Material material;

    //Build 관련 필드
    GameObject buildUI;
    bool isEnabledUI;
    public bool buildMode;
    enum towerList
    {
        BasicTower = 0,
        PunchTower = 1,
        LaserTower = 2
    }
    [SerializeField]
    GameObject[] towerObjList;
    public int choice;

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

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

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

        //Build에 쓰일 필드 초기화
        buildUI = GameObject.Find("BuildUI");
        buildUI.SetActive(false);
        isEnabledUI = false;
        buildMode = false;
}

    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"))
                {
                    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;*/
                    //오브젝트 콜라이더 제외 비활성화
                    //상속
                }
                else if (Target.tag == "Resource")
                {
                    GameManager.resource++;
                    //GameManager.PickList.Remove(Target);
                    Destroy(Target);
                    //자원 추가
                }
            }
            else
            {
                //기준 거리 밖에 있다면
                //거리가 멀다고 메세지 띄우기?
            }
        }
        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는 음수, 양수로 받아 계산
    }

    private void OnTriggerStay(Collider collider)
    {
        //체력 깎는거 Enemy에서 공격력 수치만 불러올 수 있게 설정
        if(canDamaged && collider.name == "Enemy")
        {
            canDamaged = false;
            Debug.Log("Hit");
            health.healthCheck--;
        }
        else
        {
            return;
        }
    }


    //무적 코루틴
    IEnumerator UnBeat()
    {
        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, 142, 0, 255);
            }
            yield return new WaitForSeconds(0.25f);
        }
        material.color = new Color32(255, 142, 0, 255);

        canDamaged = true;

        yield return null;
    }

    void RepairMenu()
    {
        //혹은 한번에 1씩 수리
        //타워 스크립트 안에서 인수를 만들거나 체력 인수로 조합하여 필요 자원값 설정 필요
        if (Input.GetKeyDown(KeyCode.R))
        {
            if (isEnabledUI)
            {
                isEnabledUI = false;
                buildUI.SetActive(false);
            }
            else
            {
                isEnabledUI = true;
                buildUI.SetActive(true);
            }
        }
    }


    void BuildMenu()
    {
        if(Input.GetKeyDown(KeyCode.B))
        {
            if(isEnabledUI)
            {
                isEnabledUI = false;
                buildUI.SetActive(false);
            }
            else
            {
                isEnabledUI = true;
                buildUI.SetActive(true);
            }
        }
    }

    void BuildMode()
    {
        if(buildMode)
        {
            switch(choice)
            {
                case (int)towerList.BasicTower:
                    buildMode = false;
                    Instantiate(towerObjList[(int)towerList.BasicTower],transform.position,new Quaternion(0, 0, 0, 0));
                    choice = -1;
                    break;

                case (int)towerList.PunchTower:
                    buildMode = false;
                    Instantiate(towerObjList[(int)towerList.PunchTower], transform.position, new Quaternion(0, 0, 0, 0));
                    choice = -1;
                    break;

                case (int)towerList.LaserTower:
                    buildMode = false;
                    Instantiate(towerObjList[(int)towerList.LaserTower], transform.position, new Quaternion(0, 0, 0, 0));
                    choice = -1;
                    break;
            }
        }
    }

    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);
    }
    void Update()
    {
        Move();
        OnPick();
        BuildMenu();
        BuildMode();
    }

    void LateUpdate()
    {
        //체력이 변했는지 체크
        if(healthBuffer != health.healthCheck)
        {
            //체력 변환 체크값을 현재 체력으로 바꿔줌
            healthBuffer = health.healthCheck;
            StartCoroutine("UnBeat");
        }
    }
}

현재 플레이어의 이동, 무적, 타워의 이동과 건설까지는 구현이 되었음

 

다음은 이와 연관되는 UI에 들어가는 스크립트임

 

버튼을 누르면 동작하게 간단히 구현하였음

 

//Build.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Build : MonoBehaviour
{
    Player player;
    void Start()
    {
        player = GameObject.Find("Player").GetComponent<Player>();
    }
    public void BuildBasic()
    {
        IsBuildMode();
        player.choice = 0;
    }
    public void BuildPunch()
    {
        IsBuildMode();
        player.choice = 1;
    }
    public void BuildLaser()
    {
        IsBuildMode();
        player.choice = 2;
    }
    void IsBuildMode()
    {
        player.buildMode = true;
    }
}

버튼에 따라 타워의 종류를 0~2로 주고 enum으로 player 안에서는 타워의 종류를 개발자가 알기 쉽게 만듦

 

아래는 구현된 현재 상황을 동영상으로 찍어봄