Enum 대체
Scriptable Object의 Asset으로 Enum을 대체할 수 있다.
Enum은 각 요소가 문자열과 int형으로 매핑된다. 그래서 Enum은 Dictionary의 키로 사용하거나 메뉴 인덱싱을 위해 사용한다.
Enum은 변경에 취약하다. 중간 요소를 제거하거나 추가해서 인덱스가 변경되면 관련한 모든 코드를 재정렬해야 한다. Switch/case문의 요소 제거 및 추가, 인덱스로 활용 중인 코드의 인덱스 조정 등 활용 범위가 넓을 수록 코드 수정 범위도 넓어진다.
하지만 Enum을 Asset으로 대체하면 수정 범위가 Inspector뷰에 한정된다. 따라서 변경에 대한 단점을 최소화 할 수 있다. 특히 Scriptabl Object는 기능도 정의할 수 있으므로 Enum의 인덱싱과 동시에 Data나 기능 구현의 역할을 할 수 있다.
Paper, Rock, Scissors 이 3가지는 'AttackElement'의 인스턴스로 Enum의 요소에 해당한다. 각 요소는 Defeated Elements라는 컨테이너를 통해 Data역할을 하고있다.
1
2
3
4
5
6
7
8
9
|
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu]
public class AttackElement : ScriptableObject
{
[Tooltip("The elements that are defeated by this element.")]
public List<AttackElement> DefeatedElements = new List<AttackElement>();
}
|
AttackElement 소스. Enum 구조체 정의에 해당한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
using UnityEngine;
using UnityEngine.UI;
// Enum을 사용하는 MonoBehaviour 스크립트
public class Elemental : MonoBehaviour
{
// Enum 구조체 필드
[Tooltip("Element represented by this elemental.")]
public AttackElement Element;
[Tooltip("Text to fill in with the element name.")]
public Text Label;
private void OnEnable()
{
// Enum 참조
if(Label != null)
Label.text = Element.name;
}
private void OnTriggerEnter(Collider other)
{
// 외부 개체의 Enum 추출
Elemental e = other.gameObject.GetComponent<Elemental>();
if (e != null)
{
// 외부 개체의 Enum 참조
if (e.Element.DefeatedElements.Contains(Element))
Destroy(gameObject);
}
}
}
|
Elemental 소스. AttackElement를 실제 사용하는 소스다.
오해와 진실
위에서 봤듯이 Scripatabl Object는 Data역할만 수행하지 않는다. Data의 역할과 더불어 Enum의 인덱싱 역할에 특정 기능도 정의할 수 있다.
처음에 봤던 FloatVariable은 float가 다를 바 없지만 기능이 정의된 상태다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
using UnityEngine;
[CreateAssetMenu]
public class FloatVariable : ScriptableObject
{
#if UNITY_EDITOR
// 텍스트 상자
[Multiline]
public string DeveloperDescription = "";
#endif
public float Value;
public void SetValue(float value)
{
Value = value;
}
public void SetValue(FloatVariable value)
{
Value = value.Value;
}
public void ApplyChange(float amount)
{
Value += amount;
}
public void ApplyChange(FloatVariable amount)
{
Value += amount.Value;
}
}
|
FloatVariable을 float처럼 산술연산하려면 기능 구현이 필요하다. ApplyChange를 통해 산술연산을 수행한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
using UnityEngine;
using UnityEngine.Events;
public class UnitHealth : MonoBehaviour
{
public FloatVariable HP;
public bool ResetHP;
public FloatReference StartingHP;
public UnityEvent DamageEvent;
public UnityEvent DeathEvent;
private void Start()
{
if (ResetHP)
HP.SetValue(StartingHP);
}
private void OnTriggerEnter(Collider other)
{
DamageDealer damage = other.gameObject.GetComponent<DamageDealer>();
if (damage != null)
{
HP.ApplyChange(-damage.DamageAmount);
DamageEvent.Invoke();
}
if (HP.Value <= 0.0f)
{
DeathEvent.Invoke();
}
}
}
|
UnitHealth 소스.
프로퍼티 드로워
프로퍼티 드로워로 공유데이터를 사용할 지, 따로 설정한 값을 사용할 지 정할 수 있다. 사실 'damageAmount'라는 필드의 자료형은 FloatRefernce다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
using System;
[Serializable]
public class FloatReference
{
/* Inspector뷰에 노출되는 필드들 */
// UseConstant와 ConstantValue는 '...' 버튼으로 구성된다.
public bool UseConstant = true;
public float ConstantValue;
public FloatVariable Variable;
// 생성자
public FloatReference()
{ }
// 생성자
public FloatReference(float value)
{
UseConstant = true;
ConstantValue = value;
}
// 최종 값 반환 프로퍼티
public float Value
{
get { return UseConstant ? ConstantValue : Variable.Value; }
}
// 암묵적 형변환 설정. float f = floatReference;
public static implicit operator float(FloatReference reference)
{
return reference.Value;
}
}
|
FloatReference 소스. 위와 같이 인스펙터뷰를 통해 조정 가능하려면 프로퍼티의 커스텀 작업이 필요하다.
암묵적 형변환은 float 인자를 요구하는 각종 Unity API를 사용하기 위해 반드시 필요하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
using UnityEditor;
[CustomPropertyDrawer(typeof(MyProperty))]
public class MyPropertyDrawer : PropertyDrawer
{
//which index of the popupOptions you want selected
private int selectedIndex = 0;
//the different options you want to display when the Popup is selected
private readonly string[] popupOptions =
{ "Option One", "Option Two" };
private GUIStyle popupStyle;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
//stuff above here
if (popupStyle == null)
{
popupStyle = new GUIStyle(GUI.skin.GetStyle("PaneOptions"));
popupStyle.imagePosition = ImagePosition.ImageOnly;
}
EditorGUI.Popup(position /*or whatever position you want*/,
selectedIndex, popupOptions, popupStyle);
//stuff below here
}
}
|
프로퍼티를 팝업("PaneOptions")으로 구성하는 기본적인 프레임은 위와 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(FloatReference))]
public class FloatReferenceDrawer : PropertyDrawer
{
/// <summary>
/// Options to display in the popup to select constant or variable.
/// </summary>
private readonly string[] popupOptions =
{ "Use Constant", "Use Variable" };
/// <summary> Cached style to use to draw the popup button. </summary>
private GUIStyle popupStyle;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (popupStyle == null)
{
// '...' 모양의 팝업 버튼 스타일 생성
popupStyle = new GUIStyle(GUI.skin.GetStyle("PaneOptions"));
popupStyle.imagePosition = ImagePosition.ImageOnly;
}
label = EditorGUI.BeginProperty(position, label, property);
position = EditorGUI.PrefixLabel(position, label);
EditorGUI.BeginChangeCheck();
// Get properties
SerializedProperty useConstant = property.FindPropertyRelative("UseConstant");
SerializedProperty constantValue = property.FindPropertyRelative("ConstantValue");
SerializedProperty variable = property.FindPropertyRelative("Variable");
// Calculate rect for configuration button
Rect buttonRect = new Rect(position);
buttonRect.yMin += popupStyle.margin.top;
buttonRect.width = popupStyle.fixedWidth + popupStyle.margin.right;
position.xMin = buttonRect.xMax;
// Store old indent level and set it to 0, the PrefixLabel takes care of it
int indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
int result = EditorGUI.Popup(buttonRect, useConstant.boolValue ? 0 : 1, popupOptions, popupStyle);
useConstant.boolValue = result == 0;
EditorGUI.PropertyField(position,
useConstant.boolValue ? constantValue : variable,
GUIContent.none);
if (EditorGUI.EndChangeCheck())
property.serializedObject.ApplyModifiedProperties();
EditorGUI.indentLevel = indent;
EditorGUI.EndProperty();
}
}
|
FloatReferenceDrawer.cs
FloatReference의 프로퍼티 필드를 팝업을 통해 선택할 수 있다.
'Unity > 스크립터블오브젝트' 카테고리의 다른 글
Scriptable Object - 응용: Destructible, Brain(AI) (7) (0) | 2020.06.02 |
---|---|
Scriptable Object - 응용: 오디오 이벤트, 프로퍼티 드로워, 커스텀 에디터 (6) (0) | 2020.06.01 |
Scriptable Object - 응용: 이벤트, 데이터셋 (4) (1) | 2020.06.01 |
Scriptable Object - 개념: 기본 아이디어, 기존 싱글톤의 장단점 (3) (0) | 2020.05.29 |
Scriptable Object - 개념: 인스턴스와 에셋 생성 (2) (1) | 2020.05.29 |
댓글