Full Guide

전체 어트리뷰트 문서

기준: Unity 6.x 공식 Scripting API와 Unity Test Framework, Microsoft C# 문서를 기준으로 정리했습니다.
목표: [] 형태로 붙이는 Unity/C# 어트리뷰트를 한 번에 훑고, “어디에 붙이고”, “무슨 효과가 있고”, “언제 쓰는지”를 빠르게 이해하는 것입니다.
주의: Unity 버전, 설치된 패키지, 렌더 파이프라인, 에디터 확장 환경에 따라 지원 여부가 달라질 수 있습니다. 특히 UnityEditor 네임스페이스의 어트리뷰트는 빌드된 앱 런타임 코드에서 사용할 수 없습니다.


0. 어트리뷰트 기본 개념

0-1. 어트리뷰트란?

C#에서 어트리뷰트는 클래스, 필드, 메서드, 프로퍼티, 어셈블리 등에 붙이는 “메타데이터”입니다.

Unity에서는 이 메타데이터를 읽어서 다음과 같은 일을 합니다.

예시:

[SerializeField]
private int hp;

[System.Serializable]
public class ItemData
{
    public string itemName;
}

[MenuItem("Combat/Setup Animated Agent Prefabs")]
private static void SetupPrefabs()
{
    Debug.Log("Setup!");
}

0-2. Attribute 접미사는 생략 가능

아래 둘은 같은 의미입니다.

[System.Serializable]
public class A {}

[System.SerializableAttribute]
public class B {}

직접 어트리뷰트를 만들 때 클래스명은 관례적으로 SomethingAttribute라고 짓고, 사용할 때는 [Something]처럼 씁니다.

0-3. 여러 개를 붙이는 방식

아래 두 방식 모두 가능합니다.

[SerializeField]
[Range(0, 100)]
private int hp;
[SerializeField, Range(0, 100)]
private int hp;

실무에서는 줄마다 하나씩 쓰는 방식이 읽기 쉽습니다.


1. 빠른 분류표

분류 대표 어트리뷰트 주 용도
Inspector 노출/표시 [SerializeField], [HideInInspector], [Header], [Tooltip], [Range], [TextArea] 인스펙터에 어떻게 보일지 제어
Unity 직렬화 [System.Serializable], [SerializeReference], [FormerlySerializedAs], [NonSerialized] 씬, 프리팹, ScriptableObject에 데이터 저장
컴포넌트 제약 [RequireComponent], [DisallowMultipleComponent], [AddComponentMenu] 컴포넌트 추가/중복/메뉴 위치 제어
실행 시점 [DefaultExecutionOrder], [ExecuteAlways], [RuntimeInitializeOnLoadMethod] 실행 순서와 초기화 시점 제어
에디터 메뉴/툴 [MenuItem], [ContextMenu], [DrawGizmo], [EditorTool], [Shortcut] 에디터 기능 확장
커스텀 인스펙터 [CustomEditor], [CustomPropertyDrawer], [CanEditMultipleObjects] Inspector UI 직접 커스터마이즈
ScriptableObject [CreateAssetMenu] Assets/Create 메뉴에 에셋 생성 항목 등록
UI Toolkit [UxmlElement], [UxmlAttribute], [UxmlObject] 커스텀 VisualElement를 UXML/UI Builder에 노출
빌드/임포트 콜백 [PostProcessBuild], [PostProcessScene], [ScriptedImporter], [OnOpenAsset] 빌드, 씬 처리, 에셋 임포트 확장
테스트 [Test], [UnityTest], [SetUp], [TearDown], [Category] Unity Test Framework/NUnit 테스트 정의
C# 기본 [Obsolete], [Flags], [DllImport], [AttributeUsage], [Conditional] 컴파일러, 런타임, 네이티브 연동 제어

2. UnityEngine: Inspector와 직렬화 관련

이 영역은 Unity 초반에 가장 자주 만나는 어트리뷰트입니다.

2-1. [SerializeField]

항목 내용
네임스페이스 UnityEngine
붙이는 위치 필드
핵심 private/protected 필드를 Unity 직렬화 대상으로 만들고 Inspector에 표시
자주 쓰는 상황 캡슐화는 유지하면서 Inspector에서 값 조정이 필요할 때
public class PlayerHealth : MonoBehaviour
{
    [SerializeField]
    private int maxHp = 100;
}

주의점:


2-2. [HideInInspector]

항목 내용
네임스페이스 UnityEngine
붙이는 위치 필드
핵심 public 필드라도 Inspector에서 숨김
자주 쓰는 상황 코드에서는 public 접근이 필요하지만 Inspector에는 노출하고 싶지 않을 때
[HideInInspector]
public int runtimeScore;

주의점:


2-3. [System.Serializable]

항목 내용
네임스페이스 System
붙이는 위치 class, struct, enum 등 타입
핵심 Unity가 사용자 정의 class/struct의 필드를 Inspector와 직렬화 대상으로 다룰 수 있게 함
자주 쓰는 상황 ItemData, StatData, InventorySlot 같은 데이터 클래스를 MonoBehaviour 안에 넣을 때
[System.Serializable]
public class ItemData
{
    public string itemName;
    public int count;
}

public class Inventory : MonoBehaviour
{
    [SerializeField]
    private ItemData startItem;
}

주의점:


2-4. [NonSerialized]

항목 내용
네임스페이스 System
붙이는 위치 필드
핵심 직렬화 가능한 타입 안에서 특정 필드를 직렬화하지 않음
자주 쓰는 상황 런타임 캐시, 계산으로 다시 만들 수 있는 값, 저장되면 안 되는 임시 값
[System.NonSerialized]
public int runtimeOnlyCache;

주의점:


2-5. [SerializeReference]

항목 내용
네임스페이스 UnityEngine
붙이는 위치 필드
핵심 사용자 정의 class를 값 복사가 아니라 참조 기반으로 직렬화
자주 쓰는 상황 다형성, 인터페이스/추상 클래스 기반 데이터, null 보존, 그래프/트리 구조 저장
[System.Serializable]
public abstract class SkillEffect
{
    public string effectName;
}

[System.Serializable]
public class DamageEffect : SkillEffect
{
    public int damage;
}

public class SkillData : MonoBehaviour
{
    [SerializeReference]
    private SkillEffect effect;
}

주의점:


2-6. [FormerlySerializedAs]

항목 내용
네임스페이스 UnityEngine.Serialization
붙이는 위치 필드
핵심 필드명을 바꿔도 기존 씬/프리팹/에셋에 저장된 값을 잃지 않도록 이전 이름을 알려줌
자주 쓰는 상황 hitpointshealth로 바꾸는 리팩토링
using UnityEngine.Serialization;

public class Enemy : MonoBehaviour
{
    [FormerlySerializedAs("hitpoints")]
    [SerializeField]
    private int health;
}

주의점:

[FormerlySerializedAs("hp")]
[FormerlySerializedAs("hitpoints")]
[SerializeField]
private int health;

3. UnityEngine: Inspector 표시 보조 어트리뷰트

이들은 대부분 PropertyAttribute 계열입니다. 즉, 필드의 Inspector 표시 방식을 바꿉니다.

3-1. 자주 쓰는 표시 어트리뷰트 표

