포슀트

🫐 WitchMendokusai DevLog 02

🎲 _


이번 μΌμ§€μ—μ„œλŠ” 에셋듀을 κ΄€λ¦¬ν•˜κΈ° μœ„ν•œ 툴 κΈ°λ°˜μ„ λ§Œλ“€μ–΄λ³Έλ‹€.


🎲 UI Toolkit, 기본적인 λ ˆμ΄μ•„μ›ƒ


전에 νˆ΄μ„ λ§Œλ“€μ–΄λ³΄λ €κ³  UI ToolKit을 잠깐 곡뢀해본 적이 μžˆλ‹€.
μ΄μ–΄μ„œ UI ToolKit으둜 λ§Œλ“€μ–΄λ³Έλ‹€.

기본적인 λ ˆμ΄μ•„μ›ƒμ€ μœ„ μ˜μƒμ„ μ°Έκ³ ν–ˆλ‹€.
μ˜μƒ μ„€λͺ…λž€μ— 적힌 λ§ν¬μ—μ„œ μžμ„Έν•œ νŠœν† λ¦¬μ–Όμ„ μ œκ³΅ν•œλ‹€.

ν•΄λ‹Ή νŠœν† λ¦¬μ–Όμ—μ„œλŠ” λŸ°νƒ€μž„μ—μ„œ μ‚¬μš©ν•˜κΈ° μœ„ν•œ 인벀토리 UIλ₯Ό λͺ©ν‘œλ‘œ ν•˜λŠ”λ°, UI ToolKit은 λŸ°νƒ€μž„κ³Ό 에디터 UI λͺ¨λ‘μ— μ‚¬μš©ν•  수 있기 λ•Œλ¬Έμ— 크게 λ¬Έμ œλŠ” μ—†λ‹€.

기본적인 λ ˆμ΄μ•„μ›ƒ

νŠœν† λ¦¬μ–Όμ„ 보고 따라 λ§Œλ“  UI에, λͺ‡ 가지 κΈ°λŠ₯을 μΆ”κ°€ν•œ λͺ¨μŠ΅μ΄λ‹€.

미리 InitAllList() ν•¨μˆ˜μ—μ„œ QuestData νƒ€μž…μ˜ μŠ€ν¬λ¦½ν„°λΈ” 였브젝트 리슀트λ₯Ό μ €μž₯ν•˜κ³ ,
CreateGUI() ν•¨μˆ˜μ—μ„œ 각 QuestDataλ₯Ό MArtifactVisual둜 λ§Œλ“€μ–΄ μΆ”κ°€ν•œλ‹€.

