✅ 오늘의 학습 목표
1. 하노이 타워 실습 마무리
2. Stack을 활용한 UI 처리
3. Queue을 활용한 권총 구현
4. 알고리즘 이론 및 실습
1. Hanoi Tower
어제 구현하였던 하노이 타워를 업그레이드&마무리해주려고 한다.
- 작은 도넛 위에 큰 도넛이 올라가지 못하도록 예외처리
- 도넛을 선택한 후에 ESC를 눌러 이동을 취소
- 이동 횟수에 따른 Count값 UI에 표시
1. 예외처리

기존에는 로그로만 '더 큰 도넛이 작은 도넛보다 위에있다' 라고 표시해 주었는데
이번에는 도넛 자체를 쌓을 수 없도록 구현해 줄 것이다.
public bool CheckDonut(GameObject donut)
{
if (barStack.Count > 0)
{
int pushNumber = donut.GetComponent<Donut>().donutNumber;
GameObject peekDonut = barStack.Peek();
int peekNumber = peekDonut.GetComponent<Donut>().donutNumber;
if (pushNumber < peekNumber)
return true;
else
{
Debug.Log($"현재 넣으려는 도넛은 {pushNumber}이고, 해당 기둥의 제일 위의 도넛은 {peekNumber}입니다.");
return false; // 추가
}
}
return true;
}
2. ESC
ESC키를 눌러 선택한 도넛의 이동을 취소하는 기능도 만들어주었다.
// HanoiTower 스크립트
public static BoardBar currBar;
void Update()
{
if (Input.GetKeyDown(KeyCode.Escape))
{
currBar.barStack.Push(selectedDonut);
isSelected = false;
selectedDonut = null;
}
}
// Board 스크립트
public GameObject PopDonut()
{
if (barStack.Count > 0)
{
HanoiTower.currBar = this;
HanoiTower.isSelected = true;
GameObject donut = barStack.Pop(); // Stack에서 GameObject를 꺼내는 기능
return donut; // 꺼낸 도넛을 반환
}
return null;
}
선택 중이던 도넛을 원래 위치(선택했던 막대의 Stack)에 다시 넣어주고 isSelected랑 selectedDonut을 초기화해 주었다.
그리고 ESC로 되돌리려면 원래 어떤 막대에서 도넛을 꺼냈는지 알아야 해서
HanoiTower.currBar라는 static 변수를 만들어서 도넛을 꺼낸 막대를 기억하게 해 주었다.
3. Count
마지막으로 도넛의 이동 횟수를 카운트해 주는 UI를 추가해 주었다.
public class HanoiTower : MonoBehaviour
{
public TextMeshProUGUI countTextUI;
public static int moveCount;
IEnumerator Start()
{
for (int i = (int)hanoiLevel - 1; i >= 0; i--) // 반복문으로 Level만큼 도넛 생성
{
GameObject donut = Instantiate(donutPrefabs[i]); // 도넛 생성
donut.transform.position = new Vector3(-5f, 5f, 0); // 도넛 생성 위치 : 왼쪽 막대기 + 위쪽
bars[0].PushDonut(donut); // 방금 생성한 도넛을 해당 Bar의 Stack Push
yield return new WaitForSeconds(1f); // 순차적으로 생성
}
moveCount = 0;
countTextUI.text = moveCount.ToString();
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Escape))
{
currBar.barStack.Push(selectedDonut);
isSelected = false;
selectedDonut = null;
}
countTextUI.text = moveCount.ToString();
}
}
// Board 스크립트
public void PushDonut(GameObject donut)
{
// 추가
if (HanoiTower.isSelected)
HanoiTower.moveCount++;
}

2. UI Stack
스택을 활용해서 UI 레이어도 쉽게 조절하는 실습을 진행해보려고 한다.
🥕 예행 작업
1. Scene 생성 (UI Stack)
2. Script 생성 (UIHandler, UIStackManager)
우선 마우스로 드래그 앤 드롭이 되는 UI를 만들어주겠다.
using UnityEngine;
using UnityEngine.EventSystems;
public class UIHandler : MonoBehaviour, IPointerDownHandler, IDragHandler
{
private RectTransform parentRect;
private Vector2 basePos;
private Vector2 startPos;
private Vector2 moveOffset;
void Awake()
{
parentRect = transform.parent.GetComponent<RectTransform>();
}
public void OnPointerDown(PointerEventData eventData)
{
basePos = parentRect.anchoredPosition; // 기존 UI 위치
startPos = eventData.position; // 시작점
}
public void OnDrag(PointerEventData eventData)
{
moveOffset = eventData.position - startPos; // 드래그한 상태의 Dir
parentRect.anchoredPosition = basePos + moveOffset;
}
}
팝업의 상단 패널을 잡고 눌렀을 때 이동이 되도록 구현해 주었다.
- 상단 패널은 자식 오브젝트로, 부모 오브젝트(즉 전체 UI)가 움직이도록 Awake() 안에서 변수 선언
- IPointerDownHandler → 마우스를 처음 누른 위치 기억
- IDragHandler → 드래그 중인 위치 계산해서 UI 옮기기
- basePos → 현재 UI 위치 저장
- startPos → 마우스를 누른 시작점 저장
- moveOffset → 얼마나 움직였는지 계산
- anchoredPosition → 그만큼 UI 위치를 바꿔줌


