출처 : http://www.unitystudy.net/bbs/board.php?bo_table=writings&wr_id=146&sca=&sfl=wr_subject&stx=%EC%97%90%EB%94%94%ED%84%B0+%ED%99%95%EC%9E%A5&sop=and
unitystudy.net
1. 에디터 확장 01 - 커스텀 인스펙터
에디터
인스펙터
컴포넌트 인스펙터 확장
using UnityEngine;using System.Collections;public class CubeRotator : MonoBehaviour {public float rotationSpeed = 180.0f;void Update () {gameObject.transform.Rotate(Vector3.up, Time.smoothDeltaTime * rotationSpeed);}}
using UnityEngine;using UnityEditor;[CustomEditor(typeof(CubeRotator))]public class CubeRotatorInspector : Editor {public override void OnInspectorGUI(){base.DrawDefaultInspector();}}
UnityEditor.Editor 클래스에 대한 더 자세한 사항은 다음의 자료를 찾아보도록 하자.
using UnityEngine;using UnityEditor;[CustomEditor(typeof(CubeRotator))]public class CubeRotatorInspector : Editor{public override void OnInspectorGUI(){EditorGUILayout.LabelField("Custom Inspector for", "CubeRotator");base.OnInspectorGUI();if(GUILayout.Button("Change Direction")){CubeRotator cube = target as CubeRotator;if (cube){cube.rotationSpeed = -cube.rotationSpeed;}}}}
위에서 보는 것처럼 EditorGUILayout 과 GUILayout 모두 사용 가능하다. EditorGUILayout은 Field와 관련있는 메쏘드들이 대부분이다. GUILayout은 기본 필드, 스크롤바, 토글 등이 있다.자세한 사항은 다음 링크에서 알아보자.
여러 객체 편집하기
using UnityEngine;using UnityEditor;using System.Linq;using System;[CanEditMultipleObjects][CustomEditor(typeof(CubeRotator))]public class CubeRotatorInspector : Editor{public override void OnInspectorGUI(){EditorGUILayout.LabelField("Custom Inspector for", "CubeRotator");base.OnInspectorGUI();if (GUILayout.Button("Change Direction")){CubeRotator[] cubes = Array.ConvertAll(targets, _t => _t as CubeRotator);foreach (CubeRotator cube in cubes){if (cube){cube.rotationSpeed = -cube.rotationSpeed;}}}}}
보면 알겠지만 Array를 캐스팅할 때 Linq를 사용했다. Linq 구문 대신 foreach 문으로 대신해도 된다. 저 Linq 구문은 Object[]인 targets에서 _t 이름으로 하나씩 꺼낸 후 CubeRotator 로 컨버팅 한 배열을 돌려주는 것이다. 만약 Linq에 대해서 모른다면 최소한 기본 문법 만큼은 배워두도록 하자.
정리
2. 에디터 확장 02 - 메뉴 확장(1)
메뉴 확장
주메뉴 추가
using UnityEngine;using UnityEditor;public class CustomMenu{[MenuItem("Custom Menu/public custom Menu")]static public void CustomMenuItem(){Debug.Log("public customMenuItem");}[MenuItem("Custom Menu/protected custom Menu")]static protected void CustomMenuItemProtected(){Debug.Log("protected customMenuItem");}[MenuItem("Custom Menu/private custom Menu")]static private void customMenuItemPrivate(){Debug.Log("private customMenuItem");}}
public MenuItem(string itemName);public MenuItem(string itemName, bool isValidateFunction);public MenuItem(string itemName, bool isValidateFunction, int priority);
using UnityEngine;using UnityEditor;public class CustomMenu{[MenuItem("Custom Menu/public custom Menu %1")]static public void CustomMenuItem(){Debug.Log("public customMenuItem");}[MenuItem("Custom Menu/protected custom Menu #1")]static protected void CustomMenuItemProtected(){Debug.Log("protected customMenuItem");}[MenuItem("Custom Menu/private custom Menu %&1")]static private void customMenuItemPrivate(){Debug.Log("private customMenuItem");}}
using UnityEngine;using UnityEditor;public class ValidateMenu{[MenuItem("Custom Menu/Validate/Get GameObject Id", validate = true)]static public bool GetGameObjectIdValidator(){bool selected = (Selection.activeGameObject != null);Debug.Log(string.Format("Validate({0})", selected));if (selected)return true;return false;}[MenuItem("Custom Menu/Validate/Get GameObject Id", validate = false)]static public void GetGameObjectId(){Debug.Log(string.Format("GetGameObjectId({0})", Selection.activeInstanceID));}}
using UnityEngine;using UnityEditor;public class CustomMenu{[MenuItem("Custom Menu/public custom Menu", priority = 0)]static public void CustomMenuItem(){...}[MenuItem("Custom Menu/protected custom Menu", priority = -1)]static protected void CustomMenuItemProtected(){...}[MenuItem("Custom Menu/private custom Menu", priority = 1)]static private void customMenuItemPrivate(){...}}
정리
에디터 확장 03 - 메뉴 확장(2) Context Menu와 Generic Menu
컨텍스트 메뉴
using UnityEngine;using System.Collections;public class NewContext : MonoBehaviour {#if UNITY_EDITOR[UnityEditor.MenuItem("CONTEXT/Rigidbody/Push")]static void Push(UnityEditor.MenuCommand command){Rigidbody body = (Rigidbody)command.context;body.AddForce(Vector3.up * 500f);}#endif}
컨텍스트 메뉴라면 좀 더 편하게
using UnityEngine;using System.Collections;public class NewContext : MonoBehaviour {#if UNITY_EDITOR[UnityEditor.MenuItem("CONTEXT/Rigidbody/Push")]static void Push(UnityEditor.MenuCommand command){Rigidbody body = (Rigidbody)command.context;body.AddForce(Vector3.up * 500f);}#endif[ContextMenu("Turn")]void Turn(){gameObject.transform.Rotate(Vector3.up, 30);}}
Generic Menu
using UnityEngine;using System.Collections;using UnityEditor;public class ContextEditor : EditorWindow {[MenuItem("Window/Show Context Editor Window")]static void Init(){EditorWindow.GetWindow<ContextEditor>();}void OnGUI() {Event evt = Event.current;Rect contextRect = new Rect (10, 10, 100, 100);if (evt.type == EventType.ContextClick){Vector2 mousePos = evt.mousePosition;GenericMenu menu = new GenericMenu();menu.AddItem(new GUIContent("MenuItem1"), false, new GenericMenu.MenuFunction2(MenuArg1), "Menu Item 1");menu.AddItem(new GUIContent("MenuItem2"), false, new GenericMenu.MenuFunction2(MenuArg1), "Menu Item 2");menu.AddSeparator("");menu.AddItem(new GUIContent("SubMenu/MenuItem3"), false, new GenericMenu.MenuFunction(MenuNoArg));menu.ShowAsContext();evt.Use();}}void MenuNoArg(){Debug.Log("Selected menu");}void MenuArg1(object obj){Debug.Log ("Selected: " + obj);}}
function AddItem (content : GUIContent, on : boolean, func : MenuFunction) : voidfunction AddItem (content : GUIContent, on : boolean, func : MenuFunction2, userData : object) : void
결론
4. 에디터 확장 04 - EditorWindow
EditorWindow
이번에는 EditorWindow에 대해 알아보겠다.
EditorWindow는 유니티를 툴로 만들기 위해서 사용해야하는 클래스이다.
인스펙터와 달리 EditorWindow는 프로그래머가 원하는 데로 자유롭게 꾸밀 수 있게 해준다.
기본적인 사용법은 인스펙터와 거의 비슷하지만 훨씬 자유도가 있고 다양한 응용이 가능하다.
인스펙터는 컴포넌트가 반드시 게임오브젝트에 포함되어 있어야 하고 컴포넌트를 가진 게임 오브젝트를 선택해야만 보여진다.
그런 반면 EditorWindow는 언제든 창을 띄워둘 수 있어서 그야말로 툴에 적합한 클래스라고 할 수 있다.
이번에는 CanvasWindow 라는 EditorWindow를 확장시킨 클래스를 만들어보도록 한다.
캔버스를 만들자
위 Window는 우리가 이번 시간에 만들 EditorWindow의 확장 클래스인 CanvasWindow의 샘플이다.
펜의 색상과 크기를 고를 수 있고 팔레트에서 다양한 펜을 고를 수 있다(하지만 펜의 적용을 구현하지는 않을 것이다. 내용이 너무 길어지기 때문이다).
단순하게 메뉴를 생성하고 캔버스를 지우고 출력하는 간단한 구현만 해보도록 하겠다.
우선은 Editor\CanvasWindow.cs 파일을 생성한다.
그리고 윈도우를 띄우기 위해서 메뉴를 생성한다.
using UnityEngine; using UnityEditor; using System;public class CanvasWindow : EditorWindow { [MenuItem("Canvas/Show")] public static void ShowWindow() { CanvasWindow window = (CanvasWindow)EditorWindow.GetWindow(typeof(CanvasWindow)); } }
이 코드는 Canvas/Show 메뉴를 만든다.
EditorWindow.GetWindow() 를 이용해서 CanvasWindow의 인스턴스를 만든다. 만약에 창이 띄워져 있는 경우라면 해당 창을 가져오고, 생성한 창이 없다면 새로 창을 생성한다.
위처럼 Canvas 메뉴가 생겼고 Show를 누르면 아무것도 없는 윈도우가 생성된다.
이제 이 윈도우에 컨트롤을 배치한다.
여기에 배치할 컨트롤은 펜의 크기, 펜의 색상, 그리고 가운데 표시할 텍스쳐이다.
... Texture2D mytexture; Color penColor; int penSize; readonly Rect texSize = new Rect(0, 0, 300, 400); Rect windowRect = new Rect(3, 3, 100, 400); void OnGUI() { } }
가운데 그려지는 캔버스는 Texture가 되고 나머지는 int형 펜 크기, Color형 펜 색상이다.
나머지 rect 들은 각각 텍스처 크기와 팔렛트박스의 크기이다.
... void OnGUI() { if (mytexture == null) mytexture = new Texture2D((int)texSize.width, (int)texSize.height, TextureFormat.RGBA32, false); Rect canvasPos = new Rect((position.width - texSize.width) / 2, (position.height - texSize.height) / 2, texSize.width, texSize.height); EditorGUI.DrawPreviewTexture(canvasPos, mytexture); penColor = EditorGUILayout.ColorField("Pen Color", penColor); penSize = EditorGUILayout.IntSlider("Pen Size", penSize, 1, 10); } }
우선 GUI 로직에 들어와서 texture가 null 이라면 texture를 생성한다.
캔버스의 위치는 화면의 가운데이고, 텍스처를 먼저 그리고 나머지 컨트롤을 그린다.
만약 텍스처를 나중에 그리면 윈도우를 줄였을 때 컨트롤이 텍스처 아래로 들어가서 보이지 않는다.
텍스처를 먼저 그리면 컨트롤이 위에 그려져서 화면이 작을 때에도 불편함 없이 사용할 수 있다.
이제 팔레트 윈도우를 그려보자.
... void OnGUI() { ... BeginWindows(); windowRect = GUILayout.Window(1, windowRect, DoWindow, "Palette"); EndWindows(); } enum DrawMode { point, line, rectangle, ellipse, } DrawMode drawmode; void DoWindow(int id) { EditorGUILayout.BeginVertical(); drawmode = (DrawMode)GUILayout.SelectionGrid((int)drawmode, Enum.GetNames(typeof(DrawMode)), 1); if (GUILayout.Button("clear")) { mytexture = new Texture2D((int)texSize.width, (int)texSize.height, TextureFormat.RGBA32, false); } EditorGUILayout.EndVertical(); } }
이제 그럴싸하게 기본 뷰는 다 나왔다.
팔레트 윈도우를 만드는 방법은 EditorWindow.BeginWindow() 와 EditorWindow.EndWindows() 사이에 GUILayout.Window() 함수를 이용해서 새로운 윈도우를 생성하였다.
GUILayout.Window() 에서는 윈도우 GUI에 대한 콜백을 받는다. 여기서는 DoWindow(window_id) 함수를 콜백으로 지정했고 이 안에서 GUILayout.SelectionGrid를 이용해서 여러 메뉴가 선택가능하게 만들었다. 다만 각 메뉴의 처리는 우리의 주제와 맞지 않기 때문에 point만 처리하도록 했다.
그런데 문제가 발생했다. 팔렛트 윈도우가 마우스 드래그로 창의 위치를 바뀌지가 않는다.
윈도우의 메시지 콜백에 GUI.DragWindow() 를 불러주면 이 창은 움직일 수 있게 된다.
... void DoWindow(int id) { ... GUI.DragWindow(); } }
이제 텍스처에 그리기를 구현할 차례다.
다시 OnGUI() 함수의 가장 끝으로 돌아가서 마우스 이벤트에 대한 처리를 추가한다.
... void OnGUI() { ... if (Event.current.type == EventType.MouseDrag) { int bx = (int)(Event.current.mousePosition.x - canvasPos.x); int by = (int)(texSize.height - (Event.current.mousePosition.y - canvasPos.y)); for (int x = 0; x < penSize; ++x) { for (int y = 0; y < penSize; ++y ) mytexture.SetPixel(bx - penSize / 2 + x, by - penSize / 2 + y, penColor); } mytexture.Apply(false); Repaint(); } } ...
Event.current 객체에 마우스의 이벤트가 넘어온다.
MouseDrag 뿐만 아니라 다운이나 업에 관련된 이벤트도 있다.
MouseDrag 이벤트가 오면 캔버스에 그림을 그린다.
캔버스로 사용하고 있는 mytexture에 Texture2D.SetPixel() 함수를 이용해 텍스처를 편집하고 마지막으로 Texture2D.Apply() 를 이용해 텍스처에 적용해준다. 이때 인자로 들어가는 Boolean 값은 밉맵을 갱신 시킬지에 관련된 함수다.
텍스처에 대한 접근이 끝난 후에는 EditorWindow.Repaint() 함수를 불러서 윈도우를 갱신시켜야 한다.
이제 모든 작업이 완료되었다.
정리
이번에 살펴본 EditorWindow는 우리가 지금까지 배워온 것들을 응용해보는 과정에 불과했다.
그래서 하나의 예제를 구현하는 방식으로 진행하였고, 자잘한 처리들은 패스했다.
우리는 EditorwWindow를 상속받은 새로운 윈도우를 생성하는 법과 텍스처를 직접 편집하고 그려보는 일도 해보았다.
이것을 조금 활용하면 다양한 툴을 만들 수 있을 것이다.
이 강좌의 전체 코드는 여기에 있다: https://gist.github.com/whoo24/5334402
다음 강에서는 Handles와 Gizmos에 대해서 배울 것이다.
'TA > Unity' 카테고리의 다른 글
VSync - WaitForTargetFPS (0) | 2013.09.27 |
---|---|
유니티 NGUI 논리 해상도와 픽셀 퍼펙트 (0) | 2013.09.11 |
로딩 페이지 및 로딩 프로그래스바 사용하기. (0) | 2013.09.11 |
효과적인 C# 메모리 관리 기법 (1) | 2013.09.09 |
[link] C#, Unity3D Optimization for Mobiles (0) | 2013.09.09 |