Unity의 핵심 시스템을 물리/수학적 원리와 함께 정리한 종합 복습 자료입니다. GameObject-Component 아키텍처부터 최적화까지, 실무에 필요한 개념을 심도있게 다룹니다. New Input System, TextMeshPro 등 최신 시스템 중심으로 작성되었습니다.
Unity는 게임 엔진이지만, 그 내부는 물리 시뮬레이션, 선형대수, 컴퓨터 그래픽스 등 다양한 학문적 기반 위에 구축되어 있습니다. 이와 같은 시스템의 동작 원리를 수학적으로 이해하고 실무에 적용할 수 있도록 구성했습니다.
예를 들어 물리 엔진의 뉴턴 역학 방정식, Transform의 4×4 변환 행렬, 쿼터니언의 수학적 정의, 조명 모델의 Phong Reflection 등 핵심 수식을 포함하여, 왜 그렇게 동작하는지를 이해할 수 있도록 했습니다.
대상 독자: Unity 중급 개발자, 게임 프로그래머 지망생
필요 배경: C# 기초, 물리학/수학 기본 개념
Unity는 Entity-Component-System (ECS) 패턴의 변형인 GameObject-Component 아키텍처를 기반으로 합니다. 전통적인 객체지향 상속 구조 대신 조합(Composition)을 통한 설계를 지향합니다.
핵심 원리:
Unity의 게임 루프는 전형적인 실시간 시뮬레이션 루프를 따릅니다:
| 단계 | 구분 | 설명 |
|---|---|---|
| Initialization | 초기화 | 게임 엔진, 리소스, 시스템 초기화 |
| Frame Start | 프레임 시작 | 새 프레임 시작, 타이머 설정 |
| Input | 입력 처리 | 키보드, 마우스, 컨트롤러 입력 수집 및 처리 |
| Physics Update | 물리 업데이트 | 고정 시간 간격(Fixed timestep)으로 물리 시뮬레이션 |
| Game Logic | 게임 로직 | AI, 충돌 검사, 상태 머신, 게임 규칙 처리 |
| Animation | 애니메이션 | 스프라이트, 모델 애니메이션 업데이트 |
| Rendering | 렌더링 | 화면에 그래픽 요소들을 그리기 |
| Frame End | 프레임 종료 | 프레임 완료, V-Sync 대기 |
| ↻ Repeat | 반복 | 다음 프레임으로 루프 재시작 |
주요 업데이트 함수 실행 순서:
1. FixedUpdate() - 물리 시뮬레이션 (고정 시간 간격)
2. Update() - 매 프레임 로직
3. LateUpdate() - Update 이후 처리 (카메라 추적 등)
시간 관리:
Time.deltaTime: 이전 프레임과 현재 프레임 사이의 시간 ()Time.fixedDeltaTime: 물리 업데이트 시간 간격 (기본값 0.02s, 50Hz)Reference:
GameObject는 Unity의 기본 엔티티입니다. 자체적으론 기능이 없으며, Component들의 컨테이너 역할만 수행합니다.
필수 Component:
Transform: 모든 GameObject가 반드시 가지는 Component (위치, 회전, 스케일)주요 내장 Component:
Renderer: 시각적 표현 (MeshRenderer, SpriteRenderer)Collider: 충돌 감지 영역Rigidbody: 물리적 속성Script: 사용자 정의 로직Component 접근 방법:
// 방법 1: GetComponent (런타임 탐색)
Rigidbody rb = GetComponent<Rigidbody>();
// 방법 2: SerializeField (인스펙터에서 할당)
[SerializeField] private Rigidbody rb;
// 방법 3: RequireComponent (의존성 명시)
[RequireComponent(typeof(Rigidbody))]
public class MyScript : MonoBehaviour { }
성능 고려사항:
GetComponent<T>()는 비용이 높은 연산 (리플렉션 사용)Tag:
Layer:
// Layer 비트마스크 연산
LayerMask mask = LayerMask.GetMask("Player", "Enemy");
// 내부적으로: (1 << layerIndex1) | (1 << layerIndex2)
Reference:
Unity는 왼손 좌표계(Left-Handed Coordinate System)를 사용합니다:
좌표 공간의 계층:
1. Local Space: 부모 기준 상대 좌표
2. World Space: 전역 절대 좌표
3. View Space: 카메라 기준 좌표
4. Clip Space: 투영 후 정규화된 좌표
Transform은 3가지 속성으로 구성됩니다:
Position ():
transform.localPositiontransform.positionRotation ():
Unity는 회전을 표현하기 위해 쿼터니언(Quaternion)을 사용합니다:
여기서 (단위 쿼터니언)
쿼터니언의 장점:
오일러 각과의 변환:
// Euler → Quaternion
Quaternion q = Quaternion.Euler(x, y, z);
// Quaternion → Euler
Vector3 euler = transform.rotation.eulerAngles;
회전 연산:
// 회전 합성 (순서 중요: 비가환 연산)
Quaternion combined = q2 * q1; // q1 먼저, q2 나중
// 벡터 회전
Vector3 rotated = q * vector;
// 보간
Quaternion interpolated = Quaternion.Slerp(q1, q2, t);
Scale ():
transform.localScaletransform.lossyScale (읽기 전용)Transform은 내부적으로 4×4 동차 변환 행렬로 표현됩니다:
여기서:
계층 구조에서의 변환:
// 수동 계층 변환
Matrix4x4 localToWorld = transform.localToWorldMatrix;
Matrix4x4 worldToLocal = transform.worldToLocalMatrix;
Vector3 worldPos = localToWorld.MultiplyPoint3x4(localPos);
Transform은 기본 방향 벡터를 제공합니다:
Vector3 forward = transform.forward; // (0, 0, 1) in local
Vector3 right = transform.right; // (1, 0, 0) in local
Vector3 up = transform.up; // (0, 1, 0) in local
Reference:
Unity는 NVIDIA PhysX 엔진을 사용하며, 뉴턴 역학 기반의 강체 동역학(Rigid Body Dynamics)을 시뮬레이션합니다.
기본 운동 방정식:
여기서:
주요 속성:
mass: 질량 (kg)drag: 선형 감쇠 (air resistance)angularDrag: 각 감쇠useGravity: 중력 적용 여부isKinematic: 운동학적 모드 (물리 엔진의 힘을 무시)물리 모드:
힘 적용 방법:
Rigidbody rb;
// 1. 순간 힘 (충격)
rb.AddForce(force, ForceMode.Impulse);
// Δv = F/m
// 2. 지속적 힘
rb.AddForce(force, ForceMode.Force);
// 매 FixedUpdate마다 적용
// 3. 속도 직접 변경
rb.velocity = new Vector3(x, y, z);
// 4. 토크 (회전력)
rb.AddTorque(torque);
ForceMode 비교:
| ForceMode | 수식 | 질량 영향 | 사용 예 |
|---|---|---|---|
| Force | O | 지속적 힘 | |
| Impulse | O | 순간 충격 | |
| Acceleration | X | 가속도 | |
| VelocityChange | X | 직접 속도 변경 |
Collider 종류:
Trigger vs Collider:
isTrigger = false: 물리적 충돌 (밀림)isTrigger = true: 이벤트만 발생 (통과 가능)// Collision (물리 충돌)
void OnCollisionEnter(Collision collision) { }
void OnCollisionStay(Collision collision) { }
void OnCollisionExit(Collision collision) { }
// Trigger (영역 감지)
void OnTriggerEnter(Collider other) { }
void OnTriggerStay(Collider other) { }
void OnTriggerExit(Collider other) { }
Unity는 2단계 충돌 감지를 사용합니다:
1. Broad Phase (넓은 검색):
2. Narrow Phase (정밀 검색):
광선을 쏴서 충돌을 감지하는 기법:
// 기본 Raycast
Ray ray = new Ray(origin, direction);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, maxDistance, layerMask))
{
Debug.Log($"Hit: {hit.collider.name}");
Debug.Log($"Distance: {hit.distance}");
Debug.Log($"Point: {hit.point}");
Debug.Log($"Normal: {hit.normal}");
}
// 모든 충돌 감지
RaycastHit[] hits = Physics.RaycastAll(ray, maxDistance);
// Sphere Cast (두께 있는 광선)
Physics.SphereCast(origin, radius, direction, out hit, maxDistance);
최적화 팁:
마찰과 반발 계수 정의:
PhysicMaterial material = new PhysicMaterial();
material.dynamicFriction = 0.6f; // 운동 마찰 계수 (μ_k)
material.staticFriction = 0.8f; // 정지 마찰 계수 (μ_s)
material.bounciness = 0.5f; // 반발 계수 (e)
마찰력:
반발 계수:
완전 탄성 충돌:
완전 비탄성 충돌:
Reference:
Unity의 모든 스크립트는 MonoBehaviour를 상속받아야 하며, 특정 생명주기 메서드를 통해 동작합니다.
실행 순서 (Initialization):
Awake()
↓
OnEnable()
↓
Start()
프레임 업데이트 순서:
FixedUpdate() (물리 업데이트, 고정 시간)
↓
Update() (매 프레임)
↓
LateUpdate() (Update 이후)
종료 순서:
OnDisable()
↓
OnDestroy()
주요 메서드 비교:
| 메서드 | 호출 시점 | 용도 |
|---|---|---|
Awake() | 스크립트 인스턴스 로드 시 (1회) | 초기화, 참조 설정 |
Start() | 첫 Update 전 (1회) | 게임 시작 로직 |
OnEnable() | GameObject 활성화 시 | 이벤트 구독 |
Update() | 매 프레임 | 일반 게임 로직 |
FixedUpdate() | 고정 시간 간격 | 물리 연산 |
LateUpdate() | Update 이후 | 카메라 추적 |
OnDisable() | GameObject 비활성화 시 | 이벤트 구독 해제 |
OnDestroy() | GameObject 파괴 시 | 리소스 정리 |
객체 생성:
// Prefab에서 생성
GameObject instance = Instantiate(prefab, position, rotation);
// 부모 지정
GameObject child = Instantiate(prefab, parent.transform);
// 제네릭 버전 (Component 직접 반환)
Enemy enemy = Instantiate(enemyPrefab, position, rotation);
객체 파괴:
// 즉시 파괴
Destroy(gameObject);
// 지연 파괴
Destroy(gameObject, 3.0f);
// 컴포넌트만 파괴
Destroy(GetComponent<Rigidbody>());
DestroyImmediate vs Destroy:
Destroy(): 현재 프레임 끝에 파괴 (권장)DestroyImmediate(): 즉시 파괴 (Editor 전용, 런타임 사용 금지)Unity는 두 가지 입력 시스템을 제공합니다:
1. Legacy Input Manager (구 Input System):
간단하지만 기능이 제한적입니다.
void Update()
{
// 키 입력
if (Input.GetKeyDown(KeyCode.Space)) { }
if (Input.GetKey(KeyCode.W)) { }
if (Input.GetKeyUp(KeyCode.Space)) { }
// 마우스 입력
if (Input.GetMouseButtonDown(0)) { } // 좌클릭
// 축 입력 (부드러운 입력)
float horizontal = Input.GetAxis("Horizontal"); // -1 ~ 1
float vertical = Input.GetAxis("Vertical");
}
2. New Input System (권장):
더 강력하고 유연한 입력 처리를 제공합니다.
설치:
Input Actions 방식:
using UnityEngine.InputSystem;
public class PlayerController : MonoBehaviour
{
private PlayerInput playerInput;
private InputAction moveAction;
private InputAction jumpAction;
void Awake()
{
playerInput = GetComponent<PlayerInput>();
// Input Action 참조
moveAction = playerInput.actions["Move"];
jumpAction = playerInput.actions["Jump"];
}
void Update()
{
// 연속 입력 (Vector2)
Vector2 moveInput = moveAction.ReadValue<Vector2>();
// 버튼 입력
if (jumpAction.triggered) // 한 번만 트리거
{
Jump();
}
if (jumpAction.IsPressed()) // 계속 누르고 있음
{
ChargeJump();
}
}
// 또는 C# 이벤트 방식
void OnEnable()
{
jumpAction.performed += OnJump;
jumpAction.canceled += OnJumpCanceled;
}
void OnDisable()
{
jumpAction.performed -= OnJump;
jumpAction.canceled -= OnJumpCanceled;
}
void OnJump(InputAction.CallbackContext context)
{
Jump();
}
void OnJumpCanceled(InputAction.CallbackContext context)
{
// 점프 버튼 떼었을 때
}
}
Player Input Component 방식:
// Unity Events를 사용한 더 간단한 방식
public class PlayerController : MonoBehaviour
{
public void OnMove(InputValue value)
{
Vector2 moveInput = value.Get<Vector2>();
// 이동 처리
}
public void OnJump(InputValue value)
{
if (value.isPressed)
{
Jump();
}
}
}
New Input System의 장점:
Input Action Asset:
ActionMap: Gameplay
├─ Move (Vector2): WASD, Left Stick
├─ Jump (Button): Space, South Button (A/Cross)
├─ Fire (Button): Left Mouse, Right Trigger
└─ Aim (Vector2): Mouse Delta, Right Stick
ActionMap: UI
├─ Navigate (Vector2): WASD, D-pad
└─ Submit (Button): Enter, South Button
충돌 이벤트:
void OnCollisionEnter(Collision collision)
{
// 충돌 정보
ContactPoint contact = collision.contacts[0];
Vector3 normal = contact.normal;
float impulse = collision.impulse.magnitude;
}
void OnTriggerEnter(Collider other)
{
// Trigger 충돌 (더 가벼움)
if (other.CompareTag("Player"))
{
// Player와 충돌
}
}
직렬화 (Serialization):
Unity는 Inspector에 표시하고 저장할 데이터를 직렬화합니다.
// 자동 직렬화 (public)
public float speed = 5.0f;
// 수동 직렬화 (private)
[SerializeField] private int health = 100;
// 직렬화 제외
[System.NonSerialized] public float tempValue;
// 숨김 처리
[HideInInspector] public bool debugFlag;
프로퍼티 패턴:
// C# 프로퍼티 (직렬화 안됨)
public float Speed { get; set; }
// Backing field 패턴 (직렬화 됨)
[SerializeField] private float speed;
public float Speed => speed; // 읽기 전용
// 유효성 검사
private float speed;
public float Speed
{
get => speed;
set => speed = Mathf.Max(0, value); // 음수 방지
}
Unity는 C#의 GC(Garbage Collector)를 사용하지만, 실시간 게임에서는 GC로 인한 프레임 드롭이 문제가 됩니다.
GC 유발 패턴:
// BAD: 매 프레임 allocation
void Update()
{
Vector3 temp = new Vector3(x, y, z); // 힙 할당
string text = "Score: " + score; // 문자열 연결
}
// GOOD: 재사용
private Vector3 temp;
private StringBuilder sb = new StringBuilder();
void Update()
{
temp.Set(x, y, z); // 기존 객체 재사용
sb.Clear();
sb.Append("Score: ").Append(score);
}
Object Pooling:
public class ObjectPool
{
private Queue<GameObject> pool = new Queue<GameObject>();
private GameObject prefab;
public GameObject Get()
{
if (pool.Count > 0)
{
GameObject obj = pool.Dequeue();
obj.SetActive(true);
return obj;
}
return Instantiate(prefab);
}
public void Return(GameObject obj)
{
obj.SetActive(false);
pool.Enqueue(obj);
}
}
Reference:
Coroutine은 실행을 일시 중지하고 나중에 재개할 수 있는 함수입니다. Unity에서는 IEnumerator를 반환하는 메서드로 구현됩니다.
기본 구조:
IEnumerator MyCoroutine()
{
Debug.Log("Start");
yield return null; // 다음 프레임까지 대기
Debug.Log("Next frame");
yield return new WaitForSeconds(2.0f); // 2초 대기
Debug.Log("After 2 seconds");
}
// 실행
StartCoroutine(MyCoroutine());
| Yield 명령 | 설명 | 재개 시점 |
|---|---|---|
yield return null | 한 프레임 대기 | 다음 Update |
yield return new WaitForSeconds(t) | t초 대기 | t초 후 |
yield return new WaitForFixedUpdate() | 물리 업데이트 대기 | 다음 FixedUpdate |
yield return new WaitForEndOfFrame() | 프레임 끝 대기 | 렌더링 후 |
yield return StartCoroutine(other) | 다른 Coroutine 완료 대기 | other 종료 후 |
yield return new WaitUntil(() => condition) | 조건 만족 대기 | condition이 true |
yield return new WaitWhile(() => condition) | 조건 false 대기 | condition이 false |
// Coroutine 참조 저장
private Coroutine activeCoroutine;
void Start()
{
// 시작
activeCoroutine = StartCoroutine(MyCoroutine());
}
void Stop()
{
// 중지
if (activeCoroutine != null)
{
StopCoroutine(activeCoroutine);
activeCoroutine = null;
}
// 모든 Coroutine 중지
StopAllCoroutines();
}
1. 페이드 효과:
IEnumerator FadeOut(CanvasGroup canvasGroup, float duration)
{
float elapsed = 0f;
float startAlpha = canvasGroup.alpha;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = elapsed / duration;
canvasGroup.alpha = Mathf.Lerp(startAlpha, 0f, t);
yield return null;
}
canvasGroup.alpha = 0f;
}
2. 순차 실행:
IEnumerator Sequence()
{
yield return StartCoroutine(Action1());
yield return StartCoroutine(Action2());
yield return StartCoroutine(Action3());
Debug.Log("All actions completed");
}
3. 타임아웃 패턴:
IEnumerator LoadWithTimeout(float timeout)
{
float elapsed = 0f;
bool loaded = false;
StartCoroutine(LoadData(() => loaded = true));
while (!loaded && elapsed < timeout)
{
elapsed += Time.deltaTime;
yield return null;
}
if (!loaded)
{
Debug.LogError("Timeout!");
}
}
Coroutine 특징:
Async/Await (C# Task):
async Task LoadDataAsync()
{
await Task.Delay(1000); // 1초 대기
// 주의: Unity API는 메인 스레드에서만 호출 가능
// 다시 메인 스레드로 돌아와야 함
}
주의사항:
GC 발생:
// BAD: 매번 새로운 객체 생성
IEnumerator Bad()
{
while (true)
{
yield return new WaitForSeconds(1.0f); // GC!
}
}
// GOOD: 캐싱
private WaitForSeconds oneSecond = new WaitForSeconds(1.0f);
IEnumerator Good()
{
while (true)
{
yield return oneSecond; // 재사용
}
}
Reference:
Unity의 렌더링 파이프라인은 다음 단계를 거칩니다:
Application Stage (CPU)
↓
Culling (Scene 가시성 판별)
↓
Geometry Stage (GPU)
↓
Rasterization (픽셀 생성)
↓
Fragment/Pixel Shader (색상 계산)
↓
Output Merger (최종 이미지)
투영 변환:
Unity는 두 가지 투영 방식을 지원합니다:
1. Perspective (원근) 투영:
여기서:
2. Orthographic (정사영) 투영:
카메라 설정:
Camera cam = Camera.main;
// 투영 설정
cam.orthographic = false; // Perspective
cam.fieldOfView = 60f; // FOV in degrees
cam.nearClipPlane = 0.3f;
cam.farClipPlane = 1000f;
// 렌더링 설정
cam.clearFlags = CameraClearFlags.Skybox;
cam.cullingMask = LayerMask.GetMask("Default", "UI");
Material 구조:
Material material = renderer.material; // 복사본 생성
Material sharedMaterial = renderer.sharedMaterial; // 원본 참조
// 속성 설정
material.SetColor("_Color", Color.red);
material.SetFloat("_Metallic", 0.5f);
material.SetTexture("_MainTex", texture);
주의:
material: 인스턴스별 복사본 (GC 발생)sharedMaterial: 공유 Material (모든 인스턴스에 영향)조명 종류:
조명 계산 모델 (Phong Reflection):
여기서:
PBR (Physically Based Rendering):
Unity의 Standard Shader는 PBR 기반:
Frustum Culling:
Occlusion Culling:
// Occlusion Culling 영역 설정
OcclusionArea area = gameObject.AddComponent<OcclusionArea>();
area.size = new Vector3(50, 10, 50);
Distance Culling:
// Camera의 최대 렌더링 거리 설정
Camera.main.farClipPlane = 500f;
// Layer별 거리 설정
float[] distances = new float[32];
distances[LayerMask.NameToLayer("Details")] = 50f;
Camera.main.layerCullDistances = distances;
Unity는 여러 렌더 파이프라인을 지원합니다:
1. Built-in Render Pipeline:
2. URP (Universal Render Pipeline):
3. HDRP (High Definition Render Pipeline):
Rendering Path 비교:
| 특성 | Forward | Deferred |
|---|---|---|
| 조명 수 | 제한적 (픽셀당) | 무제한 (씬당) |
| 성능 | 조명↑ 시 저하 | 안정적 |
| 투명 | 지원 | 제한적 |
| MSAA | 지원 | 비지원 |
Reference:
Unity는 Mecanim이라는 상태 기반 애니메이션 시스템을 사용합니다.
핵심 구성 요소:
상태 머신 (State Machine):
Entry
↓
Idle ←→ Run
↓
Jump
↓
Falling
↓
Land → Idle
Transition 조건:
Animator animator;
// Parameter 설정
animator.SetBool("isRunning", true);
animator.SetFloat("speed", 5.0f);
animator.SetInteger("weaponType", 2);
animator.SetTrigger("jump"); // 한 번만 트리거
// 상태 확인
AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(0);
bool isJumping = stateInfo.IsName("Jump");
float normalizedTime = stateInfo.normalizedTime; // 0~1
여러 애니메이션을 부드럽게 혼합:
1D Blend:
2D Blend:
// Blend parameter 설정
animator.SetFloat("velocityX", Input.GetAxis("Horizontal"));
animator.SetFloat("velocityZ", Input.GetAxis("Vertical"));
여러 레이어로 애니메이션 합성:
Base Layer (전신)
↓
Upper Body Layer (상체만)
↓
IK Layer (손/발 위치 조정)
Layer 설정:
// Layer weight 조정
animator.SetLayerWeight(1, 0.7f); // Upper body layer 70%
Forward Kinematics (FK):
Inverse Kinematics (IK):
void OnAnimatorIK(int layerIndex)
{
if (animator)
{
// 손 IK
animator.SetIKPositionWeight(AvatarIKGoal.RightHand, 1.0f);
animator.SetIKRotationWeight(AvatarIKGoal.RightHand, 1.0f);
animator.SetIKPosition(AvatarIKGoal.RightHand, targetPosition);
animator.SetIKRotation(AvatarIKGoal.RightHand, targetRotation);
// Look At (시선 처리)
animator.SetLookAtWeight(1.0f);
animator.SetLookAtPosition(lookTarget.position);
}
}
애니메이션 타임라인에 이벤트 삽입:
// Animation Clip의 특정 시점에 설정
// 자동으로 호출되는 함수
void OnFootstep()
{
// 발소리 재생
AudioSource.PlayClipAtPoint(footstepSound, transform.position);
}
void OnAttackHit()
{
// 공격 판정
Collider[] hits = Physics.OverlapSphere(attackPoint.position, attackRadius);
foreach (var hit in hits)
{
if (hit.CompareTag("Enemy"))
{
hit.GetComponent<Health>().TakeDamage(attackDamage);
}
}
}
애니메이션의 이동을 GameObject에 적용:
// Animator에서 Root Motion 활성화
animator.applyRootMotion = true;
// Root Motion 콜백
void OnAnimatorMove()
{
// 애니메이션의 이동량 가져오기
Vector3 deltaPosition = animator.deltaPosition;
// 커스텀 처리 (예: 중력 추가)
deltaPosition += Physics.gravity * Time.deltaTime;
// 적용
transform.position += deltaPosition;
}
Reference:
모든 UI는 Canvas 내부에 존재해야 합니다.
Render Mode:
Screen Space - Overlay:
Screen Space - Camera:
World Space:
Canvas Scaler:
화면 해상도에 따른 UI 크기 조정:
CanvasScaler scaler = canvas.GetComponent<CanvasScaler>();
// Constant Pixel Size: 픽셀 기준 (고정 크기)
scaler.uiScaleMode = CanvasScaler.ScaleMode.ConstantPixelSize;
// Scale With Screen Size: 참조 해상도 기준 (비율 유지)
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
scaler.referenceResolution = new Vector2(1920, 1080);
scaler.matchWidthOrHeight = 0.5f; // 0: width, 1: height
UI 요소의 Transform은 RectTransform을 사용합니다.
Anchor (앵커):
RectTransform rect = GetComponent<RectTransform>();
// Anchored Position
rect.anchoredPosition = new Vector2(0, 0);
// Size
rect.sizeDelta = new Vector2(200, 100);
// Pivot (회전/스케일 중심)
rect.pivot = new Vector2(0.5f, 0.5f); // 중앙
// Anchor Preset
rect.anchorMin = new Vector2(0, 1); // 좌상단
rect.anchorMax = new Vector2(0, 1);
Stretching:
앵커를 펼쳐 부모 크기에 따라 늘어나게 설정:
// 전체 화면 stretch
rect.anchorMin = new Vector2(0, 0);
rect.anchorMax = new Vector2(1, 1);
rect.offsetMin = Vector2.zero;
rect.offsetMax = Vector2.zero;
Image:
Image image = GetComponent<Image>();
image.sprite = mySprite;
image.color = Color.red;
image.fillAmount = 0.5f; // HP 바 등에 사용
image.type = Image.Type.Filled;
TextMeshPro (TMP) - 권장:
TextMeshPro는 Unity의 최신 텍스트 렌더링 시스템으로, 레거시 Text보다 훨씬 우수한 품질과 성능을 제공합니다.
설치:
TMP의 장점:
using TMPro;
// TextMeshProUGUI (UI용)
TextMeshProUGUI tmpText = GetComponent<TextMeshProUGUI>();
tmpText.text = "Score: 100";
tmpText.fontSize = 24;
tmpText.alignment = TextAlignmentOptions.Center;
tmpText.fontStyle = FontStyles.Bold;
// 리치 텍스트
tmpText.text = "<color=red>HP:</color> <b>100</b>";
tmpText.text = "<size=150%>Big Text</size>";
tmpText.text = "<link=\"url\">Clickable Link</link>";
// 그라디언트
tmpText.enableVertexGradient = true;
tmpText.colorGradient = new VertexGradient(
Color.white, // Top Left
Color.white, // Top Right
Color.blue, // Bottom Left
Color.blue // Bottom Right
);
// Outline
tmpText.outlineWidth = 0.2f;
tmpText.outlineColor = Color.black;
// TextMeshPro (3D 공간용)
TextMeshPro tmp3D = GetComponent<TextMeshPro>();
tmp3D.text = "World Space Text";
Legacy UI.Text (사용 비권장):
using UnityEngine.UI;
Text text = GetComponent<Text>();
text.text = "Score: 100";
text.fontSize = 24;
text.alignment = TextAnchor.MiddleCenter;
// 스케일 시 픽셀화, 성능 저하
마이그레이션:
Button:
Button button = GetComponent<Button>();
button.onClick.AddListener(OnButtonClicked);
void OnButtonClicked()
{
Debug.Log("Button clicked!");
}
Slider:
Slider slider = GetComponent<Slider>();
slider.minValue = 0f;
slider.maxValue = 100f;
slider.value = 50f;
slider.onValueChanged.AddListener(OnSliderValueChanged);
void OnSliderValueChanged(float value)
{
Debug.Log($"Slider value: {value}");
}
자동으로 UI 배치를 관리:
Layout Group:
Layout Element:
LayoutElement element = GetComponent<LayoutElement>();
element.preferredWidth = 200f;
element.preferredHeight = 100f;
element.flexibleWidth = 1f; // 남은 공간 분배 비율
Content Size Fitter:
내용물 크기에 맞춰 자동 조정:
ContentSizeFitter fitter = GetComponent<ContentSizeFitter>();
fitter.horizontalFit = ContentSizeFitter.FitMode.PreferredSize;
fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
UI 상호작용을 처리:
Raycast:
UI는 Graphic Raycaster를 통해 클릭 감지:
using UnityEngine.InputSystem; // New Input System용
// UI 요소 클릭 여부 확인
bool isPointerOverUI = EventSystem.current.IsPointerOverGameObject();
// Raycast 결과 가져오기
PointerEventData eventData = new PointerEventData(EventSystem.current);
// Legacy Input
eventData.position = Input.mousePosition;
// New Input System
eventData.position = Mouse.current.position.ReadValue();
List<RaycastResult> results = new List<RaycastResult>();
EventSystem.current.RaycastAll(eventData, results);
이벤트 인터페이스:
public class MyButton : MonoBehaviour,
IPointerEnterHandler,
IPointerExitHandler,
IPointerClickHandler
{
public void OnPointerEnter(PointerEventData eventData)
{
// 마우스 오버
}
public void OnPointerExit(PointerEventData eventData)
{
// 마우스 아웃
}
public void OnPointerClick(PointerEventData eventData)
{
// 클릭
}
}
스크롤 가능한 컨텐츠:
ScrollRect scrollRect = GetComponent<ScrollRect>();
scrollRect.content = contentTransform;
scrollRect.vertical = true;
scrollRect.horizontal = false;
scrollRect.scrollSensitivity = 20f;
// 스크롤 위치 제어
scrollRect.normalizedPosition = new Vector2(0, 1); // 최상단
동적 컨텐츠 추가:
void AddItem(GameObject itemPrefab)
{
GameObject item = Instantiate(itemPrefab, content);
// Layout Group이 자동으로 배치
}
Reference:
최적화의 시작은 측정입니다:
Unity Profiler:
// 커스텀 프로파일링
using Unity.Profiling;
ProfilerMarker myMarker = new ProfilerMarker("MySystem.Update");
void Update()
{
myMarker.Begin();
// 측정할 코드
myMarker.End();
}
Draw Call 감소:
Draw Call은 CPU가 GPU에게 내리는 렌더링 명령입니다. 많을수록 성능 저하.
Batching:
// Static으로 마킹
gameObject.isStatic = true;
Dynamic Batching:
GPU Instancing:
// Material Property Block으로 인스턴스별 속성
MaterialPropertyBlock props = new MaterialPropertyBlock();
props.SetColor("_Color", color);
renderer.SetPropertyBlock(props);
Occlusion Culling:
앞서 설명한 대로, 보이지 않는 객체를 렌더링하지 않음.
Level of Detail (LOD):
거리에 따라 메시 복잡도 변경:
LODGroup lodGroup = gameObject.AddComponent<LODGroup>();
LOD[] lods = new LOD[3];
lods[0] = new LOD(0.5f, highDetailRenderers); // 50% 이상
lods[1] = new LOD(0.2f, midDetailRenderers); // 20~50%
lods[2] = new LOD(0.0f, lowDetailRenderers); // 20% 이하
lodGroup.SetLODs(lods);
Layer Collision Matrix:
불필요한 충돌 검사 제거:
// Edit > Project Settings > Physics
// Layer Collision Matrix에서 체크 해제
Physics.IgnoreLayerCollision(playerLayer, playerLayer);
Fixed Timestep 조정:
// Edit > Project Settings > Time
Time.fixedDeltaTime = 0.02f; // 기본값 50Hz
// 더 높은 값 = 더 빠르지만 덜 정확
Collider 최적화:
1. 캐싱:
// BAD
void Update()
{
GetComponent<Rigidbody>().AddForce(force); // 매 프레임 탐색
}
// GOOD
private Rigidbody rb;
void Awake()
{
rb = GetComponent<Rigidbody>(); // 한 번만
}
void Update()
{
rb.AddForce(force);
}
2. 불필요한 Update 방지:
// Update가 필요 없는 경우
public class StaticObject : MonoBehaviour
{
void Awake()
{
// 초기화만
enabled = false; // Update 호출 안됨
}
}
3. 태그 비교 최적화:
// BAD
if (other.tag == "Player") { }
// GOOD
if (other.CompareTag("Player")) { } // 문자열 비교 최적화됨
4. 벡터 연산 최적화:
// BAD
float distance = Vector3.Distance(a, b);
if (distance < threshold) { }
// GOOD (제곱근 연산 생략)
float sqrDistance = (a - b).sqrMagnitude;
if (sqrDistance < threshold * threshold) { }
Object Pooling:
앞서 설명한 패턴 사용.
Texture 압축:
Texture Atlas:
여러 텍스처를 하나로 결합하여 Draw Call 감소.
Mesh 최적화:
// Mesh 최적화
mesh.Optimize();
// Read/Write 비활성화 (메모리 절약)
// Import Settings > Read/Write Enabled 체크 해제
해상도 조정:
// 동적 해상도
Screen.SetResolution(1280, 720, true);
// 렌더 스케일 (더 효율적)
QualitySettings.renderScale = 0.8f; // 80% 해상도
FPS 제한:
Application.targetFrameRate = 60; // 모바일에서 배터리 절약
MultiThreaded Rendering:
// Player Settings > Other Settings
// Graphics Jobs (Experimental) 활성화
렌더링:
물리:
스크립팅:
메모리:
Reference:
본 가이드는 Unity의 핵심 개념과 실무 활용을 물리/수학적 원리와 함께 정리한 것입니다. 각 섹션의 내용을 숙지하고 실제로 구현해보는 것이 중요합니다.
학습 권장 순서:
1. GameObject-Component 시스템 이해
2. Transform과 좌표계 마스터
3. 물리 엔진 실습
4. Coroutine으로 비동기 처리
5. UI 시스템 구축
6. 최적화 기법 적용
추가 학습 자료:
작성 일자: 2025년 10월 20일
대상: Unity 개발자 (중급)
필요 배경: C#, 물리학, 수학 기초
References:
1. Unity Technologies. (2024). Unity Documentation. https://docs.unity3d.com
2. Gregory, J. (2018). Game Engine Architecture (3rd ed.). CRC Press.
3. Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley.
4. Dunn, F., & Parberry, I. (2011). 3D Math Primer for Graphics and Game Development (2nd ed.). CRC Press.
5. Goldstein, H., Poole, C., & Safko, J. (2001). Classical Mechanics (3rd ed.). Addison-Wesley.
6. Ericson, C. (2004). Real-Time Collision Detection. Morgan Kaufmann.
7. Skeet, J. (2019). C# in Depth (4th ed.). Manning.
8. Akenine-Möller, T., Haines, E., Hoffman, N., et al. (2018). Real-Time Rendering (4th ed.). CRC Press.
9. Pharr, M., Jakob, W., & Humphreys, G. (2016). Physically Based Rendering (3rd ed.). Morgan Kaufmann.
10. Dickinson, C. (2015). Unity 5 Game Optimization. Packt.