어트리뷰트 붙이는 위치 설명 예시
[Header("...")] 필드 Inspector에 섹션 제목 표시 [Header("Health")]
[Tooltip("...")] 필드 마우스 hover 시 설명 표시 [Tooltip("최대 체력")]
[Space] 필드 Inspector에 여백 추가 [Space(10)]
[Range(min, max)] int/float 필드 슬라이더로 표시 [Range(0, 100)]
[Min(value)] int/float 필드 최소값 제한 [Min(0)]
[Multiline] string 필드 여러 줄 입력칸 [Multiline(3)]
[TextArea] string 필드 긴 텍스트 입력칸 [TextArea(3, 10)]
[Delayed] int/float/string 필드 입력 즉시 반영하지 않고 Enter 또는 포커스 해제 시 반영 [Delayed]
[ColorUsage] Color 필드 HDR/알파 등 컬러 선택 옵션 제어 [ColorUsage(true, true)]
[GradientUsage] Gradient 필드 Gradient 사용 방식 제어 [GradientUsage(true)]
[InspectorName("...")] 필드/enum 값 Inspector에 표시되는 이름 변경 [InspectorName("이동 속도")]
[ContextMenuItem] 필드 필드 우클릭 메뉴 추가 [ContextMenuItem("Reset", "ResetValue")]
[NonReorderable] 배열/List 필드 Inspector에서 배열/List 순서 변경 UI 제한 [NonReorderable]

예시:

public class WeaponConfig : MonoBehaviour
{
    [Header("Damage")]
    [SerializeField, Min(0)]
    private int damage = 10;

    [Header("UI")]
    [SerializeField, Tooltip("플레이어에게 표시할 무기 설명")]
    [TextArea(3, 8)]
    private string description;

    [SerializeField, Range(0f, 1f)]
    private float criticalChance = 0.1f;
}

3-2. [Range][Min]의 차이

[Range(0, 100)]
public int hp;

[Min(0)]
public int gold;

3-3. [ContextMenuItem]

필드 우클릭 메뉴에 액션을 추가합니다.

public class ItemIdGenerator : MonoBehaviour
{
    [ContextMenuItem("Generate ID", nameof(GenerateId))]
    [SerializeField]
    private string itemId;

    private void GenerateId()
    {
        itemId = System.Guid.NewGuid().ToString();
    }
}

4. UnityEngine: 컴포넌트와 GameObject 관련

4-1. [RequireComponent]

항목 내용
네임스페이스 UnityEngine
붙이는 위치 MonoBehaviour 클래스
핵심 이 스크립트가 붙을 때 필요한 컴포넌트를 자동으로 같이 추가
자주 쓰는 상황 Rigidbody, Collider, Animator, AudioSource 등이 필수일 때
[RequireComponent(typeof(Rigidbody))]
public class PlayerMovement : MonoBehaviour
{
    private Rigidbody rb;

    private void Awake()
    {
        rb = GetComponent<Rigidbody>();
    }
}

여러 개도 가능합니다.

[RequireComponent(typeof(Rigidbody), typeof(CapsuleCollider))]
public class CharacterMotor : MonoBehaviour
{
}

주의점:


4-2. [DisallowMultipleComponent]

항목 내용
네임스페이스 UnityEngine
붙이는 위치 MonoBehaviour 클래스
핵심 같은 GameObject에 같은 컴포넌트가 여러 개 붙는 것을 막음
[DisallowMultipleComponent]
public class PlayerController : MonoBehaviour
{
}

4-3. [AddComponentMenu]

항목 내용
네임스페이스 UnityEngine
붙이는 위치 MonoBehaviour 클래스
핵심 Add Component 메뉴에서 보이는 경로를 지정
자주 쓰는 상황 직접 만든 컴포넌트를 카테고리별로 정리하고 싶을 때
[AddComponentMenu("Combat/Enemy AI Controller")]
public class EnemyAIController : MonoBehaviour
{
}

4-4. [HelpURL]

항목 내용
네임스페이스 UnityEngine
붙이는 위치 클래스
핵심 Inspector 상단 도움말 버튼을 눌렀을 때 이동할 문서 URL 지정
[HelpURL("https://example.com/docs/enemy-ai")]
public class EnemyAIController : MonoBehaviour
{
}

팀 프로젝트에서 “이 컴포넌트는 어떤 규칙으로 써야 하는지” 문서와 연결할 때 좋습니다.


4-5. [SelectionBase]

항목 내용
네임스페이스 UnityEngine
붙이는 위치 클래스
핵심 Scene View에서 하위 오브젝트를 클릭해도 이 컴포넌트가 붙은 GameObject를 선택 기준으로 삼도록 함
[SelectionBase]
public class BuildingRoot : MonoBehaviour
{
}

복잡한 프리팹, 캐릭터, 건물처럼 하위 Mesh가 많을 때 유용합니다.


4-6. [Icon]

항목 내용
네임스페이스 UnityEngine
붙이는 위치 MonoBehaviour, ScriptableObject 클래스
핵심 스크립트/컴포넌트에 프로젝트 내 아이콘 텍스처 지정
[Icon("Assets/Editor/Icons/EnemyIcon.png")]
public class EnemyAIController : MonoBehaviour
{
}

4-7. [ExcludeFromPreset]

항목 내용
네임스페이스 UnityEngine
붙이는 위치 클래스
핵심 해당 타입 인스턴스로 Preset을 만들지 못하게 함
[ExcludeFromPreset]
public class RuntimeOnlyComponent : MonoBehaviour
{
}

4-8. [ExcludeFromObjectFactory]

항목 내용
네임스페이스 UnityEngine
붙이는 위치 클래스
핵심 ObjectFactory를 통한 생성 대상에서 제외
자주 쓰는 상황 에디터 확장이나 내부 도구에서 생성되면 안 되는 타입 보호
[ExcludeFromObjectFactory]
public class InternalOnlyComponent : MonoBehaviour
{
}

5. UnityEngine: 실행 시점과 초기화 관련

5-1. [DefaultExecutionOrder]

항목 내용
네임스페이스 UnityEngine
붙이는 위치 MonoBehaviour 클래스
핵심 Script Execution Order를 코드에서 지정
값 의미 작은 값일수록 먼저 실행, 큰 값일수록 나중 실행
[DefaultExecutionOrder(-100)]
public class GameBootstrapper : MonoBehaviour
{
    private void Awake()
    {
        Debug.Log("다른 일반 스크립트보다 먼저 초기화");
    }
}

주의점:


5-2. [ExecuteAlways]

항목 내용
네임스페이스 UnityEngine
붙이는 위치 MonoBehaviour 클래스
핵심 Play Mode가 아니어도 Edit Mode에서 스크립트 콜백 실행
자주 쓰는 상황 에디터에서 배치 미리보기, 자동 정렬, Gizmo 갱신, 데이터 검증
[ExecuteAlways]
public class LookAtCameraPreview : MonoBehaviour
{
    private void Update()
    {
        if (!Application.isPlaying)
        {
            // Edit Mode 전용 미리보기 로직
        }
    }
}

주의점:


5-3. [ExecuteInEditMode]

항목 내용
네임스페이스 UnityEngine
붙이는 위치 MonoBehaviour 클래스
핵심 예전부터 쓰이던 Edit Mode 실행 어트리뷰트
현재 권장 새 코드에서는 보통 [ExecuteAlways] 사용을 우선 고려
[ExecuteInEditMode]
public class LegacyEditModePreview : MonoBehaviour
{
}

5-4. [RuntimeInitializeOnLoadMethod]

