목차
※ 강사님의 꿀팁
: 처음에 어려운 부분부터 만드는 것이 좋다
--> 구상한대로 구현이 가능한지 확인해야하고, 구현을 했더라도 재미가 없으면 다시 구상을 바꾸거나 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화
'Development > C#' 카테고리의 다른 글
멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 55일차 (0) | 2025.02.14 |
---|---|
멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 54일차 (0) | 2025.02.13 |
멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 52일차 (0) | 2025.02.11 |
멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 51일차 (0) | 2025.02.10 |
멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 50일차 (0) | 2025.02.07 |