본문 바로가기
Development/C#

멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 53일차

by Mobics 2025. 2. 12.

 

목차


    ※ 강사님의 꿀팁

    : 처음에 어려운 부분부터 만드는 것이 좋다

    --> 구상한대로 구현이 가능한지 확인해야하고, 구현을 했더라도 재미가 없으면 다시 구상을 바꾸거나 Project를 갈아엎어야 하기 때문에

     

    ※ 그럼에도 Logo 먼저 만든 이유는 재미를 붙이기 위해서

     

    ※ 전체적인 윤곽을 잡고 서서히 디테일을 잡아가는 방법이 나쁘지 않다!


    퀴즈 게임 만들기

    25.02.12

    Main Scene

    : Main Scene 추가

     

    >> Main Panel 추가 및 설정

     

    >> Canvas 설정

    ※ 화면의 해상도를 바꿔도 가로의 여백은 동일함

    : Canvas Scaler의 Match를 Width가 기준이 되도록 설정해줬기 때문

     

    └ Logo 추가

    : Logo는 Image 3개를 사용하여 입체적으로 보이도록 만듦

     

    >> Image 3개 생성

    : Top Logo를 만들어서 Source Image를 넣고 복사하여 나머지 Middle, Bottom Logo 생성

     

    >>  Color 수정 및 Hierarchy 위치 변경

    • Top Logo (242, 242, 242, 255)
    • Middle Logo (255, 157, 216, 255)
    • Bottom Logo (181, 37, 124, 255)

     

    1. Script로 만들기

    : 이런 방법도 있음을 알려주신 것, 실제로는 사용 안함

     

    >> LogoController.cs 생성

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using DG.Tweening;
    
    public class LogoController : MonoBehaviour
    {
        [SerializeField] private RectTransform topLogoRectTransform;
        [SerializeField] private RectTransform middleLogoRectTransform;
        [SerializeField] private RectTransform bottomLogoRectTransform;
    
        private void Start()
        {
            // 3개의 로고 이미지의 위치를 변경해서 로고가 입체적으로 보이게 하는 애니메이션 실행
            topLogoRectTransform.DOAnchorPosY(15f, 2f);
            bottomLogoRectTransform.DOAnchorPosY(-15f, 2f);
        }
    }

     

    >> 빈 게임 오브젝트로 Logo 생성

    --> 테스트 하고 Active 비활성화

    2. Animation으로 제작

    : Logo 설정

    --> Width와 Height는 Logo Image의 크기에 같게 수정한 것

     

    >> Logo의 Animation 생성 (이름 : Logo Animation)

    : 두 가지 방법 모두 사용해서 테스트

     

    1. Property로 제작하는 방법

     

    2. 녹화로 제작하는 방법

    --> 다 설정 하고나면 다시 녹화 종료하기!!

     

    --> 120 에는 Canvas Group의 Alpha 값을 1로 변경

    --> 마찬가지로 녹화 종료 잊지않기!!

     

    >> Animation을 로고가 나온 뒤 입체적으로 표현되도록 수정

     

    >> Loop 해제

     

    └ Main 화면

    >> PlayButton

     

    >> Heart Text

    : 임시로 위치 조정하여 배치

     

    >> Stage Text

    : 임시로 위치 조정하여 배치

     

    >> Menu Buttons

    --> Anchor : Alt + Shift

    --> Width 계산 : 네 버튼의 Width가 140이므로 140x4 = 560, 여기에 Spacing이 30이고, 간격은 3개기 때문에 30x3 = 90 따라서 560 + 90 = 650

     

    >> Buttons

    --> 네 버튼 전부 동일하게 설정

     

    >> MainPanelController.cs 생성

     

    GameScene

    >> Game Panel 생성

    --> Color는 (242, 68, 149, 255)

     

    >> Canvas 설정

     

    >> Game Over Button 추가

    : OnClick()에 함수 바인딩

    --> 이후 삭제

     

    >> GamePanelController.cs 생성

     

    └ 게임 구조 짜기

    >> Singleton.cs 가져오기

    : 지난 TicTacToe Project에서

     

    >> GameManager.cs 작성

     

    >> Main Scene에 빈 게임 오브젝트로 Game Manager 추가

    : Transform Reset 및 GameManager.cs 추가

     

    >> Main Panel에 Main Panel Controller.cs 추가 및 바인딩

     

    >> PlayButton의 OnClick()에 함수 바인딩

     

    >> Build Settings에서 Scene 설정

     

    └ 카드 UI 제작

    >> Quiz Card 생성 (Panel로 만듦)

    : 화면의 여백이 해상도와 상관없도록 --> Anchor를 Stretch로 바꾸고 Rect Transform 설정

    --> Color의 Alpha값을 255로 수정

    --> Anchor는 Alt + Shift 

     

    >> 'quiz_card-bg'를 Sprite Editor로 수정

    : 화면의 해상도와 상관없이 라운드 부분이 매끄럽게 표현되도록 (그래서 Quiz Card의 Image Type도 Sliced로)

     

    >> Quiz Card를 Prefab화

    : 이후 Hierarchy에서 Quiz Card 삭제

     

    >> GamePanelController.cs 에서 Quiz Card Instantiate

    : Quiz Card 바운딩

     

    ▶ 카드 UI는 일단 이정도로 해두고 세부적인 건 이후에 제작

     

    └ 카드 생성 (Object Pooling)

    >> ObjectPool.cs 생성

    : 이 코드는 QuizCard에서만 사용할 것이 아니라 다른 Project에서도 간단하게 사용할 수 있을 것 같아서 범용성을 가지도록 이름을 설정함

     

    ※ Object Pool이 생성될 위치(Parent)를 설정하는 두 가지 방법

    1. SetParant를 사용

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class GamePanelController : MonoBehaviour
    {
        [SerializeField] private GameObject quizCardPrefab; // Quiz Card Prefab
        [SerializeField] private Transform quizCardParent;  // Quiz Card가 표시될 UI Parent
    
        private void Start()
        {
            var cardObject = ObjectPool.Instance.GetObject();
            cardObject.transform.SetParent(quizCardParent, false);
        }
    }

     

    2. ObjectPool.cs에서 직접 Parent를 받아서 지정 --> 현재 이 방식을 사용

     

    >> 빈 게임 오브젝트로 Object Pool 생성 후, ObjectPool.cs 추가 및 바인딩

    --> Pool Size가 3인 이유는 Quiz Card의 앞부터 순서대로 1, 2, 3이라고 할 때 1이 사라질 때 3이 나타나야 하기 때문에

     

    >> GameOverButton 삭제

    : GamePanelController.cs에 있던 OnClickGameOverButton() 함수도 삭제

     

    구현해야할 것

    : 이번 게임에서 중요한 인터페이스 구현

     

    1. 3개의 카드를 화면에 표시

    : 1번 카드가 앞에 보이고 2번 카드는 뒤에 살짝, 3번 카드는 안 보이게 배치

     

    2. 카드의 순서를 변경하는 로직

    : 1번이 3번으로, 2번이 1번으로, 3번이 2번으로 바뀌는 함수 구현

     

    최종 코드

    >> LogoController.cs

    : 사용되지 않음

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using DG.Tweening;
    
    public class LogoController : MonoBehaviour
    {
        [SerializeField] private RectTransform topLogoRectTransform;
        [SerializeField] private RectTransform middleLogoRectTransform;
        [SerializeField] private RectTransform bottomLogoRectTransform;
    
        private void Start()
        {
            // 3개의 로고 이미지의 위치를 변경해서 로고가 입체적으로 보이게 하는 애니메이션 실행
            topLogoRectTransform.DOAnchorPosY(15f, 2f);
            bottomLogoRectTransform.DOAnchorPosY(-15f, 2f);
        }
    }

     

    >> Singleton.cs

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.SceneManagement;
    
    public abstract class Singleton<T> : MonoBehaviour where T : Component
    {
        private static T _instance;
    
        public static T Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = FindObjectOfType<T>();
                    if (_instance == null)
                    {
                        GameObject obj = new GameObject();
                        obj.name = typeof(T).Name;
                        _instance = obj.AddComponent<T>();
                    }
                }
                return _instance;
            }
        }
    
        private void Awake()
        {
            if (_instance == null)
            {
                _instance = this as T;
                DontDestroyOnLoad(gameObject);
                // 경우에 따라 첫 Scene의 OnSceneLoaded가 호출이 안 되는 경우를 해결
                OnSceneLoaded(SceneManager.GetActiveScene(), LoadSceneMode.Single);
                
                // Scene 전환 시, 호출되는 Action Method 할당
                SceneManager.sceneLoaded += OnSceneLoaded;
            }
            else
            {
                Destroy(gameObject);
            }
        }
    
        // Destroy 후에는 OnSceneLoaded가 할당하지 않도록
        private void OnDestroy()
        {
            SceneManager.sceneLoaded -= OnSceneLoaded;
        }
    
        protected abstract void OnSceneLoaded(Scene scene, LoadSceneMode mode);
    }

     

    >> MainPanelController.cs

    using System.Collections;
    using System.Collections.Generic;
    using TMPro;
    using UnityEngine;
    
    public class MainPanelController : MonoBehaviour
    {
        [SerializeField] private TMP_Text heartText;    // 남은 하트 수
        [SerializeField] private TMP_Text stageText;    // 현재 스테이지
        
        /// <summary>
        /// Play Button을 눌렀을 때 호출되는 Method
        /// </summary>
        public void OnClickPlayButton()
        {
            GameManager.Instance.StartGame();
        }
    
        #region Main Menu 버튼 클릭 함수
        
        /// <summary>
        /// Shop 아이콘 터치 시, 호출되는 Method
        /// </summary>
        public void OnClickShopButton()
        {
            
        }
    
        /// <summary>
        /// Stage 아이콘 터치 시, 호출되는 Method
        /// </summary>
        public void OnClickStageButton()
        {
            
        }
        
        /// <summary>
        /// Leaderboard 아이콘 터치 시, 호출되는 Method
        /// </summary>
        public void OnClickLeaderboardButton()
        {
            
        }
    
        /// <summary>
        /// Settings 아이콘 터치 시, 호출되는 Method
        /// </summary>
        public void OnClickSettingsButton()
        {
            
        }
    
        #endregion
    }

     

    >> GamePanelController.cs

    : 구현해야할 것 해보다가 중지

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    
    public class GamePanelController : MonoBehaviour
    {
        [SerializeField] private GameObject quizCardPrefab; // Quiz Card Prefab
        [SerializeField] private Transform quizCardParent;  // Quiz Card가 표시될 UI Parent
    
        private void Start()
        {
            InitQuizCard();
        }
    
        private void InitQuizCard()
        {
            var thirdCardObject = ObjectPool.Instance.GetObject();
            var secondCardObject = ObjectPool.Instance.GetObject();
            var firstCardObject = ObjectPool.Instance.GetObject();
    
            thirdCardObject.GetComponent<Image>().color = Color.black;
            secondCardObject.GetComponent<Image>().color = Color.gray;
            
            //secondCardObject.GetComponent<RectTransform>().up = Vector3.up;
        }
    }

     

    >> QuizCardController.cs

    : CardController.cs 에서 이름 변경 + Card.cs 삭제

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class QuizCardController : MonoBehaviour
    {
        
    }

     

    >> GameManager.cs

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.SceneManagement;
    
    public class GameManager : Singleton<GameManager>
    {
        public void StartGame()
        {
            SceneManager.LoadScene("Game");
        }
    
        public void QuitGame()
        {
            SceneManager.LoadScene("Main");
        }
        
        protected override void OnSceneLoaded(Scene scene, LoadSceneMode mode)
        {
            
        }
    }

     

    >> ObjectPool.cs

    : 다른 프로젝트에서도 활용 가능함

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class ObjectPool : MonoBehaviour
    {
        [SerializeField] private GameObject prefab;
        [SerializeField] private int poolSize;
        [SerializeField] private Transform parent;  // Object Pool이 생성될 Parent
    
        private Queue<GameObject> _pool;
        private static ObjectPool _instance; // Singleton을 상속받지 않고 패턴만 사용
    
        public static ObjectPool Instance
        {
            get { return _instance; }
        }
    
        private void Awake()
        {
            _instance = this;
            _pool = new Queue<GameObject>();
    
            for (int i = 0; i < poolSize; i++)
            {
                CreateNewObject();
            }
        }
    
        /// <summary>
        /// Object Pool에 새로운 Object 생성 Method
        /// </summary>
        private void CreateNewObject()
        {
            GameObject newObject = Instantiate(prefab, parent);
            newObject.SetActive(false);
            _pool.Enqueue(newObject);
        }
    
        /// <summary>
        /// Object Pool에 있는 Object를 반환하는 Method
        /// </summary>
        /// <returns>Object Pool에 있는 Object</returns>
        public GameObject GetObject()
        {
            if (_pool.Count == 0) CreateNewObject();
                
            GameObject obj = _pool.Dequeue();
            obj.SetActive(true);
            return obj;
        }
    
        /// <summary>
        /// 사용한 Object를 Object Pool로 되돌려 주는 Method
        /// </summary>
        /// <param name="obj">반환할 Object</param>
        public void ReturnObject(GameObject obj)
        {
            obj.SetActive(false);
            _pool.Enqueue(obj);
        }
    }

     


    C# 단기 교육 보강

    7일차

    Coroutine

    : 실행 주기 조절 가능

     

    ※ 문자열 방식으로도 호출 가능하긴 하다.

    : 하지만 디버깅이 어렵고 매개변수도 1개만 받을 수 있어서 잘 사용하지 않는다.

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class CoroutineEx : MonoBehaviour
    {
        private void Start()
        {
            StartCoroutine(NewCoroutine());
            StartCoroutine("NewCoroutine"); // 문자열 방식의 호출
        }
    
        IEnumerator NewCoroutine()
        {
            yield return null;
        }
    }

     

    ※ Start() + Coroutine

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class CoroutineEx : MonoBehaviour
    {
        IEnumerator Start() // Start함수의 Coroutine화
        {
            Debug.Log("어서오게나, 모험가여");
            yield return new WaitForSeconds(3f);
            Debug.Log("마을을 도와주게");
            yield return new WaitForSeconds(3f);
            Debug.Log("보상은 꼭 챙겨주겠네");
            yield return new WaitForSeconds(3f);
        }
    }

     

    └ Coroutine을 활용한 Fade 기능

    : Fade를 한 상태에서 Scene 전환이나 환경 변화하고 다시 돌아오면 자연스럽게 전환 가능하다.

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    
    public class CoroutineEx : MonoBehaviour
    {
        public Image fadeImage;
        private float _percent;
        private float _timer;
    
        public float fadeTime;
    
        private IEnumerator Start()
        {
            while (_percent < 1f)
            {
                _timer += Time.deltaTime;
                _percent = _timer / fadeTime;
                
                fadeImage.color = new Color(fadeImage.color.r, fadeImage.color.g, fadeImage.color.b, _percent);
                yield return null;
            }
        }
    }

     

    자료구조와 알고리즘

    └ 메모리 구조

    --> 정적 변수 : static

    --> 위쪽으로 갈수록 '낮은 주소', 아래쪽으로 갈수록 '높은 주소'

     

    ※ Garbage Collector(GC)

    : 프로그램에서 더 이상 사용하지 않는 메모리를 자동으로 정리해주는 소프트웨어 구성요소 --> Heap을 관리해준다.

     

    └ 자료구조

    >> 배열 (Array)

    : 메모리 공간을 미리 만들어두고 사용하는 자료구조 --> 정적 배열

     

    >> 문자열(String)

     

    >> 리스트 (List)

    : 메모리 공간을 유동적으로 사용하는 자료구조 --> 동적 배열

     

    >> 스택 (Stack)

    : 나중에 추가된 데이터가 가장 먼저 나오는 구조 --> LIFO(Last In First Out)

     

    >> 큐 (Queue)

    : 먼저 추가된 데이터가 가장 먼저 나오는 구조 --> FIFO (First In First Out)

     

    >> 딕셔너리 (Dictionary)

    : 키(Key)와 값(Value)로 이루어진 자료구조 --> Map 방식

     

    Hanoi Tower

    >> Board 생성 (Cube로 제작)

     

    >> Bar 3개 생성 (Cylinder로 제작)

    --> Scale과 Position.Y, Position.Z는 3개 전부 동일

    --> Position.x를 Center Bar는 0, Right Bar는 3으로 설정

     

    >> Material을 만들어서 색 입히기

    : Right Bar는 목표 기둥이기 때문에 다른 색으로 표시

     

    >> ProBuilder

    : 3D 모델링을 만들 수 있는 Tool

     

    >> Package Manager로 설치

     

    >> ProBuilder를 사용하여 도넛 모양 만들기

     

    >> 수치 조정하기

     

    >> Pivot 바로잡기

    : 빈 게임 오브젝트로 부모 오브젝트를 만들어서 해결

    --> 이름 변경

    --> 색도 자유롭게 변경

     

    >> 만든 Donut을 Prefab화