자주 쓰는 Unity API 한 장

이번 글에서는 아래 내용을 정리한다.

  • 등급 기준과 표 읽는 법
  • 모르는 명령을 찾는 세 가지 방법, 그리고 Inspector 칸 이름과 코드 이름
  • 먼저 외울 최상 8개
  • 클래스별 명령 사전: 스크립트 뼈대, 오브젝트, Transform, 물리, 수학, 입력, UI, 그림·애니메이션·소리, 카메라·씬·저장, 코루틴·이벤트·속성, 디버그·풀링·색·레이어
  • 에디터 조작, Unity 6에서 바뀐 이름, 2D와 3D 대응표

들어가기에 앞서

레모네이드 가판대 회차 3까지 끝내고 나니 C# 코드는 읽힌다. 그런데 직접 쓰려고 하면 유니티 명령이 떠오르지 않는다. Rigidbody를 코드에서 움직이려면 무엇을 불러야 하는지, Inspector에서 본 그 칸이 코드에서는 무슨 이름인지가 바로 나오지 않는다. 명령어를 모르는 느낌이다.

그래서 자주 쓰는 명령을 한 장에 모았다. 처음에는 대표 명령만 추렸는데 너무 빈약해서, 클래스별 사전으로 다시 만들었다. 자주 쓰는 것일수록 크게 적었다.

기준은 Unity 6(6000.6), 2D URP, 새 Input System이다. 여기 적은 이름은 전부 지금 쓰는 6000.6.2f1 에디터에서 리플렉션으로 있는지, 폐기(Obsolete) 표시가 붙었는지 확인했다. 그 과정에서 FindFirstObjectByType이 6.6에서 폐기 예정이 된 것도 알았다.

등급 기준

등급 뜻
최상 거의 모든 스크립트에 나온다. 안 보고 쓸 수 있어야 한다
상 게임 로직을 짜면 매번 나온다. 한 번씩은 직접 써 본다
중 기능에 따라 쓴다. 필요할 때 찾아본다
하 드물게 쓰거나, 알고만 있고 되도록 피한다

표 읽는 법은 이렇다.

  • 클래스도 등급을 매겼다. 빨간 상자는 최상 클래스, 주황 상자는 상 클래스, 상자가 없는 표는 중 클래스다.
  • 표 안의 명령은 등급 순서로 적었고, 최상과 상은 굵게 적었다.
  • 예시는 그 줄만 떼어 읽을 수 있게 짧게 적었다. 변수 이름(rb, sr, cam)은 그 컴포넌트를 필드에 받아 둔 것이다.

모르는 명령을 찾는 법

외우기 전에 찾는 법부터
  1. VS Code에서 점을 찍는다. rb.까지 치면 쓸 수 있는 멤버 목록이 뜬다. 목록을 훑다 보면 이름이 기억난다. F12를 누르면 정의로 간다.
  2. Inspector가 곧 목차다. Inspector 칸 이름의 띄어쓰기를 없애고 첫 글자를 소문자로 바꾸면 대부분 코드 이름이다. Gravity Scale은 gravityScale이다.
  3. 컴포넌트 제목줄의 ? 아이콘을 누른다. 그 컴포넌트의 매뉴얼이 열리고, 거기서 Scripting API로 넘어갈 수 있다.

Inspector 칸 이름과 코드 이름을 나란히 놓으면 이렇다. 대부분 같고, 몇 개만 다르다.

컴포넌트 Inspector 칸 코드
Transform Position transform.localPosition (부모 기준)
Transform Rotation transform.localEulerAngles
Transform Scale transform.localScale
Rigidbody 2D Body Type rb.bodyType
Rigidbody 2D Gravity Scale rb.gravityScale
Rigidbody 2D Linear Damping rb.linearDamping
Rigidbody 2D Interpolate rb.interpolation (이름이 조금 다르다)
Sprite Renderer Color, Flip X sr.color, sr.flipX
Sprite Renderer Order in Layer sr.sortingOrder (이름이 다르다)
Box Collider 2D Is Trigger, Size box.isTrigger, box.size
오브젝트 이름 옆 체크박스 gameObject.SetActive(true)
스크립트 이름 옆 체크박스 enabled = true

꼭 기억할 것 ⑤ Inspector의 Position은 position이 아니라 localPosition이다

Inspector의 Transform 칸은 부모 기준 값을 보여 준다. 부모가 없으면 둘이 같아서 모르고 지나가다가, 오브젝트를 다른 오브젝트 밑에 넣는 순간 값이 어긋난다. 코드의 transform.position은 월드 좌표다.

먼저 외울 것: 최상 8개

아래 여덟 개는 거의 모든 스크립트에 나온다. 뒤의 사전에도 다시 나오지만, 여기서는 설명과 주의할 점까지 크게 적는다.

라이프사이클: Awake, Start, Update, FixedUpdate

최상 · 엔진이 불러 주는 콜백

설명: 서버의 Init과 Tick에 해당한다. 이름만 맞으면 엔진이 알아서 부른다.

  • Awake: 생성 직후 한 번. 자기 초기화(자기 컴포넌트 가져오기)
  • Start: 첫 Update 직전 한 번. 다른 오브젝트가 필요한 초기화
  • Update: 화면 프레임마다. 입력, 타이머, 화면 표시
  • FixedUpdate: 물리 고정 틱(기본 0.02초)마다. Rigidbody 이동
  • OnEnable, OnDisable: 켜질 때와 꺼질 때. 이벤트 구독과 해제 자리
void Awake()       { rb = GetComponent<Rigidbody2D>(); }
void OnEnable()    { stand.Sold += OnSold; }
void OnDisable()   { stand.Sold -= OnSold; }
void Update()      { timer -= Time.deltaTime; }
void FixedUpdate() { rb.MovePosition(rb.position + Vector2.right * speed * Time.fixedDeltaTime); }

주의: 이름 철자가 틀리면(update, Awke) 에러 없이 조용히 안 불린다. 순서는 Awake → OnEnable → Start → (FixedUpdate, Update 반복) → OnDisable → OnDestroy다.

[SerializeField]

최상 · 값을 코드 밖으로 뺀다

설명: private 필드를 Inspector에 보이게 하고, 그 값을 씬과 프리팹에 저장한다. 기획 테이블의 컬럼처럼 숫자와 참조를 코드 밖으로 뺀다.

[SerializeField] private float speed = 2f;   // 숫자는 Inspector에서 조절
[SerializeField] private Stand stand;        // 참조는 Inspector에 끌어다 놓아 연결

주의: public 필드도 Inspector에 나오지만 아무 코드나 바꿀 수 있게 되므로 [SerializeField] private를 쓴다.

꼭 기억할 것 ⑥ 코드의 초기값보다 Inspector 값이 이긴다

= 2f 같은 코드의 초기값은 컴포넌트를 처음 붙일 때나 필드를 새로 추가했을 때만 쓰인다. 그 뒤로는 씬과 프리팹에 저장된 값이 이긴다. 코드에서 초기값을 바꿔도 이미 놓인 오브젝트의 값은 그대로다. 그리고 Play 중에 Inspector에서 바꾼 값은 멈추면 되돌아가지만, Play 밖에서 바꾼 값은 Ctrl+S와 함께 씬에 저장된다.

transform.position

최상 · 위치를 읽고 옮긴다

설명: 오브젝트의 월드 좌표다. 읽으면 지금 위치이고, 넣으면 그 자리로 순간이동한다. 부모 기준 좌표는 localPosition이다.

transform.position = new Vector3(0f, 2f, 0f);                   // 그 자리로 순간이동
transform.position += Vector3.right * speed * Time.deltaTime;   // 조금씩 이동
transform.Translate(Vector3.right * speed * Time.deltaTime);    // 회전이 없으면 같은 일. 방향은 자기 기준

