ํฌ์ŠคํŠธ

WitchMendokusai DevLog 02

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 ๋ผ์ด์„ผ์Šค๋ฅผ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค.