이번에 SOLID 5원칙을 작성하면서 봤던 코드 중에서
[RequireComponent(typeof(PlayerAudio), typeof(PlayerInput),
typeof(PlayerMovement))]
public class Player : MonoBehaviour
{
[SerializeField] private PlayerAudio playerAudio;
[SerializeField] private PlayerInput playerInput;
[SerializeField] private PlayerMovement playerMovement;
private void Start()
{
playerAudio = GetComponent<PlayerAudio>();
playerInput = GetComponent<PlayerInput>();
playerMovement = GetComponent<PlayerMovement>();
}
}
public class PlayerAudio : MonoBehaviour
{
//…
}
public class PlayerInput : MonoBehaviour
{
//…
}
public class PlayerMovement : MonoBehaviour
{
//…
}
위에 붙은
[RequireComponent(typeof(PlayerAudio), typeof(PlayerInput),
typeof(PlayerMovement))]
의 기능이 궁금했다
Attribute 중 하나라고 알고는 있었지만, 제대로 조사해본 적은 없어서 이번 기회에
이번 기회에 유니티에서 자주 사용하는 Attribute를 조사하기로 하였다
Attribute 란?
유니티에서 Attribute(속성)는 클래스, 변수, 메서드 등에 메타데이터를 추가하는 역할을 한다. 이를 활용하면 인스펙터에서의 표시 방식 변경, 실행 중 특정 동작 수행, 에디터 기능 확장 등을 할 수 있다.
유니티에서 자주 사용하게 될 Attribute를 몇 가지 알아보자
1. Serializable
클래스를 직렬화 가능하게 만들어, 인스펙터에서 내부 값을 수정할 수 있도록 해주는 속성(Attribute)이다.
Unity는 기본적으로 MonoBehaviour를 상속받지 않은 클래스를 인스펙터에서 다룰 수 없기 때문에 Serializable을 사용하여 직렬화해준다.
2. SerializeField
private 또는 protected 변수도 유니티 인스펙터에서 보이게 해주는 속성(Attribute)이다.
public 변수는 기본적으로 Unity에서 자동으로 직렬화 되지만 private 변수는 직렬화되지 않기 때문에 SerializeField를 사용하여 직렬화해준다.
이 둘의 차이가 너무 헷갈려서 몇 가지 실험을 했다
① MonoBehavior가 붙지 않은 클래스에 Serializable를 사용하면 과연 인스펙터에 표시되는가?
using System; // 직렬화를 사용하기 위해 필요
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[Serializable] // MyData 클래스를 직렬화 가능하게 설정
public class MyData
{
public string name;
public int age;
}
MyData 스크립트는 MonoBehavior의 상속을 받지 않으므로 당연히 하이러키에 올릴 수 없다
알게 된 사실1. Serializable이 붙더라도 하이러키에 올리려면 MonoBehavior의 상속을 받아야 한다 ★
그렇다면, Example 스크립트를 다음처럼 만든다
1. MonoBehavior의 상속을 받는다
2. private로 MyData를 선언한다
using System; // 직렬화 사용시 필요하다
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Example : MonoBehaviour
{
private MyData playerData;
}
② 이 때 [SerializeField] 선언을 하면?
public class Example : MonoBehaviour
{
[SerializeField] private MyData playerData;
}
인스펙터 창에 표시된다!
알게 된 사실2. Serializable이 붙은 클래스를 참조하여 인스펙터창에 표시하기 위해서는 참조하는 곳에서 SerializeField 선언이 필요하다
③ 만약 Serializable을 선언하지 않고, 필드에만 SerializeField 선언을 한다면 ?
using System; // 직렬화를 사용하기 위해 필요
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MyData
{
[SerializeField] public string name;
[SerializeField] public int age;
}
알게 된 사실3. Serializable이 붙지 않은 클래스의 필드에 SerializeField을 하더라도 인스펙터창에 표시되지 않는다
④ Serializable 선언한 클래스의 내부에 private 멤버는 인스펙터에 표시되는가?
using System; // 직렬화를 사용하기 위해 필요
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[Serializable] // MyData 클래스를 직렬화 가능하게 설정
public class MyData
{
public string name;
public int age;
private int hp;
}
알게 된 사실4. Serializable이 붙은 클래스의 내부에 있는 private 필드인스펙터창에 표시되지 않는다
④ 원래 MyData 클래스에 붙은 Serializable이 우선할까? Example클래스의 SerializeField가 우선일까?
using System; // 직렬화를 사용하기 위해 필요
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MyData
{
public string name;
public int age;
}
MyData의 Serializable을 지웠다
더 이상 인스펙터창에 나오지 않는다!
알게된 사실5. 클래스에 붙은 Serializable이 그 클래스를 참조하는 곳에 선언하는 SerializeField보다 우선한다
결론
1. 하이러키에 올리기 위해서는 Serializable, SerializeField와 상관없이 MonoBehavior가 있어야 한다(Serializable은 MonoBehavior가 붙지 않은 클래스를 인스펙터창에 표시할 수 있다)
2. Serializable이 붙은 클래스를 참조하여 인스펙터창에 표시하기 위해서는 참조하는 곳에서 SerializeField 선언이 필요하다
3. Serializable이 붙지 않은 클래스의 필드에 SerializeField을 하더라도 인스펙터창에 표시되지 않는다
4. Serializable이 붙더라도 내부에 private 필드까지 인스펙터창에 인스펙터창에 표시되지 않는다
5. 클래스에 붙은 Serializable이 그 클래스를 참조하는 곳에 선언하는 SerializeField보다 우선한다
https://imperor0103.tistory.com/108
저번 C# 콘솔 프로젝트때 직렬화 관련해서 단지 json으로 변환하기 위해 byte로 바꾼다는 것만 알았는데
제대로 이해를 하지 못한 것 같았다
이번에 다시 제대로 공부하고 넘어가자
직렬화 란?
직렬화(Serialization)는 객체(Object)나 데이터 구조를 저장하거나 전송할 수 있도록 바이트(byte) 형태로 변환하는 과정을 의미한다. 반대로, 직렬화된 데이터를 다시 객체로 변환하는 과정을 역직렬화(Deserialization)라고 한다.
직렬화를 하는 이유?
데이터를 저장하거나 네트워크로 전송하기 위해서이다.
유니티에서는 변수를 직렬화하면 해당 데이터를 인스펙터에서 저장하고 유지할 수 있게 해준다.
위에서 봤던 사례들을 통해 변수 직렬화와 클래스 직렬화에 대해 설명하자
① 클래스 직렬화
유니티는 기본적으로 MonoBehaviour를 상속받지 않은 일반 클래스는 데이터를 저장하는 용도가 아니라고인식되기 때문에 인스펙터에서 다룰 수 없다. 하지만 [Serializable]을 사용하면 클래스의 데이터를 직렬화하여 클래스 내부의 변수도 유니티 인스펙터에서 보이고 변경할 수 있게 된다. (단, private 필드는 직렬화되지 않아 인스펙터에 보이지 않는다)
② 필드(변수) 직렬화
필드를 직렬화하면 유니티가 해당 값을 저장할 수 있도록 인스펙터에 표시해준다
MonoBehavior의 상속을 받는 클래스는 따로 [Serializable] 선언이 없어도 내부의 private 필드에 [SerializeField]선언을 하는 것만으로 직렬화되어 인스펙터창에 표시할 수 있다. (단 Awake, Start, OnEnable, Update 등 유니티 생명주기함수 중에서 최소한 1개는있어야 인스펙터창에 표시된다)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class YourData : MonoBehaviour
{
public string name;
public int age;
[SerializeField] private int hp;
private void Start()
{
}
}
하지만 MonoBehavior의 상속을 받지 않는 클래스나 구조체는 반드시 [Serializable] 선언이 있어야 내부의 필드가 직렬화되어 인스펙터창에 표시된다
using System; // 직렬화를 사용하기 위해 필요
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[Serializable] // MyData 클래스를 직렬화 가능하게 설정
public class MyData
{
public string name;
public int age;
[SerializeField] private int hp;
}
위의 코드를 보면 hp는 접근제한자가 private으로 유니티에서 직렬화하지 않아 인스펙터에서도 보이지 않는다 하지만 [SerializeField]를 사용하여 private 변수도 직렬화되어 인스펙터에서 수정 가능해진다.
③ [SerializeField]와 Private를 같이 쓰는 이유
캡슐화를 유지하면서도 인스펙터창에서 값을 조정할 수 있도록 하기 위해서이다
데이터 보호 유지와 디버깅과 테스트의 편의성을 모두 도모한 방법이라고 볼 수 있다
주의사항
Serializable
1. MonoBehavior 또는 ScriptableObject에 사용하지 않는다
① MonoBehaviour 또는 ScriptableObject는 기본적으로 Unity에서 직렬화된다.
② Serializable은 일반적인 데이터 저장용 클래스를 만들 때만 사용된다.
2.클래스는 public이어야한다
①internal 클래스와 private 클래스는 직렬화가 되지 않으므로 인스펙터창에서 볼 수 없다
SerializeField
1. 직렬화 가능한 자료형에만 적용된다
아래의 자료형들은 SerializeField가 되어 있어도 직렬화가 불가능하다
① C# 기본 자료형 중 일부
타입 | 이유 |
Dictionary<TKey, TValue> | Unity는 딕셔너리를 직렬화할 수 없음 |
HashSet<T> | Unity는 해시셋을 직렬화할 수 없음 |
Queue<T> / Stack<T> | 선입선출(Queue), 후입선출(Stack) 구조는 Unity의 직렬화 방식과 맞지 않음 |
LinkedList<T> | Unity는 연결 리스트를 직렬화할 수 없음 |
Tuple<T1, T2> | Unity의 직렬화 시스템에서 지원하지 않음 |
KeyValuePair<TKey, TValue> | Unity의 직렬화 시스템에서 지원하지 않음 |
IntPtr, UIntPtr | 포인터는 직렬화할 수 없음 |
Delegate, Event | 함수 포인터 개념이라 직렬화 불가능 |
abstract class (추상 클래스) | 직접 인스턴스화할 수 없어 직렬화 불가능 |
interface (인터페이스) | 직접 인스턴스화할 수 없어 직렬화 불가능 |
예시
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class YourData : MonoBehaviour
{
public string name;
public int age;
[SerializeField] private int hp;
[SerializeField] HashSet<int> Tests;
private void OnEnable()
{
}
}
HashSet은 SerializeField 선언을 해도 인스펙터 창에 안보인다
② Unity의 특정 타입
타입 | 이유 |
GameObject | SerializeField로 선언해도 직렬화되지 않음 (레퍼런스만 유지) |
Component (예: Transform, Rigidbody) | Unity의 내부 객체라 직렬화되지 않음 |
Material, Shader, Texture | Unity의 에셋으로 관리되므로 직렬화되지 않음 |
Coroutine | 실행 상태를 유지할 수 없으므로 직렬화 불가능 |
해결 방법
- GameObject, Component 등은 Unity의 SerializeField로 직렬화하는 것이 아니라 직접 참조(Reference)하는 방식으로 사용해야 한다.
- 예를 들어, GameObject를 직렬화하려면 Prefab으로 만들어 Resources.Load() 같은 방식으로 불러와야 한다.
③ 특정 필드 접근 지정자
타입 | 이유 |
private | 직렬화 불가능 (단, [SerializeField] 사용 시 가능) |
protected | 직렬화 불가능 (단, [SerializeField] 사용 시 가능) |
static | 직렬화 불가능 |
const | 직렬화 불가능 |
readonly | 직렬화 불가능 |
해결 방법
- private, protected 필드는 [SerializeField]를 사용하면 직렬화 가능
- static, const, readonly 필드는 불변(Immutable) 이므로 직렬화되지 않음
'유니티 부트캠프 8기 > Ch05. Unity 게임 개발 숙련' 카테고리의 다른 글
디버그 모드에서 자식오브젝트의 transform을 가져오는 방법 (0) | 2025.03.10 |
---|---|
최상위 부모 오브젝트를 코드 상에서 가져오는 방법: GetRootGameObjects, 검색함수 만들기, GameObject.Find와 비교 (0) | 2025.03.10 |
사운드 추가 (0) | 2025.03.05 |
몬스터 AI 만들기, 데미지 입었을 때 효과까지 (0) | 2025.03.05 |
AI Navigation (0) | 2025.03.05 |