주의: transform.position.x = 1f;는 컴파일 에러다. Vector3는 구조체라서 복사본을 고치는 셈이 되기 때문이다. 꺼내서 고친 다음 통째로 다시 넣는다. Rigidbody가 붙어 있으면 이것 대신 Rigidbody로 움직인다(아래 사전의 Rigidbody2D).

GetComponent<T>(), TryGetComponent

최상 · 같은 오브젝트의 다른 컴포넌트 가져오기

설명: 오브젝트에 붙은 다른 컴포넌트를 꺼낸다. 서버로 치면 엔티티에서 모듈을 꺼내는 것이다.

rb = GetComponent<Rigidbody2D>();                          // 없으면 null
if (other.TryGetComponent(out Stand stand)) { /* ... */ }  // 없을 수도 있을 때
var sr = GetComponentInChildren<SpriteRenderer>();         // 자식까지 찾기

주의: 느린 편이라 Update에서 매번 부르지 않는다. Awake에서 한 번 받아 필드에 둔다. 가능하면 [SerializeField]로 Inspector에서 연결하는 쪽이 더 낫다.

Time.deltaTime

최상 · 프레임 간격 dt

설명: 지난 프레임부터 걸린 시간(초)이다. 서버 틱 간격 dt와 같다. 이동과 타이머에 곱해야 프레임 수와 상관없이 같은 속도가 된다.

transform.position += Vector3.right * speed * Time.deltaTime;   // 초당 speed만큼
timer -= Time.deltaTime;                                         // 타이머 줄이기

주의: FixedUpdate 안에서는 Time.fixedDeltaTime을 쓴다. 일시정지(Time.timeScale = 0)하면 0이 된다. 일시정지와 상관없이 재려면 Time.unscaledDeltaTime이다.

Instantiate, Destroy

최상 · 스폰과 디스폰

설명: 프리팹으로 오브젝트를 만들고 없앤다. 서버의 스폰, 디스폰과 같다.

Customer c = Instantiate(customerPrefab, spawnPos, Quaternion.identity); // 위치와 회전을 주고 만들기
Customer c2 = Instantiate(customerPrefab, transform);                    // 이 오브젝트의 자식으로 만들기
Destroy(c.gameObject);                                                   // 없애기. 그 프레임 끝에 사라진다
Destroy(effectObject, 2f);                                               // 2초 뒤에 없애기

주의: Destroy(this)는 스크립트 컴포넌트만 지운다. 오브젝트를 지우려면 Destroy(gameObject)다. 초당 여러 번 만들고 지우면 풀링(ObjectPool<T>)으로 바꾼다.

SetActive

최상 · 켜고 끄기

설명: 오브젝트를 켜고 끈다. 꺼지면 Update와 코루틴이 멈추고 화면과 충돌에서 빠진다. 없애지 않고 숨기는 것이다. Inspector에서 오브젝트 이름 옆 체크박스가 이것이다.

shopPanel.SetActive(true);         // 창 열기
c.gameObject.SetActive(false);     // 풀에 넣을 때
bool on = gameObject.activeSelf;   // 지금 켜져 있나

주의: 자식까지 같이 꺼진다. 꺼진 오브젝트는 GameObject.Find로 찾을 수 없다. 스크립트 하나만 끄려면 enabled = false다.

Debug.Log

최상 · 로그

설명: Console 창에 로그를 남긴다. 서버 로그와 같다.

Debug.Log($"판매 +{price}");                     // 일반 (흰색)
Debug.LogWarning("재고가 부족하다");               // 경고 (노란색)
Debug.LogError("가판대가 연결되지 않았다", this);   // 에러 (빨간색)

this처럼 오브젝트를 두 번째 인자로 넘기면, Console에서 그 로그를 눌렀을 때 Hierarchy에서 해당 오브젝트를 짚어 준다.

주의: 빌드에도 남고 느리다. Update에서 매 프레임 찍지 않는다. 확인이 끝나면 지운다.

클래스별 명령 사전

MonoBehaviour: 스크립트의 뼈대

최상 클래스 · 모든 스크립트가 이것을 상속한다

스크립트를 오브젝트에 붙여 동작하게 하는 부모 클래스다. 정해진 이름의 함수(메시지)를 만들어 두면 엔진이 때맞춰 불러 준다. 철자가 틀리면 에러 없이 안 불린다.

명령 설명 예시
최상 Awake() 생성 직후 한 번. 자기 초기화 rb = GetComponent<Rigidbody2D>();
최상 Start() 첫 Update 직전 한 번. 다른 오브젝트가 필요한 초기화 Refresh();
최상 Update() 화면 프레임마다. 입력, 타이머, 화면 표시 timer -= Time.deltaTime;
최상 transform, gameObject 이 스크립트가 붙은 오브젝트의 Transform과 GameObject gameObject.SetActive(false);
상 FixedUpdate() 물리 고정 틱(0.02초)마다. Rigidbody 이동 rb.MovePosition(next);
상 OnEnable(), OnDisable() 켜질 때, 꺼질 때. 이벤트 구독과 해제 stand.Sold += OnSold;
상 OnTriggerEnter2D(Collider2D other) 트리거 영역에 들어왔을 때. Stay2D, Exit2D도 있다 if (other.TryGetComponent(out Stand s)) { }
상 OnCollisionEnter2D(Collision2D c) 부딪혔을 때. Stay2D, Exit2D도 있다 c.gameObject.CompareTag("Enemy")
상 StartCoroutine(Routine()) 코루틴 시작. 돌려받은 값으로 멈출 수 있다 var co = StartCoroutine(Blink());
상 enabled 이 스크립트만 켜고 끄기. 꺼지면 Update가 안 불린다 enabled = false;
중 LateUpdate() 모든 Update가 끝난 뒤. 카메라 따라가기 cam.position = target.position + offset;
중 OnDestroy() 파괴될 때. 정리 작업 stand.Sold -= OnSold;
중 StopCoroutine(co), StopAllCoroutines() 코루틴 멈추기 StopCoroutine(co);
중 destroyCancellationToken 오브젝트가 파괴되면 취소되는 토큰. async 작업에 넘긴다 await Awaitable.WaitForSecondsAsync(1f, destroyCancellationToken);
중 OnBecameVisible(), OnBecameInvisible() 카메라에 보이기 시작할 때, 안 보이게 될 때 (Renderer 필요) enabled = false;
중 OnApplicationPause(bool), OnApplicationQuit() 앱이 백그라운드로 갈 때(모바일), 종료할 때. 저장 시점 if (paused) Save();
중 OnPointerClick(PointerEventData e) IPointerClickHandler를 구현하면 클릭을 받는다. 2D 오브젝트는 카메라에 Physics 2D Raycaster가 있어야 한다 public void OnPointerClick(PointerEventData e) { Buy(); }
하 Invoke("이름", 초), InvokeRepeating, CancelInvoke 문자열로 함수를 예약한다. 쓸 거면 nameof로 오타를 막는다 Invoke(nameof(Spawn), 2f);
하 OnValidate() 에디터에서 Inspector 값이 바뀔 때. 값 검사 speed = Mathf.Max(0f, speed);
하 OnDrawGizmos(), OnDrawGizmosSelected() Scene 뷰에 보조선 그리기 Gizmos.DrawWireSphere(transform.position, range);
하 Reset() 컴포넌트를 처음 붙이거나 Reset을 눌렀을 때(에디터). 기본값 채우기 rb = GetComponent<Rigidbody2D>();

GameObject와 Object: 만들고, 찾고, 없애기

최상 클래스 · 오브젝트 자체를 다룬다

GameObject는 씬에 놓이는 모든 것의 껍데기다. 기능은 컴포넌트가 하고, GameObject는 켜짐 여부, 이름, 태그, 레이어, 컴포넌트 목록을 가진다. Instantiate와 Destroy는 모든 Unity 오브젝트의 부모인 Object의 함수다. Destroy(this)는 스크립트만 지우니, 오브젝트를 지울 때는 Destroy(gameObject)를 쓴다.