1. 레이어
이제 팝업을 여러 개 만들어서 팝업창의 레이어 순서를 조정해 주겠다.
public void OnPointerDown(PointerEventData eventData)
{
parentRect.SetAsLastSibling(); // 마우스를 클릭했을 때 위에 그려지도록 설정
basePos = parentRect.anchoredPosition; // 기존 UI 위치
startPos = eventData.position; // 시작점
}
- parentRect.SetAsFirstSibling() → 아래 그려지도록 설정
- parentRect.SetAsLastSibling() → 위에 그려지도록 설정

2. Button
이제 스택을 활용해서 여러 개의 팝업 창을 순서대로 띄우고, ESC 키로 최근에 열린 창부터 닫는 방식을 구현해 주겠다.
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UIStackManager : MonoBehaviour
{
public Stack<GameObject> uiStack = new Stack<GameObject>();
public Button[] buttons;
public GameObject[] popupUIs;
void Start()
{
buttons[0].onClick.AddListener((PopupOn1));
buttons[1].onClick.AddListener((PopupOn2));
buttons[2].onClick.AddListener((PopupOn3));
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Escape))
{
GameObject currUI = uiStack.Pop();
currUI.SetActive(false);
}
}
private void PopupOn1()
{
popupUIs[0].SetActive(true);
uiStack.Push(popupUIs[0]);
}
private void PopupOn2()
{
popupUIs[1].SetActive(true);
uiStack.Push(popupUIs[1]);
}
private void PopupOn3()
{
popupUIs[2].SetActive(true);
uiStack.Push(popupUIs[2]);
}
}
이렇게 스택을 사용하면 여러 팝업을 열고 닫는 순서를 자동으로 관리할 수 있다
- Push()을 활용해서 팝업을 화면에 띄우고, Pop()을 활용해서 팝업을 닫아주는 로직


3. Object Pool
이번에는 Queue를 활용해서 권총과 총알을 구현해 보겠다.
🥕 예행 작업
1. Scene 생성 (Object Pool)
2. Script 생성 (ObjectPoolQueue,ObjectPoolController, PoolObject)
총알을 발사할 때마다 Instantiate로 총알을 매번 생성하면 메모리 용량만 늘어나고 GC 때문에 끊길 수도 있다.
그래서 총알을 미리 10개 정도 만들어 두고 필요할 때마다 SetActive(ture or false) 시킬 수 있도록 Object Pooling을 활용해 주겠다.
using System.Collections.Generic;
using UnityEngine;
public class ObjectPoolQueue : MonoBehaviour
{
public Queue<GameObject> objQueue = new Queue<GameObject>(); // 오브젝트들이 들아갈 큐
public GameObject objPrefab; // 생성할 오브젝트
public Transform parent; // 계층 구조상 들어갈 부모 오브젝트
void Start()
{
CreateObject();
}
private void CreateObject() // 오브젝트를 생성하는 기능 -> Pool을 채우는 기능
{
for (int i = 0; i < 10; i++)
{
GameObject obj = Instantiate(objPrefab, parent); // 오브젝트를 생성하고, 계층 구조를 Parent의 자식으로 변경
EnqueueObject(obj);
}
}
public void EnqueueObject(GameObject newObj)
{
newObj.GetComponent<Rigidbody>().linearVelocity = Vector3.zero;
newObj.GetComponent<Rigidbody>().angularVelocity = Vector3.zero;
objQueue.Enqueue(newObj);
newObj.SetActive(false); // 오브젝트가 작동되지 않도록
}
public GameObject DequeueObject()
{
if (objQueue.Count < 10)
CreateObject();
GameObject obj = objQueue.Dequeue();
obj.SetActive(true);
return obj;
}
}

- CreateObject() → 총알을 10개 미리 만들어서 큐에 저장
- DequeueObject() → 총알이 필요할 때 하나 꺼내서 SetActive(true) 하고 전달
- EnqueueObject(GameObject newObj) → 총알을 다시 pool에 넣고 비활성화
using UnityEngine;
public class ObjectPoolController : MonoBehaviour
{
public ObjectPoolQueue pool;
public Transform shootPos;
void Update()
{
if (Input.GetMouseButtonDown(0))
{
// 풀에서 꺼내 사용
GameObject bullet = pool.DequeueObject();
bullet.transform.position = shootPos.position;
}
}
}
마우스 왼쪽 버튼을 클릭하면 총알이 발사되도록 구현
using UnityEngine;
public class PoolObject : MonoBehaviour
{
private ObjectPoolQueue pool;
public float bulletSpeed = 50f;
void Awake()
{
pool = FindFirstObjectByType<ObjectPoolQueue>();
}
void OnEnable()
{
Invoke("ReturnPool", 3f);
}
void Update()
{
transform.position += Vector3.forward * Time.deltaTime * bulletSpeed;
}
private void ReturnPool()
{
pool.EnqueueObject(gameObject);
}
}