항목 내용
네임스페이스 UnityEngine
붙이는 위치 static 메서드
핵심 씬 로드 전후, 서브시스템 등록 시점 등에 자동 실행
자주 쓰는 상황 매니저 초기화, 정적 캐시 리셋, 런타임 부트스트랩
public static class GameRuntimeBootstrap
{
    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    private static void InitBeforeSceneLoad()
    {
        Debug.Log("첫 씬 로드 전에 실행");
    }
}

대표 RuntimeInitializeLoadType:

의미
BeforeSceneLoad 첫 씬 로드 전
AfterSceneLoad 첫 씬 로드 후
SubsystemRegistration 서브시스템 등록 시점
AfterAssembliesLoaded 어셈블리 로드 후
BeforeSplashScreen 스플래시 화면 전

주의점:


5-5. [BeforeRenderOrder]

항목 내용
네임스페이스 UnityEngine
붙이는 위치 메서드
핵심 렌더 직전 콜백 순서 제어 용도
자주 쓰는 상황 XR, 카메라, 렌더 직전 업데이트처럼 프레임 타이밍이 민감한 코드

일반 게임 로직에서는 자주 쓰지 않습니다.


6. UnityEngine: ScriptableObject와 에셋 생성

6-1. [CreateAssetMenu]

항목 내용
네임스페이스 UnityEngine
붙이는 위치 ScriptableObject 파생 클래스
핵심 Assets/Create 메뉴에 ScriptableObject 생성 항목 등록
[CreateAssetMenu(
    fileName = "New Item Data",
    menuName = "Game Data/Item Data",
    order = 0)]
public class ItemData : ScriptableObject
{
    public string itemName;
    public Sprite icon;
}

파라미터:

파라미터 의미
fileName 생성 시 기본 파일명
menuName Assets/Create 아래 메뉴 경로
order 메뉴 표시 순서

자주 쓰는 구조:

Assets
└─ Create
   └─ Game Data
      ├─ Item Data
      ├─ Enemy Data
      └─ Skill Data

7. UnityEngine: 렌더링, 이미지 이펙트, 시각화 관련

어트리뷰트 위치 설명 비고
[ImageEffectAllowedInSceneView] 이미지 이펙트 컴포넌트 Scene View에서도 이미지 이펙트 허용 Built-in/레거시 이미지 이펙트에서 주로 등장
[ImageEffectOpaque] 이미지 이펙트 메서드/컴포넌트 불투명 렌더링 이후 이펙트 처리 렌더 파이프라인에 따라 사용성 차이
[ImageEffectTransformsToLDR] 이미지 이펙트 HDR에서 LDR로 변환하는 이펙트 표시 레거시 후처리 코드에서 볼 수 있음
[ColorUsage] Color 필드 HDR 컬러, 알파 표시 제어 Inspector 표시 보조
[GradientUsage] Gradient 필드 HDR Gradient 사용 제어 Inspector 표시 보조
[GUITarget] IMGUI 메서드 특정 디스플레이 대상으로 GUI 렌더링 일반 게임 로직에서는 드묾

요즘 프로젝트에서는 URP/HDRP의 Volume, Renderer Feature, Shader Graph, Custom Pass 등으로 후처리를 구성하는 경우가 많아 일부 이미지 이펙트 어트리뷰트는 오래된 예제에서 더 자주 보입니다.


8. UnityEngine.Scripting: 코드 스트리핑과 Unity 내부 연동

8-1. [Preserve]

항목 내용
네임스페이스 UnityEngine.Scripting
붙이는 위치 클래스, 메서드, 필드, 프로퍼티 등
핵심 Managed code stripping에서 제거되지 않도록 보존
자주 쓰는 상황 리플렉션으로 호출되는 타입/메서드, JSON 역직렬화로 생성되는 타입, IL2CPP 빌드에서 참조가 안 보이는 코드
using UnityEngine.Scripting;

[Preserve]
public class ReflectedCommand
{
    [Preserve]
    public void Execute()
    {
    }
}

주의점:


8-2. Unity 내부/네이티브 바인딩 계열

다음 어트리뷰트들은 Unity 내부 코드, 네이티브 바인딩, 패키지 내부 구현에서 보이는 경우가 많습니다. 일반 게임 코드에서 직접 사용할 일은 적습니다.

어트리뷰트 설명
[UsedByNativeCode] 네이티브 코드에서 사용됨을 표시
[RequiredByNativeCode] 네이티브 코드가 필요로 하는 타입/멤버 표시
[NativeHeader] 연결되는 네이티브 헤더 정보
[NativeName] 네이티브 쪽 이름 매핑
[NativeMethod] 네이티브 메서드 매핑
[NativeProperty] 네이티브 프로퍼티 매핑
[NativeThrows] 네이티브 예외 관련 표시
[VisibleToOtherModules] Unity 모듈 간 노출 정보
[MovedFrom] 타입/네임스페이스 이동 정보
[FreeFunction] 네이티브 free function 매핑
[StaticAccessor] 네이티브 static 접근자 매핑
[ThreadSafe] 네이티브 호출 thread-safe 정보
[NativeConditional] 네이티브 조건부 컴파일 정보

요약하면, Unity 소스나 패키지 내부 구현을 읽다가 보이면 “엔진과 C# API를 연결하기 위한 표시”로 이해하면 됩니다.


9. UnityEditor: 메뉴, 에디터 콜백, 에디터 확장

UnityEditor 네임스페이스는 에디터 전용입니다. 일반적으로 Assets/Editor 폴더 안에 넣거나, Assembly Definition에서 Editor 전용 어셈블리로 분리합니다.

9-1. [MenuItem]

항목 내용
네임스페이스 UnityEditor
붙이는 위치 static 메서드
핵심 Unity 상단 메뉴, Assets 우클릭 메뉴, GameObject 메뉴 등에 항목 추가
메서드 조건 static 메서드여야 함
using UnityEditor;
using UnityEngine;

public static class CombatMenu
{
    [MenuItem("Combat/Setup Animated Agent Prefabs")]
    private static void SetupAnimatedAgentPrefabs()
    {
        Debug.Log("프리팹 세팅 실행");
    }
}

메뉴 검증 함수

같은 경로에 true를 두 번째 인자로 넣으면 해당 메뉴가 활성화 가능한지 검사합니다.

public static class SelectionMenu
{
    [MenuItem("Tools/Print Selected Name")]
    private static void PrintSelectedName()
    {
        Debug.Log(Selection.activeGameObject.name);
    }

    [MenuItem("Tools/Print Selected Name", true)]
    private static bool ValidatePrintSelectedName()
    {
        return Selection.activeGameObject != null;
    }
}

단축키 표기

기호 의미
% Windows/Linux: Ctrl, macOS: Cmd
^ Ctrl
# Shift
& Alt
_g 단일 키 G
[MenuItem("Tools/Do Something #&g")]
private static void DoSomething()
{
}

위 예시는 Shift + Alt + G입니다.


9-2. [ContextMenu]

항목 내용
네임스페이스 UnityEngine
붙이는 위치 인스턴스 메서드
핵심 컴포넌트 Inspector 우클릭 메뉴에 실행 항목 추가
자주 쓰는 상황 디버그, 자동 세팅, 데이터 초기화
public class EnemySpawner : MonoBehaviour
{
    [ContextMenu("Spawn Test Enemy")]
    private void SpawnTestEnemy()
    {
        Debug.Log("테스트 적 생성");
    }
}

주의점:


9-3. [InitializeOnLoad]