명령 설명 예시
최상 Instantiate(prefab, pos, rot) 프리팹으로 오브젝트 만들기 (스폰) Instantiate(coinPrefab, pos, Quaternion.identity);
최상 Destroy(obj), Destroy(obj, 초) 없애기. 그 프레임 끝에 사라진다 Destroy(gameObject, 2f);
최상 SetActive(bool) 켜고 끄기 panel.SetActive(true);
최상 GetComponent<T>() 같은 오브젝트의 컴포넌트 가져오기 GetComponent<SpriteRenderer>()
상 TryGetComponent(out T c) 있으면 true와 함께 가져오기 if (TryGetComponent(out Rigidbody2D body)) { }
상 GetComponentInChildren<T>(), GetComponentInParent<T>() 자식이나 부모까지 찾기 GetComponentInChildren<Animator>()
상 name 오브젝트 이름 Debug.Log(gameObject.name);
상 activeSelf 자기 체크박스가 켜져 있나 if (!panel.activeSelf) { }
중 activeInHierarchy 부모까지 포함해 실제로 켜져 있나 if (gameObject.activeInHierarchy) { }
중 GetComponents<T>(), GetComponentsInChildren<T>() 여러 개를 배열로 GetComponentsInChildren<SpriteRenderer>()
중 AddComponent<T>() 코드로 컴포넌트 붙이기 gameObject.AddComponent<AudioSource>()
중 new GameObject("이름") 빈 오브젝트 만들기 var root = new GameObject("Pool");
중 CompareTag("태그"), tag 태그 비교. tag ==보다 빠르고, 없는 태그면 에러로 알려 준다 if (other.CompareTag("Player")) { }
중 layer 레이어 번호 gameObject.layer = LayerMask.NameToLayer("Customer");
중 DontDestroyOnLoad(obj) 씬이 바뀌어도 살아남게 DontDestroyOnLoad(gameObject);
하 FindAnyObjectByType<T>(), FindObjectsByType<T>(FindObjectsSortMode.None) 타입으로 씬 전체를 뒤진다. 느리니 초기화 때만 FindAnyObjectByType<Wallet>()
하 GameObject.Find("이름"), GameObject.FindWithTag("태그") 이름이나 태그로 씬 전체를 뒤진다. 꺼진 오브젝트는 못 찾는다 대신 Inspector로 연결한다

Transform: 위치, 회전, 크기, 계층

최상 클래스 · 모든 오브젝트에 있다

모든 오브젝트에 반드시 하나 붙어 있는 컴포넌트다. 위치, 회전, 크기와 부모·자식 관계를 가진다. 스크립트에서는 transform으로 바로 쓴다.

명령 설명 예시
최상 position 월드 좌표. 넣으면 순간이동 transform.position = spawnPos;
상 localPosition 부모 기준 좌표 (Inspector에 보이는 값) icon.localPosition = Vector3.zero;
상 Translate(Vector3) 지금 위치에서 더해 옮기기. 방향은 자기 기준 transform.Translate(Vector3.right * speed * Time.deltaTime);
상 rotation 회전 (Quaternion) transform.rotation = Quaternion.Euler(0f, 0f, 90f);
상 eulerAngles, localEulerAngles 회전을 각도(도)로 float z = transform.eulerAngles.z;
상 localScale 크기. x를 -1로 하면 좌우 반전 transform.localScale = new Vector3(2f, 2f, 1f);
상 parent, SetParent(parent, false) 부모 읽기, 바꾸기. 두 번째 인자가 true(기본)면 월드 위치를, false면 부모 기준 값을 유지한다. UI는 보통 false item.SetParent(slot, false);
중 Rotate(x, y, z) 지금 회전에서 더 돌리기 transform.Rotate(0f, 0f, 90f * Time.deltaTime);
중 right, up, forward 자기 기준 오른쪽, 위, 앞 방향 (월드 벡터) rb.AddForce(transform.up * thrust);
중 childCount, GetChild(i) 자식 수, i번째 자식 transform.GetChild(0)
중 foreach (Transform child in transform) 자식을 전부 돌기 foreach (Transform t in transform) t.gameObject.SetActive(false);
중 Find("자식이름") 이름으로 자식 찾기. 자식 중에서만 찾는다 transform.Find("Icon")
중 SetPositionAndRotation(pos, rot) 위치와 회전을 한 번에 transform.SetPositionAndRotation(p, Quaternion.identity);
중 LookAt(target) 대상을 바라보게 회전 (3D용. 2D는 아래 Quaternion 참고) transform.LookAt(player);
하 TransformPoint(local), InverseTransformPoint(world) 자기 기준 좌표와 월드 좌표 바꾸기 transform.TransformPoint(Vector3.up)
하 SetSiblingIndex(i), SetAsLastSibling() 형제 사이 순서. UI는 뒤에 있을수록 위에 그려진다 popup.SetAsLastSibling();
하 lossyScale 부모까지 곱한 실제 크기 (읽기 전용) transform.lossyScale.x
하 DetachChildren() 자식을 전부 떼어 내기 transform.DetachChildren();

Rigidbody2D: 물리로 움직이기

상 클래스 · 물리로 움직이는 오브젝트

Dynamic은 힘과 중력을 받고, Kinematic은 코드가 정한 대로만 움직이고, Static은 움직이지 않는다.

주의: Unity 6에서 velocity가 linearVelocity로, drag가 linearDamping으로, isKinematic이 bodyType으로 바뀌었다. 옛 강좌나 AI가 짠 코드에 옛 이름이 자주 보인다. 물리 이동은 FixedUpdate에서 한다.

명령 설명 예시
상 linearVelocity 속도 (초당 이동량) rb.linearVelocity = new Vector2(dir.x * speed, rb.linearVelocity.y);
상 AddForce(force, mode) 힘 주기. 점프는 ForceMode2D.Impulse (Dynamic) rb.AddForce(Vector2.up * jump, ForceMode2D.Impulse);
상 MovePosition(pos) 목표 위치로 이동. FixedUpdate에서. Kinematic 이동의 기본 rb.MovePosition(rb.position + move * Time.fixedDeltaTime);
상 bodyType Dynamic, Kinematic, Static 바꾸기 rb.bodyType = RigidbodyType2D.Kinematic;
상 gravityScale 중력 배율. 0이면 중력 없음 rb.gravityScale = 0f;
상 position 물리 바디의 위치. 넣으면 순간이동 (스폰할 때만) rb.position = spawnPos;
중 linearVelocityX, linearVelocityY 속도의 x, y만 따로 읽고 쓰기 rb.linearVelocityY = jumpSpeed;
중 angularVelocity 회전 속도 (도/초) rb.angularVelocity = 180f;
중 AddTorque(torque) 회전시키는 힘 rb.AddTorque(5f);
중 MoveRotation(angle) 목표 각도로 회전. FixedUpdate에서 rb.MoveRotation(rb.rotation + 90f * Time.fixedDeltaTime);
중 rotation 바디의 회전 각도 (float, 도) float angle = rb.rotation;
중 mass, linearDamping, angularDamping 무게, 이동 감속, 회전 감속 rb.linearDamping = 2f;
중 freezeRotation, constraints 축 고정. 캐릭터가 넘어지지 않게 회전 고정 rb.freezeRotation = true;
중 simulated 물리에서 통째로 빼기. 충돌과 트리거도 멈춘다 rb.simulated = false;
중 interpolation 물리 틱 사이 보간. 끊겨 보이면 Interpolate rb.interpolation = RigidbodyInterpolation2D.Interpolate;
중 collisionDetectionMode 빠른 물체가 벽을 뚫으면 Continuous rb.collisionDetectionMode = CollisionDetectionMode2D.Continuous;
중 IsTouchingLayers(mask), IsTouching(col) 지금 닿아 있나. 바닥 판정 bool grounded = rb.IsTouchingLayers(groundMask);
하 useFullKinematicContacts Kinematic이 Static이나 다른 Kinematic과도 닿게 rb.useFullKinematicContacts = true;
하 MovePositionAndRotation(pos, angle) 위치와 회전을 한 번에 이동 rb.MovePositionAndRotation(p, 0f);
하 Cast(dir, results, dist) 이 바디 모양 그대로 미리 쓸어 보기. 옮기기 전에 충돌 검사 int n = rb.Cast(Vector2.right, hits, 0.5f);
하 GetContacts(contacts) 지금 닿은 점들 int n = rb.GetContacts(contacts);
하 Sleep(), WakeUp(), IsSleeping() 물리 재우기, 깨우기 rb.WakeUp();

