Unity의 Attribute는 스크립트의 동작이나 Inspector의 표시 방식을 제어하기 위해 제공되는 메타데이터이다. 이들은 클래스, 필드, 메서드에 부여되어 Unity 에디터와 런타임에서 특정 동작을 수행하거나 사용자 경험을 개선하는 데 사용된다. 이를 통해 스크립트의 실행 순서를 조정하거나, Inspector에서 필드를 숨기거나 표시하고, 데이터 직렬화를 제어하거나, 개발 도구를 확장할 수 있다. 아래는 주요 Unity Attribute를 코드 예제와 함께 정리한 것이다.
[ExecuteAlways]
[ExecuteAlways]는 스크립트를 Play 모드와 에디터 모드 모두에서 실행되도록 설정한다. 이를 통해 Unity 에디터에서 작업 자동화, 실시간 업데이트, 또는 상태 동기화 기능을 구현할 수 있다.
using UnityEngine;
[ExecuteAlways]
public class ExampleExecuteAlways : MonoBehaviour
{
void Update()
{
Debug.Log("This runs in both Edit and Play mode!");
}
}
1. 오브젝트 자동 정렬
[ExecuteAlways]를 사용하면 에디터 모드에서 GameObject를 자동으로 정렬할 수 있다. 예를 들어, 자식 오브젝트를 특정 간격으로 배치하거나 정렬 상태를 유지하는 데 사용할 수 있다.
using UnityEngine;
[ExecuteAlways]
public class AutoAlignChildren : MonoBehaviour
{
[SerializeField] private float spacing = 1.0f;
void Update()
{
// 모든 자식 오브젝트를 일정한 간격으로 정렬
for (int i = 0; i < transform.childCount; i++)
{
Transform child = transform.GetChild(i);
child.localPosition = new Vector3(0, i * spacing, 0);
}
}
}
이 스크립트를 빈 GameObject에 추가하면, 자식 오브젝트들이 에디터 모드에서도 자동으로 정렬된다.
2. 씬에서 실시간 데이터 업데이트
UI 텍스트나 특정 값을 에디터 모드에서도 실시간으로 갱신하여 확인해야 할 때 사용할 수 있다.
using UnityEngine;
using UnityEngine.UI;
[ExecuteAlways]
public class DisplayGameObjectCount : MonoBehaviour
{
[SerializeField] private Text objectCountText;
void Update()
{
if (objectCountText != null)
{
// 씬에 존재하는 GameObject의 수를 표시
objectCountText.text = $"GameObjects in Scene: {FindObjectsOfType<GameObject>().Length}";
}
}
}
씬에 존재하는 GameObject의 수를 실시간으로 텍스트 UI에 표시하여, 에디터 작업 중에도 씬의 상태를 확인할 수 있다.
3. 컬러 변화를 실시간으로 미리보기
[ExecuteAlways]를 사용하여 Material의 색상 변경 효과를 에디터에서 바로 확인할 수 있다.
using UnityEngine;
[ExecuteAlways]
public class ColorCycler : MonoBehaviour
{
[SerializeField] private Renderer targetRenderer;
[SerializeField] private Color baseColor = Color.white;
[SerializeField] private float cycleSpeed = 1.0f;
void Update()
{
if (targetRenderer != null && targetRenderer.material != null)
{
float t = Mathf.PingPong(Time.time * cycleSpeed, 1.0f);
targetRenderer.material.color = Color.Lerp(baseColor, Color.red, t);
}
}
}
: 이 스크립트를 적용하면 에디터에서도 Material의 색상이 시간에 따라 변화하는 모습을 실시간으로 확인할 수 있다.
4. 간단한 에디터 Gizmo 표시
씬 뷰에서 특정 오브젝트의 상태를 시각적으로 표시하기 위해 OnDrawGizmos를 사용할 수 있다. [ExecuteAlways]와 함께 사용하면 오브젝트의 상태가 에디터 모드에서도 실시간으로 업데이트된다.
using UnityEngine;
[ExecuteAlways]
public class DrawGizmoForRadius : MonoBehaviour
{
[SerializeField] private float radius = 5.0f;
void OnDrawGizmos()
{
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(transform.position, radius);
}
}
에디터에서 특정 오브젝트의 영향을 미치는 영역(예: 스킬 범위나 충돌 반경)을 확인할 수 있다.
5. 프로퍼티 자동 보정
게임 개발 중 특정 값이 일정 범위를 벗어나지 않도록 자동으로 보정할 수 있다.
using UnityEngine;
[ExecuteAlways]
public class PropertyClamper : MonoBehaviour
{
[SerializeField] private Vector3 scaleLimits = new Vector3(10, 10, 10);
void Update()
{
// 스케일 값을 제한
transform.localScale = Vector3.Min(transform.localScale, scaleLimits);
}
}
에디터에서 GameObject의 크기를 조정할 때 자동으로 제한 범위 내로 조정되어 값이 과도하게 설정되는 것을 방지할 수 있다.
[HideInInspector]
[HideInInspector]는 필드를 Inspector에서 숨기지만, Unity의 직렬화 시스템에는 그대로 포함된다. 불필요한 정보를 숨기고 코드의 가독성을 높이는 데 유용하다.
using UnityEngine;
public class ExampleHideInInspector : MonoBehaviour
{
[HideInInspector]
public int hiddenValue = 42;
}
HideInInspector는 디자이너와의 협업에서 자주 사용된다.
using UnityEngine;
public class DebugExample : MonoBehaviour
{
public float speed = 10f; // 디자이너가 설정
[HideInInspector]
public Vector3 debugPosition; // 디버깅 용도로 사용
private void Update()
{
debugPosition = transform.position + Vector3.forward * speed * Time.deltaTime;
}
}
예를 들어, 디자이너는 speed만 설정할 수 있으며, debugPosition은 Inspector에 표시되지 않지만 코드에서 디버깅에 활용된다
[SerializeField]
[SerializeField]는 private 또는 protected 필드를 Inspector에 노출하고 싶을 때 사용한다. 이를 통해 캡슐화를 유지하면서도 Inspector에서 값 조정을 할 수 있다.
using UnityEngine;
public class ExampleSerializeField : MonoBehaviour
{
[SerializeField]
private int serializedValue = 100;
}
[RequireComponent]
[RequireComponent]는 스크립트가 특정 컴포넌트에 의존할 때, 해당 컴포넌트를 자동으로 추가하도록 강제한다. 이는 컴포넌트 간의 의존성을 보장하고, 런타임 오류를 방지한다.
using UnityEngine;
[RequireComponent(typeof(Rigidbody))]
public class ExampleRequireComponent : MonoBehaviour
{
}
1. 이동 스크립트에서 Rigidbody 강제
using UnityEngine;
[RequireComponent(typeof(Rigidbody))]
public class PlayerMovement : MonoBehaviour
{
private Rigidbody rb;
public float speed = 5f;
private void Start()
{
rb = GetComponent<Rigidbody>();
}
private void Update()
{
float moveX = Input.GetAxis("Horizontal") * speed;
float moveZ = Input.GetAxis("Vertical") * speed;
rb.velocity = new Vector3(moveX, rb.velocity.y, moveZ);
}
}
2. 오디오 관리에서 AudioSource 강제
using UnityEngine;
[RequireComponent(typeof(AudioSource))]
public class SoundManager : MonoBehaviour
{
private AudioSource audioSource;
private void Start()
{
audioSource = GetComponent<AudioSource>();
audioSource.playOnAwake = false;
}
public void PlaySound(AudioClip clip)
{
audioSource.clip = clip;
audioSource.Play();
}
}
[AddComponentMenu]
[AddComponentMenu]는 스크립트를 Add Component 메뉴에 특정 경로로 등록하여 사용자 정의 스크립트를 더 쉽게 관리할 수 있도록 한다. 프로젝트가 커질수록 사용자 정의 스크립트가 많아지는데, 이를 체계적으로 관리할 수 있다.
using UnityEngine;
[AddComponentMenu("Custom/Example Script")]
public class ExampleAddComponentMenu : MonoBehaviour
{
}
카테고리 계층화
using UnityEngine;
[AddComponentMenu("Game Mechanics/Movement/Player Controller")]
public class PlayerController : MonoBehaviour
{
public float moveSpeed = 5f;
private void Update()
{
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
Vector3 movement = new Vector3(horizontal, 0, vertical) * moveSpeed * Time.deltaTime;
transform.Translate(movement);
}
}
PlayerController 스크립트는 "Add Component" 메뉴의 Game Mechanics > Movement > Player Controller 경로에 등록된다. 여러 카테고리를 생성하여 논리적으로 스크립트를 정리할 수 있다.
동일한 이름의 스크립트 분리
using UnityEngine;
[AddComponentMenu("Physics/Basic Rigidbody Controller")]
public class BasicRigidbodyController : MonoBehaviour
{
public Rigidbody rb;
private void Start()
{
rb = GetComponent<Rigidbody>();
}
}
같은 이름의 스크립트를 서로 다른 카테고리에 분류하여 충돌을 방지한다. Add Component 메뉴의 Physics 카테고리에서 Basic Rigidbody Controller를 선택할 수 있다.
숨겨진 스크립트 만들기
using UnityEngine;
[AddComponentMenu("")]
public class HiddenScript : MonoBehaviour
{
public int hiddenValue = 42;
}
HiddenScript는 "Add Component" 메뉴에 표시되지 않는다. 디자이너가 사용할 필요가 없거나, 내부적으로만 사용되는 스크립트를 숨길 때 유용하다.
[ContextMenu]
[ContextMenu]는 Inspector에서 특정 메서드를 호출할 수 있는 메뉴 항목을 추가한다. 디버깅이나 반복 작업을 자동화하는 데 유용하다.
using UnityEngine;
public class ExampleContextMenu : MonoBehaviour
{
[ContextMenu("Print Message")]
void PrintMessage()
{
Debug.Log("Context menu method called!");
}
}
[Tooltip]
[Tooltip]은 Inspector에서 필드에 대한 설명을 제공하는 툴팁을 추가한다. 이를 통해 필드의 의미나 사용 방법을 명확히 전달할 수 있다.
using UnityEngine;
public class ExampleTooltip : MonoBehaviour
{
[Tooltip("This is a tooltip for the health value.")]
public int health = 100;
}
[Range]
[Range]는 필드 값의 범위를 슬라이더 형태로 Inspector에 표시한다. 이를 통해 데이터 범위를 제한하고 직관적으로 값을 설정할 수 있다.
using UnityEngine;
public class ExampleRange : MonoBehaviour
{
[Range(0, 100)]
public int sliderValue = 50;
}
[Header]
[Header]는 필드 그룹 위에 제목을 추가하여 Inspector를 시각적으로 정리하고 가독성을 높인다.
using UnityEngine;
public class ExampleHeader : MonoBehaviour
{
[Header("Character Stats")]
public int health = 100;
public int mana = 50;
}
[Space]
[Space]는 Inspector에서 필드 사이에 여백을 추가하여 필드를 논리적으로 그룹화하거나 구분할 때 사용된다.
using UnityEngine;
public class ExampleSpace : MonoBehaviour
{
public int firstValue;
[Space]
public int secondValue;
}
[DisallowMultipleComponent]
[DisallowMultipleComponent]는 동일한 스크립트를 같은 GameObject에 여러 개 추가하는 것을 방지한다. 이를 통해 중복 추가로 인한 런타임 오류를 방지할 수 있다.
using UnityEngine;
[DisallowMultipleComponent]
public class ExampleDisallowMultipleComponent : MonoBehaviour
{
}
[DefaultExecutionOrder]
[DefaultExecutionOrder]는 스크립트의 실행 순서를 설정한다. 이를 통해 스크립트 간의 실행 우선순위를 명확히 정의할 수 있다.
using UnityEngine;
[DefaultExecutionOrder(-10)]
public class ExampleDefaultExecutionOrder : MonoBehaviour
{
void Start()
{
Debug.Log("This script runs earlier!");
}
}
1. 입력 처리와 카메라 제어
입력 처리 스크립트가 캐릭터 이동을 처리한 뒤, 카메라가 그 이동에 따라 위치를 업데이트해야 한다면, 실행 순서를 조정해야 한다.
[DefaultExecutionOrder(-50)]
public class InputHandler : MonoBehaviour
{
public Vector3 playerPosition;
void Update()
{
playerPosition += new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
Debug.Log($"Player moved to: {playerPosition}");
}
}
[DefaultExecutionOrder(50)]
public class CameraController : MonoBehaviour
{
public Transform player;
void LateUpdate()
{
transform.position = player.position + new Vector3(0, 10, -10);
Debug.Log("Camera updated");
}
}
InputHandler가 먼저 실행되어 플레이어의 위치를 업데이트한 뒤, CameraController가 해당 위치를 기반으로 카메라를 움직인다.
2. 복잡한 시스템 간의 상호작용
다수의 스크립트가 서로 종속적인 경우 [DefaultExecutionOrder]로 순서를 명시적으로 지정한다.
[DefaultExecutionOrder(-100)]
public class SystemA : MonoBehaviour
{
void Awake()
{
Debug.Log("System A initialized");
}
}
[DefaultExecutionOrder(-50)]
public class SystemB : MonoBehaviour
{
void Awake()
{
Debug.Log("System B initialized");
}
}
[DefaultExecutionOrder(0)]
public class SystemC : MonoBehaviour
{
void Awake()
{
Debug.Log("System C initialized");
}
}
SystemA -> SystemB -> SystemC 순서로 Awake() 호출.
[System.Serializable]
[System.Serializable]는 클래스나 구조체를 Unity의 직렬화 시스템에 등록하여 Inspector에서 편집 가능하게 한다. 이를 통해 데이터를 그룹화하거나 구조화된 방식으로 관리할 수 있다.
using UnityEngine;
[System.Serializable]
public class CustomData
{
public string name;
public int value;
}
public class ExampleSerializable : MonoBehaviour
{
public CustomData data;
}
[NonSerialized]
[NonSerialized]는 직렬화를 방지하여 Inspector에 필드가 나타나지 않도록 설정한다. 런타임에서만 사용하는 데이터나 직렬화가 필요 없는 데이터를 제외할 때 사용된다.
using UnityEngine;
public class ExampleNonSerialized : MonoBehaviour
{
[NonSerialized]
public int runtimeOnlyValue;
}
유니티에서 직렬화(Serialization)란 객체 데이터를 디스크에 저장하거나 네트워크를 통해 전송할 수 있도록, 혹은 유니티 에디터에서 사용자가 설정한 데이터를 유지하고 관리할 수 있도록 데이터 구조를 일련의 바이트로 변환하는 과정을 의미한다. 이 과정은 데이터를 저장하거나 로드할 때, 그리고 Unity Inspector에서 데이터를 편집하거나 볼 때 유용하다.
반대로 역직렬화(Deserialization)란 직렬화된 데이터를 원래의 객체 형태로 복원하는 과정을 의미한다. 즉, 저장되거나 전송된 데이터를 다시 객체로 변환하여 Unity 내에서 사용할 수 있게 만드는 것이다. 직렬화된 데이터를 파일, 네트워크, 또는 메모리에서 불러온 후 역직렬화를 통해 게임에서 활용 가능한 형태로 복원한다.
[CanEditMultipleObjects]
[CanEditMultipleObjects]는 커스텀 에디터에서 여러 GameObject의 속성을 동시에 편집할 수 있도록 설정한다. 이를 통해 작업 효율성을 크게 높일 수 있다.
using UnityEditor;
using UnityEngine;
[CanEditMultipleObjects]
[CustomEditor(typeof(ExampleCanEditMultipleObjects))]
public class ExampleEditor : Editor
{
public override void OnInspectorGUI()
{
DrawDefaultInspector();
}
}
public class ExampleCanEditMultipleObjects : MonoBehaviour
{
public int value;
}
1. 적 캐릭터 설정
여러 적 캐릭터의 속성을 동시에 변경하고 싶을 때.
using UnityEngine;
using UnityEditor;
public class Enemy : MonoBehaviour
{
public int health = 100;
public float speed = 5.0f;
}
[CustomEditor(typeof(Enemy))]
[CanEditMultipleObjects] // 여러 객체 편집 가능 설정
public class EnemyEditor : Editor
{
SerializedProperty healthProp;
SerializedProperty speedProp;
void OnEnable()
{
healthProp = serializedObject.FindProperty("health");
speedProp = serializedObject.FindProperty("speed");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(healthProp, new GUIContent("Health"));
EditorGUILayout.PropertyField(speedProp, new GUIContent("Speed"));
serializedObject.ApplyModifiedProperties();
}
}
여러 적 캐릭터를 선택한 후, Inspector에서 Health와 Speed를 동시에 변경 가능.
2. 환경 설정
씬 내 여러 오브젝트의 색상, 크기 등 공통 속성을 일괄 편집할 때.
using UnityEngine;
using UnityEditor;
public class EnvironmentObject : MonoBehaviour
{
public Color objectColor = Color.white;
public Vector3 objectScale = Vector3.one;
}
[CustomEditor(typeof(EnvironmentObject))]
[CanEditMultipleObjects]
public class EnvironmentObjectEditor : Editor
{
SerializedProperty colorProp;
SerializedProperty scaleProp;
void OnEnable()
{
colorProp = serializedObject.FindProperty("objectColor");
scaleProp = serializedObject.FindProperty("objectScale");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(colorProp, new GUIContent("Object Color"));
EditorGUILayout.PropertyField(scaleProp, new GUIContent("Object Scale"));
serializedObject.ApplyModifiedProperties();
}
}
여러 환경 오브젝트를 선택하고, Inspector에서 색상과 크기를 일괄적으로 조정 가능.
[SelectionBase]
[SelectionBase]는 씬 뷰에서 오브젝트를 선택할 때 기본 선택 대상으로 지정한다. 복잡한 계층 구조에서 부모 오브젝트를 쉽게 선택하도록 돕는다.
using UnityEngine;
[SelectionBase]
public class ExampleSelectionBase : MonoBehaviour
{
}
a. 캐릭터 루트 오브젝트 지정
캐릭터가 여러 자식 오브젝트(메쉬, 콜라이더, 부착된 무기 등)를 가지고 있는 경우, 부모 GameObject를 기본 선택 대상으로 설정한다.
using UnityEngine;
[SelectionBase]
public class Character : MonoBehaviour
{
// 캐릭터의 루트 오브젝트
public string characterName = "Hero";
}
계층 구조:
- Hero (부모)
- Body (자식)
- Sword (자식)
동작:
- 씬 뷰에서 Body나 Sword를 클릭하면 부모 오브젝트인 Hero가 선택된다.
b. UI 루트 오브젝트 지정
UI 계층 구조에서 특정 컨테이너(GameObject)를 기본 선택 대상으로 설정한다.
using UnityEngine;
[SelectionBase]
public class UIContainer : MonoBehaviour
{
public string containerName = "Main Panel";
}
계층 구조:
- Main Panel (부모)
- Header (자식)
- Button (자식)
동작:
- 씬 뷰에서 Header나 Button을 클릭하면 부모 오브젝트인 Main Panel이 선택된다.
c. 건축물과 같은 복잡한 오브젝트의 루트 설정
using UnityEngine;
[SelectionBase]
public class Building : MonoBehaviour
{
public string buildingName = "House";
}
씬 뷰에서 Roof, Walls, Door를 클릭해도 부모 오브젝트인 House가 선택된다.