6 minute read

(디펜스 게임)

1. 작업 의도

:blush: 전략, RPG, 디펜스, 로그라이크 게임으로 유저가 직접 방어타워와 공격 건축물을 건설하고 강화하여 디펜스하는 게임입니다.

title

title

중세 판타지를 배경으로 '마왕군에 의해 세계가 멸망하고 희망없이 살아가던 어느날, 
세계의 희망이 될 세계수 묘목을 발견하게 되고, 세계수를 성장시켜 세계가 균형을 되찾을 수 있도록 하기 위해 
수호군은 4명의 전투 수리공을 그 곳으로 파견하여 그곳의 시설을 이용해 세계수가 다 성장 할 때까지 지키게 한다.' 
라는 스토리를 바탕으로 하고 있습니다.

2. 작업 과정

:runner: 소요 기간: 한달

:speech_balloon: Unity, Blender, Photoshop으로 제작

  • 팀명: 개발새발팀
  • 팀원: 프로그래밍 5명 (총 5명)
  • 파트: 프로그래밍(카메라, 씬 로드, 커서), 그래픽(타이틀, 로딩씬, 3d 리소스), 사운드

개발자들로만 구성되어 있어서 개발새발팀으로 명명하였습니다. 인원이 많아서 프로그래밍에서는 카메라, 씬로드, 커서, 사운드, 그래픽쪽을 나눠서 맡게 되었습니다.

카메라 무빙

public class CameraController : MonoBehaviour
{    
    /*  <카메라 컨트롤러 기능 구현>
     * 1. 마우스휠 -> 줌인, 줌아웃
     * 2. 더블 클릭 -> 캐릭터 -> 캐릭터 위치로
     * 3. 더블 클릭 -> 지형 -> 클릭한 위치로 줌
     * 4. 화면 테두리에 마우스 위치시 -> 카메라 이동 (조이스틱처럼)
     */

    private float wheelspeed = 20.0f;
    public Camera mainCamera;    
    public float CamMoveSpeed = 30.0f;

    public GameObject Target; //화면 정중앙에 위치한 빈 게임오브젝트
    Vector3 TargetPos;

    //카메라 위치 고정값
    public float offsetX = 0.0f;
    public float offsetY = 10.0f;
    public float offsetZ = -10.0f;

    //줌인 줌아웃 범위 설정
    public float ZoomIn = 0.5f;
    public float ZoomOut = 100.0f;       

    void Update()
    {       
         Zoom();  //카메라 휠 줌인, 줌아웃        
    }

    private void FixedUpdate()
    {
        //더블클릭, 마우스 화면 테두리 위치시에 빈오브젝트 움직임 -> 카메라 추적
        TargetPos = new Vector3(Target.transform.position.x + offsetX,
                                Target.transform.position.y + offsetY,
                                Target.transform.position.z + offsetZ);
        transform.position = Vector3.Lerp(transform.position, TargetPos, Time.deltaTime * CamMoveSpeed);
    }
    #region 줌인,줌아웃
    private void Zoom()
    {        
        float distance = Input.GetAxis("Mouse ScrollWheel") * -1 * wheelspeed;
        if (distance != 0)
            mainCamera.fieldOfView += distance;
        if (mainCamera.fieldOfView < ZoomIn)
            mainCamera.fieldOfView = ZoomIn;
        if (mainCamera.fieldOfView > ZoomOut)
            mainCamera.fieldOfView = ZoomOut;        
    }
    #endregion 줌인,줌아웃  
}

카메라 무빙2

public class MousePick : MonoBehaviour
{
    static MousePick _instance;
    public static MousePick Instance { get { return _instance; } }

    public float moveSpeed = 20.0f;       
    public float DoubleClickSecond = 0.25f;
    private bool IsOneClick = false;
    private double Timer = 0;    
    public bool isMove;
    public Vector3 destination;
    public bool IsDoubleClick = false;
    public Camera mainCamera;
    public float view = 60;