꼭 기억할 것 ⑦ Rigidbody가 붙으면 transform이 아니라 Rigidbody로 움직인다

물리 엔진은 자기 틱에서 바디를 움직인다. transform.position을 직접 바꾸면 엔진은 그것을 이동이 아니라 순간이동으로 본다. 그래서 지나가는 길의 충돌과 트리거를 건너뛸 수 있고, 다른 바디와 겹친 자리에 떨어지면 튕겨 나가고, 보간도 안 되어 끊겨 보인다. 스폰처럼 일부러 순간이동할 때만 rb.position을 쓰고, 그것도 주변에 아무것도 없는 자리에서 한다.

Collider2D와 Collision2D: 충돌 모양과 충돌 정보

명령 설명 예시
상 isTrigger 트리거로 쓸지. 통과하면서 이벤트만 받는다 col.isTrigger = true;
상 enabled 콜라이더만 끄기. 충돌을 잠시 끈다 col.enabled = false;
상 collision.gameObject (OnCollisionEnter2D에서) 부딪힌 상대 collision.gameObject.CompareTag("Enemy")
중 bounds 월드 기준 테두리 상자 (center, size, min, max) col.bounds.max.y
중 offset, size(Box), radius(Circle) 모양 조절 box.size = new Vector2(2f, 1f);
중 OverlapPoint(point) 점이 안에 있나. 클릭 판정 if (col.OverlapPoint(worldPos)) { }
중 IsTouching(other), IsTouchingLayers(mask) 지금 닿아 있나 col.IsTouchingLayers(groundMask)
중 attachedRigidbody 이 콜라이더가 속한 Rigidbody2D other.attachedRigidbody
중 collision.GetContact(0).point, .normal 부딪힌 점과 면의 방향 var n = collision.GetContact(0).normal;
중 collision.relativeVelocity 부딪힐 때의 상대 속도. 세게 부딪혔나 if (collision.relativeVelocity.magnitude > 5f) { }
하 ClosestPoint(point) 콜라이더에서 가장 가까운 점 col.ClosestPoint(playerPos)
하 Distance(other) 두 콜라이더 사이 거리 정보 col.Distance(otherCol).distance
하 sharedMaterial 마찰과 튕김 (Physics Material 2D) col.sharedMaterial = bouncy;

Physics2D: 영역 검사와 광선

명령 설명 예시
중 Physics2D.Raycast(origin, dir, dist, mask) 광선을 쏴서 처음 맞은 것. 결과를 if에 바로 넣을 수 있다 var hit = Physics2D.Raycast(pos, Vector2.down, 1f, groundMask);
중 hit.collider, hit.point, hit.normal, hit.distance 맞은 콜라이더, 점, 면 방향, 거리 if (hit) Debug.Log(hit.collider.name);
중 Physics2D.OverlapCircle(center, r, mask) 원 안의 콜라이더 하나 Physics2D.OverlapCircle(feet.position, 0.1f, groundMask)
중 Physics2D.OverlapCircleAll(center, r, mask) 원 안의 콜라이더 전부. 부를 때마다 배열을 새로 만든다 foreach (var c in Physics2D.OverlapCircleAll(p, 3f, enemyMask)) { }
중 Physics2D.OverlapBox(center, size, angle, mask), OverlapPoint(point, mask) 상자 안, 점 위의 콜라이더 Physics2D.OverlapPoint(worldPos)
하 Physics2D.RaycastAll, BoxCast, CircleCast, Linecast 여러 개 맞히기, 모양으로 쏘기, 두 점 사이 Physics2D.Linecast(a, b, wallMask)
하 Physics2D.IgnoreCollision(a, b), IgnoreLayerCollision(l1, l2) 특정 쌍의 충돌 끄기 Physics2D.IgnoreCollision(bulletCol, ownerCol);
하 Physics2D.gravity 전체 중력 Physics2D.gravity = new Vector2(0f, -20f);
하 Physics2D.queriesHitTriggers 광선과 영역 검사가 트리거도 맞힐지 Physics2D.queriesHitTriggers = false;

Vector2, Vector3: 위치와 방향

상 클래스 · 좌표와 방향을 담는 구조체

2D는 Vector2, 3D는 Vector3를 쓴다. 둘 다 구조체다.

주의: 구조체라서 대입하면 복사된다. 2D에서도 transform.position은 Vector3다. 여기에 Vector2를 넣으면 z가 0으로 채워진다.

명령 설명 예시
최상 new Vector2(x, y), new Vector3(x, y, z) 만들기 new Vector2(1f, 0f)
상 x, y, z 각 성분 pos.y += 1f;
상 Vector2.zero, one, up, down, left, right 자주 쓰는 상수 rb.linearVelocity = Vector2.zero;
상 normalized 길이 1로 만든 방향 (target - pos).normalized
상 Vector2.Distance(a, b) 두 점 사이 거리 if (Vector2.Distance(a, b) < 1f) { }
상 +, -, *, / 벡터끼리 더하고 빼기, 숫자 곱하고 나누기 pos + dir * speed * Time.deltaTime
중 magnitude, sqrMagnitude 길이. 비교만 할 거면 제곱 길이가 빠르다 if ((a - b).sqrMagnitude < r * r) { }
중 Vector2.Lerp(a, b, t), Vector2.MoveTowards(a, b, d) 사이 지점, 일정 속도로 다가가기 Vector2.MoveTowards(pos, target, speed * Time.deltaTime)
중 Vector2.SmoothDamp(cur, target, ref vel, time) 부드럽게 따라가기. 카메라 pos = Vector2.SmoothDamp(pos, target, ref vel, 0.2f);
중 Vector2.Dot(a, b) 두 방향이 얼마나 같은 쪽인가 (1 같음, 0 직각, -1 반대) Vector2.Dot(facing, toTarget) > 0f
중 Vector2.Angle(a, b), Vector2.SignedAngle(a, b) 사이 각도, 방향이 있는 각도 Vector2.SignedAngle(Vector2.right, dir)
중 Vector2Int, Vector3Int 정수 좌표. 격자, 타일 new Vector2Int(3, 4)
하 Vector2.Reflect(dir, normal) 반사 방향 dir = Vector2.Reflect(dir, hit.normal);
하 Vector2.Perpendicular(v), Vector2.ClampMagnitude(v, max) 수직 방향, 길이 제한 Vector2.ClampMagnitude(input, 1f)
하 Vector3.Cross(a, b), Vector3.ProjectOnPlane(v, n) 외적, 평면에 투영 (3D) Vector3.Cross(a, b)

Quaternion: 회전

명령 설명 예시
상 Quaternion.identity 회전 없음 Instantiate(prefab, pos, Quaternion.identity);
상 Quaternion.Euler(x, y, z) 각도(도)로 회전 만들기. 2D는 z만 Quaternion.Euler(0f, 0f, angle)
중 Quaternion.AngleAxis(angle, axis) 축을 기준으로 돌린 회전 Quaternion.AngleAxis(angle, Vector3.forward)
중 Quaternion.Slerp(a, b, t), Quaternion.RotateTowards(a, b, maxDeg) 부드럽게 돌리기, 일정 속도로 돌리기 Quaternion.RotateTowards(rot, goal, 180f * Time.deltaTime)
중 Quaternion.LookRotation(forward) 그 방향을 보는 회전 (3D) Quaternion.LookRotation(dir)
중 rotation * vector 벡터를 그 회전만큼 돌리기 Quaternion.Euler(0f, 0f, 30f) * Vector2.right
하 Quaternion.Inverse(q), q.eulerAngles 반대 회전, 각도로 꺼내기 q.eulerAngles.z

