모델링 팀에서는 건물 작업을 할 때 내부와 외부를 따로 제작하는 경우가 많다.
외부 작업을 하다가 내부 디테일을 조정해야 할 때 또는 내부 작업을 하다가 전체적인 모습을 확인해야 할 때
건물을 합친 상태와 분리된 상태를 반복적으로 전환해야 하는 경우가 생긴다.
처음에는 각 모델을 개별적으로 불러오고 위치를 맞추는 방식으로 작업했지만
이 과정이 반복될수록 점점 비효율적인 문제가 생겼다.
그리고 어느 날, 모델링 팀으로부터 이런 메시지가 왔다.
"이거 매번 수동으로 옮기는 게 너무 번거로워요!
합쳐진 상태랑 분리된 상태를 계속 왔다 갔다 해야 하는데,
위치 맞추는 것도 힘들고 매번 수동으로 조정하는 게 불편해요."
이처럼 모델을 유동적으로 전환하는 기능이 있다면 작업 과정이 훨씬 편해질 거라고 생각했다.
그래서, 이를 해결할 수 있는 기능을 Unity에서 직접 구현해보기로 했다.
모델링 팀과 논의해보니, 다음과 같은 기능이 필요했다.
✅ 한번의 조작으로 오브젝트의 위치 전환이 가능해야한다.
✅ 오브젝트 별로 위치 지정이 가능해야한다.
✅ 오브젝트 수를 추가할 수 있어야한다.
✅ 현재 저장된 위치가 유니티를 재실행했을 때 남아있어야한다.
이 요청을 바탕으로 어떻게 하면 모델링 팀이 더 편하게 작업할 수 있을지 고민해보았다.
결과적으로, 한 번의 클릭으로 오브젝트를 전환할 수 있는 기능을 만들기로 했다.
모델링 팀은 Unity의 Runtime 환경에서 작업하는 것이 아니라 에디터 내에서 오브젝트를 배치하고 조정하는 작업이 많았다.
따라서, 이 기능을 게임 실행 중이 아니라 Unity 에디터에서 직접 활용할 수 있도록 Tool로 제작하게 되었다.
Unity에서는 이러한 작업을 위해 EditorWindow를 제공하는데 이는 Inspector나 Scene 뷰처럼 독립적인 커스텀 윈도우를 만들어 컴포넌트를 직접 추가하지 않고도 원하는 기능을 구현할 수 있는 강력한 도구다.
Unity에서 EditorWindow를 사용하면 게임을 실행하지 않고도 에디터에서 직접 커스텀 기능을 만들 수 있다.
이를 활용하면 모델링 팀이 오브젝트의 위치를 쉽게 전환할 수 있도록 전용 도구를 제공할 수 있다.
아래 코드를 실행하면 Unity의 Tools 메뉴에 "Object Switch Tool" 이라는 항목이 추가된다.
이 메뉴를 클릭하면 커스텀 창(Object Switch Tool)이 생성되어 오브젝트를 추가하고 관리할 수 있다.
public class ObjectSwitchTool : EditorWindow
{
// 편집기에서 관리할 오브젝트 리스트
private readonly List<GameObject> objects = new();
// 각 오브젝트의 A/B 위치 저장
private readonly Dictionary<GameObject, (Vector3 A, Vector3 B)> positions = new();
// 현재 위치가 A인지 B인지 저장
private readonly Dictionary<GameObject, bool> positionSwitch = new();
private bool foldout = true; // 오브젝트 리스트 펼치기/접기 상태 저장
private ObjectPositionStorage positionStorage; // 위치 데이터를 저장할 오브젝트
private GameObject saveTargetObject; // 위치 데이터를 저장할 게임 오브젝트
private Vector2 scrollPosition; // 스크롤 위치 저장
// 0. Unity 메뉴에서 "Tools/Object Switch Tool"을 선택하면 창을 열도록 설정
[MenuItem("Tools/Object Switch Tool")]
public static void ShowWindow()
{
GetWindow<ObjectSwitchTool>("Object Switch Tool");
}
private void OnGUI()
{
DrawObjectManagement();
}
// 1. 오브젝트 관리 UI
private void DrawObjectManagement()
{
GUILayout.BeginHorizontal(); // 가로 정렬 시작
GUILayout.Label("오브젝트 관리", EditorStyles.boldLabel); // 레이블 표시 (글씨 Bold)
if (GUILayout.Button("+ 오브젝트 추가")) objects.Add(null); // 버튼 표시
GUILayout.EndHorizontal(); // 가로 정렬 끝
}
}
실행 결과 – Object Switch Tool 창
위 코드를 실행하면 Unity의 Tools 메뉴에 새로운 항목이 추가된다.
이제 Tools > Object Switch Tool을 클릭하면 아래와 같은 커스텀 창이 생성된다.
Tools > Object Switch Tool 메뉴 추가됨

