본문 바로가기
Development/C#

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

by Mobics 2025. 2. 5.

 

목차


    틱택토 게임 만들기

    25.02.05

    ※ Project의 Default Font 설정

    : Project Settings에서 font를 검색한 뒤, 폰트 수정

     

    Canvas

    >> Canvas에 있는 PanelManager.cs에 StartPanel 바인딩

     

    >> GameManager에 Canvas 바인딩

     

    StartPanel 만들기

    1. 빈 게임 오브젝트로 Buttons를 만들어서 StartButton을 자식으로 넣기

    : PosY를 -300으로, Width와 Height를 400으로 설정 --> 이후 제대로 된 버튼 이미지를 넣으면 다시 수정될 수 있음

    ※ Anchor는 그대로 middle-center

     

    2. Title Text 추가

    --> Font Size 140으로 늘림

    3. Button

    --> Image Type을 Sliced 타입으로 사용해도 되지만, 이번에는 Simple로 해봄 (디자인 형태에 따라 적용)

    --> Set Native Size를 눌러서 사이즈 때문에 모서리가 깨진 부분을 수정

     

    >> Set Native Size를 누르면 Size가 바뀌기 때문에 다시 조정

     

    >> Button의 Text 수정

     

    >> StartButton을 복사하여 Button 3개 생성 및 세팅

    --> 이름 : SinglePlayButton, DualPlayButton, SettingsButton

    --> Buttons에 Vertical Layout Group을 추가하여 세로로 정렬

    --> 각 버튼들의 Text도 수정

     

    >> 각 Button들의 OnClick() 수정

    --> DualPlayButton과 SettingsButton에도 각각 맞게 할당

     

    ConfirmPanel 만들기

    ※ panel-bg 삽입

     

    1. Panel

    --> Color의 Alpha값 255로 수정

     

    2. ConfirmPanel

     

    3. MessageText

    --> Text를 "게임을 종료하시겠습니까?" 로 수정

    4. OK Button

     

    5. Close Button

    --> Anchor는 Alt + Shift로 설정

    --> Text 삭제

     

    Settings Panel 만들기

    1. ConfirmPanel을 복붙하고 세팅

    --> CloseButton 빼고 전부 삭제

     

    2. Toggle 생성

    : SFXToggle, BGMToggle

    --> 화면상에 보이도록 PosY만 내림

    --> 지금은 임시로 만든거고, 나중에 다듬을 예정

     

    GameUIPanel 만들기

    : 다른 Panel에 가리지 않도록 Hierarchy에서 가장 위에 위치

     

    >> PlayerAPanel 만들기

    : Canvas Group 추가 --> 코드로 모든 자식 Object의 Alpha값을 수정 가능하도록

     

    >> Player1Marker 만들기

    : Image 생성

    --> Anchor는 Shift + Alt

     

    >> Player1Text 만들기

    --> Anchor는 Shift + Alt

    --> Alignment는 중앙 정렬

    --> Color는 내가 임의로 넣은 것 (0, 166, 255, 255)

     

    >> PlayerB는 PlayerA 복붙

    : Player2Marker의 Transform 조정 --> 'X' Sprite의 크기가 142x142로 'O'와 달라서 조정

     

    ※ Player2Text의 Color는 (255, 0, 94, 255)

     

    >> GameOverButton 만들기

     

    ※ Horizontal Layout Group의 꼼수 활용 : Active 돼있는 것들을 정렬해주기 때문에 코드로 SetActive를 활용하여 UI 배치

     

    >> GameManager에 바인딩

     

    Main Scene 만들기

    : Main Scene을 만들어서 StartPanel만 따로 배치

     

    1. Build Settings

    : Main Scene 다음 Game Scene이 되도록 배치

     

    2. Game Scene에 있는 StartPanel을 Main Scene으로 이동

    : Prefab으로 만들어서 이동 후, 좌표 조정

     

    3. 다시 Prefab을 해제

    : Main Scene으로 옮겨오기 위해 Prefab화 한 것이기 때문 --> 만든 Prefab도 삭제

     

    4. StartPanel을 MainPanel으로 이름 변경 후 설정

    : StartPanelController.cs을 삭제하고 MainPanelController.cs를 추가

     

    5. 버튼에 각각 함수 바인딩

    --> DualPlayButton과 SettingsButton도 각각 맞게 바인딩

     

    6. Game Scene 수정

    : StartPanel이 없어짐에 따라 코드 수정 및 StartPanelController.cs 삭제

     

    최종 코드

    >> GameManager.cs

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class GameManager : Singleton<GameManager>
    {
        [SerializeField] private BlockController blockController;
        [SerializeField] private PanelManager panelManager;
        [SerializeField] private GameUIController gameUIController;
    
        public enum PlayerType { None, PlayerA, PlayerB }
        private PlayerType[,] _board;
        
        public enum TurnType { PlayerA, PlayerB }
        private enum GameResult { None, Win, Lose, Draw }
    
        private void Start()
        {
            // 게임 초기화
            InitGame();
        }
    
        /// <summary>
        /// 게임 초기화 함수 
        /// </summary>
        public void InitGame()
        {
            // _board 초기화
            _board = new PlayerType[3, 3];
            
            // Block 초기화
            blockController.InitBlocks();
            
            // Game UI 초기화
            gameUIController.SetGameUIMode(GameUIController.GameUIMode.Init);
            
            // 게임 스타트
            StartGame();
        }
    
        /// <summary>
        /// 게임 시작
        /// </summary>
        public void StartGame()
        {
            //panelManager.ShowPanel(PanelManager.PanelType.BattlePanel);
            SetTurn(TurnType.PlayerA);
        }
        
        /// <summary>
        /// 게임 오버 시, 호출되는 함수
        /// gameResult에 따라 결과 출력
        /// </summary>
        /// <param name="gameResult">win, lose, draw</param>
        private void EndGame(GameResult gameResult)
        {
            // 게임오버 표시
            gameUIController.SetGameUIMode(GameUIController.GameUIMode.GameOver);
            
            // TODO: 나중에 구현!
            switch (gameResult)
            {
                case GameResult.Win:
                    break;
                case GameResult.Lose:
                    break;
                case GameResult.Draw:
                    break;
            }
        }
    
        /// <summary>
        /// _board에 새로운 값을 할당하는 함수
        /// </summary>
        /// <param name="playerType">할당하고자 하는 플레이어 타입</param>
        /// <param name="row">Row</param>
        /// <param name="col">Col</param>
        /// <returns>False : 할당할 수 없음, True : 할당이 완료됨</returns>
        private bool SetNewBoardValue(PlayerType playerType, int row, int col)
        {
            if (playerType == PlayerType.PlayerA)
            {
                _board[row, col] = playerType;
                blockController.PlaceMarker(Block.MarkerType.O, row, col);
                return true;
            }
            else if (playerType == PlayerType.PlayerB)
            {
                _board[row, col] = playerType;
                blockController.PlaceMarker(Block.MarkerType.X, row, col);
                return true;
            }
            return false;
        }
    
        private void SetTurn(TurnType turnType)
        {
            switch (turnType)
            {
                case TurnType.PlayerA:
                    gameUIController.SetGameUIMode(GameUIController.GameUIMode.TurnA);
                    blockController.OnBlockClickedDelegate = (row, col) =>
                    {
                        if (SetNewBoardValue(PlayerType.PlayerA, row, col))
                        {
                            var gameResult = CheckGameResult();
                            if (gameResult == GameResult.None)
                                SetTurn(TurnType.PlayerB);
                            else
                                EndGame(gameResult);
                        }
                        else
                        {
                            // TODO: 이미 있는 곳을 터치 했을 때 처리
                        }
                    };
                    break;
                case TurnType.PlayerB:
                    gameUIController.SetGameUIMode(GameUIController.GameUIMode.TurnB);
                    blockController.OnBlockClickedDelegate = (row, col) =>
                    {
                        if (SetNewBoardValue(PlayerType.PlayerB, row, col))
                        {
                            var gameResult = CheckGameResult();
                            if (gameResult == GameResult.None)
                                SetTurn(TurnType.PlayerA);
                            else
                                EndGame(gameResult);
                        }
                        else
                        {
                            // TODO: 이미 있는 곳을 터치 했을 때 처리
                        }
                    };
                    //TODO: AI에게 입력 받기
                    
                    break;
            }
        }
    
        /// <summary>
        /// 게임 결과 확인 함수
        /// </summary>
        /// <returns>플레이어 기준 게임 결과</returns>
        private GameResult CheckGameResult()
        {
            if (CheckGameWin(PlayerType.PlayerA)) return GameResult.Win;
            if (CheckGameWin(PlayerType.PlayerB)) return GameResult.Lose;
            if (IsAllBlocksPlaced()) return GameResult.Draw;
            
            return GameResult.None;
        }
        
        // 모든 마커가 보드에 배치 되었는지 확인하는 함수
        private bool IsAllBlocksPlaced()
        {
            for (var row = 0; row < _board.GetLength(0); row++)
            {
                for (var col = 0; col < _board.GetLength(1); col++)
                {
                    if (_board[row, col] == PlayerType.None)
                        return false;
                }
            }
            return true;
        }
        
        // 게임의 승패를 판단하는 함수
        private bool CheckGameWin(PlayerType playerType)
        {
            // 가로로 마커가 일치하는지 확인 
            for (var row = 0; row < _board.GetLength(0); row++)
            {
                if (_board[row, 0] == playerType && _board[row, 1] == playerType && _board[row, 2] == playerType)
                {
                    (int, int)[] blocks = { (row, 0), (row, 1), (row, 2) };
                    blockController.SetBlockColor(playerType, blocks);
                    return true;
                }
            }
            
            // 세로로 마커가 일치하는지 확인
            for (var col = 0; col < _board.GetLength(1); col++)
            {
                if (_board[0, col] == playerType && _board[1, col] == playerType && _board[2, col] == playerType)
                {
                    (int, int)[] blocks = { (0, col), (1, col), (2, col) };
                    blockController.SetBlockColor(playerType, blocks);
                    return true;
                }
            }
            
            // 대각선으로 마커가 일치하는지 확인
            if (_board[0, 0] == playerType && _board[1, 1] == playerType && _board[2, 2] == playerType)
            {
                (int, int)[] blocks = { (0, 0), (1, 1), (2, 2) };
                blockController.SetBlockColor(playerType, blocks);
                return true;
            }
    
            if (_board[0, 2] == playerType && _board[1, 1] == playerType && _board[2, 0] == playerType)
            {
                (int, int)[] blocks = { (0, 2), (1, 1), (2, 0) };
                blockController.SetBlockColor(playerType, blocks);
                return true;
            }
            
            return false;
        }
    }

     

    >> PanelManager.cs

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class PanelManager : MonoBehaviour
    {
        [SerializeField] private PanelController confirmPanelController;
        [SerializeField] private PanelController settingsPanelController;
        //[SerializeField] private PanelController battlePanelController;
        
        public enum PanelType { ConfirmPanel, SettingsPanel, BattlePanel }
    
        private PanelController _currentPanelController;
    
        /// <summary>
        /// 표시할 패널 정보 전달하는 함수
        /// </summary>
        /// <param name="panelType">표시할 패널</param>
        public void ShowPanel(PanelType panelType)
        {
            switch (panelType)
            {
                case PanelType.ConfirmPanel:
                    ShowPanelController(confirmPanelController);
                    break;
                case PanelType.SettingsPanel:
                    ShowPanelController(settingsPanelController);
                    break;
                //case PanelType.BattlePanel:
                //    ShowPanelController(battlePanelController);
                //    break;
            }
        }
    
        /// <summary>
        /// 패널을 표시하는 함수
        /// 기존 패널이 있으면 Hide하고 새로운 패널을 Show 함
        /// </summary>
        /// <param name="panelController">표시할 패널</param>
        private void ShowPanelController(PanelController panelController)
        {
            if (_currentPanelController != null)
                _currentPanelController.Hide();
            panelController.Show(() =>
            {
                _currentPanelController = null;
            });
            _currentPanelController = panelController;
        }
    }

     

    >> PanelController.cs

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    [RequireComponent(typeof(RectTransform))]
    public class PanelController : MonoBehaviour
    {
        public bool IsShow { get; private set; }
    
        public delegate void OnHide();
        private OnHide _onHideDelegate;
    
        private RectTransform _rectTransform;
        private Vector2 _hideAnchorPosition;
    
        private void Awake() // GameManager에서 Start에 InitGame()을 해주기 때문에 그 전에 Awake에서 세팅 
        {
            _rectTransform = GetComponent<RectTransform>();
            _hideAnchorPosition = _rectTransform.anchoredPosition; // 숨겨둔 StartPanel의 Anchor 위치 저장
            IsShow = false;
        }
    
        /// <summary>
        /// Panel 표시 함수
        /// </summary>
        public void Show(OnHide onHideDelegate)
        {
            _onHideDelegate = onHideDelegate;
            _rectTransform.anchoredPosition = Vector2.zero;
            IsShow = true;
        }
        
        /// <summary>
        /// Panel 숨기기 함수
        /// </summary>
        public void Hide()
        {
            _rectTransform.anchoredPosition = _hideAnchorPosition;
            IsShow = false;
            _onHideDelegate?.Invoke();
        }
    }

     

    >> StartPanelController.cs

    : 마지막에 삭제됨

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class StartPanelController : PanelController
    {
        public void OnClickSinglePlayButton()
        {
            GameManager.Instance.StartGame();
            Hide();
        }
    
        public void OnClickDualPlayButton()
        {
            
        }
        
        public void OnClickSettingsButton()
        {
            
        }
    }

     

    >> ConfirmPanelController.cs

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using TMPro;
    using UnityEngine;
    
    public class ConfirmPanelController : PanelController
    {
        [SerializeField] private TMP_Text messageText;
    
        public delegate void OnConfirmButtonClick();
        private OnConfirmButtonClick onConfirmButtonClick;
    
        public void Show(string message, OnConfirmButtonClick onConfirmButtonClick, OnHide onHide)
        {
            messageText.text = message;
            this.onConfirmButtonClick = onConfirmButtonClick;
            base.Show(onHide);
        }
    
        /// <summary>
        /// Confirm 버튼 클릭 시 호출되는 함수
        /// </summary>
        public void OnClickConfirmButton()
        {
            onConfirmButtonClick?.Invoke();
            Hide();
        }
        
        /// <summary>
        /// X 버튼 클릭 시 호출되는 함수
        /// </summary>
        public void OnClickCloseButton()
        {
            Hide();
        }
    }

     

    >> SettingsPanelController.cs

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class SettingsPanelController : PanelController
    {
        /// <summary>
        /// SFX On/Off시 호출되는 함수
        /// </summary>
        /// <param name="value">On/Off 값</param>
        public void OnSFXToggleValueChanged(bool value)
        {
            
        }
        
        /// <summary>
        /// BGM On/Off시 호출되는 함수
        /// </summary>
        /// <param name="value">On/Off 값</param>
        public void OnBGMToggleValueChanged(bool value)
        {
            
        }
        
        /// <summary>
        /// X 버튼 클릭 시 호출되는 함수
        /// </summary>
        public void OnClickCloseButton()
        {
            Hide();
        }
    }

     

    >> BlockController.cs

    using UnityEngine;
    
    public class BlockController : MonoBehaviour
    {
        [SerializeField] private Block[] blocks;
        
        public delegate void OnBlockClicked(int row, int col);
        public OnBlockClicked OnBlockClickedDelegate;
    
        public void InitBlocks()
        {
            for (int i = 0; i < blocks.Length; i++)
            {
                blocks[i].InitMarker(i, blockIndex =>
                {
                    var clickedRow = blockIndex / 3;
                    var clickedCol = blockIndex % 3;
                    
                    OnBlockClickedDelegate?.Invoke(clickedRow, clickedCol);
                });
            }
        }
    
        /// <summary>
        /// 특정 Block에 Marker를 표시하는 함수
        /// </summary>
        /// <param name="markerType">마커 타입</param>
        /// <param name="row">Row</param>
        /// <param name="col">Col</param>
        public void PlaceMarker(Block.MarkerType markerType, int row, int col)
        {
            // row, col을 index로 변환
            var markerIndex = row * 3 + col;
            
            // Block에게 마커 표시
            blocks[markerIndex].SetMarker(markerType);
        }
    
        public void SetBlockColor(GameManager.PlayerType playerType, (int row, int col)[] blockPositions)
        {
            if (playerType == GameManager.PlayerType.None) return;
    
            foreach (var blockPosition in blockPositions)
            {
                var blockIndex = blockPosition.row * 3 + blockPosition.col;
                Color32 markerColor;
                if (playerType == GameManager.PlayerType.PlayerA)
                    markerColor = new Color32(0,  166, 255, 255);
                else if (playerType == GameManager.PlayerType.PlayerB)
                    markerColor = new Color32(255, 0, 94, 255);
                else
                    markerColor = Color.black;
                
                blocks[blockIndex].SetColor(markerColor);
            }
        }
    }

     

    >> Blocks.cs

    using System;
    using UnityEngine;
    
    public class Block : MonoBehaviour
    {
        [SerializeField] private Sprite oSprite;
        [SerializeField] private Sprite xSprite;
        [SerializeField] private SpriteRenderer markerSpriteRenderer;
    
        public enum MarkerType { None, O, X }
    
        public delegate void OnBlockClicked(int index); // void를 반환하고 int를 매개변수로 삼는 형태의 함수를 받는다.
        private OnBlockClicked _onBlockClicked;
        private int _blockIndex;
        private SpriteRenderer _spriteRenderer;
        private Color _defaultColor;
    
        private void Awake()
        {
            _spriteRenderer = GetComponent<SpriteRenderer>();
            _defaultColor = _spriteRenderer.color;
        }
    
        /// <summary>
        /// 블럭의 색상을 변경하는 함수
        /// </summary>
        /// <param name="color">색상</param>
        public void SetColor(Color color)
        {
            _spriteRenderer.color = color;
        }
    
        /// <summary>
        /// Block 초기화 함수
        /// </summary>
        /// <param name="blockIndex">Block 인덱스</param>
        /// <param name="onBlockClicked">Block 터치 이벤트</param>
        public void InitMarker(int blockIndex, OnBlockClicked onBlockClicked)
        {
            _blockIndex = blockIndex;
            SetMarker(MarkerType.None);
            this._onBlockClicked = onBlockClicked;
            SetColor(_defaultColor);
        }
    
        /// <summary>
        /// 어떤 마커를 표시할지 전달하는 함수
        /// </summary>
        /// <param name="markerType">마커 타입</param>
        public void SetMarker(MarkerType markerType) // 외부에서 마크 표시
        {
            switch (markerType)
            {
                case MarkerType.O:
                    markerSpriteRenderer.sprite = oSprite;
                    break;
                case MarkerType.X:
                    markerSpriteRenderer.sprite = xSprite;
                    break;
                case MarkerType.None:
                    markerSpriteRenderer.sprite = null;
                    break;
            }
        }
    
        private void OnMouseUpAsButton()
        {
            _onBlockClicked?.Invoke(_blockIndex);
        }
    }

     

    >> GameUIController.cs

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    
    public class GameUIController : MonoBehaviour
    {
        [SerializeField] private CanvasGroup canvasGroupA;
        [SerializeField] private CanvasGroup canvasGroupB;
        [SerializeField] private Button gameOverButton;
        
        public enum GameUIMode { Init, TurnA, TurnB, GameOver }
    
        private const float DisableAlpha = 0.5f;
        private const float EnableAlpha = 1f;
    
        public void SetGameUIMode(GameUIMode gameUIMode)
        {
            switch (gameUIMode)
            {
                case GameUIMode.Init:
                    canvasGroupA.gameObject.SetActive(true);
                    canvasGroupB.gameObject.SetActive(true);
                    gameOverButton.gameObject.SetActive(false);
    
                    canvasGroupA.alpha = DisableAlpha;
                    canvasGroupB.alpha = DisableAlpha;
                    break;
                case GameUIMode.TurnA:
                    canvasGroupA.gameObject.SetActive(true);
                    canvasGroupB.gameObject.SetActive(true);
                    gameOverButton.gameObject.SetActive(false);
    
                    canvasGroupA.alpha = EnableAlpha;
                    canvasGroupB.alpha = DisableAlpha;
                    break;
                case GameUIMode.TurnB:
                    canvasGroupA.gameObject.SetActive(true);
                    canvasGroupB.gameObject.SetActive(true);
                    gameOverButton.gameObject.SetActive(false);
    
                    canvasGroupA.alpha = DisableAlpha;
                    canvasGroupB.alpha = EnableAlpha;
                    break;
                case GameUIMode.GameOver:
                    canvasGroupA.gameObject.SetActive(false);
                    canvasGroupB.gameObject.SetActive(false);
                    gameOverButton.gameObject.SetActive(true);
                    break;
            }
        }
    
        public void OnClickGameOverButton()
        {
            // TODO: 게임오버 버튼을 눌렀을 때 구현
        }
    }

     

    >> MainPanelController.cs

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.SceneManagement;
    
    public class MainPanelController : MonoBehaviour
    {
        public void OnClickSinglePlayButton()
        {
            SceneManager.LoadScene("Game");
        }
        
        public void OnClickDualPlayButton()
        {
            SceneManager.LoadScene("Game");
        }
        
        public void OnClickSettingsButton()
        {
            
        }
    }

     

    >> BattlePanelController.cs

    : GameUIController.cs 처럼 게임 중에 상단에 누구 Turn인지 표시되도록 만들어보려다가 실패한 것

     

    ※ 아이디어

    - 플레이어1 턴이면 '플레이어1' Text와 'O' Sprite의 색이 파란색으로 변하고 플레이어2는 기존의 색으로 돌아감

    - 플레이어2 턴이면 '플레이어2' Text와 'X' Sprite의 색이 빨간색으로 변하고 플레이어1은 기존의 색으로 돌아감

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    
    public class BattlePanelController : PanelController
    {
        [SerializeField] private Image player1Marker;
        [SerializeField] private Image player2Marker;
    
        private Color _defaultColor;
        
        public void SetTurnColor(GameManager.TurnType turnType)
        {
            _defaultColor = player1Marker.color;
    
            if (turnType == GameManager.TurnType.PlayerA)
            {
                player1Marker.color = new Color32(0, 166, 255, 255);
                player2Marker.color = _defaultColor;
            }
                
            else if (turnType == GameManager.TurnType.PlayerB)
            {
                player2Marker.color = new Color32(255, 0, 94, 255);
                player1Marker.color = _defaultColor;
            }
        }
    }

    C# 단기 교육 보강

    3일차

    Unity C# 프로그래밍 기초 문법

    >> 형 변환

     

    ※ boxing과 unboxing

    • boxing : 기본형 타입 데이터를 참조형(ref) 타입 데이터로 변환하는 과정
    • unboxing : 참조형(ref) 타입 데이터를 기본형 타입 데이터로 변환하는 과정

    --> 내부적으로 엄청난 오버헤드가 발생하므로 지양해야 한다.

     

    ex) 정수 변수 i boxing하고 개체 o에 할당

    int i = 123;
    // The following line boxes i.
    object o = i;

     

    ex) o 개체를 unboxing하고 정수 변수 i에 할당

    o = 123;
    i = (int)o;  // unboxing

     

    https://learn.microsoft.com/ko-kr/dotnet/csharp/programming-guide/types/boxing-and-unboxing

     

    boxing 및 unboxing - C#

    C# 프로그래밍의 boxing 및 unboxing에 대해 알아봅니다. 코드 예제를 살펴보고 사용 가능한 추가 리소스를 확인합니다.

    learn.microsoft.com

     

    └ Generic

     

    └ Overload

     

    └ 개체 지향 프로그래밍의 특징

    • 추상화 (확장성)
    • 상속 (다양성, 유연성)
    • 다형성 (다양성)
    • 캡슐화 (은닉성)

     

    └ 상속 (Inheritance)

    : 정의된 클래스를 물려받아, 확장 / 변형해서 새로운 클래스를 구현하는 방법

     

    >> 1개만 상속 가능하다. (다중 상속 불가능)

    ※ Unity는 MonoBehavior를 상속 받아야하는데, 그럼 어떡하는가?

    : 최상위 Object가 MonoBehavior를 상속받으면 자식 Object는 자연스레 이어 받는다.

     

    >> Unity에서는 생성자를 거의 안 쓴다.

    사실 생성자를 쓰려고 해도 에러난다. --> Awake()와 Start()를 활용

    ※ 생성자는 부모 Object가 먼저 호출되고 그 다음 자식 Object가 호출된다.