2D에서 어떤 방향을 바라보게 할 때는 이렇게 한다. 오른쪽이 0도다.

Vector2 dir = target - (Vector2)transform.position;
float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.Euler(0f, 0f, angle);

Time: 시간

최상 클래스 · 이동과 타이머의 기준

시간 값은 전부 static이다. 서버로 치면 틱 시각과 틱 간격이다.

명령 설명 예시
최상 Time.deltaTime 지난 프레임부터 걸린 초. 이동과 타이머에 곱한다 timer -= Time.deltaTime;
상 Time.time 게임 시작부터 흐른 초. 쿨타임과 만료 시각 if (Time.time >= nextFire) { }
상 Time.fixedDeltaTime 물리 틱 간격 (기본 0.02초). FixedUpdate 안에서 speed * Time.fixedDeltaTime
중 Time.timeScale 시간 배율. 0이면 일시정지, 0.5면 느리게 Time.timeScale = 0f;
중 Time.unscaledDeltaTime, Time.unscaledTime 일시정지와 상관없는 시간. 멈춘 동안 움직이는 UI fade += Time.unscaledDeltaTime;
하 Time.frameCount 지금까지 그린 프레임 수 if (Time.frameCount % 30 == 0) { }
하 Time.realtimeSinceStartup 실제 흐른 시간. 성능 측정 float t0 = Time.realtimeSinceStartup;

Mathf: 수학 함수

상 클래스 · 자르기, 보간, 다가가기

숫자 계산 함수 모음이다. 전부 static이라 Mathf.으로 부른다.

주의: float끼리는 == 대신 Mathf.Approximately로 비교한다. 다만 MoveTowards는 도착하면 목표값을 그대로 돌려주므로 도착 판정에 ==를 써도 된다.

명령 설명 예시
상 Mathf.Clamp(v, min, max) 범위 안으로 자르기 hp = Mathf.Clamp(hp, 0, maxHp);
상 Mathf.Abs, Mathf.Min, Mathf.Max 절댓값, 작은 값, 큰 값 Mathf.Max(0, money - price)
상 Mathf.Lerp(a, b, t) a와 b 사이에서 t(0~1) 지점 Mathf.Lerp(0f, 1f, 0.5f)
상 Mathf.MoveTowards(cur, target, d) 목표까지 일정 속도로. 지나치지 않는다 x = Mathf.MoveTowards(x, targetX, speed * Time.deltaTime);
중 Mathf.Clamp01(v) 0~1로 자르기 Mathf.Clamp01(t)
중 Mathf.InverseLerp(a, b, v) v가 ab 중 어디쯤인지 (01). 게이지 Mathf.InverseLerp(0, maxHp, hp)
중 Mathf.RoundToInt, Mathf.FloorToInt, Mathf.CeilToInt 반올림, 내림, 올림해서 int로 Mathf.RoundToInt(3.6f)
중 Mathf.Sin, Mathf.Cos, Mathf.Atan2, Mathf.PI 삼각함수 (라디안) Mathf.Sin(Time.time * 2f)
중 Mathf.Rad2Deg, Mathf.Deg2Rad 라디안과 도 바꾸기 Mathf.Atan2(y, x) * Mathf.Rad2Deg
중 Mathf.Approximately(a, b) float끼리 거의 같은가 Mathf.Approximately(v, 0f)
중 Mathf.SmoothDamp(cur, target, ref vel, time) 부드럽게 따라가기 x = Mathf.SmoothDamp(x, t, ref vel, 0.3f);
중 Mathf.Repeat(t, len), Mathf.PingPong(t, len) 0~len 반복, 왕복 Mathf.PingPong(Time.time, 1f)
중 Mathf.Sign(v) 부호. 0이어도 1을 돌려준다 Mathf.Sign(dir.x)
하 Mathf.Pow, Mathf.Sqrt, Mathf.Log, Mathf.Infinity 거듭제곱, 제곱근, 로그, 무한대. 타이쿤의 비용 증가 공식 cost = baseCost * Mathf.Pow(1.15f, level);
하 Mathf.LerpUnclamped, Mathf.MoveTowardsAngle, Mathf.DeltaAngle 범위 밖까지 보간, 각도용 함수 Mathf.DeltaAngle(a, b)

Random: 무작위

상 클래스 · 무작위 값

무작위 값을 뽑는 static 함수 모음이다.

주의: float 버전 Random.Range(1f, 5f)는 5도 나오지만, int 버전 Random.Range(0, 3)은 0, 1, 2만 나온다. 파일 위에 using System;이 있으면 System.Random과 이름이 겹치니 UnityEngine.Random.Range로 쓴다.

명령 설명 예시
상 Random.Range(min, max) 범위 안의 무작위 값. int 버전은 max가 안 나온다 Random.Range(0, list.Count)
중 Random.value 0~1 무작위 float. 확률 판정 if (Random.value < 0.1f) Crit();
중 Random.insideUnitCircle 반지름 1인 원 안의 무작위 점 pos + Random.insideUnitCircle * 2f
하 Random.InitState(seed) 시드 고정. 매번 같은 순서로 나온다 Random.InitState(42);
하 Random.ColorHSV(), Random.onUnitSphere, Random.rotation 무작위 색, 구 위의 점, 무작위 회전 sr.color = Random.ColorHSV();

Input System: 키보드, 마우스, 터치

상 클래스 · 입력

파일 위에 using UnityEngine.InputSystem;이 필요하다. 장치를 직접 읽거나, 액션(Jump, Move 같은 추상 입력)으로 받는다.

주의: 이 프로젝트는 옛 Input.GetKey를 부르면 예외가 난다. UI 버튼 클릭은 이 코드 없이 EventSystem이 처리한다. 장치가 없으면 Keyboard.current, Gamepad.current는 null이다.

명령 설명 예시
상 Keyboard.current.spaceKey.wasPressedThisFrame 이번 프레임에 눌렀나 if (Keyboard.current.spaceKey.wasPressedThisFrame) Jump();
상 .isPressed 누르고 있나 if (Keyboard.current.aKey.isPressed) { }
상 Mouse.current.leftButton.wasPressedThisFrame 마우스 클릭 if (Mouse.current.leftButton.wasPressedThisFrame) Click();
상 Mouse.current.position.ReadValue() 마우스의 화면 좌표 (픽셀) Vector2 sp = Mouse.current.position.ReadValue();
상 InputSystem.actions.FindAction("이름") 프로젝트 기본 액션에서 찾기. Awake에서 한 번 move = InputSystem.actions.FindAction("Move");
상 action.ReadValue<Vector2>() 액션의 지금 값. 이동 입력 Vector2 m = move.ReadValue<Vector2>();
상 action.WasPressedThisFrame() 액션을 이번 프레임에 눌렀나 if (jump.WasPressedThisFrame()) Jump();
중 .wasReleasedThisFrame, action.WasReleasedThisFrame(), action.IsPressed() 뗐나, 누르고 있나 if (jump.IsPressed()) { }
중 action.performed += ctx => ... 이벤트 방식으로 받기. started, canceled도 있다 jump.performed += ctx => Jump();
중 action.Enable(), action.Disable() 액션 켜고 끄기. 코드로 만든 액션은 켜야 동작한다 jump.Enable();
중 Mouse.current.scroll.ReadValue() 마우스 휠. y가 위아래 float wheel = Mouse.current.scroll.ReadValue().y;
중 Gamepad.current 게임패드. 없으면 null if (Gamepad.current != null && Gamepad.current.buttonSouth.wasPressedThisFrame) { }
중 Touchscreen.current.primaryTouch.position.ReadValue() 첫 번째 손가락 위치 (모바일) var tp = Touchscreen.current.primaryTouch.position.ReadValue();
중 InputActionReference Inspector에서 액션을 골라 연결 [SerializeField] InputActionReference jumpRef;
하 Mouse.current.delta.ReadValue() 마우스가 움직인 양 Vector2 d = Mouse.current.delta.ReadValue();
하 PlayerInput 컴포넌트 액션을 메시지나 UnityEvent로 받게 해 주는 컴포넌트 Inspector에서 설정