Object Switch Tool 창이 뜨고 ‘+ 오브젝트 추가’ 버튼이 표시됨
(이곳에서 이후 기능을 추가하여 오브젝트의 A/B 위치를 관리할 예정)
이제 오브젝트를 관리하기 위한 UI와 기능을 추가해보자
필요한 기능을 정리해보면
1) 오브젝트 리스트를 표시하고 추가/삭제 가능
2) A/B 위치를 저장하고 이동하는 버튼 제공
3) 모든 오브젝트의 위치를 한 번에 변경하는 기능
4) 현재 저장 내역 유지 기능
와 같다. 이를 구현해보자
오브젝트를 편집기에 추가하는 기능은 DrawObjectManagement()에서 구현
✔️ "+ 오브젝트 추가" 버튼을 눌러 새로운 오브젝트 슬롯을 추가
✔️ 각 오브젝트 옆에 삭제 버튼을 추가하여 관리 가능
📌 DrawObjectManagement() 코드
// UI에서 오브젝트를 추가 및 삭제하는 부분
GUILayout.Label("오브젝트 관리", EditorStyles.boldLabel);
if (GUILayout.Button("+ 오브젝트 추가")) objects.Add(null);
// 오브젝트 리스트를 표시하는 UI
foldout = EditorGUILayout.Foldout(foldout, $"오브젝트 리스트 ({objects.Count})");
if (!foldout) return;
List<GameObject> toRemove = new();
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, GUILayout.Height(300));
for (int i = 0; i < objects.Count; i++)
{
GUILayout.BeginVertical("box");
objects[i] = (GameObject)EditorGUILayout.ObjectField($"Element {i}", objects[i], typeof(GameObject), true);
if (GUILayout.Button("삭제")) toRemove.Add(objects[i]);
if (objects[i] != null) HandleObjectEntry(objects[i]);
GUILayout.EndVertical();
}
EditorGUILayout.EndScrollView();
// 삭제할 오브젝트 리스트 업데이트
toRemove.ForEach(obj => { objects.Remove(obj); positions.Remove(obj); positionSwitch.Remove(obj); });
📌 HandleObjectEntry() 코드
/// <summary>
/// 개별 오브젝트에 대한 UI 및 기능 처리
/// </summary>
private void HandleObjectEntry(GameObject obj)
{
if (!positions.ContainsKey(obj)) InitializeObjectPosition(obj); // 오브젝트 위치 초기화
DisplayPositionInfo(obj); // 오브젝트 위치 정보 표시
DrawPositionControls(obj); // 오브젝트 위치 제어 버튼 표시
}

