본문 바로가기
Development/C#

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

by Mobics 2025. 1. 23.

 

목차


    ※ 오브젝트 풀링을 정리해주신 다른 수강생님

    https://yukihirahole.tistory.com/186

     

    [Unity] 오브젝트 풀링 (Object Pooling)

    오브젝트 풀링이란?오브젝트 풀링은 객체를 반복해서 생성하고 삭제하는 대신, 미리 만들어 놓고 재사용하는 디자인 패턴이다.“필요할 때 꺼내 쓰고, 쓰지 않을 때는 돌려주는 대여 서비스라

    blog.ysizuku.com


    레이싱 게임 제작

    25.01.23

    아이디어 구상

    : Figma로 구상

    • 하이퍼 캐주얼 레이싱 게임
    • 모바일 플랫폼
    • 업적
    • 리더보드
    • 광고 추가
    • 상점 : 광고 제거, 아이템 판매
    • 세로 화면으로 제작 --> 모바일 특화, 한 손 플레이가 가능하다는 장점

     

    Class 구분

    • GameManager --> 게임 상태 처리
    • Road Controller(도로 컨트롤) --> 도로 생성 및 관리(무한 맵), Gas Item 생성
    • Car Controller(자동차 컨트롤) --> 이동 컨트롤, Gas
    • UI Controller --> Gas 표시, 좌/우 이동 버튼

     

    Jira와 Confluence로 일감 계획

    : Jira에 'Hyper Racer' 프로젝트 생성

    • 에픽 생성 : 프로토타입 개발, 안드로이드용 개발, iOS용 개발
    • 스토리 생성 : 기획, 메인화면 개발, 게임 화면 개발, 게임오버 화면 개발
    • 스프린트 생성

    ※ 스프린트는 월요일 기준으로 만드는 게 좋다

     

    게임 흐름 구상

    --> Scene 단위도 구상 (A+B로 묶어서 Scene을 제작하기로 구상)

    ※ Scene에 너무 많은 것을 담고 있으면 로딩하는 데에 시간이 많이 걸린다.

     

    Unity 프로젝트 제작 후 Github와 연동

    1. Unity 프로젝트 제작

     

    2. Github에서 New repository 생성

     

    3. Github Desktop에서 Clone repository

    4. Unity Project 파일들을 복사하여 Clone한 폴더에 붙여넣기

    5. Github Desktop에서 Commit & Push (커밋명 : 프로젝트 시작)

    6. Push한 파일들 로컬에서 전부 삭제 후 새로 Clone (생략 가능)

    7. Unity Project 원본 삭제

    8. Unity Hub에서 원본 프로젝트 삭제 및 Github와 연동한 프로젝트 추가

     

    Jira의 일감에 브랜치 연결

     

    레이싱 게임 제작

    >> Scene 생성

    Ctrl + N, Ctrl + S

     

    >> 빌드 세팅을 안드로이드로 변경

     

    └ 기본 맵 생성

    : Road, Car, Gas를 만들고 Prefab

     

    1. Road

    - Floor

     

    - Wall

    --> Right는 Position.x = 2

     

    >> 추가 설정

      • Road Prefab의 양 쪽 Wall에서 Box Collider를 삭제
      • 부모 Object인 Road에 BoxCollider 추가 및 크기 조정
      • IsTrigger 체크
      • Road Controller.cs 추가

    --> 원래 Center.Z가 0, Size.Z가 10이었는데, 차가 감지될 때 맵이 어색하게 사라져서 Collider의 범위를 넓힘

     

    2. Car

     

    >> 추가 설정

      • Car model에서 BoxCollider를 삭제
      • 부모 Object인 Car에 BoxCollider와 Rigidbody 추가
      • 'Player' 태그 추가
      • Collider 크기 조정
      • 중력의 영향을 안 받도록 Is Kinematic 체크
      • Car Controller.cs 추가

     

    3. Gas

     

    >> 부모 Object인 Gas에 'Gas' 태그 생성하여 추가

     

    └ UI 생성

    : Canvas, Gas량 표시할 Text, Button 2개 추가

     

    1. Canvas

    : 게임 해상도 조정

     

    ※ 먼저 Build Settings에서 Android로 Build

    --> 기준이 되는 해상도 설정 (이후 작아진 글씨 크기 수정)

     

    2. Text

    --> 사진은 글씨 크기 수정 완료한 것

     

    3. Button

    : 원래는 투명도를 0으로 둬서 완전히 안 보이게 하는데, 지금은 보이도록 색과 투명도 설정해둠

    >> 화면 비율과 상관없이 좌/우로 나뉘도록 설정

     

    >> 추가 설정

    • 버튼의 Button Component를 삭제하고 Event Trigger 추가 --> Pointer Down, Pointer Up 추가
    • MoveButton.cs 추가
    • 좌/우 각각 버튼에 맞게 Button 바인딩 및 함수 설정 --> Pointer Down과 Up 각각 맞게 설정

     

    └ 코드 작성

    • GameManager.cs
    • CarController.cs
    • RoadController.cs
    • MoveButton.cs

     

    >> GameManager.cs

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using TMPro;
    using UnityEngine;
    
    public class GameManager : MonoBehaviour
    {
        [SerializeField] private GameObject carPrefab;
        [SerializeField] private GameObject roadPrefab;
        
        // UI 관련 코드
        [SerializeField] private MoveButton leftMoveButton;
        [SerializeField] private MoveButton rightMoveButton;
        [SerializeField] private TMP_Text gasText;
        
        // 자동차
        private CarController _carController;
        
        // 도로 오브젝트 풀
        private Queue<GameObject> _roadPool = new();
        private int _roadPoolSize = 3; // 도로가 생기는 개수 제한
        
        // 도로 이동
        private List<GameObject> _activeRoads = new();
        
        // 상태
        public enum State { Start, Play, End }
        public State GameState { get; private set; } = State.Start;
    
        // Singleton
        private static GameManager _instance;
        public static GameManager Instance
        {
            get
            {
                if (_instance == null)
                    _instance = FindObjectOfType<GameManager>();
                return _instance;
            }
        }
    
        private void Awake()
        {
            if (_instance != null && _instance != this)
                Destroy(this.gameObject);
            else
            {
                _instance = this;
            }
        }
    
        private void Start()
        {
            // Road Object Pool 초기화
            InitializeRoadPool();
            
            // 게임 시작
            StartGame();
        }
    
        private void Update()
        {
            // 활성화 된 도로를 아래로 서서히 이동
            foreach (var activeRoad in _activeRoads)
            {
                activeRoad.transform.Translate(-Vector3.forward * Time.deltaTime);
            }
            
            // Gas 정보 출력
            if (_carController != null)
                gasText.text = _carController.Gas.ToString();
        }
    
        private void StartGame() // 게임 재시작 시, 다시 생성할 수 있게끔 따로 빼둠
        {
            // 도로 생성
            SpawnRoad(Vector3.zero);
            
            // 자동차 생성
            _carController = Instantiate(carPrefab, new Vector3(0, 0, -3f), Quaternion.identity).GetComponent<CarController>();
            
            // Left, Right move button에 자동차 컨트롤 기능 적용
            leftMoveButton.OnMoveButtonDown += () => _carController.Move(-1f);
            rightMoveButton.OnMoveButtonDown += () => _carController.Move(1f);
        }
    
        #region 도로 생성 및 관리
        
        // 도로 오브젝트 풀 초기화
        private void InitializeRoadPool()
        {
            for (int i = 0; i < _roadPoolSize; i++)
            {
                GameObject road = Instantiate(roadPrefab);
                road.SetActive(false);
                _roadPool.Enqueue(road);
            }
        }
        
        // 도로 오브젝트 풀에서 불러와 배치하는 함수 --> 계속 새로 생성되지 않게 Object Pool 활용
        public void SpawnRoad(Vector3 position)
        {
            if (_roadPool.Count > 0)
            {
                GameObject road = _roadPool.Dequeue();
                road.transform.position = position;
                road.SetActive(true);
                
                // 활성화 된 길을 움직이기 위해 List에 저장
                _activeRoads.Add(road);
            }
            else
            {
                GameObject road = Instantiate(roadPrefab, position, Quaternion.identity);
                _activeRoads.Add(road);
            }
        }
    
        public void DestroyRoad(GameObject road)
        {
            road.SetActive(false);
            _activeRoads.Remove(road);
            _roadPool.Enqueue(road); // 다시 road를 사용하기 위해 Enqueue
        }
        #endregion
    }

     

    --> 빈 GameObject로 GameManager 만들고 GameManager.cs 할당 및 각각 바인딩

     

    >> CarController.cs

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class CarController : MonoBehaviour
    {
        [SerializeField] private int gas = 100;
        [SerializeField] private float moveSpeed = 1f;
        
        public int Gas { get => gas; } // Gas 정보
    
        private void Start()
        {
            StartCoroutine(GasCoroutine());
        }
    
        IEnumerator GasCoroutine()
        {
            while (true)
            {
                gas -= 10;
                if (gas <= 0) break;
                yield return new WaitForSeconds(1f);
            }
            // TODO: 게임 종료
        }
        
        // 자동차 이동 Method
        public void Move(float direction)
        {
            transform.Translate(Vector3.right * (direction * moveSpeed * Time.deltaTime));
            transform.position = new Vector3(Mathf.Clamp(transform.position.x, -2f, 2f), 0, transform.position.z);
        }
        
        // Gas Item 확득 시, 호출되는 Method
        public void OnTriggerEnter(Collider other)
        {
            if (other.CompareTag("Gas"))
            {
                gas += 30;
                
                // TODO: 가스 아이템 제거
            }
        }
    }

     

    >> MoveButton.cs

    : 버튼을 누르고 있는 동안 움직이고, 버튼을 뗐을 때 멈추는 방식으로 구현하기 위해 생성

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class MoveButton : MonoBehaviour
    {
        public delegate void MoveButtonDelegate();
        public event MoveButtonDelegate OnMoveButtonDown; // 외부 객체에서 전달받으면 실행
        
        private bool _isDown;
    
        private void Update()
        {
            if (_isDown)
            {
                OnMoveButtonDown?.Invoke();
            }
        }
    
        public void ButtonDown()
        {
            _isDown = true;
        }
    
        public void ButtonUp()
        {
            _isDown = false;
        }
    }

     

    >> RoadController.cs

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class RoadController : MonoBehaviour
    {
        // 플레이어 차량이 도로에 진입하면 다음 도로를 생성
        private void OnTriggerEnter(Collider other)
        {
            if (other.CompareTag("Player"))
            {
                GameManager.Instance.SpawnRoad(transform.position + new Vector3(0, 0, 10));
            }
        }
    
        // 플레이어 차량이 도로를 벗어나면 해당 도로를 풀에서 제거
        private void OnTriggerExit(Collider other)
        {
            if (other.CompareTag("Player"))
            {
                GameManager.Instance.DestroyRoad(gameObject);
            }
        }
    }

     

    WinMerge

    : 코드 비교 프로그램

    --> 1번을 눌러 새 문서를 추가하고, 비교할 코드를 작성한 뒤 2번을 누르면 사진과 같이 다른 부분을 비교해준다.