ReactionCollection
ReactionCollection은 Reaction들을 저장한다.
ReactionCollection의 React()함수는 Interactable에 의해 호출된다.
React()함수가 호출되면 저장된 Reaction들을 모두 실행한다.
위 Reaction들은 ScriptableObject로 만들어진 것이며 추상클래스로부터 구체화된 것이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
using UnityEngine;
public abstract class Reaction : ScriptableObject
{
public void Init ()
{
SpecificInit ();
}
protected virtual void SpecificInit()
{}
public void React (MonoBehaviour monoBehaviour)
{
ImmediateReaction ();
}
protected abstract void ImmediateReaction ();
}
|
Reaction.cs. 리액션 클래스들의 최상위 클래스다.
시간 지연(delay)없이 곧바로 실행되는 리액션을 정의한다.
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
|
using UnityEngine;
using System.Collections;
public abstract class DelayedReaction : Reaction
{
public float delay;
protected WaitForSeconds wait;
public new void Init ()
{
wait = new WaitForSeconds (delay);
SpecificInit ();
}
public new void React (MonoBehaviour monoBehaviour)
{
monoBehaviour.StartCoroutine (ReactCoroutine ());
}
protected IEnumerator ReactCoroutine ()
{
yield return wait;
ImmediateReaction ();
}
}
|
DelayedReaction.cs. Reaction의 파생클래스이며 시간 지연(delay) 후 실행된다.
팝업과 드래그박스, 폴드아웃
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
|
private void OnEnable ()
{
reactionCollection = (ReactionCollection)target;
reactionsProperty = serializedObject.FindProperty(reactionsPropName);
CheckAndCreateSubEditors (reactionCollection.reactions);
SetReactionNamesArray ();
}
private void SetReactionNamesArray ()
{
Type reactionType = typeof(Reaction);
// Reaction이 속하는 어셈블리의 모든 클래스 추출
Type[] allTypes = reactionType.Assembly.GetTypes();
List<Type> reactionSubTypeList = new List<Type>();
for (int i = 0; i < allTypes.Length; i++)
{
// Reaction클래스의 파생클래스와 추상 클래스 필터링
if (allTypes[i].IsSubclassOf(reactionType) && !allTypes[i].IsAbstract)
{
// 추상클래스가 아닌 클래스 저장.
reactionSubTypeList.Add(allTypes[i]);
}
}
// 리스트의 배열 반환.
reactionTypes = reactionSubTypeList.ToArray();
List<string> reactionTypeNameList = new List<string>();
for (int i = 0; i < reactionTypes.Length; i++)
{
// Type의 Name을 저장.
reactionTypeNameList.Add(reactionTypes[i].Name);
}
// 팝업 리스트를 위한 string[] 초기화.
reactionTypeNames = reactionTypeNameList.ToArray();
}
|
ReactionCollectionEditor.cs.
활성화 시 Reaction들의 에디터와 팝업을 구성하기 위한 팝업 리스트를 매번 생성한다.
Reflection과 Type API
System.Reflection.Assembly.GetTypes() : 해당 어셈블리의 모든 클래스 타입을 반환한다.
Type.IsAbstract : 추상클래스를 판단하는 플래그 프로퍼티.
Type.IsSubclassOf(Type) : 파생클래스를 판단하고 플래그를 반환한다.
팝업
팝업의 리스트는 string[]로 구성된다.
Popup은 선택된 항목의 index를 반환한다.
index를 사용하려면 팝업 리스트와 매핑된 컨테이너가 필요하다
ConditionEditor는 index로부터 얻은 값을 인스턴스 초기화에 사용한다.
ReactionCollectionEditor는 index로부터 Type 값을 얻어 해당 Reaction 타입의 인스턴스 생성에 사용한다.
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
|
public override void OnInspectorGUI ()
{
serializedObject.Update ();
CheckAndCreateSubEditors(reactionCollection.reactions);
for (int i = 0; i < subEditors.Length; i++)
{
subEditors[i].OnInspectorGUI ();
}
if (reactionCollection.reactions.Length > 0)
{
EditorGUILayout.Space();
EditorGUILayout.Space ();
}
Rect fullWidthRect = GUILayoutUtility.GetRect(GUIContent.none, GUIStyle.none, GUILayout.Height(dropAreaHeight + verticalSpacing));
Rect leftAreaRect = fullWidthRect;
leftAreaRect.y += verticalSpacing * 0.5f;
leftAreaRect.width *= 0.5f;
leftAreaRect.width -= controlSpacing * 0.5f;
leftAreaRect.height = dropAreaHeight;
Rect rightAreaRect = leftAreaRect;
rightAreaRect.x += rightAreaRect.width + controlSpacing;
TypeSelectionGUI (leftAreaRect);
DragAndDropAreaGUI (rightAreaRect);
DraggingAndDropping(rightAreaRect, this);
serializedObject.ApplyModifiedProperties ();
}
private void TypeSelectionGUI (Rect containingRect)
{
Rect topHalf = containingRect;
topHalf.height *= 0.5f;
Rect bottomHalf = topHalf;
bottomHalf.y += bottomHalf.height;
selectedIndex = EditorGUI.Popup(topHalf, selectedIndex, reactionTypeNames);
if (GUI.Button (bottomHalf, "Add Selected Reaction"))
{
Type reactionType = reactionTypes[selectedIndex];
Reaction newReaction = ReactionEditor.CreateReaction (reactionType);
reactionsProperty.AddToObjectArray (newReaction);
}
}
|
ReactionCollectionEditor.cs. TypeSlectionGUI 함수를 통해 팝업을 띄운다.
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
private static void DragAndDropAreaGUI (Rect containingRect)
{
// GUIStyle 생성 및 초기화
GUIStyle centredStyle = GUI.skin.box;
// Text 중앙 정렬
centredStyle.alignment = TextAnchor.MiddleCenter;
centredStyle.normal.textColor = GUI.skin.button.normal.textColor;
GUI.Box (containingRect, "Drop new Reactions here", centredStyle);
}
private static void DraggingAndDropping (Rect dropArea, ReactionCollectionEditor editor)
{
// Drag 이벤트 추출
Event currentEvent = Event.current;
// Rect.Contains(vector2) 해당 벡터가 Rect 내에 있는지 체크
// mousePosiiton은 현재 마우스 커서의 위치를 반환한다.
if (!dropArea.Contains (currentEvent.mousePosition))
return;
switch (currentEvent.type)
{
// Drag 이벤트
case EventType.DragUpdated:
// DragAndDrop의 아이템 유효 체크
// 유효하면 Link 아이콘, 그렇지 않으면 Rejected 아이콘
DragAndDrop.visualMode = IsDragValid () ? DragAndDropVisualMode.Link : DragAndDropVisualMode.Rejected;
// DragUpdated 이벤트 실행
currentEvent.Use ();
break;
// Drop 이벤트
case EventType.DragPerform:
// 용도 불분명, 주석 후에도 정상 동작함.
//DragAndDrop.AcceptDrag();
for (int i = 0; i < DragAndDrop.objectReferences.Length; i++)
{
// MonoScript는 MonoBehaiour 또는 ScriptableObject을 가리킨다.
// MonoScript 변수에 드래그 아이템을 저장한다.
MonoScript script = DragAndDrop.objectReferences[i] as MonoScript;
// MonoScript.GetClass()는 해당 클래스를 반환한다.
Type reactionType = script.GetClass();
// 해당 타입의 Reaction 생성
Reaction newReaction = ReactionEditor.CreateReaction (reactionType);
/* editor는 인자로 넘어온 ReactionCollectionEditor 본인의 인스턴스다.
이 함수는 static이기 때문에 직접 참조할 수 없어서 인자로부터 우회 참조한다.
"static 함수 입장에서는 인스턴스를 분별할 수 없지만 인자로 받으면 분별 가능하므로" */
// reactions에 추가 할당.
editor.reactionsProperty.AddToObjectArray (newReaction);
}
// DragPerform 이벤트 실행
currentEvent.Use();
break;
}
}
private static bool IsDragValid ()
{
// 필터링으로 거른다.
for (int i = 0; i < DragAndDrop.objectReferences.Length; i++)
{
if (DragAndDrop.objectReferences[i].GetType () != typeof (MonoScript))
return false;
MonoScript script = DragAndDrop.objectReferences[i] as MonoScript;
Type scriptType = script.GetClass ();
if (!scriptType.IsSubclassOf (typeof(Reaction)))
return false;
if (scriptType.IsAbstract)
return false;
}
// 필터링을 통과하면 true 반환.
return true;
}
|
ReactionCollectionEditor.cs. 드래그박스의 구현부다.
팝업 vs 드래그박스
팝업은 팝업 리스트와 index를 사용할 매핑된 컨테이너를 요구한다.
드래그박스는 팝업이 요구하는 작업은 필요없지만 드래그 아이템의 판별 작업이 필요하다.
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
60
61
62
63
64
65
66
|
using System;
using UnityEngine;
using UnityEditor;
public abstract class ReactionEditor : Editor
{
public bool showReaction;
public SerializedProperty reactionsProperty;
private Reaction reaction;
private const float buttonWidth = 30f;
private void OnEnable ()
{
reaction = (Reaction)target;
Init ();
}
protected virtual void Init () {}
public override void OnInspectorGUI ()
{
serializedObject.Update ();
EditorGUILayout.BeginVertical (GUI.skin.box);
EditorGUI.indentLevel++;
EditorGUILayout.BeginHorizontal ();
// FoldOut(펼침 플래그, "폴드의 라벨")
showReaction = EditorGUILayout.Foldout (showReaction, GetFoldoutLabel ());
// 폴드 옆 '-' 버튼 생성.
if (GUILayout.Button ("-", GUILayout.Width (buttonWidth)))
{
reactionsProperty.RemoveFromObjectArray (reaction);
}
EditorGUILayout.EndHorizontal ();
// 펼쳐지면
if (showReaction)
{
// 프로퍼티 GUI 호출
DrawReaction ();
}
EditorGUI.indentLevel--;
EditorGUILayout.EndVertical ();
serializedObject.ApplyModifiedProperties ();
}
public static Reaction CreateReaction (Type reactionType)
{
return (Reaction)CreateInstance (reactionType);
}
protected virtual void DrawReaction ()
{
DrawDefaultInspector ();
}
// 초기화구문은 따로 없이 파생 클래스마다 하드코딩 되어있다.
protected abstract string GetFoldoutLabel ();
}
|
ReactionEditor.cs.
추상클래스이며 GetFoldoutLabel()을 위해 파생 Reaction클래스의 Editor클래스를 추가작성해야한다.
'Unity > 스크립터블오브젝트' 카테고리의 다른 글
Scriptable Object - 응용: SaveData, Saver, SceneController (15) (1) | 2020.06.11 |
---|---|
Scriptable Object - 응용: 씬 직렬화의 특징과 활용 (14) (0) | 2020.06.11 |
Scriptable Object - 응용: ConditionEditor, AllConditionsEditor, 프로젝트뷰 정리 (12) (0) | 2020.06.09 |
Scriptable Object - 응용: EditorWithSubEditors, 확장 메서드 (11) (2) | 2020.06.08 |
Scriptable Object - 응용: Interactable, 커스텀 에디터 (10) (0) | 2020.06.08 |
댓글