UI: uGUI와 TextMeshPro

상 클래스 · 화면 UI

파일 위에 using TMPro;, using UnityEngine.UI;, 클릭 판정에는 using UnityEngine.EventSystems;가 필요하다.

주의: 한글은 한글 폰트로 만든 폰트 에셋(SDF)이 있어야 나온다. 없으면 네모로 보인다. onClick은 C# event가 아니라 UnityEvent라서 += 대신 AddListener를 쓴다. 옛 UnityEngine.UI.Text는 쓰지 않는다.

명령 설명 예시
상 TMP_Text.text 글자 바꾸기 moneyText.text = $"돈: {money}";
상 Button.onClick.AddListener(fn) 클릭 연결. 해제는 RemoveListener buyButton.onClick.AddListener(Buy);
상 Button.interactable 누를 수 있게, 없게 (회색) buyButton.interactable = money >= price;
상 Image.sprite, Image.color UI 그림과 색 icon.sprite = upgrade.icon;
중 Image.fillAmount 0~1만큼 채우기. 게이지 (Image Type을 Filled로) hpBar.fillAmount = hp / (float)maxHp;
중 TMP_Text.color, TMP_Text.fontSize 글자 색과 크기 label.color = Color.red;
중 TMP_Text.SetText("{0}", v) 숫자를 넣을 때 문자열을 새로 만들지 않는다 moneyText.SetText("{0}", money);
중 Slider.value, Slider.onValueChanged 슬라이더 값과 변경 이벤트 volumeSlider.onValueChanged.AddListener(SetVolume);
중 Toggle.isOn, Toggle.onValueChanged 체크박스 soundToggle.isOn = true;
중 TMP_InputField.text, TMP_InputField.onEndEdit 입력칸 nameInput.onEndEdit.AddListener(SetName);
중 CanvasGroup.alpha, interactable, blocksRaycasts 패널을 통째로 흐리게, 막기 group.alpha = 0.5f;
중 RectTransform.anchoredPosition, sizeDelta UI 위치(앵커 기준)와 크기 panel.anchoredPosition = new Vector2(0f, -100f);
중 EventSystem.current.IsPointerOverGameObject() 마우스가 UI 위에 있나. UI 클릭이 게임으로 새지 않게 if (EventSystem.current.IsPointerOverGameObject()) return;
하 RectTransform.anchorMin, anchorMax, pivot 앵커와 피벗 rt.pivot = new Vector2(0.5f, 0f);
하 ScrollRect.verticalNormalizedPosition 스크롤 위치 (1이 맨 위, 0이 맨 아래) scroll.verticalNormalizedPosition = 1f;
하 LayoutRebuilder.ForceRebuildLayoutImmediate(rt) 레이아웃을 바로 다시 계산 LayoutRebuilder.ForceRebuildLayoutImmediate(content);

SpriteRenderer: 2D 그림

상 클래스 · 스프라이트를 그린다

2D 그림 한 장을 그리는 컴포넌트다.

주의: 그리는 순서는 z값이 아니라 Sorting Layer와 Order in Layer로 정한다.

명령 설명 예시
상 sprite 그림 바꾸기 sr.sprite = happySprite;
상 color 색 곱하기. 흰 그림이면 그 색이 되고, 알파로 반투명 sr.color = new Color(1f, 1f, 1f, 0.5f);
중 flipX, flipY 뒤집기 sr.flipX = dir.x < 0f;
중 sortingOrder, sortingLayerName 그리는 순서 (Inspector의 Order in Layer, Sorting Layer) sr.sortingOrder = 10;
중 enabled 그림만 숨기기. 오브젝트는 계속 동작한다 sr.enabled = false;
하 bounds 그림이 차지하는 월드 범위 sr.bounds.size.x

Animator: 애니메이션

명령 설명 예시
중 SetTrigger(id), SetBool, SetFloat, SetInteger Animator 파라미터 바꾸기 animator.SetFloat(SpeedHash, speed);
중 Animator.StringToHash("이름") 파라미터 이름을 숫자로 한 번만 바꿔 둔다 static readonly int SpeedHash = Animator.StringToHash("Speed");
중 Play("상태이름") 그 상태로 바로 넘어가기 animator.Play("Idle");
하 speed 재생 속도 배율 animator.speed = 0f;
하 ResetTrigger(id) 쌓여 있는 트리거 지우기 animator.ResetTrigger(HitHash);
하 GetCurrentAnimatorStateInfo(0).IsName("이름"), .normalizedTime 지금 상태, 진행도 (0~1, 반복하면 계속 늘어난다) animator.GetCurrentAnimatorStateInfo(0).normalizedTime

AudioSource: 소리

명령 설명 예시
중 PlayOneShot(clip) 효과음 한 번. 겹쳐 틀어도 된다 sfx.PlayOneShot(coinClip);
중 Play(), Stop(), Pause() 배경음 재생, 정지, 일시정지 bgm.Play();
중 clip, volume, pitch, loop 소리 파일, 크기(0~1), 높낮이, 반복 sfx.pitch = Random.Range(0.9f, 1.1f);
중 isPlaying 재생 중인가 if (!bgm.isPlaying) bgm.Play();
하 AudioSource.PlayClipAtPoint(clip, pos) 그 자리에 임시 오브젝트를 만들어 재생 AudioSource.PlayClipAtPoint(boom, pos);
하 AudioListener.volume 전체 음량 AudioListener.volume = 0.5f;

ParticleSystem: 파티클

명령 설명 예시
중 Play(), Stop() 재생, 정지 dust.Play();
중 Emit(count) 입자를 바로 뿜기 sparks.Emit(20);
중 isPlaying 재생 중인가 if (!dust.isPlaying) dust.Play();
하 main 설정 모듈. 변수에 받아서 고친다 var main = ps.main; main.startColor = Color.yellow;

Camera: 카메라와 좌표 변환

명령 설명 예시
중 Camera.main 태그가 MainCamera인 카메라. Awake에서 한 번 받아 둔다 cam = Camera.main;
중 ScreenToWorldPoint(screenPos) 화면 좌표를 월드 좌표로. 2D 클릭 위치 Vector2 wp = cam.ScreenToWorldPoint(screenPos);
중 WorldToScreenPoint(worldPos) 월드 좌표를 화면 좌표로. 머리 위 UI cam.WorldToScreenPoint(head.position)
중 orthographicSize 2D 카메라가 보이는 높이의 절반. 줌 cam.orthographicSize = 6f;
하 backgroundColor 배경색 cam.backgroundColor = Color.black;
하 ViewportToWorldPoint(v) 화면 비율 좌표(0~1)를 월드로. 화면 가장자리 구하기 cam.ViewportToWorldPoint(new Vector3(1f, 1f, 0f))

씬과 앱

명령 설명 예시
중 SceneManager.LoadScene("이름") 씬 전환. using UnityEngine.SceneManagement; 필요 SceneManager.LoadScene("Title");
중 SceneManager.GetActiveScene().name, .buildIndex 지금 씬의 이름과 번호. 다시 시작할 때 SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
중 Application.persistentDataPath 세이브 파일을 둘 폴더. 기기마다 다르다 Path.Combine(Application.persistentDataPath, "save.json")
하 SceneManager.LoadSceneAsync("이름") 로딩 화면용 비동기 전환 var op = SceneManager.LoadSceneAsync("Game");
하 SceneManager.sceneLoaded += fn 씬이 바뀐 뒤 알림 SceneManager.sceneLoaded += OnSceneLoaded;
하 Application.targetFrameRate 목표 프레임. 모바일 배터리 Application.targetFrameRate = 60;
하 Application.Quit() 게임 종료. 에디터에서는 무시된다 Application.Quit();
하 Application.platform, Application.version 실행 중인 플랫폼, 앱 버전 Debug.Log(Application.version);