    void Start()
    {
        Init();
    }       
    void Update()
    {       
        DoubleClicked();
        Move();
        Debug.Log(destination);

        Vector2 MousePosition = Input.mousePosition;

        if ((MousePosition.x <= 0) || (MousePosition.y <= 0)
             || (MousePosition.x >= Screen.width) || (MousePosition.y >= Screen.height))
        {
            MoveScreen(MousePosition);
        }
    }
    #region 더블클릭 인식
    public void DoubleClicked()
    {
        if (IsOneClick && ((Time.time - Timer) > DoubleClickSecond))
        {
            IsOneClick = false;
            IsDoubleClick = false;
        }
        if (Input.GetMouseButtonDown(0))
        {
            if (!IsOneClick)
            {
                Timer = Time.time;
                IsOneClick = true;
                IsDoubleClick = false;
            }
            else if (IsOneClick && ((Time.time - Timer) < DoubleClickSecond))
            {
                Debug.Log("Double Click");
                //if (ResourceManager.Instance != null) ResourceManager.Instance.Destination(destination);
                IsDoubleClick = true;

                IsOneClick = false;

                RaycastHit hit;
                if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit))
                {
                    destination = hit.point;
                    isMove = true;                    
                }                
            }
        }
    }
    #endregion 더블클릭 인식

    #region 빈오브젝트 이동
    public void Move()
    {        
        if (isMove)
        {
            bool isAlived = Vector3.Distance(destination, transform.position) <= 0.1f;
            if (isAlived)
            {
                isMove = false;                                
            }
            else
            {
                //Vector3 direction = destination - transform.position;
                //transform.forward = direction;
                //transform.position += direction.normalized * moveSpeed * Time.deltaTime;
                transform.position = Vector3.MoveTowards(transform.position, destination, Time.deltaTime * moveSpeed);
                mainCamera.fieldOfView = view;
            }
        }
    }
    #endregion 빈오브젝트 이동

    #region 화면모서리 이동
    private void MoveScreen(Vector2 MousePosition)
    {
        if (MousePosition.x <= 0)
            transform.position += new Vector3(-1, 0, 0) * Time.deltaTime * moveSpeed;
        else if (MousePosition.y <= 0)
            transform.position += new Vector3(0, 0, -1) * Time.deltaTime * moveSpeed;
        else if (MousePosition.x >= Screen.width)
            transform.position += new Vector3(1, 0, 0) * Time.deltaTime * moveSpeed;
        else if (MousePosition.y >= Screen.height)
            transform.position += new Vector3(0, 0, 1) * Time.deltaTime * moveSpeed;
        else
            return;
    }
    #endregion 화면 모서리 이동

    static void Init()
    {
        if (_instance == null)
        {
            GameObject go = GameObject.Find("Target");
            if (go == null)
            {
                go = new GameObject { name = "Target" };
                go.AddComponent<MousePick>();
            }
            DontDestroyOnLoad(go);
            _instance = go.GetComponent<MousePick>();
        }
    }
}

마우스가 화면 상하좌우에 위치시에 스타크래프트처럼 화면이 이동하도록 하는 기능과 줌인 줌아웃, 더블 클릭시에 줌이 해제되고 화면의 중앙이 좌표로 이동하도록 하였습니다.

목적지 표시

public class ResourceManager : MonoBehaviour
{    
    static ResourceManager _instance;
    public static ResourceManager Instance { get { return _instance; } }

    private GameObject obj1;
    private GameObject obj2;
    private bool isdc = false;
    private bool next = false;
    private float frameTimer = 0.2f;
    private float currentTimer;

    private void Start()
    {
        Init();
        obj1 = Resources.Load<GameObject>("Prefabs/click1");
        obj2 = Resources.Load<GameObject>("Prefabs/click2");      
    }

    private void Update()
    {
        GameObject obj1Clone = GameObject.Find("click1(Clone)");
        GameObject obj2Clone = GameObject.Find("click2(Clone)");
        currentTimer += Time.deltaTime;        

        if (MousePick.Instance.IsDoubleClick)
        {
            Vector3 a = MousePick.Instance.destination;
            a.y = 1;
            obj1.transform.position = a;
            obj2.transform.position = a;           
            isdc = true;
            MousePick.Instance.IsDoubleClick = false;                       
        }       
            if (isdc && next == false)
            {
                Instantiate(obj1);
                isdc = false;
                currentTimer = 0;
            }
            if (obj1Clone != null && currentTimer >= frameTimer)
            {
                Destroy(obj1Clone);
                next = true;                
            }
            if (next && isdc == false)
            {                
                Instantiate(obj2);
                next = false;
                currentTimer = 0;              
            }
            if (obj2Clone != null && currentTimer >= frameTimer)
            {
                Destroy(obj2Clone);                
                currentTimer = 0;
                //if (MousePick.Instance.destination == MousePick.Instance.transform.position) isdc = false;
                if (MousePick.Instance.isMove) isdc = true;
                else isdc = false;
            }         
    }    
}

더블 클릭시에 바닥에 목적지를 표시하는 이미지가 뜨도록 하였습니다.

씬 로드

public class LoadingSceneManager : MonoBehaviour
{
    public static string nextScene;
    [SerializeField] Image progressBar;

    private void Start()
    {
        StartCoroutine(LoadScene());
    }

    public static void LoadScene(string sceneName)
    {
        nextScene = sceneName;
        SceneManager.LoadScene("LoadingScene");
    }