오브젝트의 A/B 위치를 저장하고 이동하는 기능은 DrawPositionControls()에서 구현
✔️ "A 위치 저장" / "B 위치 저장" 버튼을 눌러 현재 위치를 저장
✔️ "A 위치 이동" / "B 위치 이동" 버튼으로 지정한 위치로 이동 가능
✔️ "위치 토글" 버튼으로 A/B 전환 가능
✔️ 위치정보를 표시하는 기능 추가 DisplayPositionInfo() 에서 구현
📌 DrawPositionControls() 코드
/// <summary>
/// 오브젝트의 위치를 저장, 이동, 토글하는 UI 버튼 추가
/// </summary>
private void DrawPositionControls(GameObject obj)
{
GUILayout.BeginHorizontal(); // 가로 정렬 시작
if (GUILayout.Button("A 위치 저장")) SavePosition(obj, true);
if (GUILayout.Button("B 위치 저장")) SavePosition(obj, false);
GUILayout.EndHorizontal(); // 가로 정렬 끝
GUILayout.BeginHorizontal();
if (GUILayout.Button("A 위치 이동")) MoveObject(obj, true);
if (GUILayout.Button("B 위치 이동")) MoveObject(obj, false);
if (GUILayout.Button("위치 토글")) ToggleObjectPosition(obj);
GUILayout.EndHorizontal();
}
📌 DisplayPositionInfo() 코드
/// <summary>
/// 현재 오브젝트의 위치 정보를 표시
/// </summary>
private void DisplayPositionInfo(GameObject obj)
{
string positionState
= positions.ContainsKey(obj) ? ($"A: {positions[obj].A}, B: {positions[obj].B}") : "위치 미저장";
EditorGUILayout.LabelField("저장된 위치:", positionState);
EditorGUILayout.LabelField("현재 위치:", positionSwitch[obj] ? "A 위치" : "B 위치");
}
📌 기능 코드
/// <summary>
/// 오브젝트의 위치를 저장
/// </summary>
/// <param name="obj">대상 오브젝트</param>
/// <param name="isA">현재 위치가 A인지 여부</param>
private void SavePosition(GameObject obj, bool isA)
{
if (obj != null) positions[obj] = isA ? (obj.transform.position, positions[obj].B) : (positions[obj].A, obj.transform.position);
}
/// <summary>
/// 오브젝트를 이동
/// </summary>
/// <param name="obj">대상 오브젝트</param>
/// <param name="toA">A로 이동시킬지 여부</param>
private void MoveObject(GameObject obj, bool toA)
{
if (obj != null && positions.ContainsKey(obj))
{
obj.transform.position = toA ? positions[obj].A : positions[obj].B;
positionSwitch[obj] = toA;
}
}
private void ToggleObjectPosition(GameObject obj)
{
if (obj != null && positions.ContainsKey(obj)) MoveObject(obj, !positionSwitch[obj]);
}


전체 오브젝트의 위치 변경 기능은 DrawActionsAllObject()에서 구현
✔️ "A / B 위치로 이동" 버튼을 눌러 위치로 이동
✔️ "위치 토글" 버튼으로 A/B 전환 가능
📌 DrawActionsAllObject() 코드
/// <summary>
/// 현재 오브젝트의 위치 정보를 표시
/// </summary>
private void DrawActionsAllObject()
{
GUILayout.Space(10);
GUILayout.Label("전체 오브젝트 위치 변경", EditorStyles.boldLabel);
GUILayout.BeginHorizontal();
if (GUILayout.Button("A 위치로 이동")) MoveAllObjects(true);
if (GUILayout.Button("B 위치로 이동")) MoveAllObjects(false);
if (GUILayout.Button("위치 토글")) ToggleAllObjectsPosition();
GUILayout.EndHorizontal();
}
📌 기능 코드
private void MoveAllObjects(bool toA)
{
foreach (var obj in objects) MoveObject(obj, toA);
}
private void ToggleAllObjectsPosition()
{
foreach (var obj in objects) ToggleObjectPosition(obj);
}