항목 내용
네임스페이스 UnityEditor
붙이는 위치 클래스
핵심 Unity 에디터 로드 또는 스크립트 재컴파일 시 static 생성자 실행
using UnityEditor;
using UnityEngine;

[InitializeOnLoad]
public static class EditorBoot
{
    static EditorBoot()
    {
        Debug.Log("에디터 로드 또는 스크립트 리로드 시 실행");
    }
}

주의점:


9-4. [InitializeOnLoadMethod]

항목 내용
네임스페이스 UnityEditor
붙이는 위치 static 메서드
핵심 클래스 static 생성자 없이 메서드 단위로 에디터 로드 시 실행
public static class EditorInitMethod
{
    [InitializeOnLoadMethod]
    private static void Init()
    {
        Debug.Log("에디터 로드 시 메서드 실행");
    }
}

9-5. [InitializeOnEnterPlayMode]

항목 내용
네임스페이스 UnityEditor
붙이는 위치 static 메서드
핵심 Play Mode 진입 시점에 실행
자주 쓰는 상황 Domain Reload 비활성화 환경에서 정적 캐시 초기화
using UnityEditor;

public static class PlayModeReset
{
    [InitializeOnEnterPlayMode]
    private static void ResetOnEnterPlayMode(EnterPlayModeOptions options)
    {
        // static cache reset
    }
}

9-6. [DidReloadScripts]

항목 내용
네임스페이스 UnityEditor.Callbacks
붙이는 위치 static 메서드
핵심 스크립트 리로드 후 콜백 실행
특징 callback order 지정 가능
using UnityEditor.Callbacks;
using UnityEngine;

public static class ReloadCallback
{
    [DidReloadScripts]
    private static void OnScriptsReloaded()
    {
        Debug.Log("스크립트 리로드 완료");
    }
}

9-7. [PostProcessBuild]

항목 내용
네임스페이스 UnityEditor.Callbacks
붙이는 위치 static 메서드
핵심 플레이어 빌드 직후 실행
자주 쓰는 상황 빌드 산출물 복사, iOS Xcode 프로젝트 수정, 빌드 후 파일 생성
using UnityEditor;
using UnityEditor.Callbacks;

public static class BuildPostProcessor
{
    [PostProcessBuild]
    private static void OnPostProcessBuild(BuildTarget target, string pathToBuiltProject)
    {
        UnityEngine.Debug.Log($"Build finished: {target}, {pathToBuiltProject}");
    }
}

9-8. [PostProcessScene]

항목 내용
네임스페이스 UnityEditor.Callbacks
붙이는 위치 static 메서드
핵심 씬이 빌드 처리된 직후 실행
자주 쓰는 상황 빌드 전용 씬 데이터 변환, 검증, 자동 세팅
using UnityEditor.Callbacks;
using UnityEngine;

public static class ScenePostProcessor
{
    [PostProcessScene]
    private static void OnPostProcessScene()
    {
        Debug.Log("씬 후처리");
    }
}

9-9. [OnOpenAsset]

항목 내용
네임스페이스 UnityEditor.Callbacks
붙이는 위치 static 메서드
핵심 에디터에서 에셋을 열려고 할 때 커스텀 처리
자주 쓰는 상황 특정 커스텀 에셋을 더블클릭하면 자체 에디터 창 열기
using UnityEditor.Callbacks;

public static class OpenAssetHandler
{
    [OnOpenAsset]
    private static bool OnOpenAsset(int instanceID, int line)
    {
        // true를 반환하면 Unity의 기본 열기 동작을 막고 직접 처리했다는 뜻
        return false;
    }
}

9-10. [DrawGizmo]

항목 내용
네임스페이스 UnityEditor
붙이는 위치 static 메서드
핵심 특정 컴포넌트에 대한 Gizmo 렌더링을 외부 클래스에서 정의
using UnityEditor;
using UnityEngine;

public class PatrolPoint : MonoBehaviour
{
    public float radius = 1f;
}

public static class PatrolPointGizmoDrawer
{
    [DrawGizmo(GizmoType.Selected | GizmoType.NonSelected)]
    private static void DrawPatrolPointGizmo(PatrolPoint point, GizmoType gizmoType)
    {
        Gizmos.DrawWireSphere(point.transform.position, point.radius);
    }
}

10. UnityEditor: Custom Inspector와 Property Drawer

10-1. [CustomEditor]

항목 내용
네임스페이스 UnityEditor
붙이는 위치 Editor 파생 클래스
핵심 특정 타입의 Inspector를 직접 그림
using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(EnemySpawner))]
public class EnemySpawnerEditor : Editor
{
    public override void OnInspectorGUI()
    {
        DrawDefaultInspector();

        var spawner = (EnemySpawner)target;

        if (GUILayout.Button("Spawn Test Enemy"))
        {
            // spawner.Spawn...
        }
    }
}

10-2. [CanEditMultipleObjects]

항목 내용
네임스페이스 UnityEditor
붙이는 위치 Editor 파생 클래스
핵심 여러 오브젝트를 동시에 선택했을 때 커스텀 에디터가 다중 편집을 지원한다고 표시
[CustomEditor(typeof(EnemySpawner))]
[CanEditMultipleObjects]
public class EnemySpawnerEditor : Editor
{
}

주의점:


10-3. [CustomPropertyDrawer]

항목 내용
네임스페이스 UnityEditor
붙이는 위치 PropertyDrawer 또는 DecoratorDrawer 파생 클래스
핵심 특정 serializable class 또는 custom PropertyAttribute의 Inspector 표시 방식 정의

Serializable class에 drawer 붙이기

[System.Serializable]
public class Stat
{
    public string name;
    public int value;
}
using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(Stat))]
public class StatDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        EditorGUI.PropertyField(position, property, label, true);
    }
}

직접 만든 어트리뷰트에 drawer 붙이기

using UnityEngine;

public class ReadOnlyAttribute : PropertyAttribute
{
}
using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(ReadOnlyAttribute))]
public class ReadOnlyDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        bool oldEnabled = GUI.enabled;
        GUI.enabled = false;
        EditorGUI.PropertyField(position, property, label, true);
        GUI.enabled = oldEnabled;
    }
}
[ReadOnly]
[SerializeField]
private int generatedId;

10-4. [CustomPreview]

항목 내용
네임스페이스 UnityEditor
붙이는 위치 ObjectPreview 파생 클래스
핵심 Inspector Preview 영역을 커스터마이즈
[CustomPreview(typeof(Texture2D))]
public class MyTexturePreview : ObjectPreview
{
}

실무에서는 일반 Inspector 커스터마이즈보다 사용 빈도가 낮지만, 에셋 프리뷰 도구를 만들 때 유용합니다.


10-5. [EditorWindowTitle]

항목 내용
네임스페이스 UnityEditor
붙이는 위치 EditorWindow 파생 클래스
핵심 EditorWindow의 제목과 아이콘 지정
using UnityEditor;

[EditorWindowTitle(title = "Combat Tool")]
public class CombatToolWindow : EditorWindow
{
}

11. UnityEditor: 툴바, 단축키, EditorTool

11-1. [Shortcut]

항목 내용
네임스페이스 UnityEditor.ShortcutManagement
붙이는 위치 static 메서드
핵심 Shortcut Manager에 단축키 등록
메서드 조건 인자 없음 또는 ShortcutArguments 하나
using UnityEditor.ShortcutManagement;
using UnityEngine;