    IEnumerator LoadScene()
    {
        yield return null;
        AsyncOperation op = SceneManager.LoadSceneAsync(nextScene);
        op.allowSceneActivation = false;
        float timer = 0.0f;
        while (!op.isDone)
        {
            yield return null;
            timer += Time.deltaTime;
            if (op.progress < 0.9f)
            {
                progressBar.fillAmount = Mathf.Lerp(progressBar.fillAmount, op.progress, timer);
                if (progressBar.fillAmount >= op.progress)
                {
                    timer = 0f;
                }
            }
            else
            {
                progressBar.fillAmount = Mathf.Lerp(progressBar.fillAmount, 1f, timer);
                if (progressBar.fillAmount == 1.0f)
                {
                    op.allowSceneActivation = true;
                    yield break;
                }
            }
        }
    }

}
public class Title : MonoBehaviour
{
    AudioSource audioSource;
    Collider2D collision;
    public GameObject Button;

    public void OnTriggerExit2D(Collider2D collision)
    {                
        SceneManager.LoadScene("MainScene"); //클릭했을때 로드할 씬 이름
    }

    public void Press_Play()
    {
        audioSource = Button.gameObject.GetComponent<AudioSource>();
        this.audioSource.Play();
        OnTriggerExit2D(collision);
    }
}

타이틀 화면에서 스타트 버튼을 클릭시에 로딩화면을 거쳐서 메인 화면으로 넘어갈 수 있도록 코딩하였습니다.

마우스커서

public class CursorController : MonoBehaviour
{
    static CursorController _instance;
    public static CursorController Instance { get { return _instance; } }
    public List<CursorAnimation> cursorAnimationList;
    public CursorAnimation cursorAnimation;
    private int currentFrame;
    private float frameTimer;
    private int frameCount;

    public enum CursorType
    {
        Arrow,
        Grab
    }

    public void Start()
    {
        SetActiveCursorAnimation(cursorAnimationList[0]);
        Init();        
    }

    private void Update()
    {
        frameTimer -= Time.deltaTime;
        if (frameTimer <= 0f)
        {
            frameTimer += cursorAnimation.frameRate;
            currentFrame = (currentFrame + 1) % frameCount;
            Cursor.SetCursor(cursorAnimation.textureArray[currentFrame],cursorAnimation.offset, CursorMode.Auto);
        }               
        //if (gameObject.CompareTag("Wall")) SetActiveCursorAnimation(cursorAnimationList[1]);      
    }

    public void SetActiveCursorAnimation(CursorAnimation cursorAnimation)
    {
        this.cursorAnimation = cursorAnimation;
        currentFrame = 0;
        frameTimer = cursorAnimation.frameRate;
        frameCount = cursorAnimation.textureArray.Length;
    }

    [System.Serializable]
    public class CursorAnimation
    {
        public CursorType cursorType;
        public Texture2D[] textureArray;
        public float frameRate;
        public Vector2 offset;
    }

    static void Init()
    {
        if (_instance == null)
        {
            GameObject go = GameObject.Find("@CursorManager");
            if (go == null)
            {
                go = new GameObject { name = "@CursorManager" };
                go.AddComponent<CursorController>();
            }
            DontDestroyOnLoad(go);
            _instance = go.GetComponent<CursorController>();
        }        
    }       
}
public class CursorObject : MonoBehaviour
{
    //[SerializeField] private CursorController.CursorType cursorType;

    //마우스가 오브젝트 안으로 들어가면 커서가 손 모양으로 바뀜
    private void OnMouseEnter()
    {
        CursorController.Instance.SetActiveCursorAnimation(CursorController.Instance.cursorAnimationList[1]);
        Debug.Log("성벽 터치");
    }

    private void OnMouseExit()
    {
        CursorController.Instance.SetActiveCursorAnimation(CursorController.Instance.cursorAnimationList[0]);
    }
}

마우스 커서를 바꿔주었고 성벽을 터치하면 성벽 터치 로그를 표시하고 커서가 변경되도록 하였습니다.

3. 결과물

title

title

title

:thought_balloon: 게임을 제작해본적 없는 열정 가득한 프로그래머 5명이 모였으나 용두사미로 끝나서 아쉬웠습니다. 깃허브를 사용하지 않고 각자 맡은 부분을 코딩하고 취합하려다 보니 마무리가 잘 되지 않았습니다. 아카데미가 끝나고 프로젝트를 제대로 마무리하기로 하였으나 게임 업계에 취업이 되었거나, 취업을 비희망하는 팀원이 발생함에 따라 마무리가 잘 되지 않았습니다. 매우 아쉬웠습니다. 다음에 비슷한 상황이 생기게 된다면 꼭 깃허브를 이용해서 작업하고, 폴더 정리나 리소스 관리에 관한 규칙을 정해서 좋은 결과물이 나올 수 있도록 하고 싶습니다.

:books: **깃허브 주소