커스텀 에디터(Custom Editor)
Unity는 게임 개발뿐 아니라 에디터 자체도 커스터마이징할 수 있는 기능을 제공한다. Inspector 창의 UI를 바꾸거나 새로운 창을 만들어서 툴처럼 쓸 수 있다.이 기능은 대부분 Editor 폴더 안에 스크립트를 넣고 사용한다.
핵심 클래스들
Editor: 기존 컴포넌트의 Inspector 커스터마이징
EditorWindow: 독립적인 에디터 창 생성
GUI/GUILayout: 즉시 모드 GUI 시스템 (레거시)
EditorGUI/EditorGUILayout: 에디터 전용 GUI 컨트롤
에디터 편집을 위해 작성한 스크립트들을 저장하는 Editor 폴더가 존재한다.
Editor 폴더란?
Unity에서 Editor 전용 API를 사용할 수 있도록 만든 전용 폴더이다.
특징
실행시점 : 런타임(게임 실행 중)에서 동작하지 않음 (에디터에서만 작동)
위치 : Assets폴더 하위라면 Editor폴더의 위치는 중요하지 않음(여러개 생성 가능)
빌드 에러 주의 : Editor 관련 코드는 반드시 Editor 폴더 안에 있어야 빌드 에러 안 남
예외 처리 : 만약 Editor 폴더 밖에서 에디터 코드를 작성해야 한다면 전처리기를 사용할 수 있다.
#if UNITY_EDITOR
using UnityEditor;
public void EditorOnlyFunction()
{
Debug.Log("이 코드는 에디터에서만 실행됩니다!");
}
#endif
전처리기(Preprocessor) 란?
전처리기는 코드가 실제로 실행되기 전에 컴파일러가 "이 코드를 포함할지 말지"를 미리 결정하는 시스템이다.
Unity에서는 특정 환경(에디터, 안드로이드 등)에서만 동작해야 하는 코드를 작성할 때 매우 유용하게 사용된다.
쉽게 비유로 알아보자
상황 : 매운맛 선택
만약 매운맛을 좋아하는 사람이라면
고추 2개 추가
그렇지 않다면
이 부분은 건너뛰기
이렇듯 상황에 따라 코드를 사용하는 것이다.
Unity에서 왜 필요한가요?
Unity는 게임을 여러 환경(에디터, 안드로이드, IOS)등에서 돌릴 수 있다. 그런데 모든 환경에서 똑같은 코드를 실행하면 문제가 생길 수 있다. 그러므로 각 환경마다 다른 코드가 필요한 경우가 많다.
예시로 더 알아보자
아래 코드를 안드로이드용으로 빌드하면 에러가 발생한다. 왜냐하면 AssetDatabase는 에디터에서만 존재하는 기능이기 때문이다.
using UnityEngine;
using UnityEditor; // ← 이건 에디터에서만 사용 가능
public class MyScript : MonoBehaviour
{
void Start()
{
Debug.Log("게임 시작!");
// 에디터 전용 기능
AssetDatabase.Refresh(); // ← 안드로이드에서는 이 기능이 없어서 에러 발생
}
}
아래 코드와 같이 전처리기로 감싸줘야한다.
using UnityEngine;
using UnityEditor; // ← 이건 에디터에서만 사용 가능
public class MyScript : MonoBehaviour
{
void Start()
{
Debug.Log("게임 시작!");
// 전처리기 사용
#if UNITY_EDITOR
AssetDatabase.Refresh();
#endif
}
}
Unity에서 전처리기
01 기본 구조
// 기본 구조
#if 조건
// 조건이 참일 때만 실행되는 코드
#endif
// if-else 구조
#if 조건A
// 조건A가 참일 때
#elif 조건B
// 조건A는 거짓이고 조건B가 참일 때
#else
// 모든 조건이 거짓일 때
#endif
// 복합 구조
#if UNITY_EDITOR && UNITY_ANDROID
// 에디터이면서 동시에 안드로이드 타겟일 때
#endif
#if UNITY_IOS || UNITY_ANDROID
// iOS이거나 안드로이드일 때
#endif
#if !UNITY_EDITOR
// 에디터가 아닐 때 (실제 빌드된 게임에서)
#endif
02 플랫폼별 전처리
#if UNITY_ANDROID
Debug.Log("Android에서 실행 중");
#endif
03 실제 예시
1) 에디터 전용 디버그 기능
public class PlayerController : MonoBehaviour
{
public float speed = 5f;
void Update()
{
// 일반적인 플레이어 이동
float horizontal = Input.GetAxis("Horizontal");
transform.Translate(Vector3.right * horizontal * speed * Time.deltaTime);
#if UNITY_EDITOR
// 에디터에서만 작동하는 치트 기능
if (Input.GetKeyDown(KeyCode.T))
{
speed *= 2f;
Debug.Log("에디터 치트: 속도 2배 증가!");
}
// 에디터에서만 보이는 디버그 정보
Debug.DrawRay(transform.position, Vector3.up * 2f, Color.red);
#endif
}
}
2) 플랫폼별 다른 UI크기
public class UIManager : MonoBehaviour
{
void Start()
{
float buttonSize;
#if UNITY_ANDROID || UNITY_IOS
// 모바일에서는 버튼을 크게
buttonSize = 80f;
Debug.Log("모바일용 큰 버튼 사용");
#else
// PC에서는 버튼을 작게
buttonSize = 50f;
Debug.Log("PC용 작은 버튼 사용");
#endif
// 버튼 크기 적용
GetComponent<RectTransform>().sizeDelta = new Vector2(buttonSize, buttonSize);
}
}
Editor Default Resources 폴더
Resources 폴더와 마찬가지로 에디터 전용 리소스(이미지, 아이콘 등)를 저장하는 특별한 폴더이다.
var test = EditorGUIUtility.Load("SampleText.png");
Editor 클래스
Editor 클래스는 Unity의 Inspector 창을 커스터마이징할 수 있게 해준다.
예를 들어 버튼을 추가하거나 슬라이더로 바꾸거나 더 복잡한 UI를 넣고 싶을 때 Editor 클래스를 만들어 인스펙터를 "내 맘대로" 그리는 기능을 제공한다.
코드 예시
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(MyComponent))]
public class MyComponentEditor : Editor
{
public override void OnInspectorGUI()
{
MyComponent comp = (MyComponent)target;
comp.value = EditorGUILayout.IntField("값", comp.value);
if (GUI.changed)
EditorUtility.SetDirty(comp);
}
}
01 CustomEditor(typeof(MyComponent))
CustomEditor는 이 에디터 클래스가 어떤 컴포넌트 또는 ScriptableObject를 편집할 것인지 지정하는 어트리뷰트(Attribute)이다.
// 기본 사용법
[CustomEditor(typeof(MyComponent))]
// 상속 클래스에도 적용
[CustomEditor(typeof(BaseComponent), true)]
// 여러 타입에 같은 에디터 적용
[CustomEditor(typeof(ComponentA))]
[CustomEditor(typeof(ComponentB))]
public class MultiComponentEditor : Editor { }
// CanEditMultipleObjects - 여러 객체 동시 편집 허용
[CustomEditor(typeof(MyComponent))]
[CanEditMultipleObjects]
public class MyComponentEditor : Editor { }
02 OnInspectorGUI()
Unity는 인스펙터를 그릴 때 매 프레임마다 이 메서드를 호출한다. EditorGUILayout, EditorGUI를 사용해 원하는 UI를 자유롭게 배치할 수 있다.
public override void OnInspectorGUI()
{
MyComponent comp = (MyComponent)target;
comp.value = EditorGUILayout.IntField("값", comp.value);
}
EditorGUILayout.IntField()는 숫자 입력 칸이 나오고 이 안에 값을 넣으면 화면에 인풋 필드가 만들어지고 사용자 입력을 받을 수 있다.
중요 포인트
직접 comp.value = ...처럼 값을 바꾸면 Unity가 “얘가 언제, 왜 바뀌었는지”를 기억하지 못한다.
그러므로 Undo/Redo도 안 되고 Prefab에도 반영 안 될 수 있다. 그래서 나온 개념이 Undo, SetDirty, serializedObject이다.
03 target
target은 현재 내가 커스터마이징하고 있는 오브젝트이다. 즉 유니티에서 직므 선택한 컴포넌트 객체
MyComponent comp = (MyComponent)target;
object 타입이며, 보통 (MyType)target으로 캐스팅해서 사용한다. targets는 멀티 선택 시 모든 오브젝트 배열을 제공한다.
위에서 설명했듯이 값을 직접 수정하면 Undo나 Prefab 시스템과의 연동은 안된다. 그래서 이럴 때는 serializedObject 사용해야 한다.
04 serializedObject
Unity가 내부적으로 변수 상태를 안전하게 관리하기 위해 만든 구조이다. 이를 사용하면 Undo/Redo 자동 지원, Prefab 변경 자동 추적, 여러 개 선택해도 동기화 가능하다.
public override void OnInspectorGUI()
{
// 안전한 방법 (Undo/Redo 지원)
serializedObject.Update(); // 최신 데이터
// 프로퍼티 찾기
SerializedProperty healthProp = serializedObject.FindProperty("health");
EditorGUILayout.PropertyField(healthProp); // UI로 보여줌
serializedObject.ApplyModifiedProperties(); // 변경사항 적용
}
SerializedObject는 target을 래핑한 구조체이다. 내부의 SerializedProperty를 통해 변수 접근한다.
FindProperty("value")에 전달하는 문자열은 해당 클래스 필드 이름과 일치해야 한다. 그리고 직렬화된 필드만 접근 가능하다. 그러므로 public이거나 [SerializeField]여야 한다.
05 직접 수정했는데 Undo가 되도록 만들고 싶은 경우
Undo.RecordObject() - 변경 전 상태를 기억하게 한다.
EditorUtility.SetDirty() - 오브젝트가 변경되었다고 Unity에 알려준다.
public override void OnInspectorGUI()
{
MyComponent comp = (MyComponent)target;
Undo.RecordObject(comp, "값 변경"); // 되돌리기 등록
comp.value = EditorGUILayout.IntField("값", comp.value);
if (GUI.changed)
EditorUtility.SetDirty(comp); // 변경사항 저장하도록 표시
}
그래도 serializedObject 방식을 사용하는걸 더 추천한다!
06 캐싱 활용
[CustomEditor(typeof(MyComponent))]
public class MyComponentEditor : Editor
{
private SerializedProperty healthProp;
private SerializedProperty nameProp;
private SerializedProperty itemListProp;
private void OnEnable()
{
// 프로퍼티들을 미리 캐싱
healthProp = serializedObject.FindProperty("health");
nameProp = serializedObject.FindProperty("characterName");
itemListProp = serializedObject.FindProperty("itemList");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
// 캐싱된 프로퍼티 사용
EditorGUILayout.PropertyField(healthProp);
EditorGUILayout.PropertyField(nameProp);
EditorGUILayout.PropertyField(itemListProp);
serializedObject.ApplyModifiedProperties();
}
}
EditorWindow 클래스 – 독립적인 창 생성
EditorWindow는 Unity 에디터 내에서 새로운 창(Window)을 생성하고 UI를 구성할 수 있도록 해주는 클래스이다.
실제 사용 예시들
아이템 생성 도구: 게임의 아이템을 대량으로 생성하는 도구
레벨 에디터: 맵을 쉽게 편집할 수 있는 도구
퀘스트 편집기: 퀘스트를 시각적으로 관리하는 도구
리소스 관리 도구: 프로젝트의 리소스를 정리하는 도구
예시 코드
using UnityEditor;
using UnityEngine;
public class MyEditorWindow : EditorWindow
{
private string inputText = "Hello";
[MenuItem("Tools/My Window")]
public static void ShowWindow()
{
GetWindow<MyEditorWindow>("My Window");
}
private void OnGUI()
{
inputText = EditorGUILayout.TextField("텍스트 입력", inputText);
if (GUILayout.Button("출력"))
{
Debug.Log($"입력값: {inputText}");
}
}
}
01 윈도우 생성 - EditorWindow.CreateInstance<T>()
특정타입의 인스턴스를 생성하는 메서드
// //TestWindow 타입의 EditorWindow를 새로 하나 만들어주는 것
var window = CreateInstance<TestWindow>();
왜 new 대신 CreateInstance를 써야 하나요?
Unity에서 EditorWindow나 ScriptableObject 같은 클래스는 일반적인 방식인 new 키워드로 생성해서는 제대로 작동하지 않습니다. 겉보기에는 객체가 생성된 것처럼 보이지만, Unity 내부 시스템(에디터 이벤트 처리, 메모리 관리, 직렬화 등)과 연결되지 않기 때문에 실제로는 정상적으로 작동하지 않거나 완전히 무시됩니다.
예를 들어, EditorWindow를 new TestWindow()처럼 생성하면 컴파일은 되지만 Unity가 이 창을 에디터 UI에 띄우지 않습니다. 반면 CreateInstance<TestWindow>()로 생성하면 Unity가 내부적으로 "아, 이건 내가 관리해야 할 창이구나!"라고 인식하고, 에디터 UI에 정상적으로 창을 표시해줍니다.
new로 만들면 어떻게 되나요?
컴파일은 될 수도 있지만, Unity의 내부 시스템에 등록되지 않아서 동작하지 않거나 예외 발생한다
예: 에디터 창을 띄우려 해도 창이 보이지 않거나, 이벤트가 작동하지 않을 수 있다.
// 잘못된 방식
var window = new TestWindow(); // 컴파일 에러는 없지만 Unity 내부 연결이 안 됨
// 올바른 방식
var window = CreateInstance<TestWindow>();
비유로 더 쉽게 알아보자
🏠 집 (Unity 에디터)
├── 🚪 정문 (Unity 시스템)
├── 🪟 창문들 (EditorWindow들)
new로 창 만들기 = 집 밖에 창문 설치 (집과 연결 안됨)
CreateInstance로 창 만들기 = 집 안에 창문 설치 (Unity가 관리함)
02 윈도우 출력
1) Show() - 일반 탭 윈도우
window.Show(); // 다른 에디터 창들과 함께
2) ShowUtility() - 독립적인 윈도우
window.ShowUtility(); // 탭으로 합쳐지지 않는 독립 창
3) ShowPopup() - 팝업 윈도우( 타이틀, 닫기 버튼이 없는 윈도우 출력 )
window.ShowPopup(); // 제목 표시줄과 닫기 버튼이 없는 팝업
팁
ShowPopup()을 사용할 때는 스크립트 내부에서 창을 닫을 수 있는 방법을 추가해야한다.
private void OnGUI()
{
if (GUILayout.Button("닫기") || Event.current.keyCode == KeyCode.Escape)
{
this.Close(); // 창 닫기
}
}
04 윈도우 중복 생성 방지
아래와 같이 적으면 매번 새 창이 생성된다.
// 새로운 윈도우를 생성
var window = CreateInstance<TestWindow>();
GetWindow<T>()
창을 띄움 (T는 EditorWindow 파생 클래스)
[MenuItem("Window/TestWindow")]
public static void Setup()
{
// GetWindow는 자동으로 중복 생성을 방지한다.
TestWindow window = GetWindow<TestWindow>();
window.titleContent = new GUIContent("My Test");
window.Show();
}
05 특정 창에 탭으로 붙이기
[MenuItem("Tools/Attach to Scene View")]
public static void AttachToSceneView()
{
// Scene View와 같은 구역에 탭으로 표시
GetWindow<MyTool>(typeof(SceneView));
}
[MenuItem("Tools/Attach to Inspector")]
public static void AttachToInspector()
{
// Inspector와 같은 구역에 탭으로 표시
GetWindow<MyTool>(typeof(EditorWindow).Assembly.GetType("UnityEditor.InspectorWindow"));
}
IMGUI(Immediate Mode GUI) 시스템의 기본 클래스
즉시 모드 그래픽 사용자 인터페이스라고 부르며 Unity의 에디터 UI 시스템 중 하나이다. 쉽게 말해서 매 프레임미다 GUI를 코드로 그리는 방식이다. Unity는 OnGUI() 또는 OnInspectorGUI() 같은 메서드를 통해 UI를 매번 코드로 다시 그린다(draw) 그래서 즉시(Immediate)라고 부른다.
// 일반적인 UI (uGUI) - 한 번 생성하면 유지됨
Button myButton = CreateButton();
myButton.text = "클릭하세요";
// IMGUI - 매 프레임마다 다시 그려짐
void OnGUI()
{
if (GUI.Button(new Rect(10, 10, 100, 30), "클릭하세요"))
{
// 버튼이 클릭되었을 때의 처리
}
}
EditorGUI – 에디터 전용 수동 배치
EditorGUI는 Unity 에디터에서만 사용할 수 있는 더 고급 GUI 컨트롤들을 제공한다. 위치를 직접 계산해서 Rect로 지정해줘야 한다.
public override void OnInspectorGUI()
{
Rect r = EditorGUILayout.GetControlRect(); // 일단 한 줄 확보
r.height = 18;
EditorGUI.LabelField(r, "수동 레이블");
r.y += 20;
EditorGUI.IntSlider(r, serializedObject.FindProperty("health"), 0, 100, "체력");
}
rect.y 조정을 잘못하면 UI가 겹치는 문제가 발생할 수 있다. 아래 이미지처럼..
위치 계산 팁
EditorGUI는 Rect 좌표 수동 조작이 필요합니다. 일반적으로 rect.y += height를 반복해서 다음 줄로 이동시킨다.
EditorGUILayout.GetControlRect()를 써서 자동 배치도 가능하다.
// 수동 계산
rect.y += 20;
EditorGUI.IntField(rect, "체력", 100);
// 자동 계산
Rect rect = EditorGUILayout.GetControlRect();
EditorGUI.LabelField(rect, "자동 계산된 위치에 텍스트 출력");
EditorGUI는 위치 보정이 필요해서 자동 배치가 되는 EditorGUILayout을 많이 사용한다.
EditorGUILayout – 에디터 전용 자동 배치
Unity의 에디터 확장에서 가장 널리 사용되는 GUI 클래스이다. GUI 요소들을 자동으로 위치 계산 없이 아래로 정렬해주므로 Rect 계산 없이도 빠르게 에디터 툴을 제작할 수 있다.
1) LabelField
단순한 텍스트를 표시한다. 사용자의 입력을 받지 않는다.
용도: 구분선, 설명 텍스트 등
EditorGUILayout.LabelField("이것은 레이블입니다.");
EditorGUILayout.LabelField("타이틀", "서브 텍스트");
2) TextField
문자열(string) 입력 필드, 변수에 직접 연결 가능
comp.name = EditorGUILayout.TextField("이름", comp.name);
3) IntField, FloatField
숫자(int, float) 값을 입력받는 필드
comp.health = EditorGUILayout.IntField("체력", comp.health);
comp.speed = EditorGUILayout.FloatField("속도", comp.speed);
4) Slider
슬라이더로 숫자 값을 조절
comp.volume = EditorGUILayout.Slider("볼륨", comp.volume, 0f, 1f);
5) Toggle
참/거짓 값을 체크박스로 표현
comp.isVisible = EditorGUILayout.Toggle("표시 여부", comp.isVisible);
6) ObjectField
Unity 오브젝트(GameObject, ScriptableObject 등)를 할당받는 필드
comp.target = EditorGUILayout.ObjectField("타겟 오브젝트", comp.target, typeof(GameObject), true) as GameObject;
주의: 마지막 인자는 true면 하위 오브젝트까지 허용함
7) ColorField
색상 선택 필드
comp.tintColor = EditorGUILayout.ColorField("색상", comp.tintColor);
8) Vector2Field, Vector3Field, Vector4Field
벡터(Vector) 값을 입력
comp.offset = EditorGUILayout.Vector3Field("오프셋", comp.offset);
9) EnumPopup
열거형(enum)을 드롭다운 메뉴로 표시
comp.state = (MyComponent.State)EditorGUILayout.EnumPopup("상태", comp.state);
10) Popup
문자열 리스트 중 하나 선택 (일반적인 드롭다운).
string[] options = { "Option A", "Option B", "Option C" };
comp.index = EditorGUILayout.Popup("옵션", comp.index, options);
11) Foldout
접히는 영역(펼침/접힘 토글)
comp.showAdvanced = EditorGUILayout.Foldout(comp.showAdvanced, "고급 설정");
if (comp.showAdvanced)
{
EditorGUI.indentLevel++;
comp.advancedSpeed = EditorGUILayout.FloatField("고급 속도", comp.advancedSpeed);
EditorGUI.indentLevel--;
}
indentLevel
EditorGUI.indentLevel은 Unity 에디터에서 GUI 요소의 들여쓰기 수준을 조절할 수 있는 정적 변수
예시
EditorGUILayout.LabelField("1단계");
EditorGUI.indentLevel++;
EditorGUILayout.LabelField("2단계 - 들여쓰기 1");
EditorGUI.indentLevel++;
EditorGUILayout.LabelField("3단계 - 들여쓰기 2");
EditorGUI.indentLevel = 0; // 초기화
EditorGUILayout.LabelField("다시 0단계");
결과
1단계
2단계 - 들여쓰기 1
3단계 - 들여쓰기 2
다시 0단계
12) HelpBox
메시지를 정보, 경고, 오류 형태로 표시
EditorGUILayout.HelpBox("데이터가 유효하지 않습니다.", MessageType.Error);
13) GUI.enabled 사용
GUI.enabled = comp.isReady;
if (GUILayout.Button("준비 상태일 때만 실행"))
{
Debug.Log("실행됨");
}
GUI.enabled = true;
14) DisplayDialog + backgroundColor
Color prevColor = GUI.backgroundColor;
GUI.backgroundColor = Color.red;
if (GUILayout.Button("위험한 작업"))
{
if (EditorUtility.DisplayDialog("경고", "정말 삭제하시겠습니까?", "예", "아니오"))
{
Debug.Log("삭제됨");
}
}
GUI.backgroundColor = prevColor;
Editor 실제 예시 : EnemyHealth
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(HealthManager))]
public class HealthManagerEditor : Editor
{
public override void OnInspectorGUI()
{
HealthManager hm = (HealthManager)target;
// 기본 필드
hm.characterName = EditorGUILayout.TextField("이름", hm.characterName);
hm.maxHP = EditorGUILayout.IntField("최대 HP", hm.maxHP);
hm.currentHP = EditorGUILayout.IntSlider("현재 HP", hm.currentHP, 0, hm.maxHP);
// 체력 바 시각화
float hpPercent = hm.maxHP > 0 ? (float)hm.currentHP / hm.maxHP : 0f;
Rect rect = GUILayoutUtility.GetRect(100, 20);
EditorGUI.ProgressBar(rect, hpPercent, "HP");
EditorGUILayout.Space();
// 버튼: 피해 입기
if (GUILayout.Button("10 데미지 입기"))
{
hm.TakeDamage(10);
}
if (GUILayout.Button("체력 리셋"))
{
hm.ResetHP();
}
// 변경 사항 저장
if (GUI.changed)
{
EditorUtility.SetDirty(hm);
}
}
}
Editor 적용 전
Editor 적용 후
EditorWindow 실제 예시 : ItemCreator
ItemCreator는 Unity 에디터 상단 메뉴에 "Window > Item Creator" 라는 도구를 만들고 해당 창에서 사용자 입력을 받아 ItemData.asset을 자동 생성하는 간단한 툴이다.
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
public class ItemCreator : EditorWindow
{
private string itemName = "New Item";
private ItemType itemType = ItemType.Weapon;
private int quantity = 1;
private bool isMulitple = false;
// Window > Item Creator 메뉴 항목 추가
[MenuItem("Window/Item Creator")]
private static void ShowWindow()
{
GetWindow<ItemCreator>("아이템 생성");
}
private void OnGUI()
{
itemName = EditorGUILayout.TextField("아이템 명", itemName);
itemType = (ItemType)EditorGUILayout.EnumPopup("아이템 종류", itemType);
quantity = EditorGUILayout.IntField("수량", quantity);
isMulitple = EditorGUILayout.Toggle("복수 보유 여부", isMulitple);
if (GUILayout.Button("아이템 생성"))
{
ItemData data = ScriptableObject.CreateInstance<ItemData>();
data.itemName = itemName;
data.itemType = itemType;
data.quantity = quantity;
data.isMultiple = isMulitple;
AssetDatabase.CreateAsset(data, $"Assets/{itemName}.asset");
AssetDatabase.SaveAssets();
}
}
}
아래와 같이 에디터 창으로 쉽게 ScritableObject를 만들 수 있다.
'유니티 공부 > Unity' 카테고리의 다른 글
Unity - Animation.Play vs Animation.CrossFade +) 애니메이션 전환 기법들 (0) | 2025.07.02 |
---|---|
Unity - Unity에서 도형을 그려보자(패턴 기반이동, 스킬범위 시각화 ) (0) | 2025.06.30 |
Unity - Unity에서 수학 개념을 이해해보자 2편 - 행렬,동차 좌표계,TRS, 아크 탄젠트 (0) | 2025.06.09 |
Unity - 캐싱(Caching) +) FSM 패턴,GetComponent ,Distance (2) | 2025.06.04 |
Unity - Unity에서 수학 개념을 이해해보자 1편 - 벡터,내적,외적 (0) | 2025.05.23 |
댓글