public static class MyShortcuts
{
    [Shortcut("My Tools/Log Hello")]
    private static void LogHello()
    {
        Debug.Log("Hello");
    }
}

11-2. [ClutchShortcut]

항목 내용
네임스페이스 UnityEditor.ShortcutManagement
붙이는 위치 static 메서드
핵심 누르고 있는 동안 동작하는 clutch 방식 단축키
자주 쓰는 상황 임시 도구 활성화, Scene View 도구 전환

11-3. [EditorTool]

항목 내용
네임스페이스 UnityEditor.EditorTools
붙이는 위치 EditorTool 파생 클래스
핵심 Scene View 커스텀 툴 등록
using UnityEditor.EditorTools;
using UnityEngine;

[EditorTool("Patrol Point Tool", typeof(PatrolPoint))]
public class PatrolPointTool : EditorTool
{
    public override void OnToolGUI(EditorWindow window)
    {
        // Scene View tool GUI
    }
}

11-4. [EditorToolContext]

항목 내용
네임스페이스 UnityEditor.EditorTools
붙이는 위치 EditorToolContext 파생 클래스
핵심 커스텀 툴 컨텍스트 등록
자주 쓰는 상황 특정 타입/모드에서만 활성화되는 도구 환경 구성

11-5. [EditorToolbarElement]

항목 내용
네임스페이스 UnityEditor.Toolbars
붙이는 위치 Toolbar UI 요소 클래스
핵심 Editor Toolbar/Overlay에서 사용할 VisualElement 등록
using UnityEditor.Toolbars;
using UnityEngine.UIElements;

[EditorToolbarElement("MyTools/ExampleButton")]
public class ExampleToolbarButton : EditorToolbarButton
{
}

12. UnityEditor: Settings, Search, Asset Import, Build 확장

12-1. [SettingsProvider]

항목 내용
네임스페이스 UnityEditor
붙이는 위치 static 메서드
핵심 Project Settings 또는 Preferences에 커스텀 설정 페이지 등록
메서드 반환 SettingsProvider
using UnityEditor;

public static class MySettingsProvider
{
    [SettingsProvider]
    public static SettingsProvider CreateProvider()
    {
        return new SettingsProvider("Project/My Game Settings", SettingsScope.Project);
    }
}

12-2. [SettingsProviderGroup]

항목 내용
네임스페이스 UnityEditor
붙이는 위치 static 메서드
핵심 여러 SettingsProvider를 한 번에 등록

12-3. [ScriptedImporter]

항목 내용
네임스페이스 UnityEditor.AssetImporters
붙이는 위치 ScriptedImporter 파생 클래스
핵심 커스텀 파일 확장자를 Unity 에셋으로 임포트
자주 쓰는 상황 .csv, .bytes, 자체 데이터 포맷, 레벨 데이터 임포터
using UnityEditor.AssetImporters;
using UnityEngine;

[ScriptedImporter(1, "mydata")]
public class MyDataImporter : ScriptedImporter
{
    public override void OnImportAsset(AssetImportContext ctx)
    {
        var asset = ScriptableObject.CreateInstance<MyImportedData>();
        ctx.AddObjectToAsset("main", asset);
        ctx.SetMainObject(asset);
    }
}

12-4. [AssetPostprocessorStaticVariableIgnore]

항목 내용
네임스페이스 UnityEditor
붙이는 위치 AssetPostprocessor, ScriptedImporter 내부 static 변수
핵심 Import Activity Window의 static variable warning에서 특정 변수를 제외
주의 static 변수는 Asset Import Worker 도메인 차이 때문에 예상과 다르게 동작할 수 있음

12-5. Search 관련 어트리뷰트

Unity Search/Quick Search 확장에 사용하는 어트리뷰트입니다.

어트리뷰트 설명
[SearchItemProvider] 새 검색 provider 등록
[SearchActionsProvider] 검색 결과에 사용할 액션 등록
[SearchColumnProvider] 검색 테이블 컬럼 포맷 등록
[SearchExpressionEvaluator] Search Expression 함수 등록
[SearchExpressionEvaluatorSignatureOverload] Search Expression 시그니처 오버로드
[SearchSelector] Search item에서 값을 선택하는 방식 등록
[CustomObjectIndexer] 특정 타입에 대한 인덱싱 함수 등록

일반 게임플레이 코드보다 에디터 도구나 대규모 에셋 검색 도구에서 사용합니다.


12-6. Build/Profile/Profiler 관련 어트리뷰트

어트리뷰트 설명
[BuildCallbackVersion] 빌드 콜백 버전 정보 제공
[BuildProfileSettingsProvider] Build Profile 설정 섹션 등록
[ProfilerModuleMetadata] ProfilerModule의 이름, 아이콘 등 메타데이터 제공
[DiagnosticParameter] Project Auditor/Analyzer 계열 진단 파라미터 표시
[AdaptivePerformanceSupportedBuildTarget] Adaptive Performance provider가 지원하는 빌드 타겟 표시

12-7. Shader keyword filtering 관련 어트리뷰트

Unity 6 계열 API 목록에서 확인되는 Shader keyword filtering 관련 어트리뷰트입니다. Shader variant 빌드 포함/제외 규칙을 코드로 제어하는 데 사용됩니다.

어트리뷰트 설명
[Filter] shader keyword 포함/제거 규칙의 기반
[SelectIf] 조건이 맞으면 지정 keyword 포함
[SelectIfNot] 조건이 맞지 않으면 지정 keyword 포함
[SelectOrRemove] 조건 결과에 따라 keyword 포함 또는 제거
[GraphicsAPIConstraint] 그래픽스 API 조건 기반
[ApplyRulesIfGraphicsAPI] 특정 Graphics API일 때 규칙 적용
[ApplyRulesIfNotGraphicsAPI] 특정 Graphics API가 아닐 때 규칙 적용
[ApplyRulesIfTagsEqual] Shader tag가 같을 때 규칙 적용
[ApplyRulesIfTagsNotEqual] Shader tag가 다를 때 규칙 적용

이 영역은 일반적인 MonoBehaviour 개발보다 렌더링/빌드 최적화/셰이더 variant 관리 쪽에 가깝습니다.


13. UnityEditor 전체 목록에서 볼 수 있는 주요 Attribute 계열

UnityEditor API에는 일반 개발자가 자주 쓰는 것부터 내부 도구용에 가까운 것까지 매우 많습니다. 아래는 Unity 6.x API 목록에서 Attribute로 확인되는 것들을 공부용으로 묶은 표입니다.

