[38회차] 수업내용 250710
1. 하노이의 타워
2. UI Stack
3. Queue object pooling
4. 알고리즘 1
1. 하노이의 탑 횟수 세는 코드 추가
using System.Collections;
using TMPro; // [추가] TextMeshPro 텍스트 사용
using UnityEngine;
public class HanoiTower : MonoBehaviour
{
public enum HanoiLevel { Lv1 = 3, Lv2, Lv3 }
public HanoiLevel hanoiLevel;
public GameObject[] donutPrefabs;
public BoardBar[] bars; // L, C, R
public TextMeshProUGUI countTextUI; // [추가] 이동 횟수를 표시할 UI 텍스트
public static GameObject selectedDonut;
public static bool isSelected;
public static BoardBar currBar; // 도넛을 꺼낸 막대를 기억하기 위한 변수
public static int moveCount; // [추가] 도넛 이동 횟수 저장 변수
IEnumerator Start()
{
for (int i = (int)hanoiLevel - 1; i >= 0; i--)
{
GameObject donut = Instantiate(donutPrefabs[i]);
donut.transform.position = new Vector3(-5f, 5f, 0);
bars[0].PushDonut(donut);
yield return new WaitForSeconds(1f);
}
moveCount = 0; // [추가] 시작 시 이동 횟수 초기화
countTextUI.text = moveCount.ToString(); // [추가] UI에 초기 이동 횟수 표시
}
void Update() // 키보드 입력감지는 update 나 fixed update에서만
{
if (Input.GetKeyDown(KeyCode.Escape)) // [추가] ESC 키를 눌렀을 때
{
currBar.barStack.Push(selectedDonut); // 선택했던 도넛을 다시 원래 막대에 올림
isSelected = false;
selectedDonut = null;
}
countTextUI.text = moveCount.ToString(); // [추가] 이동 횟수를 실시간으로 UI에 표시
}
}
using System.Collections.Generic;
using UnityEngine;
public class BoardBar : MonoBehaviour
{
public enum BarType { Left, Center, Right }
public BarType barType;
public Stack<GameObject> barStack = new Stack<GameObject>();
void OnMouseDown()
{
if (!HanoiTower.isSelected) /
HanoiTower.selectedDonut = PopDonut();
else
PushDonut(HanoiTower.selectedDonut);
}
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;
}
public void PushDonut(GameObject donut)
{
if (!CheckDonut(donut))
return;
HanoiTower.moveCount++; // [추가] 도넛이 성공적으로 쌓일 때 이동 횟수 증가
HanoiTower.isSelected = false;
HanoiTower.selectedDonut = null;
donut.transform.position = transform.position + Vector3.up * 5f;
donut.GetComponent<Rigidbody>().linearVelocity = Vector3.zero;
donut.GetComponent<Rigidbody>().angularVelocity = Vector3.zero;
barStack.Push(donut); // Stack에 GameObject를 넣는 기능
}
public GameObject PopDonut()
{
if (barStack.Count > 0)
{
HanoiTower.currBar = this; // [추가] ESC 누를 경우 도넛을 다시 되돌릴 막대를 저장
HanoiTower.isSelected = true; // [이동] isSelected 설정을 Pop 쪽으로 이동 (onmousedown에서)
GameObject donut = barStack.Pop(); // Stack에서 GameObject를 꺼내는 기능
return donut; // 꺼낸 도넛을 반환
}
return null; // [추가] 비어 있으면 null 반환 (안전성 증가)
}
}
2. UI Stack
UI들을 스택 구조(후입선출, LIFO)로 관리해 한 번에 하나씩 표시하고, 닫을 때는 최근 UI부터 하나씩 제거하는 시스템
| UIManager | UI 전반을 관리하는 싱글톤 매니저 |
| Stack<UIBase> | 열린 UI들을 순서대로 저장 |
| UIBase | 모든 UI 창의 공통 부모 클래스 |
| OpenUI() | UI 열기, Stack에 Push |
| CloseUI() | UI 닫기, Stack에서 Pop |
2.1 UI 이동
using UnityEngine;
using UnityEngine.EventSystems;
// UI를 드래그해서 이동할 수 있게 하는 스크립트
// IPointerDownHandler, IDragHandler 인터페이스 구현 필요
public class UIHandler : MonoBehaviour, IPointerDownHandler, IDragHandler
{
private RectTransform parentRect; // 드래그 대상 UI의 부모 RectTransform
private Vector2 basePos; // 드래그 시작 시 부모 UI의 위치 저장 변수
private Vector2 startPos; // 드래그 시작 시 마우스(터치) 위치 저장 변수
private Vector2 moveOffset; // 드래그 중 이동한 거리
void Awake()
{
// 부모 오브젝트에서 RectTransform 컴포넌트 가져오기
parentRect = transform.parent.GetComponent<RectTransform>();
}
// UI를 클릭(터치)했을 때 호출되는 함수
public void OnPointerDown(PointerEventData eventData)
{
// 부모 UI를 UI 계층 상(형제들중)에서 최상위로 올려 다른 UI 위에 표시되도록 함
parentRect.SetAsLastSibling();
// 현재 부모 UI 위치 저장
basePos = parentRect.anchoredPosition;
// 클릭(터치) 위치 저장
startPos = eventData.position;
}
// 마우스(터치)를 드래그할 때 호출되는 함수
public void OnDrag(PointerEventData eventData)
{
// 현재 터치 위치와 드래그 시작 위치 차이 계산 (이동 거리)
moveOffset = eventData.position - startPos;
// 부모 UI의 위치를 드래그 시작 위치에서 이동 거리만큼 옮김
parentRect.anchoredPosition = basePos + moveOffset;
}
}
* 리마인드
인터페이스 = 무조건 선언된 모든 메서드를 구현해주어야함
- 유니티 인터페이스
| IPointerDownHandler | void OnPointerDown(PointerEventData e) |
| IDragHandler | void OnDrag(PointerEventData e) |
| IPointerEnterHandler | void OnPointerEnter(PointerEventData e) |
| IPointerClickHandler | void OnPointerClick(PointerEventData e) |
*parentRect.SetAsLastSibling();
: parentRect가 형제들 중 가장 뒤(=위)에 오도록 계층 순서를 바꿔, UI가 다른 UI들보다 위에 표시되게 만드는 코드
- Unity UI 시스템에서 마우스 클릭, 터치, 드래그 등 포인터 기반 이벤트 정보를 담는 클래스 = PointerEventData
| PointerEventData | 타입 이름 (클래스) |
| eventData | 매개변수로 전달된 해당 이벤트의 인스턴스 |
| eventData.position | 클릭(터치)된 위치 (스크린 좌표) |
[주요 속성]
| position | Vector2 | 현재 포인터 위치 (스크린 좌표) |
| delta | Vector2 | 이전 프레임 대비 포인터가 움직인 거리 |
| pressPosition | Vector2 | 처음 클릭/터치한 위치 |
| clickTime | float | 마지막 클릭한 시간 |
| pointerEnter | GameObject | 마우스가 올라간 오브젝트 |
| pointerPress | GameObject | 클릭 중인 오브젝트 |
| dragging | bool | 현재 드래그 중인지 여부 |
| button | PointerEventData.InputButton | 클릭한 마우스 버튼 (왼쪽/오른쪽/가운데) |
2.2. UI Stack : 가장 나중에 킨 UI 끄기
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UIStackManager : MonoBehaviour
{
public Stack<GameObject> uiStack = new Stack<GameObject>();
// 현재 열려있는 팝업 UI들을 후입선출 방식으로 관리하는 스택
public Button[] buttons; // 팝업을 열기 위한 버튼 배열
public GameObject[] popupUIs; // 각 버튼에 대응하는 팝업 UI 오브젝트 배열
void Start()
{
// 각 버튼에 클릭 시 실행할 팝업 열기 함수 등록
buttons[0].onClick.AddListener(PopupOn1);
buttons[1].onClick.AddListener(PopupOn2);
buttons[2].onClick.AddListener(PopupOn3);
}
void Update()
{
// ESC 키 입력 감지
if (Input.GetKeyDown(KeyCode.Escape))
{
// 스택에서 가장 마지막에 열린 팝업 UI를 꺼내서 비활성화 (닫기)
GameObject currUI = uiStack.Pop();
currUI.SetActive(false);
}
}
// 버튼 0 클릭 시 호출: 첫 번째 팝업 UI 활성화 및 스택에 추가
private void PopupOn1()
{
popupUIs[0].SetActive(true);
uiStack.Push(popupUIs[0]);
}
// 버튼 1 클릭 시 호출: 두 번째 팝업 UI 활성화 및 스택에 추가
private void PopupOn2()
{
popupUIs[1].SetActive(true);
uiStack.Push(popupUIs[1]);
}
// 버튼 2 클릭 시 호출: 세 번째 팝업 UI 활성화 및 스택에 추가
private void PopupOn3()
{
popupUIs[2].SetActive(true);
uiStack.Push(popupUIs[2]);
}
}
2.3 더블클릭 감지 / 풀스크린 기능
public class UIHandler: MonoBehaviour, IPointerDownHandler, IDragHandler
{
// ...
private Vector2 minAnchor, maxAnchor, anchorPos, deltaSize;
// 풀스크린 전 상태의 앵커와 위치, 크기 저장용 변수들
// 동일한 타입 변수 여러 개를 한 줄에 선언할 때 쉼표로 구분해서 쓸 수 있음
private float timer; // 더블 클릭 타이머
private float doubleClickedTime = 0.15f; // 더블 클릭 인식 시간 간격 (0.15초)
private bool isDoubleClicked = false; // 더블 클릭 감지 상태 플래그
private bool isFullScreen = false; // 현재 UI가 풀스크린 상태인지 여부
void Update()
{
if (isDoubleClicked) // 더블 클릭 감지 중일 때
{
timer += Time.deltaTime; // 시간 누적
if (timer >= doubleClickedTime) // 지정 시간 지나면 더블 클릭 상태 초기화
{
timer = 0f;
isDoubleClicked = false;
}
}
}
public void OnPointerDown(PointerEventData eventData)
{
if (!isDoubleClicked) // 첫 번째 클릭이면 더블 클릭 상태로 설정
isDoubleClicked = true;
else // 두 번째 클릭이면 풀스크린 토글 함수 호출
SetFullScreen();
if (isFullScreen) // 풀스크린 상태면 이후 처리 중단
return;
// ...
}
// ...
private void SetFullScreen()
{
if (!isFullScreen) // 현재 풀스크린이 아니면 풀스크린으로 전환
{
// 현재 UI 앵커와 위치, 크기 저장
minAnchor = parentRect.anchorMin;
maxAnchor = parentRect.anchorMax;
anchorPos = parentRect.anchoredPosition;
deltaSize = parentRect.sizeDelta;
// 앵커를 화면 전체로 맞추고 위치/크기를 초기화하여 풀스크린 모드 설정
//(0,0)은 부모의 왼쪽 아래 (1,1)은 부모의 오른쪽 위 = 부모 영역 전체
parentRect.anchorMin = Vector2.zero;
parentRect.anchorMax = Vector2.one;
parentRect.anchoredPosition = Vector2.zero;
parentRect.sizeDelta = Vector2.zero;
}
else // 풀스크린 상태면 이전 상태로 복원
{
parentRect.anchorMin = minAnchor;
parentRect.anchorMax = maxAnchor;
parentRect.anchoredPosition = anchorPos;
parentRect.sizeDelta = deltaSize;
}
isFullScreen = !isFullScreen; // 상태 토글
}
}
3. Queue Object pool
3.1 Scene 뷰 Gizmo 아이콘

