사용 목표
Cheat Tool 을 제작하고 있는데, 각 직업군의 Job을 선택하는 드롭다운 스타일의 기능이 있으면 편할 것 같아서 도입
AdvanceDropDown 말고도 새롭게 Editor Window를 열어서 선택한 것에 대한 콜백을 넘겨 받고 세팅해도 될것 같지만, AdvanceDropdown이 가장 제가 원하는 기능에 가까웠습니다.
결과

Advanced Dropdown 이란?

유니티 사용자라면 Unity Component 추가할 때마다 보는 드롭다운입니다.
이 드롭 다운을 내 Cheat Tool에 선택 기능으로 들어가면 좋다고 생각해서 사용했습니다.
코드
이 코드는 데이터, 드롭다운 상태, 그리고 드롭 다운 클래스로 나뉩니다.
데이터 : BRSDropDownNode.cs
public class BRSDropDownNode
{
string _mName;
string _mData;
HashSet<BRSDropDownNode> _mChild;
public string Name => _mName;
public string Data => _mData;
public HashSet<BRSDropDownNode> Child => _mChild;
public BRSDropDownNode(string _name, string _data)
{
this._mName = _name;
this._mData = _data;
_mChild = new HashSet<BRSDropDownNode>();
}
public void AddChild(BRSDropDownNode _child)
{
if(IsEqualHashCode(_child))
{
UnityLogger.GetInstance().LogError($"동일한 해쉬를 가진 오브젝트를 검출했습니다. {_child._mName}, {_child._mData} ");
return;
}
_mChild.Add(_child);
}
bool IsEqualHashCode(BRSDropDownNode _child)
{
// myHash
int _nameHash = _mName.GetHashCode();
int _dataHash = _mData.GetHashCode();
int _retHash = _nameHash + _dataHash;
//Child Hash
int _childNameHash = _child._mName.GetHashCode();
int _childDataHash = _child._mData.GetHashCode();
int _retChildHash = _childNameHash + _childDataHash;
return _retHash == _retChildHash;
}
}드롭다운 상태 관리 : AdvancedDropDownItemEx.cs
public class AdvancedDropDownItemEx : AdvancedDropdownItem
{
string _mData;
public string Data
{
set { _mData = value; }
get { return _mData; }
}
public AdvancedDropDownItemEx(string name, string data) : base(name)
{
this._mData = data;
}
}드롭 다운 클래스 : BRSAdvancedDropdown.cs
class BRSAdvancedDropdown : AdvancedDropdown
{
Action<AdvancedDropDownItemEx> _onSelectItem;
BRSDropDownNode _mNode;
public BRSAdvancedDropdown(AdvancedDropdownState state, BRSDropDownNode node, Action<AdvancedDropDownItemEx> selectedCallback) : base(state)
{
_mNode = node;
_onSelectItem = selectedCallback;
}
protected override AdvancedDropdownItem BuildRoot()
{
var _item = RecursiveConvertItem(_mNode);
return _item;
}
public AdvancedDropDownItemEx RecursiveConvertItem(BRSDropDownNode _node)
{
var item = new AdvancedDropDownItemEx(_node.Name, _node.Data)
{
name = _node.Name,
Data = _node.Data,
};
foreach (var itemChild in _node.Child)
{
var convertItem = RecursiveConvertItem(itemChild);
item.AddChild(convertItem);
}
return item;
}
protected override void ItemSelected(AdvancedDropdownItem item)
{
base.ItemSelected(item);
_onSelectItem?.Invoke(item as AdvancedDropDownItemEx);
}
}설명
가장 먼저 데이터를 관리하는 BRSDropDownNode 부분은 탐색에서 이득을 보기 위해HashSet<string> 으로 관리하려고 자료구조를 잡았지만, 트리형으로 관리하는 AdvancedDropdown을 조금 더 직관적으로 사용하기 위함을 고려하여 Node 형식으로 데이터를 작성했습니다.
이전 AdvancedDropdown 에서 확장한 이유는, 항목 선택에 대한 Callback 을 받기 위한것과, 세팅될 때 외부에서 내가 밀어넣어준 데이터를 기반으로 드롭다운을 내용을 세팅하면 좋겠다는 이유가 가장 큽니다.
드롭다운의 생성 흐름은 아래와 같습니다.
BrsAdvancedDropDown의 생성자에서 사용자가 세팅하기 바라는 데이터를 외부에서 작성해 밀어넣어줍니다.
그 다음 BrsAdvancedDropDown 의 Base Class에서 BuildRoot를 실행할때 외부에서 밀어넣어준 데이터를 기반으로 Recursive 항목을 세팅합니다.
세팅한 항목을 선택하게 되면 등록해둔 ItemSelected Callback을 받아 내가 원하는 함수 및 툴 데이터를 세팅합니다.
이제 외부에서 드롭다운이 필요할 때, 지금과 같이 외부에서 데이터를 세팅하고, Callback을 등록하는 것만으로 툴 기능을 재사용할 수 있습니다.
실 사용 예
사용 구문
public class TestClass
{
public void OpenDropdown()
{
var rect = GUILayoutUtility.GetRect(new GUIContent("Show"), EditorStyles.toolbarButton);
// 버튼을 만들 Rect 생성
if (GUI.Button(rect, new GUIContent("Job List"), EditorStyles.toolbarButton))
{
var node = OnCreateDropDownData();
// Node 데이터 생성
var state = new UnityEditor.IMGUI.Controls.AdvancedDropdownState();
// Dropdown의 State 생성
var advDropdown = new BRSAdvancedDropdown(state, node, OnSelectedJobDropDown);
// Data와 State를 관리하는 AdvancedDropdown 생성
advDropdown.Show(rect);
}
}
// 내가 원하는 데이터 세팅을 외부에서 생성
private BRSDropDownNode OnCreateDropDownData()
{
BRSDropDownNode root = new BRSDropDownNode($"Jobs", "root");
// P - Melee
BRSDropDownNode physicsMeleeJobs = new BRSDropDownNode($"Physics Melee", "This P Melee Job Pool");
physicsMeleeJobs.AddChild(new BRSDropDownNode($"SwordMan", "This Melee Job Pool"));
physicsMeleeJobs.AddChild(new BRSDropDownNode($"DualAxer", "This Melee Job Pool"));
physicsMeleeJobs.AddChild(new BRSDropDownNode($"TwoHandSwordman", "This Melee Job Pool"));
// M - Mage
root.AddChild(physicsMeleeJobs);
return root;
}
// 선택한 항목에 대한 Callback을 외부에서 세팅
public void OnSelectedJobDropDown(AdvancedDropDownItemEx _item)
{
UnityLogger.GetInstance().Log($"[OnSelectedJobDropDown] Selected : {_item.name} , {_item.Data}");
}
}실 사용을 이렇게 세팅하면 제가 만들어놓은 확장 클래스와 기능들을 사용할 수 있습니다.
이해하기 용이하게 각 함수가 어떤 기능을 하는지도 주석으로 달아두었습니다.
이제 OnSelectJobDropDown을 커스텀으로 어디에 세팅하고 어느 액션을 할지만 세팅하신다면 선택과 동시에 본인이 원하는 액션을 볼 수 있을 것입니다.
결과는 가장 위에 올려두었습니다.
긴 글 읽어주셔서 감사합니다.