어트리뷰트 대략적 용도
[AdvancedObjectSelector] 커스텀 advanced object selector 등록
[AdvancedObjectSelectorValidator] advanced object selector validator 등록
[AdaptivePerformanceSupportedBuildTarget] Adaptive Performance 지원 빌드 타겟 표시
[ApplyRulesIfGraphicsAPI] 그래픽스 API 조건에 따라 shader keyword 규칙 적용
[ApplyRulesIfNotGraphicsAPI] 특정 그래픽스 API가 아닐 때 shader keyword 규칙 적용
[ApplyRulesIfTagsEqual] shader tag 조건이 같을 때 규칙 적용
[ApplyRulesIfTagsNotEqual] shader tag 조건이 다를 때 규칙 적용
[AssetPostprocessorStaticVariableIgnore] AssetPostprocessor/ScriptedImporter static variable warning 제외
[BuildCallbackVersion] 빌드 콜백 버전 정보
[BuildProfileSettingsProvider] Build Profile 설정 Provider 등록
[CallbackOrder] 콜백 순서가 필요한 어트리뷰트의 기반
[CanEditMultipleObjects] 커스텀 에디터 다중 선택 편집 지원
[ClutchShortcut] 누르고 있는 동안 동작하는 단축키 등록
[CollectImportedDependencies] AssetDatabase import dependency 선언
[CustomEditor] 특정 타입의 Inspector 커스터마이즈
[CustomObjectIndexer] Unity Search 인덱싱 확장
[CustomPivot] 커스텀 pivot 모드/회전 등록
[CustomPreview] Inspector Preview 커스터마이즈
[CustomPropertyDrawer] Serializable 타입 또는 PropertyAttribute 표시 방식 커스터마이즈
[DeeplinkHandler] Unity Editor deeplink handler 등록
[DiagnosticParameter] Analyzer 진단 파라미터 표시
[DidReloadScripts] 스크립트 리로드 이후 콜백
[DiffuseProfileCallback] SRP 관련 importer callback
[DrawGizmo] 컴포넌트별 Gizmo 렌더링 함수 등록
[EditorTool] Scene View 커스텀 도구 등록
[EditorToolContext] 커스텀 Tool Context 등록
[EditorToolbarElement] Toolbar/Overlay UI 요소 등록
[EditorWindowTitle] EditorWindow 제목/아이콘 지정
[FilePath] ScriptableSingleton 등의 저장 위치 지정
[Filter] shader keyword filtering 기반
[Graph] graph type 선언
[GraphicsAPIConstraint] graphics API 조건 지정
[InitializeOnEnterPlayMode] Play Mode 진입 시 실행
[InitializeOnLoad] 에디터 로드/스크립트 컴파일 후 클래스 초기화
[InitializeOnLoadMethod] 에디터 로드/스크립트 컴파일 후 메서드 실행
[LightingExplorerTab] Lighting Explorer 커스텀 탭
[LightingExplorerTableColumn] Lighting Explorer 컬럼
[MenuItem] 에디터 메뉴 항목 등록
[OnOpenAsset] 에셋 열기 시도 시 콜백
[PostProcessBuild] 빌드 후처리 콜백
[PostProcessScene] 씬 빌드 후처리 콜백
[ProfilerModuleMetadata] Profiler module 메타데이터
[ScriptedImporter] 커스텀 에셋 임포터 등록
[SearchActionsProvider] Search action provider 등록
[SearchColumnProvider] Search column provider 등록
[SearchExpressionEvaluator] Search expression evaluator 등록
[SearchExpressionEvaluatorSignatureOverload] Search expression evaluator 오버로드
[SearchItemProvider] Search item provider 등록
[SearchSelector] Search selector 등록
[SelectIf] 조건 기반 shader keyword 포함
[SelectIfNot] 조건 부정 기반 shader keyword 포함
[SelectOrRemove] shader keyword 포함/제거 조건
[SettingsProvider] Project Settings/Preferences 설정 페이지 등록
[SettingsProviderGroup] 여러 SettingsProvider 등록
[Shortcut] 단축키 등록
[UxmlNamespacePrefix] UXML namespace prefix 지정
[VersionControl] 버전 관리 시스템 객체 표시

14. UI Toolkit 관련 어트리뷰트

Unity 2023.2 이후/Unity 6 계열에서는 커스텀 UI Toolkit 요소를 UXML과 UI Builder에 노출하기 위한 어트리뷰트가 추가되어 있습니다.

14-1. [UxmlElement]

항목 내용
네임스페이스 UnityEngine.UIElements
붙이는 위치 VisualElement 파생 partial class
핵심 커스텀 VisualElement를 UXML/UI Builder에 노출
조건 partial 클래스여야 함
using UnityEngine.UIElements;

[UxmlElement]
public partial class HealthBarElement : VisualElement
{
}

14-2. [UxmlAttribute]

항목 내용
네임스페이스 UnityEngine.UIElements
붙이는 위치 필드 또는 프로퍼티
핵심 UXML에서 설정 가능한 속성으로 노출
[UxmlElement]
public partial class HealthBarElement : VisualElement
{
    [UxmlAttribute]
    public int maxHp { get; set; }

    [UxmlAttribute]
    public string label { get; set; }
}

UXML 예시:

<ui:UXML xmlns:ui="UnityEngine.UIElements">
    <HealthBarElement max-hp="100" label="Player HP" />
</ui:UXML>

14-3. 기타 UXML 관련

어트리뷰트 설명
[UxmlObject] UXML에서 복합 객체 타입 선언
[UxmlObjectReference] UXML object 참조 필드/프로퍼티 지정
[UxmlTypeReference] UXML에서 특정 타입 참조 제한/지정
[UxmlCreateInstanceMethod] UxmlSerializedData의 기본 생성 방식을 대체할 메서드 지정
[UxmlNamespacePrefix] 어셈블리 단위로 UXML 네임스페이스 prefix 지정

15. Unity Test Framework / NUnit 어트리뷰트

Unity Test Framework는 NUnit 기반입니다. EditMode Test와 PlayMode Test에서 자주 씁니다.

15-1. 가장 자주 쓰는 테스트 어트리뷰트

어트리뷰트 설명 예시
[Test] 일반 동기 테스트 메서드가 void
[UnityTest] 코루틴 테스트 IEnumerator 반환
[SetUp] 각 테스트 전 실행 테스트별 초기화
[TearDown] 각 테스트 후 실행 테스트별 정리
[UnitySetUp] yield 가능한 테스트 전 초기화 IEnumerator
[UnityTearDown] yield 가능한 테스트 후 정리 IEnumerator
[OneTimeSetUp] fixture 전체에서 한 번 실행 전체 초기화
[OneTimeTearDown] fixture 전체 종료 후 한 번 실행 전체 정리
[TestFixture] 테스트 클래스 표시/파라미터화 NUnit
[Category] 테스트 카테고리 지정 [Category("Combat")]
[Ignore] 테스트 제외 사유 작성 권장
[Explicit] 명시 실행 시에만 실행 오래 걸리는 테스트
[Timeout] 제한 시간 지정 ms 단위
[Retry] 실패 시 재시도 PlayMode에서는 제한 주의
[Repeat] 반복 실행 UnityTest와 조합 제한
[TestCase] 파라미터 테스트 [TestCase(1,2,3)]
[Values] 파라미터 값 목록 [Values(1,2,3)]
[ValueSource] 파라미터 소스 지정 UnityTest 일부 지원
[Combinatorial] 파라미터 조합 테스트 NUnit
[Sequential] 파라미터 순차 조합 NUnit

15-2. [Test] 예시

using NUnit.Framework;

public class DamageCalculatorTests
{
    [Test]
    public void Damage_Cannot_Be_Negative()
    {
        int damage = DamageCalculator.Calculate(-10, 5);
        Assert.GreaterOrEqual(damage, 0);
    }
}

15-3. [UnityTest] 예시

using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;

public class SpawnTests
{
    [UnityTest]
    public IEnumerator Enemy_Spawns_After_One_Frame()
    {
        var go = new GameObject("Spawner");
        go.AddComponent<EnemySpawner>();

        yield return null;

        Assert.IsNotNull(GameObject.FindWithTag("Enemy"));
    }
}

주의점:


16. C# / .NET 기본 어트리뷰트

Unity 스크립트에서도 C# 기본 어트리뷰트를 많이 사용합니다.

