코드가 커지면 한 파일에 다 넣기 어렵고, 남이 만든 코드와 이름이 겹치기도 합니다. 이번 편에서는 클래스를 여러 파일로 나누는 partial, 같은 이름을 구분하는 namespace 와 using 별칭, 그리고 반복 작업을 줄이는 에디터 메뉴를 봅니다.
2024년 부산 콘텐츠 아카데미 퍼스널 트레이닝 수업에서 MyLesson Unity 실습 프로젝트의 Strategy_09_Partial.cs · Strategy_11_Namespace.cs · ContextMenu.cs 로 가르친 내용입니다. 수업은 다섯 단계로 나뉘고, 단계마다 코드가 조금씩 바뀝니다.
한 클래스를 두 파일에
public partial class LobbyScene : MonoBehaviour
{
public LobbyScene()
{
OnConstruction();
}
public override string ToString()
{
string ret = GetName();
return ret;
}
public void Destroy()
{
Program.WriteLine("Before OnLobbyDestroyed()");
OnLobbyDestroyed();
Program.WriteLine("After OnLobbyDestroyed()");
}
private partial void OnConstruction();
partial void OnLobbyDestroyed();
}public partial class LobbyScene : MonoBehaviour
{
private partial void OnConstruction()
{
Program.WriteLine("Lobby scene has been created.");
}
private string GetName()
{
return "LobbyScene";
}
}두 파일 모두 public partial class LobbyScene 입니다. 컴파일러는 둘을 합쳐 한 클래스로 만듭니다. 첫 파일의 ToString 이 둘째 파일의 GetName() 을 부를 수 있는 이유입니다. 자동으로 만들어지는 코드(UI 바인딩, 직렬화 코드)와 사람이 쓰는 코드를 나눌 때 많이 씁니다.
public void Run()
{
LobbyScene lobbyScene = new LobbyScene();
Program.WriteLine("Scene Name: {0}", lobbyScene);
lobbyScene.Destroy();
}Lobby scene has been created.
Scene Name: LobbyScene
Before OnLobbyDestroyed()
After OnLobbyDestroyed()| partial 메서드 | 구현이 없으면 |
|---|---|
partial void OnLobbyDestroyed(); (접근 제한자 없음) | 호출 자체가 컴파일에서 지워진다. 그래서 Before 와 After 사이에 아무것도 없다 |
private partial void OnConstruction(); (C# 9, 접근 제한자 있음) | 구현이 반드시 있어야 한다. 없으면 컴파일 오류 |
LobbyScene 은 MonoBehaviour 를 상속하는데 new LobbyScene() 으로 만들었습니다. 수업에서 partial 만 보여 주려고 그렇게 했고, Unity 는 이때 경고를 냅니다. 실제 컴포넌트는 AddComponent<LobbyScene>() 로 만들어야 합니다.
같은 이름의 GameObject 가 둘
namespace NameSP1
{
public class GameObject
{
public override string ToString()
{
return "Fake GameObject";
}
}
}using NameSP1;
using UnityEngine;
public class Strategy_11_Namespace : Strategy
{
public void Run()
{
// Error
GameObject error = new GameObject();
}NameSP1 에도, UnityEngine 에도 GameObject 가 있습니다. 두 namespace 를 모두 using 하면, 컴파일러는 GameObject 가 어느 쪽인지 알 수 없어 오류(CS0104)를 냅니다.
using FakeGameObject = NameSP1.GameObject;
using RealGameObject = UnityEngine.GameObject;
public class Strategy_11_Namespace : Strategy
{
public void Run()
{
NameSP1.GameObject go1 = new NameSP1.GameObject();
UnityEngine.GameObject ugo1 = new UnityEngine.GameObject();
Program.WriteLine(go1);
Program.WriteLine(ugo1);
Program.WriteLine();
FakeGameObject go2 = new FakeGameObject();
RealGameObject ugo2 = new RealGameObject();
Program.WriteLine(go2);
Program.WriteLine(ugo2);Fake GameObject
New Game Object (UnityEngine.GameObject)
Fake GameObject
New Game Object (UnityEngine.GameObject)완전한 이름
NameSP1.GameObject, UnityEngine.GameObject 처럼 namespace 까지 적는다
using 별칭
using FakeGameObject = NameSP1.GameObject; 로 이름을 따로 붙인다
필요 없는 경고 끄기
#pragma warning disable CS0414
public class Strategy_11_Namespace : Strategy
{
private int noAffectedValue = 0;쓰지 않는 private 필드에 값을 넣으면 컴파일러가 경고(CS0414)를 냅니다. #pragma warning disable CS0414 는 그 파일에서 이 경고를 끕니다. 경고는 대개 실수를 알려 주는 것이니, 끄는 범위는 좁게 잡고 이유를 주석으로 남기는 편이 좋습니다.
에디터 메뉴로 경로 복사하기
수업 코드에는 Program.FindObject("/Canvas/Game/Start/Button (Play)") 처럼 오브젝트 경로가 자주 나옵니다. 손으로 적으면 틀리기 쉽습니다. 그래서 하이어라키에서 오브젝트를 고르고 메뉴를 누르면 경로를 클립보드에 복사하는 에디터 기능을 만들었습니다.
[MenuItem("GameObject/*Copy Object Path", false, 0)]
static void CopyObjectPath()
{
if (UnityEditor.Selection.activeGameObject == null) return;
string path = "";
Transform trans = UnityEditor.Selection.activeGameObject.transform;
while (trans != null)
{
if (trans.gameObject.name != "Canvas(Environment)")
if (path != "")
path = trans.gameObject.name + "/" + path;
else
path = trans.gameObject.name;
trans = trans.parent;
if (trans != null && trans.parent != null && trans.parent.gameObject.name == "Canvas(Environment)")
break;
}
GUIUtility.systemCopyBuffer = "/" + path;
}[MenuItem("Assets/*Copy Asset Path", false, 0)]
static void CopyAssetPath()
{
if (UnityEditor.Selection.activeObject == null) return;
string path = AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[0]).Replace("Assets/Resources/", string.Empty);
int dotPos = path.LastIndexOf(".");
if (dotPos >= 0)
{
path = path.Substring(0, dotPos);
}
GUIUtility.systemCopyBuffer = path;
}- [MenuItem("GameObject/…")]하이어라키 우클릭 메뉴에 추가
- Selection.activeGameObject지금 고른 오브젝트
- parent 를 거슬러 오름경로 조립
- GUIUtility.systemCopyBuffer클립보드에 넣기
에셋 쪽 메뉴는 Project 창에서 고른 파일의 경로를 Resources.Load 에 바로 쓸 수 있는 모양(Resources/ 와 확장자를 뺀 경로)으로 복사합니다.
using UnityEditor 를 쓰는 코드는 게임 빌드에 들어갈 수 없습니다. 이 파일은 Editor 폴더에 두거나 #if UNITY_EDITOR 로 감싸야 빌드가 깨지지 않습니다. 클래스 이름 ContextMenu 는 Unity 의 [ContextMenu] 속성과 이름이 같으니 다른 이름이 더 안전합니다.
돌아보며
이번 편에서 얻은 것
- partial 로 한 클래스를 여러 파일에 나눈다. 구현 없는 partial void 는 호출째 사라진다
- 이름이 겹치면 완전한 이름이나 using 별칭으로 구분한다
- MenuItem 으로 반복 작업을 에디터 메뉴로 만든다
아쉬운 점
- MonoBehaviour 를 new 로 만드는 예라 Unity 가 경고를 낸다
- 에디터 코드가 일반 스크립트 폴더에 있어 빌드할 때 문제가 될 수 있다
정리
- partial 은 한 클래스를 여러 파일로 나눕니다. 자동 생성 코드와 손으로 쓴 코드를 나누는 데 씁니다.
- namespace 는 이름의 성(姓)입니다. 겹치면 완전한 이름이나 using 별칭으로 부릅니다.
- 반복 작업은 도구로 만드세요. MenuItem 몇 줄로 경로를 손으로 적는 실수를 없앴습니다.
다음 편에서는 어디서나 꺼내 쓰는 static 클래스로 플레이어 데이터를 저장하고, SQLite 데이터베이스에서 캐릭터 정보를 읽어 옵니다.