저장: JSON 파일과 PlayerPrefs

명령 설명 예시
중 JsonUtility.ToJson(obj, true) 객체를 JSON 문자열로. true면 보기 좋게 string json = JsonUtility.ToJson(save, true);
중 JsonUtility.FromJson<T>(json) JSON을 객체로 var save = JsonUtility.FromJson<SaveData>(json);
중 File.WriteAllText, File.ReadAllText, File.Exists 파일 쓰기, 읽기, 있나. using System.IO; 필요 if (File.Exists(path)) json = File.ReadAllText(path);
중 Path.Combine(a, b) 경로 합치기 Path.Combine(Application.persistentDataPath, "save.json")
중 PlayerPrefs.SetInt, PlayerPrefs.GetInt(key, 기본값) 작은 값 저장. Float, String 버전도 있다 int best = PlayerPrefs.GetInt("Best", 0);
하 PlayerPrefs.HasKey, DeleteKey, DeleteAll, Save() 있나, 지우기, 전부 지우기, 바로 저장 PlayerPrefs.DeleteKey("Best");
하 JsonUtility.FromJsonOverwrite(json, obj) 이미 있는 객체에 덮어쓰기 JsonUtility.FromJsonOverwrite(json, settings);

주의: JsonUtility는 [System.Serializable]이 붙은 클래스의 public 또는 [SerializeField] 필드만 저장한다. Dictionary와 프로퍼티는 저장되지 않는다. 세이브는 임시 파일에 쓴 다음 교체해야 쓰는 도중에 꺼져도 안전하다.

코루틴과 Awaitable: 기다리기

상 클래스 · 지연 실행과 타이머

기다렸다가 이어서 하는 방법은 두 가지다. 코루틴은 IEnumerator와 yield return을, Awaitable은 async와 await를 쓴다.

주의: 코루틴은 오브젝트가 꺼지면 멈추고, async는 토큰으로 취소한다. 반복문 안에서 같은 시간을 계속 기다리면 new WaitForSeconds를 필드에 한 번만 만들어 둔다.

명령 설명 예시
상 yield return new WaitForSeconds(초) 그 시간만큼 쉬기. 일시정지의 영향을 받는다 yield return new WaitForSeconds(2f);
상 yield return null 다음 프레임까지 쉬기 yield return null;
중 yield return new WaitForSecondsRealtime(초) 일시정지와 상관없이 쉬기 yield return new WaitForSecondsRealtime(1f);
중 yield return new WaitUntil(() => 조건), WaitWhile 조건이 참이 될 때까지, 참인 동안 yield return new WaitUntil(() => loaded);
중 yield return StartCoroutine(Other()) 다른 코루틴이 끝날 때까지 yield return StartCoroutine(FadeOut());
중 await Awaitable.WaitForSecondsAsync(초, 토큰) async/await로 쉬기 (Unity 6) await Awaitable.WaitForSecondsAsync(1f, destroyCancellationToken);
중 await Awaitable.NextFrameAsync(토큰) 다음 프레임까지 await Awaitable.NextFrameAsync(destroyCancellationToken);
하 WaitForEndOfFrame, WaitForFixedUpdate 프레임 끝까지, 다음 물리 틱까지 yield return new WaitForFixedUpdate();
하 Awaitable.BackgroundThreadAsync(), Awaitable.MainThreadAsync() 무거운 계산을 백그라운드로 보냈다가 메인으로 돌아오기. Unity API는 메인에서만 await Awaitable.BackgroundThreadAsync();

이벤트: 알리고 구독하기

상 클래스 · 옵저버 패턴

알리는 쪽은 누가 듣는지 모른다. 서버의 옵저버, 이벤트 버스와 같다.

주의: 구독한 쪽이 먼저 사라지면 반드시 해제한다. 같은 함수를 두 번 +=하면 두 번 불린다.

명령 설명 예시
상 public event Action<T> 이름; 알림 선언. Action은 반환값 없는 함수 타입. using System; 필요 public event Action<int> Sold;
상 이름?.Invoke(값) 알리기. 구독자가 없으면 건너뛴다 Sold?.Invoke(price);
상 +=, -= 구독, 해제. 보통 OnEnable과 OnDisable에서 stand.Sold += OnSold;
중 UnityEvent, UnityEvent<T> Inspector에서 연결하는 이벤트. using UnityEngine.Events; 필요 [SerializeField] UnityEvent onBought;
중 AddListener, RemoveListener, Invoke UnityEvent 쓰는 법 onBought.Invoke();
하 Func<T> 반환값이 있는 함수 타입 Func<int> getMoney = () => money;
하 RemoveAllListeners() 코드로 붙인 리스너를 전부 떼기 button.onClick.RemoveAllListeners();

속성(Attribute): Inspector와 직렬화 조절

상 클래스 · 필드와 클래스에 붙이는 꼬리표

필드나 클래스 위에 대괄호로 붙인다. 대부분 Inspector 표시를 바꾸고, 일부는 직렬화와 실행 방식을 바꾼다.