16-1. 자주 쓰는 C# 기본 어트리뷰트

어트리뷰트 네임스페이스 붙이는 위치 용도
[Obsolete] System 클래스/메서드/필드 등 더 이상 쓰지 말아야 하는 API 표시
[Flags] System enum 비트 플래그 enum 표시
[Serializable] System class/struct 직렬화 가능 타입 표시
[NonSerialized] System field 직렬화 제외
[AttributeUsage] System Attribute class 커스텀 어트리뷰트 사용 위치 제한
[Conditional] System.Diagnostics method/attribute class 특정 심볼이 있을 때만 호출 포함
[DebuggerDisplay] System.Diagnostics class/struct 디버거 표시 문자열 지정
[DebuggerStepThrough] System.Diagnostics class/method 디버거 step 진입 생략
[DllImport] System.Runtime.InteropServices static extern method 네이티브 DLL 함수 호출
[StructLayout] System.Runtime.InteropServices class/struct 메모리 배치 제어
[FieldOffset] System.Runtime.InteropServices field 명시적 필드 오프셋 지정
[MarshalAs] System.Runtime.InteropServices field/parameter/return interop marshaling 방식 지정
[MethodImpl] System.Runtime.CompilerServices method/constructor inline, synchronization 등 구현 옵션 지정
[CallerMemberName] System.Runtime.CompilerServices optional parameter 호출한 멤버 이름 자동 전달
[CallerFilePath] System.Runtime.CompilerServices optional parameter 호출한 소스 파일 경로 자동 전달
[CallerLineNumber] System.Runtime.CompilerServices optional parameter 호출한 라인 번호 자동 전달
[CallerArgumentExpression] System.Runtime.CompilerServices optional parameter 전달된 인자 표현식 문자열 자동 전달
[InternalsVisibleTo] System.Runtime.CompilerServices assembly internal 멤버를 특정 어셈블리에 공개
[AssemblyVersion] System.Reflection assembly 어셈블리 버전 지정
[AssemblyTitle] System.Reflection assembly 어셈블리 제목 지정

16-2. [Obsolete]

[Obsolete("Use NewAttack() instead.")]
public void OldAttack()
{
}

컴파일 에러로 만들 수도 있습니다.

[Obsolete("Use NewAttack() instead.", true)]
public void OldAttack()
{
}

16-3. [Flags]

[System.Flags]
public enum DamageType
{
    None = 0,
    Fire = 1 << 0,
    Ice = 1 << 1,
    Poison = 1 << 2
}

DamageType type = DamageType.Fire | DamageType.Poison;

주의점:


16-4. [Conditional]

using System.Diagnostics;

public static class DebugLogHelper
{
    [Conditional("UNITY_EDITOR")]
    public static void EditorOnlyLog(string message)
    {
        UnityEngine.Debug.Log(message);
    }
}

UNITY_EDITOR 심볼이 없으면 호출 코드가 컴파일 결과에서 빠집니다.


16-5. [DllImport]

using System.Runtime.InteropServices;

public static class NativePlugin
{
    [DllImport("MyNativePlugin")]
    private static extern int Add(int a, int b);
}

주의점:


16-6. Caller Info 어트리뷰트

로그 유틸리티에서 자주 쓸 수 있습니다.

using System.Runtime.CompilerServices;
using UnityEngine;

public static class LogUtil
{
    public static void Log(
        string message,
        [CallerMemberName] string memberName = "",
        [CallerFilePath] string filePath = "",
        [CallerLineNumber] int lineNumber = 0)
    {
        Debug.Log($"[{memberName}:{lineNumber}] {message}");
    }
}

16-7. [AttributeUsage]로 커스텀 어트리뷰트 만들기

using System;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class DevNoteAttribute : Attribute
{
    public string Note { get; }

    public DevNoteAttribute(string note)
    {
        Note = note;
    }
}

사용:

[DevNote("전투 테스트용 임시 클래스")]
public class TempCombatTester
{
}

17. Unity에서 특히 헷갈리는 조합

17-1. [SerializeField] vs public

public int hp;
[SerializeField]
private int hp;

17-2. [System.Serializable] vs [SerializeField]

[System.Serializable]
public class ItemData
{
    public string name;
}
[SerializeField]
private ItemData itemData;

둘은 역할이 다릅니다. 커스텀 class를 private 필드로 Inspector에 보이게 하려면 보통 둘 다 필요합니다.


17-3. [SerializeReference] vs ScriptableObject

[SerializeReference]가 적합한 경우:

ScriptableObject가 적합한 경우:


17-4. [ContextMenu] vs [MenuItem]

항목 [ContextMenu] [MenuItem]
네임스페이스 UnityEngine UnityEditor
붙이는 대상 인스턴스 메서드 static 메서드
표시 위치 컴포넌트 Inspector 우클릭 메뉴 상단 메뉴, Assets, GameObject 등
빌드 포함 코드 자체는 런타임 스크립트에 있을 수 있음 Editor 전용
용도 특정 컴포넌트의 편의 실행 프로젝트/에디터 전체 도구

17-5. [ExecuteAlways] 사용 시 주의

[ExecuteAlways]
public class AutoAligner : MonoBehaviour
{
    private void Update()
    {
        if (Application.isPlaying)
        {
            return;
        }

        // Edit Mode에서만 실행할 코드
    }
}

주의할 점:


18. 커스텀 어트리뷰트 작성 패턴

18-1. 단순 마커 어트리뷰트

using System;

[AttributeUsage(AttributeTargets.Class)]
public class AutoRegisterAttribute : Attribute
{
}

사용:

[AutoRegister]
public class FireSkill
{
}

리플렉션으로 찾기:

var types = AppDomain.CurrentDomain.GetAssemblies()
    .SelectMany(a => a.GetTypes())
    .Where(t => Attribute.IsDefined(t, typeof(AutoRegisterAttribute)));

18-2. Unity Inspector용 커스텀 PropertyAttribute

런타임 어셈블리:

using UnityEngine;

public class RequiredFieldAttribute : PropertyAttribute
{
}

Editor 어셈블리:

#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(RequiredFieldAttribute))]
public class RequiredFieldDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        EditorGUI.PropertyField(position, property, label);

        if (property.propertyType == SerializedPropertyType.ObjectReference &&
            property.objectReferenceValue == null)
        {
            var warningRect = new Rect(position.x, position.y + EditorGUIUtility.singleLineHeight, position.width, EditorGUIUtility.singleLineHeight);
            EditorGUI.HelpBox(warningRect, "필수 참조가 비어 있습니다.", MessageType.Warning);
        }
    }

    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        bool missing = property.propertyType == SerializedPropertyType.ObjectReference &&
                       property.objectReferenceValue == null;

        return missing
            ? EditorGUIUtility.singleLineHeight * 2.2f
            : EditorGUIUtility.singleLineHeight;
    }
}
#endif

사용:

[RequiredField]
[SerializeField]
private Transform target;

19. 실무에서 먼저 외우면 좋은 순서

1순위: 거의 매일 보거나 쓰는 것

2순위: 프로젝트가 커지면 중요해지는 것

3순위: 도구/렌더링/고급 에디터 확장


20. 간단한 암기법