- 크기 고정(3D 씬 내 크기와 무관), Scene에서 오브젝트를 쉽게 찾을 수 있음
- Gizmo 토글이 꺼져 있으면 Scene 뷰에서 안 보임
- GameView에는 보이지 않고, SceneView에서만 보임
3.2 오브젝트 풀링
: 오브젝트를 필요할 때마다 만들고 없애는 대신, 미리 일정 개수를 만들어두고 필요할 때마다 '빌려 쓰고' 다 쓴 다음에는 '반납'하는 방식
: Instantiate (생성)와 Destroy (파괴) 연산에서 발생하는 성능 저하를 크게 줄임
: 총알, 적, 파티클 효과 등과 같이 자주 생성되고 파괴되는 오브젝트들에 유용
using System.Collections.Generic;
using UnityEngine;
// 오브젝트 풀링을 Queue(큐)로 관리하는 클래스
public class ObjectPoolQueue : MonoBehaviour
{
public Queue<GameObject> objQueue = new Queue<GameObject>();
// 미리 생성한 오브젝트들을 보관할 큐 (FIFO 구조)
public GameObject objPrefab; // 생성할 오브젝트의 원형(Prefab)
public Transform parent; // 생성된 오브젝트들의 부모 Transform (계층 정리용)
void Start()
{
CreateObject(); // 시작 시 오브젝트들을 미리 생성해서 풀링
}
private void CreateObject() // 오브젝트 풀을 채우는 함수
{
for (int i = 0; i < 100; i++) // 총 100개 생성
{
// objPrefab을 parent의 자식으로 생성
GameObject obj = Instantiate(objPrefab, parent);
// Instantiate(GameObject original, Vector3 position, Quaternion rotation, Transform 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()
{
GameObject obj = objQueue.Dequeue(); // 큐에서 맨 앞 오브젝트 꺼내기
obj.SetActive(true); // 활성화해서 화면에 보이게
return obj; // 꺼낸 오브젝트 반환
}
}
*복습
Instantiate(prefab, parentTransform, false);
으로 하면 부모에 붙으면서도 월드 좌표 유지. / true면 이동
3.2.1 총알 발사
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 = 100f; // 총알 속도
void Awake()
{
// 씬 내에서 ObjectPoolQueue를 찾아서 참조
pool = FindFirstObjectByType<ObjectPoolQueue>();
}
void OnEnable()
{
// 오브젝트가 활성화될 때 3초 뒤에 ReturnPool() 실행 예약
Invoke("ReturnPool", 3f);
}
void Update()
{
// 매 프레임마다 앞으로 이동 (Vector3.forward는 z축 기준)
transform.position += Vector3.forward * Time.deltaTime * bulletSpeed;
}
// 풀로 총알 반환
private void ReturnPool()
{
pool.EnqueueObject(gameObject);
}
}
4. 알고리즘
4.1 BIG-O (빅오 표기법)
:알고리즘의 시간 복잡도(연산 횟수 증가율)를 표현하는 표기법
:입력 크기(N)가 커질 때 성능이 어떻게 변하는지 근사적으로 나타낸 것
- 코드나 알고리즘의 성능과 효율성 분석
- 하드웨어와 무관하게 이론적 최대 성능 예측
| O(1) | 상수 시간 | 배열에서 인덱스로 바로 접근 |
| O(log N) | 로그 시간 | 이진 탐색 |
| O(N) | 선형 시간 | 단순 반복문 |
| O(N log N) | 로그 곱 선형 | 퀵 정렬 |
| O(N²) | 이차 시간 | 이중 반복문 |
4.1.1 O(1) 인덱서
: 배열의 특정 인덱스에 접근
int[] numbers = { 1, 2, 3, 4, 5 };
int firstNumber = numbers[0];
4.1.2 O(n) - Fisher–Yates Shuffle(피셔-예이츠 셔플)
: 단순 반복문 -배열의 모든 요소를 정확히 한 번씩 총 n번 스왑
- 진짜 공정한 랜덤 셔플 방식
- 매우 빠르고 간단하며, 전 세계 카드 게임, 보드게임, 컴퓨터 게임의 표준 알고리즘
private void FisherYatesShuffle()
{
for (int i = array.Length - 1; i > 0; i--)
{
int j = Random.Range(0, i + 1); // 0부터 i까지
Swap(i, j);
}
}
public void Swap(int i, int j)
{
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
*실습에서는 100번 swap했으나 피셔예이츠는 차례대로 n번
4.1.3 O(2^n) : 피보나치 수열
using UnityEngine;
// 피보나치 수열 계산 예제 (재귀 방식)
public class Fibonacci : MonoBehaviour
{
void Start()
{
// 0부터 9까지의 피보나치 수를 계산하고 출력
for (int i = 0; i < 10; i++)
{
int result = FibonacciFunction(i);
Debug.Log(result);
}
}
// 피보나치 수열의 n번째 값을 재귀적으로 구하는 함수
private int FibonacciFunction(int n)
{
// n이 0 또는 1이면 그대로 반환 (기저 조건)
if (n <= 1)
return n;
// n번째 값은 n-1번째 + n-2번째 값의 합
// 이로 인해 재귀 호출이 계속 분기됨
return FibonacciFunction(n - 1) + FibonacciFunction(n - 2);
}
}
F(4)
├─ F(3)
│ ├─ F(2)
│ │ ├─ F(1)
│ │ └─ F(0)
│ └─ F(1)
└─ F(2)
├─ F(1)
└─ F(0)
4.1.4 O(n) 팩토리얼
using UnityEngine;
// 팩토리얼 계산 예제 (재귀 방식)
public class Factorial : MonoBehaviour
{
void Start()
{
// 0부터 9까지의 팩토리얼 값을 계산하고 출력
for (int i = 0; i < 10; i++)
{
int result = FactorialFunction(i);
Debug.Log(result);
}
}
// n의 팩토리얼(n!)을 재귀적으로 계산하는 함수
private int FactorialFunction(int n)
{
// 기저 조건: 0!은 1
if (n == 0)
return 1;
else
// n! = n * (n-1)!
return n * FactorialFunction(n - 1);
}
}
- 재귀가 n번 호출되기 때문에 선형 시간 복잡도
4.1.5 하노이의 타워 재귀 = O(2ⁿ)
// 하노이의 탑 문제를 해결하는 메인 함수
public void HanoiAnswer()
{
// hanoiLevel 높이의 탑을 0번 기둥에서 2번 기둥으로 옮김 (1번은 보조)
HanoiRoutine((int)hanoiLevel, 0, 1, 2);
}
// 재귀적으로 하노이의 탑을 푸는 함수
private void HanoiRoutine(int n, int from, int temp, int to)
{
// 원판이 1개 남으면 바로 목적지로 이동
if (n == 1)
Debug.Log($"{n}번 도넛을 {from}에서 {to}로 이동");
else
{
// 1단계: n-1개의 원판을 보조 기둥(temp)으로 이동
HanoiRoutine(n - 1, from, to, temp);
// 2단계: 가장 큰 원판(n)을 목적지로 이동
Debug.Log($"{n}번 도넛을 {from}에서 {to}로 이동");
// 3단계: 보조 기둥에 있던 n-1개의 원판을 목적지로 이동
HanoiRoutine(n - 1, temp, from, to);
}
}
4.1.6 O(n!) 순열
using System.Collections.Generic;
using UnityEngine;
public class StudyAlgorithm : MonoBehaviour
{
// 숫자 리스트 초기화 (순열을 만들 숫자들)
private List<int> nums = new List<int> { 1, 2, 3 };
void Start()
{
// 순열 만들기 시작 (0번째 인덱스부터 시작)
Permute(nums, 0);
}
// 순열을 만드는 함수
void Permute(List<int> nums, int start)
{
// start가 리스트의 길이와 같으면 순열이 완성된 상태
if (start == nums.Count)
{
// 리스트의 숫자들을 쉼표와 공백(", ")으로 구분한 문자열로 변환해서 출력
// 예: nums = [1, 2, 3] 이면 "1, 2, 3" 출력
Debug.Log(string.Join(", ", nums));
return;
}
// start부터 끝까지 i를 돌면서 숫자를 교환
for (int i = start; i < nums.Count; i++)
{
// start 위치와 i 위치의 숫자를 교환 (swap)
int temp = nums[start];
nums[start] = nums[i];
nums[i] = temp;
// 다음 위치(start + 1)에 대해 다시 순열 생성
Permute(nums, start + 1);
// 순열 생성이 끝난 뒤 숫자를 원래대로 돌려놓음 (백트래킹)
temp = nums[start];
nums[start] = nums[i];
nums[i] = temp;
}
}
}
//1, 2, 3
//1, 3, 2
//2, 1, 3
//2, 3, 1
//3, 2, 1
//3, 1, 2
<적어보자>
1,2,3
Start 0
i=0 1,2,3 -permute start = 1.
i=1 1,2,3 permute start =2
i= 2 1,2,3, permute start =3 -> {1,2,3} 출력
i=2 1,3,2 permute start =2
i=2 1,3,2 permute start =3 -> {1,3,2} 출력
i=1 2, 1, 3 permute start =1
i=1 2,1,3, permute start = 2
i=2 2,1,3 , permute start=3 -> {2,1,3}출력
i=2 2,3,1 permute start =2
i=2 2,3,1 , permute start=3 -> {2,3,1} 출력
i=2 3,2,1 permute start = 1
i=1 3,2,1 permute start =2
i=2 3,2,1 permute start=3 -> {3,2,1} 출력
i=2 3,1,2, permute start =2
i=2 3,1,2 permute start=3 -> {3,1,2}출력
'프로그래밍 > 유니티 부트캠프' 카테고리의 다른 글
| 유니티(다익스트라 알고리즘, 정렬 - 선택 정렬, 삽입 정렬, 버블 정렬, 퀵 정렬) _ 멋쟁이사자처럼 유니티 부트캠프 후기 40회차 (1) | 2025.07.14 |
|---|---|
| 유니티(탐색 알고리즘) _ 멋쟁이사자처럼 유니티 부트캠프 후기 39회차 (0) | 2025.07.12 |
| 유니티(리스트, 스택 실습 _ 하노이의 탑) _ 멋쟁이사자처럼 유니티 부트캠프 후기 37회차 (0) | 2025.07.10 |
| 유니티(자료구조2, 형상관리 깃브랜치, 배열 예제_3D_폭탄) _ 멋쟁이사자처럼 유니티 부트캠프 후기 36회차 (0) | 2025.07.08 |
| 유니티(자료구조1) _ 멋쟁이사자처럼 유니티 부트캠프 후기 35회차 (2) | 2025.07.07 |