μ•„λž˜κ°€ κ·Έ μ½”λ“œλ‹€.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MArtifactVisual : Button
{
	public Artifact Artifact { get; private set;}

	public MArtifactVisual(Artifact artifact)
	{
		this.Artifact = artifact;

		name = $"{artifact.Name}";
		
		Add(new Label(){ text = artifact.Name });
		Add(new Label(){ text = artifact.ID.ToString() });

		if (artifact.Sprite != null)
			style.backgroundImage = artifact.Sprite.texture;
		AddToClassList("slot-icons");
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private void InitAllList()
{
	const string QUEST_DIRECTORY_PATH = "Assets/_Mascari4615/ScriptableObjects/Quest/";
	questDatas = new();
	InitList(ref questDatas, QUEST_DIRECTORY_PATH);

	static void InitList<T>(ref List<T> list, string dirPath, bool searchSubDir = true) where T : ScriptableObject
	{
		const string extension = ".asset";

		DirectoryInfo dir = new(dirPath);
		foreach (FileInfo file in dir.GetFiles())
		{
			if (string.Compare(file.Extension, extension, StringComparison.Ordinal) != 0)
				continue;

			// QuestData μŠ€ν¬λ¦½ν„°λΈ” 객체가 μ•„λ‹ˆλ©΄ Continue
			if (AssetDatabase.GetMainAssetTypeAtPath($"{dirPath}/{file.Name}") != typeof(T))
				continue;

			list.Add(AssetDatabase.LoadAssetAtPath<T>($"{dirPath}/{file.Name}"));
		}

		if (searchSubDir)
		{
			// dir μ•„λž˜ λͺ¨λ“  폴더 μ•ˆμ— μžˆλŠ” νŒŒμΌμ„ 탐색
			foreach (DirectoryInfo subDir in dir.GetDirectories())
				InitList(ref list, $"{dirPath}/{subDir.Name}/");
		}
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
public void CreateGUI()
{
	// ...
	
	VisualElement grid = rootVisualElement.Q<VisualElement>(name: "Grid");
	foreach (QuestData questData in questDatas)
	{
		MAritifactVisual mAritifactVisual = new(questData);
		mAritifactVisual.RegisterCallback<ClickEvent>(ShowArtifact);
		grid.Add(mAritifactVisual);
	}
}


🎲 에셋 μ •λ ¬


λ‹€μ‹œ μ‚΄νŽ΄λ³΄μž

μœ„ μŠ€ν¬λ¦°μƒ·μ„ λ‹€μ‹œ μ‚΄νŽ΄λ³΄λ©΄, 에셋 μˆœμ„œκ°€ 엉망인걸 μ•Œ 수 μžˆλ‹€.

각 에셋 μŠ¬λ‘―μ—μ„œ 이름 ν…μŠ€νŠΈ μœ„μ— μžˆλŠ” 것은 λ‚΄κ°€ μž„μ˜λ‘œ μ„€μ •ν•œ IDλ‹€.

보면 ID 1 에셋 λ‹€μŒ ID 2 에셋이 μ•„λ‹ˆλΌ ID 100 에셋이 였고 있고,
또 ID 3 에셋은 맨 뒀에 μœ„μΉ˜ν•œλ‹€.

파일 μˆœμ„œ

μ΄λ ‡κ²Œ μˆœμ„œκ°€ 엉망인 μ΄μœ λŠ”,

νŒŒμΌμ„ 뢈러올 λ•Œ 파일 이름 λ¬Έμžμ—΄ μˆœμ„œλ‘œ 뢈러였고, (ν•¨μˆ˜ λ¬Έμ„œμ— 그런 언급은 μ—†μ§€λ§Œ)
λ¨Όμ € ν˜„μž¬ 디렉토리에 μžˆλŠ” νŒŒμΌλ“€μ„ λ¦¬μŠ€νŠΈμ— μΆ”κ°€ν•œ λ’€ ν•˜μœ„ 디렉토리λ₯Ό νƒμƒ‰ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€.

μ–΄μ¨Œκ±°λ‚˜, 에셋을 μ •λ ¬ν•΄μ€˜μ•Ό ν•œλ‹€.


πŸ‘Ύ λ”•μ…”λ„ˆλ¦¬?

ꡳ이 μ •λ ¬ν•˜μ§€ μ•Šκ³  Dictionary<int, Artifact> λͺ¨μ–‘μœΌλ‘œ λ”•μ…”λ„ˆλ¦¬λ₯Ό 써도 될 것 κ°™κΈ΄ν•˜λ‹€.

IDλ₯Ό Artifact의 κ³ μœ ν•œ κ°’μœΌλ‘œ μ„€μ •ν•  것이기 λ•Œλ¬Έμ— 같은 IDλ₯Ό 가진 ArtifactλŠ” μ—†λ‹€.
λ§Œμ•½ μ‘΄μž¬ν•œλ‹€λ©΄ 잘λͺ»λœ ID이기 λ•Œλ¬Έμ— IDλ₯Ό μˆ˜μ •ν•΄μ•Ό ν•œλ‹€.

μ •λ ¬ μ•Œκ³ λ¦¬λ“¬μ„ μ“΄λ‹€λ©΄, 이미 μ‘΄μž¬ν•˜λŠ” ID인지 ν™•μΈν•˜κΈ° μœ„ν•΄ λ³„λ„μ˜ μ»¨ν…Œμ΄λ„ˆλ₯Ό 써야 ν•˜λŠ”λ°,
κ·Έλƒ₯ λ”•μ…”λ„ˆλ¦¬λ₯Ό μ“΄λ‹€λ©΄, λ‹¨μˆœνžˆ ID둜 Containsλ‚˜ TryGetValueλ₯Ό 써보면 μ•Œ 수 μžˆλ‹€.

슬둯 UI μΆ”κ°€λŠ”,
IDκ°€ 컀봀자 10λ§Œλ³΄λ‹€λ„ μž‘μ„ κ±°λΌμ„œ, for문으둜 0 ~ MAX_IDκΉŒμ§€ λŒλ¦¬λ©΄μ„œ TryGetValueκ°€ true인 것에 λŒ€ν•΄ μŠ¬λ‘―μ„ μΆ”κ°€ν•˜λ©΄ 될 것 κ°™λ‹€.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void CreateGUI()
{
	// ...
	System.Diagnostics.Stopwatch sw = new();
	sw.Start();

	VisualElement grid = rootVisualElement.Q<VisualElement>(name: "Grid");
	for (int i = 0; i < ID_MAX; i++)
	{
		if (questDataDic.TryGetValue(i, out QuestData questData))
		{
			MArtifactVisual mAritifactVisual = new(questData);
			mAritifactVisual.RegisterCallback<ClickEvent>(ShowArtifact);
			grid.Add(mAritifactVisual);
		}
	}

	sw.Stop();
	Debug.Log($"TryGetValue x {ID_MAX} = {sw.ElapsedMilliseconds}ms");
}

짧닀

음, λ‹Ήμž₯ μ†λ„λŠ” κ±±μ •μ—†λ‹€!
νŽ˜μ΄μ§€ λ§Œλ“œλŠ” 것도 λ‹¨μˆœνžˆ 이 λͺ¨μ–‘μ—μ„œ ν•œ νŽ˜μ΄μ§€μ— ν‘œμ‹œν•  μš”μ†Œ 수 만큼 TryGetValueκ°€ trueλ©΄ μΆ”κ°€ν•˜λ©΄ 될 것이닀.

λ‹€μŒ λ‹¨κ³„λ‘œ λ„˜μ–΄κ°„λ‹€.


🎲 에셋 관리


에셋을 κ΄€λ¦¬ν•˜λŠ” κΈ°λŠ₯을 λ§Œλ“€μ–΄λ³΄μž.

에셋을 λ§Œλ“œλŠ” κΈ°λŠ₯은 λ³„λ„μ˜ UIκ°€ ν•„μš”ν•  것 κ°™μ•„μ„œ,
λ‹Ήμž₯ λ²„νŠΌ ν•˜λ‚˜λ§Œ 있으면 λ˜λŠ” 에셋 λ³΅μ œμ™€ μ‚­μ œ κΈ°λŠ₯λΆ€ν„° κ΅¬ν˜„ν•΄λ³Έλ‹€.

πŸ‘Ύ 에셋 볡제

에셋 볡제 κΈ°λŠ₯이닀.

λ ˆμ΄μ•„μ›ƒ μš°μΈ‘μ— μœ„μΉ˜ν•œ 툴팁의 κΈ°λŠ₯을 λ‹΄λ‹Ήν•˜λŠ” MArtifactDetail 클래슀λ₯Ό μƒˆλ‘œ λ§Œλ“€κ³ ,
MArtifactDetailμ—μ„œ λ²„νŠΌμ„ 클릭 ν–ˆμ„ λ–„, ν˜„μž¬ μ„ νƒλœ Artifact에 λŒ€ν•΄ MArtifact의 DuplicateArtifactλ₯Ό ν˜ΈμΆœν•˜λ„λ‘ ν•œλ‹€.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MArtifactDetail
{
	// ... 

	public MArtifactDetail()
	{
		// ...
		duplicateButton = root.Q<Button>(name: "BTN_Dup");
		duplicateButton.clicked += DuplicateCurArtifact;
	}

	public void DuplicateCurArtifact()
	{
		MArtifact.Instance.DuplicateArtifact(CurArtifact);
	}

	// ...
}

이후 MArttifact μ—μ„œ μ‹€μ œ DuplicateArtifact ν•¨μˆ˜λ₯Ό κ΅¬ν˜„ν–ˆλ‹€.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class MArtifact : EditorWindow
{
	public void DuplicateArtifact(Artifact artifact)
	{
		string nName = artifact.Name + " Copy";
		// μ€‘λ³΅λ˜μ§€ μ•ŠλŠ” IDλ₯Ό μ°ΎλŠ”λ‹€.
		int nID = artifact.ID + 1;
		while (questDataDic.ContainsKey(nID))
			nID++;

		string assetName = $"Q_{nID}_{nName}";
		string path = AssetDatabase.GenerateUniqueAssetPath($"{QUEST_DIRECTORY_PATH}{assetName}.asset");

		AssetDatabase.CopyAsset(AssetDatabase.GetAssetPath(artifact), path);
		QuestData newQuestData = AssetDatabase.LoadAssetAtPath<QuestData>(path);
		newQuestData.ID = nID;
		newQuestData.Name = nName;

		questDataDic.Add(nID, newQuestData);
		VisualElement grid = rootVisualElement.Q<VisualElement>(name: "Grid");
		grid.Add(new MArtifactVisual(newQuestData));

		Repaint();
	}
}

μ½”λ“œλ₯Ό 보면 while (questDataDic.ContainsKey(nID))와 questDataDic.Add(nID, newQuestData); μ—μ„œ questDataDic을 μ‚¬μš©ν•˜κ³ , 또 에셋을 λ‹€μ‹œ λ‘œλ“œν•  λ•Œ QuestData νƒ€μž…μœΌλ‘œ 뢈러였고 μžˆλ‹€.

ν•˜μ§€λ§Œ 이 ν•¨μˆ˜λŠ” λͺ¨λ“  Artifact μŠ€ν¬λ¦½ν„°λΈ” μ˜€λΈŒμ νŠΈμ— λŒ€ν•΄μ„œ μ‚¬μš©ν•  것이기 λ•Œλ¬Έμ—, μΌλ°˜ν™” ν•΄μ•Όν•œλ‹€.
μ–΄λ–»κ²Œ μΌλ°˜ν™” μ‹œν‚€λ©΄ μ’‹μ„κΉŒ?

일단 ν€˜μŠ€νŠΈλ₯Ό μ €μž₯해두기 μœ„ν•œ questDataDic이 μžˆλŠ” 것 처럼, λ‹€λ₯Έ Artifact듀도 각자 μ»¨ν…Œμ΄λ„ˆκ°€ μžˆμ„ 것이닀.
전달받은 Artifact artifactλ₯Ό μ‹€μ œ νƒ€μž…μ— λ§žλŠ” μ»¨ν…Œμ΄λ„ˆμ— μΆ”κ°€ν•΄ 데이터λ₯Ό κ°±μ‹ ν•΄μ€˜μ•Ό ν•œλ‹€.

Dictionary<Type, Dictionary>λ₯Ό λ§Œλ“€μ–΄μ„œ Dictionary듀도 ν•˜λ‚˜μ˜ Dictionary둜 κ΄€λ¦¬ν•΄λ³ΌκΉŒ?
Dictionary의 Value νƒ€μž…μ€ κ³ μœ ν•΄μ•Ό ν•˜κΈ°μ— Dictionary<Type, Dictionary<int, Artifact>> νƒ€μž…μœΌλ‘œ λ§Œλ“€κ³ , μ“Έ λ•ŒλŠ” λ‹€μš΄μΊμŠ€νŒ…μ΄ ν•΄μ„œ μ“°λ©΄ 크게 λ¬Έμ œκ°€ 될 것 같지 μ•Šλ‹€.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public void DuplicateArtifact(Artifact artifact)
{
	Type type = artifact.GetType();
	Dictionary<int, Artifact> dic = dataDics[type];

	string nName = artifact.Name + " Copy";
	// μ‚¬μš©λ˜μ§€ μ•Šμ€ IDλ₯Ό μ°ΎλŠ”λ‹€.
	int nID = artifact.ID + 1;
	while (dic.ContainsKey(nID))
		nID++;

	string assetName = $"Q_{nID}_{nName}";
	string path = AssetDatabase.GenerateUniqueAssetPath($"{QUEST_DIRECTORY_PATH}{assetName}.asset");

	AssetDatabase.CopyAsset(AssetDatabase.GetAssetPath(artifact), path);
	Artifact newArtifact = AssetDatabase.LoadAssetAtPath<Artifact>(path);
	newArtifact.ID = nID;
	newArtifact.Name = nName;

	dic.Add(nID, newArtifact);

	VisualElement grid = rootVisualElement.Q<VisualElement>(name: "Grid");
	grid.Add(new MArtifactVisual(newArtifact));
	Repaint();
}

μˆ˜μ •λœ μ½”λ“œλ‹€.

에셋 볡제

μ΄λ ‡κ²Œ 볡제 κΈ°λŠ₯을 λ§Œλ“€μ–΄λ΄€λ‹€.

πŸ‘Ύ 에셋 μ‚­μ œ

에셋 μ‚­μ œ κΈ°λŠ₯을 κ΅¬ν˜„ν•΄λ³Έλ‹€.

볡제 κΈ°λŠ₯처럼 주어진 Artifact에 λ§žλŠ” μ»¨ν…Œμ΄λ„ˆλ₯Ό μ°Ύμ•„ μ‚­μ œν•΄μ•Ό ν•œλ‹€.
.. 또 ν•˜λ“œ 코딩을 ν•΄μ•Ό ν•˜λŠ” 것인가? 고민슀러운 λ¬Έμ œλ‹€.

.. κ·Έλƒ₯ Dictionary<Type, Dictionary<int, Artifact>>λ₯Ό μ“ΈκΉŒ?
생각해보면 λ‹€μš΄μΊμŠ€νŒ…μ„ ν•„μš”ν•˜λ‹€κ³  ν•΄μ„œ μ„±λŠ₯μ΄λ‚˜ κ΅¬ν˜„μ— 크게 λ¬Έμ œκ°€ 될 것 같지 μ•Šλ‹€.
κ·Έλ ‡κ²Œ μˆ˜μ •ν•˜κ³  κ΅¬ν˜„ν•΄λ³΄μž.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private readonly Dictionary<Type, Dictionary<int, Artifact>> dataDics = new();

public void DeleteArtifact(Artifact artifact)
{
	Type type = artifact.GetType();
	Dictionary<int, Artifact> dic = dataDics[type];

	string assetName = $"Q_{artifact.ID}_{artifact.Name}";
	string path = AssetDatabase.GenerateUniqueAssetPath($"{QUEST_DIRECTORY_PATH}{assetName}.asset");

	dic.Remove(artifact.ID);
	AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(artifact));

	UpdateGrid();
}

// ...


πŸ‘Ύ 에셋 μΆ”κ°€

이제 에셋 μΆ”κ°€ κΈ°λŠ₯을 κ΅¬ν˜„ν•΄λ³Έλ‹€.

근데.. 생각해보면 에셋 μΆ”κ°€λ₯Ό μœ„ν•œ λ³„λ„μ˜ UIκ°€ 없어도 될 것 κ°™λ‹€.

κΈ°λ³Έ 에셋을 μΆ”κ°€ν•˜λŠ” λ²„νŠΌ ν•˜λ‚˜λ§Œ κ°„λ‹¨νžˆ λ§Œλ“€κ³ ,
에셋 정보λ₯Ό λ‚˜νƒ€λ‚΄λŠ” ν™”λ©΄μ—μ„œ λ“œλ‘­λ°•μŠ€ λ“±μœΌλ‘œ κ³§λ°”λ‘œ μˆ˜μ • κ°€λŠ₯ν•˜λ„λ‘ ν•˜λ©΄ λ˜μ§€ μ•Šμ„κΉŒ?

그러기 μœ„ν•΄μ„œλŠ” μ—μ…‹μ˜ 정보λ₯Ό μˆ˜μ • κ°€λŠ₯ν•˜λ„λ‘ UIλ₯Ό μΆ”κ°€ν•΄μ•Όν•œλ‹€.
.. μ–΄μ°¨ν”Ό UIλŠ” λ§Œλ“€μ–΄μ•Όν–ˆλ‹€.

(λͺ‡ μ‹œκ°„ 뒀…)

.. PropertyField의 쑴재λ₯Ό λͺ¨λ₯΄κ³  직접 ν”„λ‘œνΌν‹° 정보λ₯Ό κ°€μ Έμ™€μ„œ λ§Œλ“€κ³  μžˆμ—ˆλ‹€.
μ—΄μ‹¬νžˆ λ§Œλ“€μ—ˆλŠ”λ° κ·Έλƒ₯ μ§€μš°κΈ° μ•„μ‰¬μ›Œμ„œ 남겨본닀.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public PropertyBlock(Artifact artifact, PropertyInfo propertyInfo)
{
	this.artifact = artifact;
	this.propertyInfo = propertyInfo;

	PropertyName = new Label(propertyInfo.Name);
	PropertyName.AddToClassList("property-name");
	Add(PropertyName);

	SetPropertyValue();

	AddToClassList("property-block");
}

private void SetPropertyValueWithType<T, U>() where U : BaseField<T>, new()
{
	T value = (T)propertyInfo.GetValue(artifact);
	PropertyValue = new U();
	(PropertyValue as U).value = value;
}

private void SetPropertyValue()
{
	Type propertyType = propertyInfo.PropertyType;
	switch (propertyType)
	{
		case Type intType when intType == typeof(int):
			SetPropertyValueWithType<int, IntegerField>();
			break;
		case Type stringType when stringType == typeof(string):
			SetPropertyValueWithType<string, TextField>();
			bool isDescription = propertyInfo.Name == nameof(Artifact.Description);
			if (isDescription)
			{
				(PropertyValue as TextField).multiline = true;
				(PropertyValue as TextField).style.minHeight = 100;
			}

			PropertyValue.RegisterCallback<ChangeEvent<string>>(evt =>
			{
				propertyInfo.SetValue(artifact, evt.newValue);
			});
			break;
		// ...
	}
}

// ...

PropertyFieldλ₯Ό μ΄μš©ν•œ μ½”λ“œλŠ” μ•„λž˜μ™€ κ°™λ‹€.

μ΄λ•Œ, PropertyFieldλ₯Ό μ΄ˆκΈ°ν™”ν•˜λŠ” λΆ€λΆ„μ—μ„œ 많이 μ• λ₯Ό λ¨Ήμ—ˆλ‹€.

λ‚˜λŠ” μŠ€ν¬λ¦½ν„°λΈ” μ˜€λΈŒμ νŠΈμ— λͺ¨λ“  ν•„λ“œλ₯Ό [field: SerializeField] public int ID { get; set; } 같이 μžλ™κ΅¬ν˜„ ν”„λ‘œνΌν‹°λ‘œ λ§Œλ“€μ—ˆλŠ”λ°,
CurArtifact.GetType().GetFields()λ‘œλŠ” ν•„λ“œκ°€ κ²€μƒ‰λ˜μ§€ μ•Šμ•˜κ³ ,
serializedObject.FindProperty()μ—μ„œλ„ PropertyInfo.NameμœΌλ‘œλŠ” ν•„λ“œλ₯Ό 찾을 수 μ—†μ—ˆλ‹€.

ν”„λ‘œνΌν‹° ν•„λ“œ 이름

ν•„λ“œ 이름이 λ„λŒ€μ²΄ 뭘까 ν•˜κ³  sharplab.ioμ—μ„œ IL μ½”λ“œλ₯Ό ν™•μΈν•΄λ³΄κ³ λ‚˜μ„œμ•Ό, ν”„λ‘œνΌν‹°μ˜ ν•„λ“œ 이름이 <ν”„λ‘œνΌν‹°μ΄λ¦„>k__BackingField 같은 λͺ¨μ–‘μœΌλ‘œ μƒκΈ΄λ‹€λŠ” 것을 μ•Œμ•˜λ‹€.

μ΄λ ‡κ²Œ κ΅¬ν˜„ν•΄λ„ λ˜λŠ” κ±΄μ§€λŠ” 잘 λͺ¨λ₯΄κ² μ§€λ§Œ, μ–΄μ¨Œλ“  잘 λ™μž‘ν•˜κΈ°μ— κ·ΈλŒ€λ‘œ 써보기둜 ν•œλ‹€.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public void UpdateUI()
{
	SerializedObject serializedObject = new(CurArtifact);

	// CurArtifact의 λͺ¨λ“  ν”„λ‘œνΌν‹°λ₯Ό λ¦¬ν”Œλ ‰μ…˜μœΌλ‘œ κ°€μ Έμ˜€κΈ°
	List<PropertyInfo> propertyInfos = CurArtifact.GetType()
	.GetProperties()
	.OrderBy(
		p =>
		{
			var attribute = p.GetCustomAttribute(typeof(PropertyOrderAttribute));
			if (attribute == null)
				return int.MaxValue;
			else
				return ((PropertyOrderAttribute)attribute).Order;
		}).ToList();

	// CurArtifact의 λͺ¨λ“  ν”„λ‘œνΌν‹°λ₯Ό PropertyBlock으둜 λ§Œλ“€μ–΄μ„œ artifactContent에 μΆ”κ°€
	artifactContent.Clear();
	foreach (PropertyInfo propertyInfo in propertyInfos)
	{
		if (propertyInfo.Name == "name" || propertyInfo.Name == "hideFlags")
			continue;

		// HACK : μžλ™μœΌλ‘œ μƒμ„±λ˜λŠ” ν”„λ‘œνΌν‹°μ˜ ν•„λ“œμ˜ 이름 = <ν”„λ‘œνΌν‹°μ΄λ¦„>k__BackingField
		PropertyField propertyField = new (serializedObject.FindProperty($"<{propertyInfo.Name}>k__BackingField"));
		propertyField.Bind(serializedObject);
		artifactContent.Add(propertyField);
	}
}

ν”„λ‘œνΌν‹° ν•„λ“œ 이름

잘 λ™μž‘ν•œλ‹€.

근데 이럴거면 κ·Έλƒ₯ μΈμŠ€νŽ™ν„°λ₯Ό ν™•μž₯μ‹œν‚€λ©΄ λ˜λŠ”κ±° μ•„λ‹ˆμ˜€λ‚˜?
.. 일단 κ·Έλƒ₯ μ“°μž

μ΄μ–΄μ„œ κ°„λ‹¨ν•œ 에셋 μΆ”κ°€ λ²„νŠΌλ„ λ§Œλ“€μ–΄λ³Έλ‹€.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void UpdateGrid()
{
	// ...

	Button addButton = new()
	{
		text = "+",
	};
	addButton.AddToClassList("slot-icons");
	addButton.RegisterCallback<ClickEvent>(ev =>
	{
		AddArtifact(MArtifactDetail.CurArtifact.GetType());
	});
	grid.Add(addButton);

	Repaint();
}

λ‹¨μˆœνžˆ λ§ˆμ§€λ§‰ λ²„νŠΌμœΌλ‘œ μΆ”κ°€ν–ˆλ‹€.

🎲 μ—¬λŸ¬ 에셋


μ§€κΈˆμ€ QuestData μŠ€ν¬λ¦½ν„°λΈ” 였브젝트만 뢈러였고 μžˆλ‹€.
UIλ₯Ό κ°„λ‹¨νžˆ μˆ˜μ •ν•œ ν›„, λ‹€λ₯Έ νƒ€μž…μ˜ μŠ€ν¬λ¦½ν„°λΈ” μ˜€λΈŒμ νŠΈλ„ 뢈러였자.

ν˜„μž¬ UI둜 ν‘œμ‹œν•  νƒ€μž…μ„ CurType으둜 μ •μ˜ν•˜κ³ ,
κ·Έ CurType을 μ›ν•˜λŠ” νƒ€μž…μœΌλ‘œ μ„€μ •ν•˜λŠ” λ²„νŠΌλ“€μ΄ λͺ¨μΈ 메뉴λ₯Ό λ§Œλ“€μ—ˆλ‹€.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private Type CurType { get; set; } = typeof(QuestData);

public void CreateGUI()
{
	// ...

	VisualElement menu = rootVisualElement.Q<VisualElement>(name: "Menu");
	foreach (Type type in dataDics.Keys)
	{
		Button button = new()
		{
			text = type.Name,
		};
		button.clicked += () =>
		{
			CurType = type;
			UpdateGrid();
			MArtifactDetail.UpdateCurArtifact(dataDics[CurType].Values.First());
		};
		menu.Add(button);
	}

	// ...
}

μ—¬λŸ¬ 에셋

To Be Continued..

이 κΈ°μ‚¬λŠ” μ €μž‘κΆŒμžμ˜ CC BY 4.0 λΌμ΄μ„ΌμŠ€λ₯Ό λ”°λ¦…λ‹ˆλ‹€.