하고 싶은 일 떠올릴 어트리뷰트
private 값을 Inspector에 보이고 싶다 [SerializeField]
커스텀 class를 Inspector에 펼쳐 보이고 싶다 [System.Serializable]
필드명을 바꿨는데 기존 프리팹 값을 살리고 싶다 [FormerlySerializedAs]
추상 클래스/인터페이스 기반 데이터를 저장하고 싶다 [SerializeReference]
컴포넌트가 Rigidbody를 반드시 필요로 한다 [RequireComponent]
같은 컴포넌트 중복 추가를 막고 싶다 [DisallowMultipleComponent]
ScriptableObject 생성 메뉴를 만들고 싶다 [CreateAssetMenu]
Unity 상단 메뉴에 도구를 만들고 싶다 [MenuItem]
컴포넌트 우클릭 메뉴에 기능을 넣고 싶다 [ContextMenu]
Inspector를 직접 만들고 싶다 [CustomEditor]
특정 필드 표시 방식을 바꾸고 싶다 [CustomPropertyDrawer]
에디터 로드 시 자동 실행하고 싶다 [InitializeOnLoad], [InitializeOnLoadMethod]
런타임 시작 시 자동 실행하고 싶다 [RuntimeInitializeOnLoadMethod]
IL2CPP 빌드에서 리플렉션 대상이 삭제되지 않게 하고 싶다 [Preserve]
Unity Test Framework에서 코루틴 테스트를 만들고 싶다 [UnityTest]

21. 자주 하는 실수

21-1. 프로퍼티에 [SerializeField]를 붙이려고 함

[SerializeField]
public int Hp { get; private set; } // 일반적으로 원하는 대로 직렬화되지 않음

보통은 backing field를 사용합니다.

[SerializeField]
private int hp;

public int Hp => hp;

최신 C#의 field target 문법을 쓰는 경우도 있지만, Unity 버전과 팀 컨벤션에 따라 혼란이 생길 수 있어 초반에는 backing field가 안전합니다.


21-2. Editor 코드가 Runtime Assembly에 들어감

using UnityEditor; // 런타임 빌드에서 문제

해결:


21-3. [ExecuteAlways]에서 Play Mode와 Edit Mode를 구분하지 않음

Edit Mode에서 오브젝트 생성/삭제/값 변경이 일어나면 씬이 의도치 않게 바뀔 수 있습니다.


21-4. [FormerlySerializedAs] 없이 필드명을 바꿈

프리팹, 씬, ScriptableObject에 저장된 값이 사라진 것처럼 보일 수 있습니다.


21-5. [SerializeReference]를 모든 곳에 쓰려 함

다형성이 필요 없다면 기본 직렬화나 ScriptableObject가 더 단순하고 안정적일 수 있습니다.


21-6. [ThreadStatic] 사용 주의

Unity 공식 문서에는 Unity script에 .NET [ThreadStatic]을 사용하지 말라는 주의가 있습니다. 스레드별 정적 상태가 필요한 경우 Unity 버전과 사용 맥락을 확인하고 대체 설계를 고려하는 편이 안전합니다.


22. 대표 예제: 에디터 메뉴로 프리팹 자동 세팅

질문에 나온 [MenuItem("Combat/Setup Animated Agent Prefabs")] 형태의 예시입니다.

using UnityEditor;
using UnityEngine;

public static class CombatPrefabSetupTool
{
    [MenuItem("Combat/Setup Animated Agent Prefabs")]
    private static void SetupAnimatedAgentPrefabs()
    {
        foreach (GameObject obj in Selection.gameObjects)
        {
            var animator = obj.GetComponent<Animator>();
            if (animator == null)
            {
                animator = obj.AddComponent<Animator>();
            }

            var agent = obj.GetComponent<UnityEngine.AI.NavMeshAgent>();
            if (agent == null)
            {
                agent = obj.AddComponent<UnityEngine.AI.NavMeshAgent>();
            }

            EditorUtility.SetDirty(obj);
        }

        Debug.Log("Selected prefabs setup complete.");
    }

    [MenuItem("Combat/Setup Animated Agent Prefabs", true)]
    private static bool ValidateSetupAnimatedAgentPrefabs()
    {
        return Selection.gameObjects != null && Selection.gameObjects.Length > 0;
    }
}

포인트:


23. 대표 예제: 데이터 클래스 + ScriptableObject + Inspector 정리

using UnityEngine;
using UnityEngine.Serialization;

[System.Serializable]
public class ItemStat
{
    [Tooltip("공격력 보정값")]
    [Min(0)]
    public int attackBonus;

    [Tooltip("방어력 보정값")]
    [Min(0)]
    public int defenseBonus;
}

[CreateAssetMenu(fileName = "New Item", menuName = "Game Data/Item")]
public class ItemData : ScriptableObject
{
    [Header("Basic")]
    [SerializeField]
    private string itemName;

    [SerializeField]
    private Sprite icon;

    [Header("Stats")]
    [SerializeField]
    private ItemStat stat;

    [FormerlySerializedAs("desc")]
    [TextArea(3, 6)]
    [SerializeField]
    private string description;
}

이 예시에서 사용한 것:


24. 대표 예제: 커스텀 어트리뷰트와 PropertyDrawer

런타임 코드

using UnityEngine;

public class SceneNameAttribute : PropertyAttribute
{
}

에디터 코드

#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(SceneNameAttribute))]
public class SceneNameDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        EditorGUI.PropertyField(position, property, label);
    }
}
#endif

사용

public class SceneLoaderConfig : MonoBehaviour
{
    [SceneName]
    [SerializeField]
    private string sceneName;
}

처음에는 단순히 표시만 해도 되고, 나중에 Build Settings의 씬 목록을 드롭다운으로 보여주도록 확장할 수 있습니다.


25. 외부 패키지에서 자주 보이는 Attribute

Unity 기본은 아니지만, 실무나 예제에서 자주 보이는 패키지성 어트리뷰트도 있습니다.

25-1. Odin Inspector

예시:

[Button]
private void ResetData() {}

[ReadOnly]
[SerializeField]
private int id;

[ShowIf(nameof(useAdvanced))]
public float advancedValue;

특징:

25-2. NaughtyAttributes

예시:

[Button]
private void Generate() {}

[Required]
public Transform target;

[ShowIf("isDebug")]
public int debugValue;

특징:

25-3. Netcode for GameObjects

예시:

[ServerRpc]
private void FireServerRpc() {}

[ClientRpc]
private void HitClientRpc() {}

특징:


26. 참고 링크

공식 문서를 중심으로 확인한 링크입니다.


27. 공부 추천 루트

  1. 먼저 SerializeField, System.Serializable, Header, Tooltip, Range, RequireComponent, CreateAssetMenu를 직접 써보기
  2. 작은 ScriptableObject 데이터 에셋 만들기
  3. [MenuItem]으로 상단 메뉴에 자동화 함수 하나 만들기
  4. [ContextMenu]로 컴포넌트 우클릭 실행 함수 만들기
  5. [CustomEditor]로 버튼 있는 Inspector 만들기
  6. [CustomPropertyDrawer]로 직접 만든 필드 어트리뷰트 표시 바꿔보기
  7. 필드명 리팩토링 시 [FormerlySerializedAs] 붙이는 습관 만들기
  8. 다형성 데이터가 필요할 때만 [SerializeReference] 공부하기
  9. 빌드/에디터 자동화가 필요해지면 [PostProcessBuild], [InitializeOnLoad], [ScriptedImporter] 보기
  10. UI Toolkit 에디터 도구를 만들 때 [UxmlElement], [UxmlAttribute]로 넘어가기