유니티에 쓰이는 C#만 빠르게 학습하는 방법 — 20%로 80%를 움직이기
목표: 에디터에서 곧장 게임 오브젝트를 움직이고, 버튼을 눌러 반응시키며, 시간을 기다렸다가 동작시키는 수준까지 도달합니다. 이를 위해 Unity에 바로 쓰이는 C# 핵심 8가지만 전문적 관점에서 추려, 왜 필요한지와 무엇은 과감히 건너뛰어도 되는지를 분명히 합니다.
아래 로드맵은 3시간 집중 러닝을 기준으로 설계했습니다. 각 섹션은 단문 이론 ▶ 미니 실습 ▶ 흔한 오해/실수 ▶ 체크포인트로 구성되어 있으며, 코드 조각은 그대로 붙여 넣어 바로 동작하도록 구성했습니다.
학습 전 전제: “Unity 스크립트 = 컴포넌트”
유니티 스크립트는 대체로 MonoBehaviour
를 상속하는 컴포넌트입니다. “클래스를 만들고 오브젝트에 붙인다”가 기본 흐름입니다. 즉, 객체지향의 거대한 이론보다 에디터 상호작용을 먼저 익히는 편이 빠릅니다.
1) 변수/자료형, 그리고 public
vs private
+ [SerializeField]
대부분의 초반 문제는 “인스펙터에서 값이 안 보인다/안 바뀐다”입니다. Unity는 직렬화 가능한 필드를 인스펙터에 노출합니다. public
은 자동 노출되지만, 캡슐화를 위해서는 private
+ [SerializeField]
를 권장합니다. 이 조합은 “외부 공개는 막되, 에디터에서 세팅은 가능”하게 합니다.
// Move.cs using UnityEngine;
public class Move : MonoBehaviour
{
[SerializeField] private float speed = 5f; // 인스펙터에서 조정
[SerializeField] private Transform target; // 드래그 앤 드롭으로 할당
void Update()
{
if (target == null) return;
transform.position = Vector3.MoveTowards(
transform.position, target.position, speed * Time.deltaTime);
}
}
private
만 쓰고 [SerializeField]
를 빼먹은 케이스. 또는 static
으로 선언해서 인스펙터 노출이 막힌 케이스를 점검하세요. 2) 조건/반복: if
, for
, foreach
— 프레임 기반 사고
Unity의 Update()
는 “매 프레임 호출”입니다. 따라서 if
로 상태를 거르고, for/foreach
로 다수 오브젝트에 일괄 적용하는 패턴이 빈번합니다. 프레임 독립을 위해 Time.deltaTime
을 곱하는 습관을 들이세요.
void Update() { if (Input.GetKey(KeyCode.W)) transform.Translate(Vector3.forward * speed * Time.deltaTime);
foreach (var e in enemies) // List<Transform> enemies
e.LookAt(transform.position);
}
경계하기: 무한 루프를 만들면 에디터가 멈춥니다. 프레임 루프는 Update
가 이미 제공하므로, 별도의 while(true)
는 특별한 이유가 없다면 지양하세요.
3) 메서드(매개변수/반환), 그리고 static
은 “공유 상태”임을 자각
메서드는 행동을 이름으로 묶어 재사용성을 높입니다. 반환형은 값을 돌려주어 다른 계산의 재료가 되게 합니다. static
은 “인스턴스와 무관한 공유”란 뜻이라, 편리하지만 전역 상태로 번지기 쉽습니다.
public int CalcDamage(int atk, int def) { int raw = atk - def; return Mathf.Max(1, raw); // 최소 1 보장 }
// static 유틸 예시
public static class MathUtil
{
public static float Lerp01(float t) => Mathf.Clamp01(t);
}
static
필드가 안 보여 “왜죠?” → 설계상 노출되지 않습니다. 에디터에서 조절해야 할 값은 static
으로 만들지 마세요. 4) 클래스/컴포넌트: “스크립트 = 붙여 쓰는 동작”
MonoBehaviour
를 상속한 클래스는 컴포넌트로서 게임 오브젝트에 부착됩니다. 즉, “데이터와 동작이 씬 상의 오브젝트에 귀속된다”는 사고가 중요합니다. 생성자 대신 Awake
/Start
/OnEnable
생명주기를 이해하세요.
public class Spawner : MonoBehaviour { [SerializeField] private GameObject prefab; [SerializeField] private int count = 5;
void Start()
{
for (int i = 0; i < count; i++)
Instantiate(prefab, Random.insideUnitSphere * 3f, Quaternion.identity);
}
}
핵심: new
로 MonoBehaviour
를 직접 만들지 않습니다. AddComponent<T>()
, Instantiate
를 사용하세요.
5) 컬렉션: List<T>
, Dictionary<K,V>
— “여러 개”와 “빠른 조회”
다수의 대상에 동일 로직을 적용할 때 List<T>
를, 특정 키로 빠르게 찾을 때 Dictionary<K,V>
를 씁니다. 에디터에서 드래그해 리스트를 채우는 패턴이 실전에서 잦습니다.
public class Party : MonoBehaviour { [SerializeField] private List<Transform> members; private Dictionary<string, Transform> map = new();
void Awake()
{
foreach (var m in members) map[m.name] = m;
}
public Transform FindByName(string key)
=> map.TryGetValue(key, out var t) ? t : null;
}
6) 구조체 vs 클래스: 값과 참조의 차이 (예: Vector3
는 struct)
구조체(struct)는 값 형식이라 복사가 기본이고, 클래스(class)는 참조 형식이라 주소를 공유합니다. Vector3
를 지역변수로 받아 수정해도 원본 transform.position
이 바뀌지 않는 이유가 여기에 있습니다.
항목 | struct(값) | class(참조) |
---|---|---|
대입/파라미터 | 복사본 전달 | 참조(주소) 전달 |
GC 영향 | 적음(스택/복사) | 있음(힙/수명) |
Unity 예시 | Vector3 , Quaternion | GameObject , Transform |
Vector3 p = transform.position; // 복사본 p.x += 1f; transform.position = p; // 다시 할당해야 적용
7) 이벤트/델리게이트 맛보기: UI 버튼과 코드 연결
C# 델리게이트/이벤트는 “무엇이 일어났을 때 누군가에게 알린다”입니다. 초반에는 UnityEvent와 UI Button의 OnClick을 통해 시각적으로 체득하세요. 복잡한 제네릭 델리게이트 문법은 뒤로 미루고, 연결과 해제의 타이밍을 익히는 게 먼저입니다.
using UnityEngine; using UnityEngine.Events;
public class Door : MonoBehaviour
{
public UnityEvent OnOpened;
public void Open()
{
// ... 애니메이션, 사운드 등
OnOpened?.Invoke();
}
}
UI Button의 OnClick에 Door.Open
을 드래그로 연결해 보세요. 이후에는 event Action
으로 일반 C# 이벤트도 단계적으로 확장하면 됩니다.
8) 코루틴: IEnumerator
와 yield return new WaitForSeconds
코루틴은 메서드를 프레임들에 걸쳐 진행시키는 유니티 특화 패턴입니다. 애니메이션 대기, 쿨다운, 파동적 스폰 등 “시간이 흐른다”는 표현이 핵심일 때 가장 단순하고 직관적입니다.
public class Blinker : MonoBehaviour { [SerializeField] private Renderer rend;
void OnEnable() => StartCoroutine(Blink());
private IEnumerator Blink()
{
while (true)
{
rend.enabled = !rend.enabled;
yield return new WaitForSeconds(0.5f);
}
}
}
StartCoroutine("Blink")
문자열 호출은 오타에 취약합니다. 가능하면 StartCoroutine(Blink())
처럼 메서드 참조를 사용하세요. 또한 오브젝트가 비활성화되면 코루틴도 중지됨을 기억하세요. 비판적 체크: 지금 굳이 안 배워도 되는 것
- 언어 이론 전범위(제네릭의 고급 제약, LINQ의 복잡한 쿼리 연산, 비동기
async/await
의 심층 패턴) - 디자인 패턴의 과도한 주입(초기에는 컴포지션 위주로 단순하게)
- 커스텀 에디터/툴링(기초 플레이 루프 확보 뒤에 투자)
필요해지면 그때 문제-해결 맥락으로 학습하세요. 동작하는 것이 먼저입니다.
90분 실습 플랜(권장)
세션 A (45분): 이동·입력·상호작용
- 10′ Move.cs로 대상 추적(변수/자료형,
[SerializeField]
) - 10′ 입력으로 WASD 이동(
if
+Update
) - 15′ Spawner로 프리팹 다중 생성(
for
,List<T>
) - 10′ Button → Door.Open 연결(UnityEvent)
세션 B (45분): 타이밍·상태·구조
- 15′ Blinker 코루틴으로 점멸
- 10′ 쿨다운 구현(코루틴으로
yield WaitForSeconds
) - 10′ Vector3 값-재할당 패턴 퀴즈
- 10′ 에러 습관: 콘솔 읽기,
NullReferenceException
제거 루틴
디버깅 체크리스트(초보자 필수)
- 스크립트가 붙어 있는가? (오브젝트 선택 → 인스펙터)
[SerializeField]
값이 비어 있지 않은가? (드래그 앤 드롭 확인)Update
가 비활성 오브젝트에 있지 않은가?- 프레임 독립 이동에
Time.deltaTime
을 곱했는가? - 콘솔의 첫 에러부터 해결했는가? (첫 에러가 이후의 연쇄 오류를 유발)
미니 실전 예제: 10줄로 “타겟 쫓고, 클릭 시 정지”
using UnityEngine; public class Chase : MonoBehaviour { [SerializeField] Transform target; [SerializeField] float speed = 3f; bool stopped;
void Update()
{
if (stopped || target == null) return;
transform.position = Vector3.MoveTowards(transform.position, target.position, speed * Time.deltaTime);
}
public void StopChase() => stopped = true; // UI Button에 연결
}
자주 묻는 오해 5가지 — 간단 반박
- “인스펙터에 안 보여서 public으로 다 열자?” → 캡슐화 붕괴.
private + [SerializeField]
를 기본값으로. - “코루틴 = 스레드?” → 아닙니다. 메인 스레드에서 프레임 단위로 중단·재개될 뿐.
- “Vector3는 클래스니까 참조겠지?” → struct입니다. 값 복사 개념을 명확히.
- “무한 while이 더 간단!” → 에디터 프리즈의 지름길. 이미
Update
가 루프입니다. - “static으로 전역 접근하면 편함” → 테스트·동시성·수명 관리가 어려워집니다. 신중히.
요약 & 다음 단계
핵심 8가지(변수/직렬화, 조건/반복, 메서드/반환과 static, 컴포넌트 생명주기, List/Dictionary
, struct vs class, 이벤트/델리게이트, 코루틴)를 Unity 에디터 맥락에서 익히면, 곧바로 “움직이고, 기다리고, 반응하는” 프로토타입을 만들 수 있습니다. 기존 C# 전체 스펙을 통과할 필요는 없습니다.
다음 단계로는 입력 시스템(New Input System), ScriptableObject 기반 데이터 자산, 씬 전환/어드레서블 등을 학습하면 확장성이 좋아집니다. 그러나 오늘의 목표는 작동하는 게임 루프 확보입니다. 우선 위 실습을 끝까지 완주하고, 콘솔 무오류 상태를 유지해 보세요.
부록: 한눈에 보는 문법 스니펫
인스펙터 노출 3형제
public float speed; // 노출 O, 외부 접근 O [SerializeField] private float hp; // 노출 O, 외부 접근 X [HideInInspector] public int score; // 노출 X, 외부 접근 O
Dictionary 안전 조회
if (map.TryGetValue(key, out var t)) Use(t); // 안전 else Debug.LogWarning($"No key: {key}");
본 문서는 블로거/노션/워드프레스 등 HTML 기반 편집에 바로 붙여 넣을 수 있도록, 전역 스크립트 없이 단일 섹션으로 구성했습니다.
Comments
Post a Comment