- ReturnPool → 총알이 켜지면 3초 후 다시 pool에 복귀되도록 구현
- 총알이 앞으로 발사될 수 있도록 위치 업데이트

4. 알고리즘
1. Big O (시간복잡도)



2. Swap & Shuffle
🥕 예행 작업
1. Scene 생성 (Swap and Shuffle)
2. Script 생성 (Shuffle)

using UnityEngine;
public class Shuffle : MonoBehaviour
{
public int[] array = new int[10] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
void Start()
{
ShuffleFunction();
}
private void ShuffleFunction()
{
for (int i = 0; i < 100; i++)
{
int ranInt1 = Random.Range(0, array.Length);
int ranInt2 = Random.Range(0, array.Length);
Swap(ranInt1, ranInt2);
}
}
public void Swap(int i, int j)
{
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
3. 재귀 (Recursion)

자기 자신을 다시 호출하는 방식
🥕 예행 작업
1. Scene 생성 (Recursion)
2. Script 생성 (Finonacci, Factorial, Permutation)
▶ 피보나치 수열
재귀적으로 호출하는 수열 O(2^n)
using UnityEngine;
public class Fibonacci : MonoBehaviour
{
void Start()
{
for (int i = 0; i < 10; i++)
{
int result = FibonacciFunction(i);
Debug.Log(result);
}
}
private int FibonacciFunction(int n)
{
if (n <= 1)
return n;
return FibonacciFunction(n - 1) + FibonacciFunction(n - 2);
}
}

▶ 팩토리얼
using UnityEngine;
public class Factorial : MonoBehaviour
{
void Start()
{
for (int i = 0; i < 10; i++)
{
int result = FactorialFunction(i);
Debug.Log(result);
}
}
private int FactorialFunction(int n)
{
if (n == 0)
return 1;
else
return n * FactorialFunction(n - 1);
}
}

▶ 순열
모든 경우의 수 O(n!)

using UnityEngine;
public class Permutation : MonoBehaviour
{
public int[] array = new int[3] { 1, 2, 3 };
void Start()
{
PermutationFunction(array, 0);
}
private void PermutationFunction(int[] arr, int start)
{
if (start == arr.Length)
{
Debug.Log(string.Join(", ", arr));
return;
}
for (int i = start; i < arr.Length; i++)
{
// Swap : 자리 바꾸기
var temp = arr[start];
arr[start] = arr[i];
arr[i] = temp;
PermutationFunction(arr, start + 1); // 재귀
// 원상복구 BackTracking
temp = arr[start];
arr[start] = arr[i];
arr[i] = temp;
}
}
}

마지막에 알고리즘 수업 들을 때에는 무슨 정처기 실기 문제 푸는 거 같았다..ㅋㅋㅋ
중간에 잠깐 멍 때리면 어디까지 했더라 기억이 안나는 바보 멍청이!!
나는 정처기 쉽다 해서 올해 처음으로 시험 준비할 때 그냥 마음 편하게 준비했었는데
시험 문제가 안 그래도 짜증나는 재귀에다가 몇의 배수이면서 몇의 약수이면 안 되는 그런 조건문까지 추가되어 있었어서 애먹었던 기억이 난다 그래서 그 시험은 합격률이 15%라고...
암튼.. 그냥 싫다.. 그냥 싫다.....
'Unity > 멋쟁이사자처럼 부트캠프' 카테고리의 다른 글
| [멋쟁이사자처럼부트캠프] 유니티 게임 개발 5기(40일차) - 알고리즘 (3) (0) | 2025.07.14 |
|---|---|
| [멋쟁이사자처럼부트캠프] 유니티 게임 개발 5기(39일차) - 재귀 및 알고리즘 (2) (1) | 2025.07.11 |
| [멋쟁이사자처럼부트캠프] 유니티 게임 개발 5기(37일차) - 자료구조 (3) 하노이 타워 (0) | 2025.07.09 |
| [멋쟁이사자처럼부트캠프] 유니티 게임 개발 5기(36일차) - 자료구조 (2) 및 실습 (1) | 2025.07.08 |
| [멋쟁이사자처럼부트캠프] 유니티 게임 개발 5기(35일차) - 자료구조 이론 (1) (1) | 2025.07.07 |