Unity 에디터에서 가장 많은 시간을 보내는 곳이 Scene View입니다. 오브젝트를 배치하고, 카메라 앵글을 조정하고, 셰이더의 결과를 확인하는 모든 작업이 이곳에서 이루어집니다. 그런데 기본 기능만으로는 부족할 때가 있습니다. 특정 컴포넌트가 선택되었을 때 추가 정보를 표시하고 싶거나, 레벨 디자인을 위한 전용 도구를 만들고 싶거나, 아티스트가 쉽게 사용할 수 있는 시각적 피드백을 제공하고 싶을 때가 그렇습니다.
이 글에서는 Unity 엔진 소스코드를 직접 분석하면서 SceneView가 어떻게 구현되어 있는지 살펴보고, 이를 바탕으로 어떤 확장 방법이 어떤 상황에 적합한지 깊이있게 다룹니다.
SceneView의 클래스 계층 구조
Unity 엔진 소스코드에서 SceneView 클래스의 정의를 보면 다음과 같습니다.
[EditorWindowTitle(title = "Scene", useTypeNameAsIconName = true)]
public class SceneView : SearchableEditorWindow, IHasCustomMenu
SceneView는 SearchableEditorWindow를 상속받습니다. 이것이 의미하는 바는 SceneView가 단순한 EditorWindow가 아니라 검색 기능이 내장된 특수한 에디터 창이라는 점입니다. 상단 툴바에서 계층 구조 필터링이나 오브젝트 검색이 가능한 이유가 바로 이 상속 관계 때문입니다.
IHasCustomMenu 인터페이스는 SceneView 탭을 우클릭했을 때 나타나는 컨텍스트 메뉴를 확장할 수 있게 해줍니다. 실제로 "Maximize", "Close Tab" 외에 SceneView 전용 메뉴 항목들이 이 인터페이스를 통해 추가됩니다.
흥미로운 점은 SceneView가 정적 필드를 통해 현재 상태를 관리한다는 것입니다.
private static SceneView s_LastActiveSceneView;
private static SceneView s_CurrentDrawingSceneView;
public static SceneView lastActiveSceneView
{
get
{
if (s_LastActiveSceneView == null && s_SceneViews.Count > 0)
s_LastActiveSceneView = s_SceneViews[0] as SceneView;
return s_LastActiveSceneView;
}
}
public static SceneView currentDrawingSceneView { get { return s_CurrentDrawingSceneView; } }
lastActiveSceneView는 가장 최근에 활성화된 SceneView를 반환하는데, 만약 null이면 존재하는 SceneView 중 첫 번째 것을 반환합니다. 이 방어적 코딩이 의미하는 바는 분명합니다. Unity 에디터에서는 여러 개의 SceneView가 동시에 존재할 수 있고, 사용자가 레이아웃을 변경하면서 SceneView가 닫히거나 새로 생성될 수 있기 때문입니다.
currentDrawingSceneView는 현재 OnGUI가 실행 중인 SceneView를 가리킵니다. 이것은 나중에 살펴볼 duringSceneGui 이벤트에서 매우 중요합니다. 여러 SceneView가 있을 때 어떤 View에서 렌더링되고 있는지 구분해야 하기 때문입니다.
OnGUI 렌더링 파이프라인 심층 분석
SceneView의 핵심은 OnGUI 메서드입니다. 엔진 소스코드에서 이 메서드의 구조를 살펴보면 Unity가 왜 특정 순서로 이벤트를 발생시키는지 이해할 수 있습니다.
protected virtual void OnGUI()
{
onGUIStarted?.Invoke(this);
s_CurrentDrawingSceneView = this;
Event evt = Event.current;
if (evt.type == EventType.Repaint)
{
s_MouseRects.Clear();
Tools.InvalidateHandlePosition();
}
sceneViewGrids.UpdateGridColor();
// ... 초기 설정 ...
HandleClickAndDragToFocus();
m_ShowSceneViewWindows = (lastActiveSceneView == this);
m_SceneViewOverlay.Begin();
// ... 카메라 설정 및 3D 렌더링 ...
SetupCamera();
DoDrawCamera(windowSpaceCameraRect, groupSpaceCameraRect, out pushedGUIClip);
// ... 선택 및 OnSceneGUI 처리 ...
HandleSelectionAndOnSceneGUI();
DefaultHandles();
SceneViewMotion.DoViewTool(this);
// ... 마무리 ...
m_SceneViewOverlay.End();
s_CurrentDrawingSceneView = null;
onGUIEnded?.Invoke(this);
}
가장 먼저 눈에 띄는 것은 s_CurrentDrawingSceneView = this입니다. OnGUI 시작 시점에 현재 인스턴스를 정적 변수에 저장하고, 종료 시점에 null로 초기화합니다. 이것이 SceneView.currentDrawingSceneView가 작동하는 원리입니다.
HandleClickAndDragToFocus()는 마우스 클릭으로 SceneView에 포커스를 줄 때 호출됩니다. 이것이 SetupCamera() 이전에 호출되는 이유는 포커스 상태가 카메라 설정과 입력 처리에 영향을 미치기 때문입니다.
m_SceneViewOverlay.Begin()과 End() 사이에 모든 렌더링이 이루어집니다. 오버레이 시스템은 SceneView 내부에 떠 있는 작은 창들(예: 카메라 프리뷰, 라이트맵 미리보기)을 관리합니다.
HandleSelectionAndOnSceneGUI의 비밀
확장 개발자에게 가장 중요한 부분은 HandleSelectionAndOnSceneGUI 메서드입니다.
private void HandleSelectionAndOnSceneGUI()
{
m_RectSelection.OnGUI();
CallOnSceneGUI();
}
단순해 보이지만, CallOnSceneGUI 내부에서 중요한 일이 일어납니다.
void CallOnSceneGUI()
{
foreach (Editor editor in activeEditors)
{
if (!drawGizmos || !EditorGUIUtility.IsGizmosAllowedForObject(editor.target))
continue;
MethodInfo method = editor.GetType().GetMethod(
"OnSceneGUI",
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy,
null,
Type.EmptyTypes,
null);
if (method != null)
{
// ... OnSceneGUI 호출 ...
}
}
EditorToolManager.InvokeOnSceneGUICustomEditorTools();
if (duringSceneGui != null)
{
ResetOnSceneGUIState();
if (duringSceneGui != null)
duringSceneGui(this);
// ... 레거시 지원 ...
ResetOnSceneGUIState();
}
}
이 코드가 밝히는 중요한 사실들이 있습니다.
첫째, Custom Editor의 OnSceneGUI가 먼저 호출됩니다. activeEditors를 순회하면서 각 Editor의 OnSceneGUI 메서드를 리플렉션으로 찾아 호출합니다. 이것은 특정 컴포넌트에 종속된 SceneView 확장을 만들 때 Custom Editor를 사용해야 하는 이유입니다.
둘째, EditorTool의 OnSceneGUI가 그 다음입니다. EditorToolManager.InvokeOnSceneGUICustomEditorTools()가 호출되어 활성화된 EditorTool들의 OnSceneGUI를 실행합니다.
셋째, duringSceneGui 이벤트가 마지막입니다. 가장 늦게 호출되기 때문에 다른 모든 SceneGUI 처리 결과를 기반으로 추가 작업을 할 수 있습니다. 하지만 동시에 이벤트를 먼저 소비할 기회가 적다는 의미이기도 합니다.
넷째, ResetOnSceneGUIState가 전후로 호출됩니다. 이것은 각 핸들러가 독립적인 상태에서 실행되도록 보장합니다. 한 핸들러가 Handles.color를 변경해도 다음 핸들러에 영향을 주지 않습니다.
확장 포인트별 상세 분석
1. duringSceneGui 이벤트
가장 일반적인 확장 방법입니다. 어떤 오브젝트가 선택되었는지와 관계없이 항상 실행됩니다.
using UnityEditor;
using UnityEngine;
[InitializeOnLoad]
public static class SceneViewGridHelper
{
static SceneViewGridHelper()
{
SceneView.duringSceneGui += OnSceneGUI;
}
static void OnSceneGUI(SceneView sceneView)
{
// 2D GUI 요소 그리기
Handles.BeginGUI();
var rect = new Rect(10, 10, 200, 60);
GUILayout.BeginArea(rect, EditorStyles.helpBox);
GUILayout.Label("Grid Helper", EditorStyles.boldLabel);
if (GUILayout.Button("Reset View"))
{
sceneView.LookAt(Vector3.zero, Quaternion.Euler(30, 45, 0), 10f, false);
}
GUILayout.EndArea();
Handles.EndGUI();
// 3D 핸들 그리기
Handles.color = new Color(1, 1, 1, 0.3f);
for (int x = -10; x <= 10; x++)
{
Handles.DrawLine(new Vector3(x, 0, -10), new Vector3(x, 0, 10));
Handles.DrawLine(new Vector3(-10, 0, x), new Vector3(10, 0, x));
}
}
}
[InitializeOnLoad] 속성은 에디터가 로드될 때 정적 생성자를 자동으로 호출합니다. 이를 통해 SceneView.duringSceneGui에 이벤트 핸들러를 등록합니다.
주의할 점은 이벤트 중복 등록입니다. 도메인 리로드(스크립트 컴파일, 플레이 모드 진입/종료) 시 정적 생성자가 다시 호출됩니다. 하지만 C#의 이벤트 시스템 특성상 같은 메서드를 여러 번 등록하면 여러 번 호출됩니다. 해결책은 등록 전에 먼저 해제하는 것입니다.
static SceneViewGridHelper()
{
SceneView.duringSceneGui -= OnSceneGUI; // 먼저 해제
SceneView.duringSceneGui += OnSceneGUI; // 그 다음 등록
}
2. Custom Editor의 OnSceneGUI
특정 컴포넌트가 선택되었을 때만 SceneView에 UI를 표시하고 싶다면 Custom Editor를 사용합니다.
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(WaypointController))]
public class WaypointControllerEditor : Editor
{
private void OnSceneGUI()
{
WaypointController controller = (WaypointController)target;
if (controller.waypoints == null || controller.waypoints.Length == 0)
return;
// 웨이포인트 간 연결선 그리기
Handles.color = Color.yellow;
for (int i = 0; i < controller.waypoints.Length - 1; i++)
{
if (controller.waypoints[i] != null && controller.waypoints[i + 1] != null)
{
Handles.DrawLine(
controller.waypoints[i].position,
controller.waypoints[i + 1].position
);
}
}
// 각 웨이포인트에 인터랙티브 핸들 추가
for (int i = 0; i < controller.waypoints.Length; i++)
{
if (controller.waypoints[i] == null)
continue;
EditorGUI.BeginChangeCheck();
Vector3 newPosition = Handles.PositionHandle(
controller.waypoints[i].position,
controller.waypoints[i].rotation
);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(controller.waypoints[i], "Move Waypoint");
controller.waypoints[i].position = newPosition;
}
}
}
}
이 방식의 장점은 컨텍스트 인식입니다. WaypointController가 선택되었을 때만 웨이포인트 편집 UI가 나타나고, 다른 오브젝트를 선택하면 자동으로 사라집니다. 사용자가 명시적으로 도구를 켜거나 끌 필요가 없습니다.
또한 target 속성을 통해 현재 편집 중인 오브젝트에 직접 접근할 수 있습니다. duringSceneGui에서는 Selection.activeObject를 통해 간접적으로 접근해야 하지만, Custom Editor에서는 타입 캐스팅만으로 바로 사용할 수 있습니다.
3. EditorTool
Unity 2019.1부터 도입된 EditorTool 시스템은 내장 Transform 도구(이동, 회전, 스케일)처럼 작동하는 커스텀 도구를 만들 수 있게 해줍니다.
using UnityEditor;
using UnityEditor.EditorTools;
using UnityEngine;
[EditorTool("Vertex Painter")]
public class VertexPainterTool : EditorTool
{
private GUIContent m_IconContent;
public override GUIContent toolbarIcon
{
get
{
if (m_IconContent == null)
{
m_IconContent = new GUIContent(
EditorGUIUtility.IconContent("d_Grid.PaintTool").image,
"Vertex Painter Tool"
);
}
return m_IconContent;
}
}
public override void OnToolGUI(EditorWindow window)
{
if (!(window is SceneView sceneView))
return;
HandleUtility.AddDefaultControl(GUIUtility.GetControlID(FocusType.Passive));
Event e = Event.current;
// 마우스 위치에서 메시 버텍스 찾기
Ray ray = HandleUtility.GUIPointToWorldRay(e.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit))
{
// 히트 지점에 브러시 미리보기
Handles.color = new Color(1, 0.5f, 0, 0.5f);
Handles.DrawSolidDisc(hit.point, hit.normal, 0.5f);
if (e.type == EventType.MouseDown && e.button == 0)
{
PaintVerticesAt(hit);
e.Use();
}
}
sceneView.Repaint();
}
private void PaintVerticesAt(RaycastHit hit)
{
MeshFilter meshFilter = hit.collider.GetComponent<MeshFilter>();
if (meshFilter == null) return;
// 버텍스 컬러 페인팅 로직...
}
}
EditorTool의 핵심 특징은 배타적 활성화입니다. 한 번에 하나의 EditorTool만 활성화될 수 있습니다. 내장 이동 도구를 선택하면 Vertex Painter는 비활성화되고, Vertex Painter를 선택하면 이동 도구가 비활성화됩니다.
HandleUtility.AddDefaultControl은 도구가 마우스 이벤트를 "캡처"할 수 있게 합니다. 이것이 없으면 마우스 클릭이 오브젝트 선택으로 처리되어 버립니다.
4. SceneViewOverlay (내부 API)
Unity 엔진 소스코드를 보면 SceneViewOverlay라는 내부 클래스가 있습니다.
internal class SceneViewOverlay
{
public enum Ordering
{
Camera = -100,
ClothConstraints = 0,
ClothSelfAndInterCollision = 100,
OcclusionCulling = 200,
Lightmapping = 300,
NavMesh = 400,
PhysicsDebug = 450,
TilemapRenderer = 500,
ParticleEffect = 600
}
public enum WindowDisplayOption
{
MultipleWindowsPerTarget,
OneWindowPerTarget,
OneWindowPerTitle
}
public delegate void WindowFunction(Object target, SceneView sceneView);
public static void Window(GUIContent title, WindowFunction sceneViewFunc, int order, WindowDisplayOption option)
{
// ...
}
}
이 API는 internal로 선언되어 있어 직접 사용할 수 없지만, 리플렉션을 통해 접근할 수 있습니다. Camera Preview, Lightmapping 오버레이 등 Unity 내장 기능들이 이 시스템을 사용합니다.
Ordering enum은 여러 오버레이가 표시될 때의 순서를 결정합니다. 낮은 값이 아래에 표시됩니다. WindowDisplayOption은 같은 타이틀이나 타겟에 대해 창을 어떻게 관리할지 결정합니다.
SceneView 카메라 제어
SceneView의 카메라를 프로그래밍적으로 제어하는 것은 에디터 도구 개발에서 자주 필요한 기능입니다.
LookAt 메서드의 동작 원리
엔진 소스코드에서 SceneViewMotion.cs를 보면 LookAt이 어떻게 작동하는지 알 수 있습니다.
view.LookAt(hit.point, view.rotation, targetSize);
이 메서드는 카메라를 특정 지점으로 부드럽게 이동시킵니다. 내부적으로 AnimVector3와 AnimQuaternion을 사용하여 애니메이션 보간을 수행합니다.
// SceneView.cs
AnimQuaternion m_Rotation = new AnimQuaternion(kDefaultRotation);
AnimVector3 m_Position = new AnimVector3(kDefaultPivot);
즉시 이동이 아니라 애니메이션이 적용되는 이유는 사용자 경험 때문입니다. 갑작스러운 카메라 점프는 방향 감각을 잃게 하지만, 부드러운 전환은 공간적 연속성을 유지합니다.
// 특정 오브젝트로 카메라 이동
public static void FocusOnObject(GameObject target)
{
if (target == null) return;
SceneView sceneView = SceneView.lastActiveSceneView;
if (sceneView == null) return;
Bounds bounds = CalculateBounds(target);
float size = bounds.extents.magnitude * 2f;
// 현재 회전을 유지하면서 새 위치로 이동
sceneView.LookAt(
bounds.center,
sceneView.rotation,
size,
sceneView.orthographic
);
}
private static Bounds CalculateBounds(GameObject obj)
{
Bounds bounds = new Bounds(obj.transform.position, Vector3.zero);
foreach (Renderer renderer in obj.GetComponentsInChildren<Renderer>())
{
bounds.Encapsulate(renderer.bounds);
}
return bounds;
}
2D 모드와 직교 뷰
SceneView는 2D 모드와 3D 모드를 지원합니다. 엔진 소스에서 이 차이가 어떻게 처리되는지 확인할 수 있습니다.
public Quaternion rotation
{
get => m_2DMode ? Quaternion.identity : m_Rotation.value;
set
{
if (m_2DMode)
Debug.LogWarning("SceneView rotation is fixed to identity when in 2D mode.");
else
m_Rotation.value = value;
}
}
2D 모드에서는 회전이 항상 Quaternion.identity를 반환합니다. 이는 2D 게임 개발 시 의도치 않게 카메라가 회전하는 것을 방지합니다. 회전을 설정하려고 하면 경고 메시지가 출력되지만, 값은 변경되지 않습니다.
Handles API 심층 분석
Handles는 SceneView에서 3D 인터랙티브 요소를 그리기 위한 핵심 API입니다.
기본 그리기 함수들
// 와이어프레임 도형
Handles.DrawWireCube(center, size);
Handles.DrawWireSphere(center, radius);
Handles.DrawWireDisc(center, normal, radius);
// 솔리드 도형
Handles.DrawSolidDisc(center, normal, radius);
Handles.DrawSolidArc(center, normal, from, angle, radius);
// 선
Handles.DrawLine(start, end);
Handles.DrawDottedLine(start, end, screenSpaceSize);
Handles.DrawPolyLine(points);
Handles.DrawAAPolyLine(width, points); // 안티앨리어싱 적용
DrawAAPolyLine과 DrawPolyLine의 차이점이 중요합니다. AA 버전은 더 부드러운 선을 그리지만 성능 비용이 더 높습니다. 많은 수의 선을 그려야 할 때는 일반 버전을 사용하고, 시각적 품질이 중요한 소수의 선에만 AA 버전을 사용하는 것이 좋습니다.
인터랙티브 핸들
// 위치 핸들
EditorGUI.BeginChangeCheck();
Vector3 newPosition = Handles.PositionHandle(currentPosition, currentRotation);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(target, "Move Object");
target.position = newPosition;
}
// 회전 핸들
EditorGUI.BeginChangeCheck();
Quaternion newRotation = Handles.RotationHandle(currentRotation, currentPosition);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(target, "Rotate Object");
target.rotation = newRotation;
}
// 스케일 핸들
EditorGUI.BeginChangeCheck();
Vector3 newScale = Handles.ScaleHandle(currentScale, currentPosition, currentRotation, HandleUtility.GetHandleSize(currentPosition));
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(target, "Scale Object");
target.localScale = newScale;
}
EditorGUI.BeginChangeCheck()와 EndChangeCheck() 패턴은 핸들 값이 변경되었는지 감지하는 표준 방법입니다. 이 패턴을 사용하면 사용자가 실제로 핸들을 조작했을 때만 Undo 기록과 오브젝트 수정이 이루어집니다.
HandleUtility.GetHandleSize(position)은 카메라 거리에 따라 적절한 핸들 크기를 계산합니다. 이것을 사용하면 카메라가 멀리 있어도 핸들이 너무 작아지지 않고, 가까이 있어도 너무 커지지 않습니다.
커스텀 핸들 버튼
float handleSize = HandleUtility.GetHandleSize(position) * 0.1f;
float pickSize = handleSize * 1.5f; // 클릭 가능 영역은 시각적 크기보다 크게
if (Handles.Button(position, rotation, handleSize, pickSize, Handles.SphereHandleCap))
{
// 버튼 클릭 처리
Selection.activeGameObject = targetObject;
}
handleSize와 pickSize를 분리한 이유가 있습니다. 시각적으로 작은 핸들이라도 클릭하기 쉬워야 합니다. pickSize를 handleSize보다 크게 설정하면 사용자가 정확히 점 위에 마우스를 올리지 않아도 클릭할 수 있습니다.
이벤트 처리와 입력 관리
SceneView에서 마우스와 키보드 이벤트를 처리하는 방법을 이해하는 것이 중요합니다.
static void OnSceneGUI(SceneView sceneView)
{
Event e = Event.current;
// 이벤트 타입에 따른 처리
switch (e.type)
{
case EventType.MouseDown:
if (e.button == 0) // 좌클릭
{
HandleLeftClick(e);
}
break;
case EventType.MouseDrag:
if (e.button == 0)
{
HandleDrag(e);
}
break;
case EventType.MouseUp:
if (e.button == 0)
{
HandleRelease(e);
}
break;
case EventType.KeyDown:
if (e.keyCode == KeyCode.Escape)
{
CancelOperation();
e.Use(); // 이벤트 소비
}
break;
case EventType.Repaint:
DrawVisuals();
break;
case EventType.Layout:
// 레이아웃 계산
break;
}
}
e.Use()는 이벤트를 "소비"하여 다른 핸들러가 같은 이벤트를 처리하지 못하게 합니다. 하지만 주의가 필요합니다. 너무 일찍 Use()를 호출하면 Unity의 기본 동작(오브젝트 선택, 카메라 회전 등)이 작동하지 않을 수 있습니다.
마우스 좌표 변환
SceneView에서 마우스 위치를 3D 공간으로 변환하는 여러 방법이 있습니다.
// 마우스 위치에서 월드 공간으로 레이 발사
Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
// 평면과의 교차점 계산
Plane groundPlane = new Plane(Vector3.up, Vector3.zero);
if (groundPlane.Raycast(ray, out float distance))
{
Vector3 worldPoint = ray.GetPoint(distance);
}
// 물리 레이캐스트
if (Physics.Raycast(ray, out RaycastHit hit))
{
Vector3 hitPoint = hit.point;
Vector3 hitNormal = hit.normal;
GameObject hitObject = hit.collider.gameObject;
}
HandleUtility.GUIPointToWorldRay는 2D 화면 좌표를 3D 레이로 변환합니다. 반환된 레이의 origin은 카메라 위치(또는 직교 모드에서는 카메라 앞의 근평면 위 점)이고, direction은 해당 화면 픽셀을 통과하는 방향입니다.
성능 최적화 팁
SceneView 확장을 개발할 때 성능은 매우 중요합니다. OnGUI는 매 프레임마다 여러 번 호출될 수 있기 때문입니다.
이벤트 타입별 처리 분리
static void OnSceneGUI(SceneView sceneView)
{
Event e = Event.current;
// Repaint 이벤트에서만 비용이 큰 시각화 수행
if (e.type == EventType.Repaint)
{
DrawExpensiveVisualization();
}
// Layout 이벤트에서 데이터 준비
if (e.type == EventType.Layout)
{
PrepareData();
}
// 나머지 이벤트는 빠르게 처리
HandleInput(e);
}
Unity의 GUI 시스템은 한 프레임에 여러 번 OnGUI를 호출합니다. Layout, Repaint, 그리고 각종 입력 이벤트가 별도로 처리됩니다. 비용이 큰 작업은 Repaint 이벤트에서만 수행하고, 데이터 준비는 Layout 이벤트에서 하면 됩니다.
캐싱 활용
private static GUIStyle s_LabelStyle;
private static GUIContent s_LabelContent;
static void OnSceneGUI(SceneView sceneView)
{
// GUIStyle과 GUIContent는 한 번만 생성
if (s_LabelStyle == null)
{
s_LabelStyle = new GUIStyle(EditorStyles.boldLabel);
s_LabelStyle.normal.textColor = Color.yellow;
}
if (s_LabelContent == null)
{
s_LabelContent = new GUIContent("Label Text");
}
Handles.Label(Vector3.zero, s_LabelContent, s_LabelStyle);
}
GUIStyle과 GUIContent 생성은 비용이 있습니다. 매 프레임 새로 생성하는 대신 정적 필드에 캐싱하면 GC 압력을 줄일 수 있습니다.
결론
SceneView 확장은 Unity 에디터 개발에서 가장 강력한 기능 중 하나입니다. 엔진 소스코드를 분석해보면 Unity 팀이 왜 API를 그렇게 설계했는지 이해할 수 있고, 이를 통해 더 효율적이고 안정적인 에디터 도구를 만들 수 있습니다.
핵심 포인트를 정리하면 다음과 같습니다.
- duringSceneGui는 전역적인 SceneView 확장에 적합합니다. 특정 오브젝트와 관계없이 항상 표시되어야 하는 UI나 시각화에 사용합니다.
- Custom Editor의 OnSceneGUI는 특정 컴포넌트에 종속된 확장에 적합합니다. 해당 컴포넌트가 선택되었을 때만 활성화됩니다.
- EditorTool은 내장 Transform 도구처럼 배타적으로 작동하는 도구에 적합합니다. 툴바에 아이콘이 표시되고 사용자가 명시적으로 선택해야 합니다.
- 이벤트 처리 순서를 이해하면 입력을 적절히 가로채거나 기본 동작과 공존하게 할 수 있습니다.
- 성능을 위해 이벤트 타입별로 작업을 분리하고, GUIStyle과 GUIContent를 캐싱하세요.
SceneView 확장을 통해 팀의 워크플로우를 크게 개선할 수 있습니다. 레벨 디자이너가 더 빠르게 작업하고, 아티스트가 셰이더 결과를 쉽게 확인하고, 프로그래머가 디버깅 정보를 시각적으로 확인할 수 있게 됩니다.
'UNITY3D' 카테고리의 다른 글
| Scene View와 Game View의 차이점 (0) | 2026.01.04 |
|---|---|
| 알쓸신잡 - SurfaceData - ambiguous symbol (1) | 2025.12.12 |
| Unity URP Edge Fusion 간단 소개 및 분석 (0) | 2025.11.01 |
| Greedy Meshing(그리디 메싱) 이론, 처리 구조, 활용 이유 및 Voxel Chunk(복셀 청크) (0) | 2025.09.27 |
| Voxel 게임의 최적화 기술 및 최신 Voxel 렌더링 최적화 트렌드 분석 (0) | 2025.09.26 |