명령 설명 예시
최상 [SerializeField] private 필드를 Inspector에 [SerializeField] private float speed = 2f;
상 [System.Serializable] 직접 만든 클래스를 Inspector 목록이나 JSON에 쓸 수 있게 [System.Serializable] public class SaveData { public int money; }
상 [RequireComponent(typeof(T))] 필요한 컴포넌트를 자동으로 붙이고 못 지우게 [RequireComponent(typeof(Rigidbody2D))]
중 [Header("제목")], [Space] Inspector에 소제목, 간격 [Header("이동")]
중 [Tooltip("설명")] Inspector에서 마우스를 올리면 설명 [Tooltip("초당 이동 거리")]
중 [Range(min, max)], [Min(0)] 슬라이더, 최솟값 [Range(0f, 10f)]
중 [TextArea] 여러 줄 입력칸 [TextArea] public string desc;
중 [HideInInspector] public이어도 Inspector에서 숨기기 [HideInInspector] public int id;
중 [CreateAssetMenu(menuName = "...")] ScriptableObject를 Create 메뉴에 넣기 [CreateAssetMenu(menuName = "Lemonade/Upgrade")]
중 [ContextMenu("이름")] 컴포넌트 ⋮ 메뉴에서 그 함수를 실행. 테스트용 [ContextMenu("돈 1000 추가")] void AddMoney() { money += 1000; }
하 [DisallowMultipleComponent] 같은 컴포넌트를 두 번 못 붙이게 [DisallowMultipleComponent]
하 [FormerlySerializedAs("옛이름")] 필드 이름을 바꿔도 Inspector 값이 유지되게 [FormerlySerializedAs("spd")]
하 [DefaultExecutionOrder(-100)] 이 스크립트를 먼저 실행 [DefaultExecutionOrder(-100)]
하 [SerializeReference] 인터페이스나 상속 타입을 직렬화 [SerializeReference] IEffect effect;
하 [RuntimeInitializeOnLoadMethod] Play 시작 전에 실행. 도메인 리로드를 끈 이 프로젝트에서 static을 쓰면 리셋용으로 필수 [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
하 [ExecuteAlways] 에디터에서도 Update 실행 [ExecuteAlways]

ScriptableObject: 데이터 에셋

상 클래스 · 마스터 데이터

설명: 게임 데이터를 에셋 파일로 만든다. 서버의 읽기 전용 마스터 데이터 테이블과 같다.

[CreateAssetMenu(menuName = "Lemonade/Upgrade")]
public class UpgradeData : ScriptableObject
{
    public string title;
    public int cost;
}

Project 창에서 우클릭 > Create > Lemonade > Upgrade로 에셋을 만들고, [SerializeField] private UpgradeData[] upgrades;로 Inspector에서 연결해 읽는다. 코드로 만들 때는 ScriptableObject.CreateInstance<UpgradeData>()다.

주의: 게임 중에 값을 바꾸지 않는다. 에디터에서는 Play 중에 바꾼 값도 에셋 파일에 그대로 저장돼 버린다.

Debug와 Gizmos: 확인하기

최상 클래스 · 로그와 보조선

Debug는 Console에, Gizmos는 Scene 뷰에 그린다. 둘 다 확인용이라 출시 전에 정리한다.

명령 설명 예시
최상 Debug.Log(message) Console에 로그 Debug.Log($"판매 +{price}");
상 Debug.LogWarning, Debug.LogError 경고(노랑), 에러(빨강). 두 번째 인자로 오브젝트를 넘기면 누를 때 짚어 준다 Debug.LogError("연결 안 됨", this);
중 Debug.DrawLine(a, b, color, 초), Debug.DrawRay Play 중에 Scene 뷰에 선 그리기. 광선 확인 Debug.DrawRay(pos, Vector2.down, Color.red);
중 Debug.LogException(e) 예외 기록 catch (Exception e) { Debug.LogException(e); }
하 Debug.Break() 에디터 일시정지 if (hp < 0) Debug.Break();
하 Debug.Assert(조건, 메시지) 조건이 거짓이면 에러 로그 Debug.Assert(stand != null, "가판대 없음");
하 Gizmos.color, DrawWireSphere, DrawWireCube, DrawLine OnDrawGizmos 안에서 보조 도형 그리기 Gizmos.DrawWireSphere(transform.position, range);

ObjectPool: 풀링

명령 설명 예시
중 new ObjectPool<T>(create, onGet, onRelease, onDestroy) 객체 풀 만들기. using UnityEngine.Pool; 필요 pool = new ObjectPool<Customer>(Create, OnGet, OnRelease, OnDestroyItem);
중 pool.Get(), pool.Release(obj) 꺼내기, 돌려주기 var c = pool.Get();
하 pool.CountActive, pool.CountInactive, pool.Clear() 나가 있는 수, 쉬는 수, 비우기 Debug.Log(pool.CountActive);
하 ListPool<T>.Get(), ListPool<T>.Release(list) 임시 List를 빌려 쓰기. GC 줄이기 var tmp = ListPool<Collider2D>.Get();

주의: 같은 객체를 두 번 Release하면 예외가 난다. 풀에 돌려줄 때는 위치, 타이머, 이벤트 구독을 모두 초기화한다.

Color, LayerMask, 태그

명령 설명 예시
중 new Color(r, g, b, a) 색 만들기. 0~1 실수 new Color(1f, 0.8f, 0.2f)
중 Color.red, Color.white, Color.black, Color.clear 자주 쓰는 색. clear는 완전 투명 sr.color = Color.white;
중 Color.Lerp(a, b, t) 색 섞기 Color.Lerp(Color.white, Color.red, t)
중 color.a 알파 (투명도). 꺼내서 고친 다음 다시 넣는다 var c = sr.color; c.a = 0.5f; sr.color = c;
중 [SerializeField] LayerMask mask Inspector에서 레이어를 골라 받기 [SerializeField] private LayerMask groundMask;
중 LayerMask.GetMask("A", "B") 이름으로 마스크 만들기. 검사 함수에 넘긴다 LayerMask.GetMask("Customer")
중 LayerMask.NameToLayer("A") 이름을 레이어 번호로. gameObject.layer에 넣는다 gameObject.layer = LayerMask.NameToLayer("Customer");
하 LayerMask.LayerToName(n), mask.value 번호를 이름으로, 비트 값 LayerMask.LayerToName(gameObject.layer)

꼭 기억할 것 ⑧ gameObject.layer에는 번호, 검사 함수에는 마스크를 넣는다

NameToLayer는 레이어 번호(0~31)를 돌려주고, GetMask는 그 번호 자리의 비트를 켠 마스크를 돌려준다. Physics2D.OverlapCircle(pos, r, LayerMask.NameToLayer("Customer"))처럼 번호를 마스크 자리에 넣으면 에러 없이 엉뚱한 레이어를 검사한다. 번호 3을 마스크로 읽으면 0번과 1번 레이어가 되기 때문이다.

에디터 조작: Hierarchy와 Inspector

명령어뿐 아니라 에디터 조작도 같은 등급으로 나눴다.

조작 하는 일
최상 Ctrl+S 씬 저장. Git은 저장된 파일만 본다
최상 Ctrl+P Play 시작과 중지
최상 F 선택한 오브젝트로 Scene 뷰 이동
최상 W, E, R 이동, 회전, 크기 도구
상 Ctrl+D 복제
상 Hierarchy에서 Project 창으로 끌기 프리팹 만들기
상 오브젝트를 Inspector 칸에 끌어다 놓기 [SerializeField] 참조 연결
상 숫자 칸의 이름을 좌우로 끌기 값을 부드럽게 조절
상 컴포넌트 ⋮ > Copy Component, 멈춘 뒤 ⋮ > Paste Component Values Play 중에 맞춘 값을 살려 오기
중 Inspector 오른쪽 위 ⋮ > Debug private 필드까지 보기. 디버거의 조사식과 같다
중 Hierarchy 검색창에 t:Rigidbody2D 그 컴포넌트가 붙은 오브젝트만 보기
중 Inspector 자물쇠 다른 것을 골라도 Inspector 고정. 여러 개를 끌어다 연결할 때
중 프리팹 인스턴스의 Overrides > Apply All 씬에서 고친 값을 프리팹 원본에 반영
중 Edit > Preferences > Colors > Playmode tint Play 중에 에디터 색을 바꿔 Play 중인지 헷갈리지 않게
하 Alt + Hierarchy 화살표 클릭 자식까지 한 번에 펼치기
하 Ctrl+Shift+N 빈 오브젝트 만들기

Unity 6에서 바뀐 이름

옛 강좌나 AI가 짠 코드에서 왼쪽 이름이 보이면 오른쪽으로 바꿔 읽는다. 아래 옛 이름은 6000.6 에디터에서 전부 폐기 표시가 붙어 있는 것을 확인했다.

옛 이름 지금 (Unity 6.6)
rb.velocity rb.linearVelocity
rb.drag, rb.angularDrag rb.linearDamping, rb.angularDamping
rb.isKinematic = true rb.bodyType = RigidbodyType2D.Kinematic
FindObjectOfType<T>(), FindFirstObjectByType<T>() FindAnyObjectByType<T>()
Input.GetKey, Input.GetAxis Input System (Keyboard.current, InputAction)
UnityEngine.UI.Text TMP_Text (TextMeshPro)
CinemachineVirtualCamera CinemachineCamera (Cinemachine 3)
File > Build Settings File > Build Profiles

2D와 3D 대응표

3D로 넘어가면 이름 끝의 2D가 빠지는 것이 대부분이다. 다만 2D와 3D 물리는 서로 다른 엔진이라 섞어 쓸 수 없다.

2D 3D
Rigidbody2D Rigidbody
Collider2D, BoxCollider2D, CircleCollider2D Collider, BoxCollider, SphereCollider
OnTriggerEnter2D(Collider2D other) OnTriggerEnter(Collider other)
OnCollisionEnter2D(Collision2D c) OnCollisionEnter(Collision c)
Physics2D.Raycast(...)가 RaycastHit2D를 돌려준다 Physics.Raycast(..., out RaycastHit hit)가 bool을 돌려준다
ForceMode2D ForceMode
rb.rotation은 float 각도 rb.rotation은 Quaternion
Vector2 위주 Vector3 위주

마치며

이 표는 외우려고 만든 것이 아니다. 최상은 안 보고 쓸 수 있을 때까지 직접 쳐 보고, 상은 한 번씩 써 보고, 중과 하는 필요할 때 여기서 찾는다.

명령이 떠오르지 않으면 점을 찍어 목록을 훑고, Inspector 칸 이름을 코드 이름으로 바꿔 본다. 대부분은 그것으로 찾아진다.