오브젝트의 A/B 위치를 저장하고, 에디터를 닫아도 유지할 수 있도록 구현
이 기능을 통해, Editor를 닫거나 Unity를 재시작해도 오브젝트의 위치 정보를 유지할 수 있다.
즉, 위치를 저장했다가 나중에 다시 불러오는 기능을 제공하며,
이를 위해 ObjectPositionStorage와 ObjectPositionEntry를 활용하여 데이터를 관리한다.
✅ 1) 위치 데이터 저장 구조
위치 저장을 위해 두 개의 클래스를 사용한다.
✔️ ObjectPositionStorage -> 위치 데이터를 저장하는 컴포넌트
✔️ ObjectPositionEntry -> 각 오브젝트별 저장 데이터 구조체
📌 ObjectPositionStorage 코드
public class ObjectPositionStorage : MonoBehaviour
{
public List<ObjectPositionEntry> objectPositions = new();
/// <summary>
/// 오브젝트 위치 저장
/// </summary>
/// <param name="obj">대상 오브젝트</param>
/// <param name="posA">위치 A의 좌표</param>
/// <param name="posB">위치 B의 좌표</param>
/// <param name="isAtA">현 위치가 A인지 여부</param>
public void SavePosition(GameObject obj, Vector3 posA, Vector3 posB, bool isAtA)
{
var entry = objectPositions.Find(e => e.gameObject == obj);
if (entry != null)
{
entry.positionA = posA;
entry.positionB = posB;
entry.isAtA = isAtA;
}
else
{
objectPositions.Add(new ObjectPositionEntry()
{
gameObject = obj,
positionA = posA,
positionB = posB,
isAtA = isAtA
});
}
}
/// <summary>
/// 오브젝트 위치를 가져옴
/// </summary>
/// <param name="obj">대상 오브젝트</param>
/// <returns>(A좌표, B좌표. A여부)</returns>
public (Vector3 A, Vector3 B, bool isAtA)? GetPosition(GameObject obj)
{
var entry = objectPositions.Find(e => e.gameObject == obj);
if (entry != null)
{
return (entry.positionA, entry.positionB, entry.isAtA);
}
return null;
}
}
📌 ObjectPositionEntry 코드
[System.Serializable]
public class ObjectPositionEntry
{
public GameObject gameObject;
public Vector3 positionA;
public Vector3 positionB;
public bool isAtA;
}
✅ 2) UI – 위치 데이터 저장 & 불러오기
데이터를 저장/불러오는 UI 구현을 진행
✔️ "세이브" → 현재 위치 데이터를 저장
✔️ "로드" → 저장된 데이터를 불러와 복구
✔️ "세이브 오브젝트 설정" → 데이터를 저장할 오브젝트를 선택
📌 DrawSaveObjectSettings() 코드
/// <summary>
/// 위치 저장 대상 오브젝트를 선택하고 데이터를 저장/불러오는 UI
/// </summary>
private void DrawSaveObjectSettings()
{
GUILayout.Label("게임 세이브 오브젝트 설정", EditorStyles.boldLabel);
saveTargetObject = (GameObject)EditorGUILayout.ObjectField("세이브 오브젝트", saveTargetObject, typeof(GameObject), true);
GUILayout.BeginHorizontal();
if (GUILayout.Button("세이브")) SavePositionData();
if (GUILayout.Button("로드")) LoadPositionData();
if (GUILayout.Button("세이브 오브젝트 설정")) SetStorageTarget();
GUILayout.EndHorizontal();
}

✅ 3) 위치 데이터 저장 & 불러오기 기능
UI 버튼을 눌렀을 때 실행되는 저장 및 불러오기 기능을 구현
📌 기능 코드
/// <summary>
/// 위치 데이터를 저장
/// </summary>
private void SavePositionData()
{
foreach (var obj in objects) positionStorage.SavePosition(obj, positions[obj].A, positions[obj].B, positionSwitch[obj]);
EditorUtility.SetDirty(positionStorage);
}
/// <summary>
/// 저장된 위치 데이터를 불러옴
/// </summary>
private void LoadPositionData()
{
objects.Clear();
foreach (var entry in positionStorage.objectPositions)
{
if (entry.gameObject == null) continue;
objects.Add(entry.gameObject);
positions[entry.gameObject] = (entry.positionA, entry.positionB);
positionSwitch[entry.gameObject] = entry.isAtA;
}
}
전체 코드 및 패키지는 GitHub에서 다운로드할 수 있습니다.
🔗 GitHub Repository : https://github.com/ArgonautJH/ObjectSwitchTool
📌 설치 방법:
https://github.com/ArgonautJH/ObjectSwitchTool.git
.unitypackage로 설치현재 구현된 기능은 에디터에서 오브젝트의 위치를 A/B로 전환하는 기능을 제공하지만,
몇 가지 추가하면 더욱 유용하게 활용할 수 있을 것 같습니다.
1) Undo 기능 추가
2) 오브젝트 그룹 지정 기능