Recommended Posts

유니티에 쓰이는 C#만 빠르게 학습하는 방법 — 20%로 80%를 움직이기

유니티에 쓰이는 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);
}
}

핵심: newMonoBehaviour를 직접 만들지 않습니다. 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, QuaternionGameObject, Transform
Vector3 p = transform.position; // 복사본 p.x += 1f; transform.position = p; // 다시 할당해야 적용

7) 이벤트/델리게이트 맛보기: UI 버튼과 코드 연결

C# 델리게이트/이벤트는 “무엇이 일어났을 때 누군가에게 알린다”입니다. 초반에는 UnityEventUI 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) 코루틴: IEnumeratoryield 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분): 이동·입력·상호작용

  1. 10′ Move.cs로 대상 추적(변수/자료형, [SerializeField])
  2. 10′ 입력으로 WASD 이동(if + Update)
  3. 15′ Spawner로 프리팹 다중 생성(for, List<T>)
  4. 10′ Button → Door.Open 연결(UnityEvent)

세션 B (45분): 타이밍·상태·구조

  1. 15′ Blinker 코루틴으로 점멸
  2. 10′ 쿨다운 구현(코루틴으로 yield WaitForSeconds)
  3. 10′ Vector3 값-재할당 패턴 퀴즈
  4. 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가지 — 간단 반박

  1. “인스펙터에 안 보여서 public으로 다 열자?” → 캡슐화 붕괴. private + [SerializeField]를 기본값으로.
  2. “코루틴 = 스레드?” → 아닙니다. 메인 스레드에서 프레임 단위로 중단·재개될 뿐.
  3. “Vector3는 클래스니까 참조겠지?”struct입니다. 값 복사 개념을 명확히.
  4. “무한 while이 더 간단!” → 에디터 프리즈의 지름길. 이미 Update가 루프입니다.
  5. “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