Creating Custom Editor Tools

Learn how to create your own Unity editor tools and inspectors for the RPG system.

Editor Tools Overview

Custom editor tools can dramatically improve your workflow by automating repetitive tasks and providing better interfaces.

⚡ Productivity

Automate repetitive tasks and speed up development

🎨 Custom UI

Create tailored interfaces for your specific needs

🔧 Validation

Build tools to validate and fix common issues

👥 Team Efficiency

Share tools with team members for consistency

Custom Inspectors

Enhanced Quest Inspector

Create a better inspector for quest configuration:

1#"keyword">if UNITY_EDITOR
2"keyword">using UnityEditor;
3"keyword">using UnityEngine;
4
5[CustomEditor(typeof(Quest))]
6"keyword">public "keyword">class QuestInspector : Editor
7{
8 "keyword">private SerializedProperty questId;
9 "keyword">private SerializedProperty questName;
10 "keyword">private SerializedProperty description;
11 "keyword">private SerializedProperty objectives;
12 "keyword">private SerializedProperty rewards;
13 "keyword">private SerializedProperty prerequisites;
14
15 "keyword">private "keyword">bool showObjectives = true;
16 "keyword">private "keyword">bool showRewards = true;
17 "keyword">private "keyword">bool showPrerequisites = true;
18 "keyword">private "keyword">bool showValidation = false;
19
20 "keyword">private GUIStyle headerStyle;
21 "keyword">private GUIStyle warningStyle;
22
23 "keyword">void OnEnable()
24 {
25 "comment">// Find serialized properties
26 questId = serializedObject.FindProperty("questId");
27 questName = serializedObject.FindProperty("questName");
28 description = serializedObject.FindProperty("description");
29 objectives = serializedObject.FindProperty("objectives");
30 rewards = serializedObject.FindProperty("rewards");
31 prerequisites = serializedObject.FindProperty("prerequisites");
32 }
33
34 "keyword">public "keyword">override "keyword">void OnInspectorGUI()
35 {
36 InitializeStyles();
37
38 serializedObject.Update();
39
40 Quest quest = (Quest)target;
41
42 "comment">// Header
43 EditorGUILayout.LabelField("Quest Configuration", headerStyle);
44 EditorGUILayout.Space();
45
46 "comment">// Basic Information
47 DrawBasicInfo();
48
49 EditorGUILayout.Space();
50
51 "comment">// Objectives Section
52 DrawObjectivesSection();
53
54 EditorGUILayout.Space();
55
56 "comment">// Rewards Section
57 DrawRewardsSection();
58
59 EditorGUILayout.Space();
60
61 "comment">// Prerequisites Section
62 DrawPrerequisitesSection();
63
64 EditorGUILayout.Space();
65
66 "comment">// Validation Section
67 DrawValidationSection(quest);
68
69 EditorGUILayout.Space();
70
71 "comment">// Action Buttons
72 DrawActionButtons(quest);
73
74 serializedObject.ApplyModifiedProperties();
75 }
76
77 "keyword">void InitializeStyles()
78 {
79 "keyword">if (headerStyle == null)
80 {
81 headerStyle = "keyword">new GUIStyle(EditorStyles.boldLabel);
82 headerStyle.fontSize = 16;
83 headerStyle.normal.textColor = Color.white;
84 }
85
86 "keyword">if (warningStyle == null)
87 {
88 warningStyle = "keyword">new GUIStyle(EditorStyles.helpBox);
89 warningStyle.normal.textColor = Color.yellow;
90 }
91 }
92
93 "keyword">void DrawBasicInfo()
94 {
95 EditorGUILayout.LabelField("Basic Information", EditorStyles.boldLabel);
96
97 EditorGUI.BeginChangeCheck();
98 EditorGUILayout.PropertyField(questId, "keyword">new GUIContent("Quest ID"));
99 "keyword">if (EditorGUI.EndChangeCheck())
100 {
101 "comment">// Auto-generate quest name from ID "keyword">if empty
102 "keyword">if ("keyword">string.IsNullOrEmpty(questName.stringValue))
103 {
104 questName.stringValue = questId.stringValue.Replace("_", " ").Replace("-", " ");
105 questName.stringValue = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(questName.stringValue.ToLower());
106 }
107 }
108
109 EditorGUILayout.PropertyField(questName, "keyword">new GUIContent("Quest Name"));
110
111 "comment">// Multi-line description with character count
112 EditorGUILayout.LabelField($"Description ({description.stringValue.Length} characters)");
113 description.stringValue = EditorGUILayout.TextArea(description.stringValue, GUILayout.Height(60));
114 }
115
116 "keyword">void DrawObjectivesSection()
117 {
118 showObjectives = EditorGUILayout.Foldout(showObjectives, $"Objectives ({objectives.arraySize})", true);
119
120 "keyword">if (showObjectives)
121 {
122 EditorGUI.indentLevel++;
123
124 "keyword">for ("keyword">int i = 0; i < objectives.arraySize; i++)
125 {
126 DrawObjective(i);
127 }
128
129 EditorGUILayout.BeginHorizontal();
130 GUILayout.FlexibleSpace();
131 "keyword">if (GUILayout.Button("Add Objective", GUILayout.Width(120)))
132 {
133 objectives.InsertArrayElementAtIndex(objectives.arraySize);
134 }
135 EditorGUILayout.EndHorizontal();
136
137 EditorGUI.indentLevel--;
138 }
139 }
140
141 "keyword">void DrawObjective("keyword">int index)
142 {
143 var objective = objectives.GetArrayElementAtIndex(index);
144 var objectiveType = objective.FindPropertyRelative("type");
145 var description = objective.FindPropertyRelative("description");
146 var targetCount = objective.FindPropertyRelative("targetCount");
147 var isCompleted = objective.FindPropertyRelative("isCompleted");
148
149 EditorGUILayout.BeginVertical("box");
150
151 EditorGUILayout.BeginHorizontal();
152 EditorGUILayout.LabelField($"Objective {index + 1}", EditorStyles.boldLabel);
153
154 GUI.backgroundColor = Color.red;
155 "keyword">if (GUILayout.Button("X", GUILayout.Width(20), GUILayout.Height(20)))
156 {
157 objectives.DeleteArrayElementAtIndex(index);
158 "keyword">return;
159 }
160 GUI.backgroundColor = Color.white;
161 EditorGUILayout.EndHorizontal();
162
163 EditorGUILayout.PropertyField(objectiveType);
164 EditorGUILayout.PropertyField(description);
165 EditorGUILayout.PropertyField(targetCount);
166
167 "comment">// Show completion status in play mode
168 "keyword">if (Application.isPlaying)
169 {
170 GUI.enabled = false;
171 EditorGUILayout.Toggle("Completed", isCompleted.boolValue);
172 GUI.enabled = true;
173 }
174
175 EditorGUILayout.EndVertical();
176 }
177
178 "keyword">void DrawRewardsSection()
179 {
180 showRewards = EditorGUILayout.Foldout(showRewards, $"Rewards ({rewards.arraySize})", true);
181
182 "keyword">if (showRewards)
183 {
184 EditorGUI.indentLevel++;
185 EditorGUILayout.PropertyField(rewards, true);
186 EditorGUI.indentLevel--;
187 }
188 }
189
190 "keyword">void DrawPrerequisitesSection()
191 {
192 showPrerequisites = EditorGUILayout.Foldout(showPrerequisites, "Prerequisites", true);
193
194 "keyword">if (showPrerequisites)
195 {
196 EditorGUI.indentLevel++;
197 EditorGUILayout.PropertyField(prerequisites, true);
198 EditorGUI.indentLevel--;
199 }
200 }
201
202 "keyword">void DrawValidationSection(Quest quest)
203 {
204 showValidation = EditorGUILayout.Foldout(showValidation, "Validation", true);
205
206 "keyword">if (showValidation)
207 {
208 EditorGUI.indentLevel++;
209
210 "comment">// Validate quest data
211 var issues = ValidateQuest(quest);
212
213 "keyword">if (issues.Count == 0)
214 {
215 EditorGUILayout.HelpBox("No issues found!", MessageType.Info);
216 }
217 "keyword">else
218 {
219 "keyword">foreach (var issue in issues)
220 {
221 EditorGUILayout.HelpBox(issue, MessageType.Warning);
222 }
223 }
224
225 EditorGUI.indentLevel--;
226 }
227 }
228
229 "keyword">void DrawActionButtons(Quest quest)
230 {
231 EditorGUILayout.BeginHorizontal();
232
233 "keyword">if (GUILayout.Button("Export Quest Data"))
234 {
235 ExportQuestData(quest);
236 }
237
238 "keyword">if (GUILayout.Button("Import Quest Data"))
239 {
240 ImportQuestData(quest);
241 }
242
243 "keyword">if (GUILayout.Button("Duplicate Quest"))
244 {
245 DuplicateQuest(quest);
246 }
247
248 EditorGUILayout.EndHorizontal();
249 }
250
251 System.Collections.Generic.List<"keyword">string> ValidateQuest(Quest quest)
252 {
253 var issues = "keyword">new System.Collections.Generic.List<"keyword">string>();
254
255 "keyword">if ("keyword">string.IsNullOrEmpty(quest.questId))
256 issues.Add("Quest ID is required");
257
258 "keyword">if ("keyword">string.IsNullOrEmpty(quest.questName))
259 issues.Add("Quest Name is required");
260
261 "keyword">if ("keyword">string.IsNullOrEmpty(quest.description))
262 issues.Add("Description is recommended");
263
264 "keyword">if (quest.objectives == null || quest.objectives.Length == 0)
265 issues.Add("At least one objective is required");
266
267 "comment">// Check "keyword">for duplicate quest IDs
268 var allQuests = Resources.FindObjectsOfTypeAll<Quest>();
269 var duplicateIds = allQuests.Where(q => q != quest && q.questId == quest.questId);
270 "keyword">if (duplicateIds.Any())
271 {
272 issues.Add($"Duplicate Quest ID found in: {">string.Join(", ", duplicateIds.Select(q => q.name))}");
273 }
274
275 "keyword">return issues;
276 }
277
278 "keyword">void ExportQuestData(Quest quest)
279 {
280 "keyword">string path = EditorUtility.SaveFilePanel("Export Quest", "", quest.questId + "_export", "json");
281 "keyword">if (!"keyword">string.IsNullOrEmpty(path))
282 {
283 "keyword">string json = JsonUtility.ToJson(quest, true);
284 System.IO.File.WriteAllText(path, json);
285 EditorUtility.DisplayDialog("Export Complete", "Quest data exported successfully!", "OK");
286 }
287 }
288
289 "keyword">void ImportQuestData(Quest quest)
290 {
291 "keyword">string path = EditorUtility.OpenFilePanel("Import Quest", "", "json");
292 "keyword">if (!"keyword">string.IsNullOrEmpty(path))
293 {
294 try
295 {
296 "keyword">string json = System.IO.File.ReadAllText(path);
297 JsonUtility.FromJsonOverwrite(json, quest);
298 EditorUtility.SetDirty(quest);
299 EditorUtility.DisplayDialog("Import Complete", "Quest data imported successfully!", "OK");
300 }
301 catch (System.Exception e)
302 {
303 EditorUtility.DisplayDialog("Import Failed", $"Failed to import quest data: {e.Message}", "OK");
304 }
305 }
306 }
307
308 "keyword">void DuplicateQuest(Quest quest)
309 {
310 Quest newQuest = Instantiate(quest);
311 newQuest.questId = quest.questId + "_copy";
312 newQuest.questName = quest.questName + " (Copy)";
313
314 "keyword">string path = AssetDatabase.GetAssetPath(quest);
315 "keyword">string directory = System.IO.Path.GetDirectoryName(path);
316 "keyword">string newPath = AssetDatabase.GenerateUniqueAssetPath($"{directory}/{newQuest.questId}.asset");
317
318 AssetDatabase.CreateAsset(newQuest, newPath);
319 AssetDatabase.SaveAssets();
320 AssetDatabase.Refresh();
321
322 Selection.activeObject = newQuest;
323 }
324}
325#endif

Item Inspector Enhancement

Create a specialized inspector for items:

1#"keyword">if UNITY_EDITOR
2[CustomEditor(typeof(Item), true)]
3"keyword">public "keyword">class ItemInspector : Editor
4{
5 "keyword">private "keyword">bool showPreview = true;
6 "keyword">private "keyword">bool showStats = true;
7 "keyword">private Texture2D previewTexture;
8
9 "keyword">public "keyword">override "keyword">void OnInspectorGUI()
10 {
11 serializedObject.Update();
12
13 Item item = (Item)target;
14
15 "comment">// Header with item icon
16 DrawItemHeader(item);
17
18 "comment">// Basic properties
19 DrawBasicProperties();
20
21 "comment">// Type-specific properties
22 DrawTypeSpecificProperties(item);
23
24 "comment">// Preview section
25 DrawPreviewSection(item);
26
27 "comment">// Statistics section
28 DrawStatsSection(item);
29
30 serializedObject.ApplyModifiedProperties();
31 }
32
33 "keyword">void DrawItemHeader(Item item)
34 {
35 EditorGUILayout.BeginHorizontal("box");
36
37 "comment">// Item icon
38 "keyword">if (item.icon != null)
39 {
40 GUILayout.Label(item.icon.texture, GUILayout.Width(64), GUILayout.Height(64));
41 }
42 "keyword">else
43 {
44 GUILayout.Label("No Icon", GUILayout.Width(64), GUILayout.Height(64));
45 }
46
47 EditorGUILayout.BeginVertical();
48 EditorGUILayout.LabelField(item.itemName, EditorStyles.boldLabel);
49 EditorGUILayout.LabelField($"Type: {item.type}");
50 EditorGUILayout.LabelField($"Value: {item.value} gold");
51 EditorGUILayout.EndVertical();
52
53 EditorGUILayout.EndHorizontal();
54 }
55
56 "keyword">void DrawBasicProperties()
57 {
58 EditorGUILayout.Space();
59 EditorGUILayout.LabelField("Basic Properties", EditorStyles.boldLabel);
60
61 EditorGUILayout.PropertyField(serializedObject.FindProperty("itemName"));
62 EditorGUILayout.PropertyField(serializedObject.FindProperty("description"));
63 EditorGUILayout.PropertyField(serializedObject.FindProperty("icon"));
64 EditorGUILayout.PropertyField(serializedObject.FindProperty("type"));
65 EditorGUILayout.PropertyField(serializedObject.FindProperty("rarity"));
66 EditorGUILayout.PropertyField(serializedObject.FindProperty("value"));
67 EditorGUILayout.PropertyField(serializedObject.FindProperty("stackSize"));
68 }
69
70 "keyword">void DrawTypeSpecificProperties(Item item)
71 {
72 EditorGUILayout.Space();
73
74 switch (item.type)
75 {
76 case ItemType.Equipment:
77 DrawEquipmentProperties();
78 break;
79 case ItemType.Consumable:
80 DrawConsumableProperties();
81 break;
82 case ItemType.KeyItem:
83 DrawKeyItemProperties();
84 break;
85 }
86 }
87
88 "keyword">void DrawEquipmentProperties()
89 {
90 EditorGUILayout.LabelField("Equipment Properties", EditorStyles.boldLabel);
91
92 "keyword">if (target is Equipment equipment)
93 {
94 EditorGUILayout.PropertyField(serializedObject.FindProperty("equipmentSlot"));
95 EditorGUILayout.PropertyField(serializedObject.FindProperty("statModifiers"), true);
96 EditorGUILayout.PropertyField(serializedObject.FindProperty("levelRequirement"));
97 EditorGUILayout.PropertyField(serializedObject.FindProperty("durability"));
98 }
99 }
100
101 "keyword">void DrawConsumableProperties()
102 {
103 EditorGUILayout.LabelField("Consumable Properties", EditorStyles.boldLabel);
104
105 "keyword">if (target is ConsumableItem consumable)
106 {
107 EditorGUILayout.PropertyField(serializedObject.FindProperty("healthRestore"));
108 EditorGUILayout.PropertyField(serializedObject.FindProperty("manaRestore"));
109 EditorGUILayout.PropertyField(serializedObject.FindProperty("statusEffect"));
110 EditorGUILayout.PropertyField(serializedObject.FindProperty("effectDuration"));
111 }
112 }
113
114 "keyword">void DrawKeyItemProperties()
115 {
116 EditorGUILayout.LabelField("Key Item Properties", EditorStyles.boldLabel);
117
118 "keyword">if (target is KeyItem keyItem)
119 {
120 EditorGUILayout.PropertyField(serializedObject.FindProperty("canBeDiscarded"));
121 EditorGUILayout.PropertyField(serializedObject.FindProperty("relatedQuest"));
122 }
123 }
124
125 "keyword">void DrawPreviewSection(Item item)
126 {
127 EditorGUILayout.Space();
128 showPreview = EditorGUILayout.Foldout(showPreview, "Preview", true);
129
130 "keyword">if (showPreview)
131 {
132 EditorGUI.indentLevel++;
133
134 "comment">// Show tooltip preview
135 EditorGUILayout.LabelField("Tooltip Preview:");
136 EditorGUILayout.TextArea(item.GetTooltip(), GUILayout.Height(80));
137
138 "comment">// Show item in different UI contexts
139 "keyword">if (GUILayout.Button("Preview in Inventory"))
140 {
141 "comment">// This would open a preview window showing how the item looks in inventory
142 ItemPreviewWindow.ShowWindow(item);
143 }
144
145 EditorGUI.indentLevel--;
146 }
147 }
148
149 "keyword">void DrawStatsSection(Item item)
150 {
151 EditorGUILayout.Space();
152 showStats = EditorGUILayout.Foldout(showStats, "Statistics", true);
153
154 "keyword">if (showStats)
155 {
156 EditorGUI.indentLevel++;
157
158 "comment">// Show item statistics in play mode
159 "keyword">if (Application.isPlaying)
160 {
161 EditorGUILayout.LabelField("Runtime Statistics:");
162 "comment">// This would show how many times the item was used, found, etc.
163 }
164 "keyword">else
165 {
166 EditorGUILayout.HelpBox("Statistics available in Play Mode", MessageType.Info);
167 }
168
169 EditorGUI.indentLevel--;
170 }
171 }
172}
173
174"comment">// Preview window "keyword">for items
175"keyword">public "keyword">class ItemPreviewWindow : EditorWindow
176{
177 "keyword">private Item previewItem;
178 "keyword">private Vector2 scrollPosition;
179
180 "keyword">public "keyword">static "keyword">void ShowWindow(Item item)
181 {
182 var window = GetWindow<ItemPreviewWindow>("Item Preview");
183 window.previewItem = item;
184 window.Show();
185 }
186
187 "keyword">void OnGUI()
188 {
189 "keyword">if (previewItem == null)
190 {
191 EditorGUILayout.LabelField("No item selected ">for preview");
192 "keyword">return;
193 }
194
195 scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
196
197 "comment">// Draw item as it would appear in different contexts
198 DrawInventorySlotPreview();
199 DrawTooltipPreview();
200 DrawShopItemPreview();
201
202 EditorGUILayout.EndScrollView();
203 }
204
205 "keyword">void DrawInventorySlotPreview()
206 {
207 EditorGUILayout.LabelField("Inventory Slot Preview", EditorStyles.boldLabel);
208
209 EditorGUILayout.BeginHorizontal("box", GUILayout.Height(64));
210
211 "keyword">if (previewItem.icon != null)
212 {
213 GUILayout.Label(previewItem.icon.texture, GUILayout.Width(48), GUILayout.Height(48));
214 }
215
216 EditorGUILayout.BeginVertical();
217 EditorGUILayout.LabelField(previewItem.itemName, EditorStyles.boldLabel);
218 EditorGUILayout.LabelField($"x{previewItem.stackSize}");
219 EditorGUILayout.EndVertical();
220
221 EditorGUILayout.EndHorizontal();
222 }
223
224 "keyword">void DrawTooltipPreview()
225 {
226 EditorGUILayout.Space();
227 EditorGUILayout.LabelField("Tooltip Preview", EditorStyles.boldLabel);
228
229 EditorGUILayout.BeginVertical("box");
230 "keyword">string tooltip = previewItem.GetTooltip();
231 EditorGUILayout.TextArea(tooltip, EditorStyles.wordWrappedLabel, GUILayout.Height(100));
232 EditorGUILayout.EndVertical();
233 }
234
235 "keyword">void DrawShopItemPreview()
236 {
237 EditorGUILayout.Space();
238 EditorGUILayout.LabelField("Shop Item Preview", EditorStyles.boldLabel);
239
240 EditorGUILayout.BeginHorizontal("box");
241
242 "keyword">if (previewItem.icon != null)
243 {
244 GUILayout.Label(previewItem.icon.texture, GUILayout.Width(32), GUILayout.Height(32));
245 }
246
247 EditorGUILayout.BeginVertical();
248 EditorGUILayout.LabelField(previewItem.itemName);
249 EditorGUILayout.LabelField($"{previewItem.value} gold", EditorStyles.miniLabel);
250 EditorGUILayout.EndVertical();
251
252 EditorGUILayout.EndHorizontal();
253 }
254}
255#endif

Custom Editor Windows

RPG Database Manager

Create a comprehensive database management window:

1#"keyword">if UNITY_EDITOR
2"keyword">public "keyword">class RPGDatabaseManager : EditorWindow
3{
4 "keyword">private "keyword">int selectedTab = 0;
5 "keyword">private "keyword">string[] tabNames = { "Quests", "Items", "NPCs", "Enemies", "Dialogue" };
6
7 "keyword">private Vector2 scrollPosition;
8 "keyword">private "keyword">string searchFilter = "";
9
10 "comment">// Data collections
11 "keyword">private Quest[] allQuests;
12 "keyword">private Item[] allItems;
13 "keyword">private NPC[] allNPCs;
14 "keyword">private Enemy[] allEnemies;
15
16 [MenuItem("RPG Tools/Database Manager")]
17 "keyword">public "keyword">static "keyword">void ShowWindow()
18 {
19 var window = GetWindow<RPGDatabaseManager>("RPG Database");
20 window.minSize = "keyword">new Vector2(800, 600);
21 window.Show();
22 }
23
24 "keyword">void OnEnable()
25 {
26 RefreshDatabase();
27 }
28
29 "keyword">void OnGUI()
30 {
31 DrawToolbar();
32
33 EditorGUILayout.Space();
34
35 DrawSearchBar();
36
37 EditorGUILayout.Space();
38
39 "comment">// Tab selection
40 selectedTab = GUILayout.Toolbar(selectedTab, tabNames, GUILayout.Height(30));
41
42 EditorGUILayout.Space();
43
44 "comment">// Content area
45 scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
46
47 switch (selectedTab)
48 {
49 case 0: DrawQuestsTab(); break;
50 case 1: DrawItemsTab(); break;
51 case 2: DrawNPCsTab(); break;
52 case 3: DrawEnemiesTab(); break;
53 case 4: DrawDialogueTab(); break;
54 }
55
56 EditorGUILayout.EndScrollView();
57
58 DrawStatusBar();
59 }
60
61 "keyword">void DrawToolbar()
62 {
63 EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
64
65 "keyword">if (GUILayout.Button("Refresh", EditorStyles.toolbarButton))
66 {
67 RefreshDatabase();
68 }
69
70 GUILayout.FlexibleSpace();
71
72 "keyword">if (GUILayout.Button("Export All", EditorStyles.toolbarButton))
73 {
74 ExportDatabase();
75 }
76
77 "keyword">if (GUILayout.Button("Import", EditorStyles.toolbarButton))
78 {
79 ImportDatabase();
80 }
81
82 EditorGUILayout.EndHorizontal();
83 }
84
85 "keyword">void DrawSearchBar()
86 {
87 EditorGUILayout.BeginHorizontal();
88 EditorGUILayout.LabelField("Search:", GUILayout.Width(50));
89 searchFilter = EditorGUILayout.TextField(searchFilter);
90
91 "keyword">if (GUILayout.Button("Clear", GUILayout.Width(50)))
92 {
93 searchFilter = "";
94 }
95 EditorGUILayout.EndHorizontal();
96 }
97
98 "keyword">void DrawQuestsTab()
99 {
100 EditorGUILayout.LabelField($"Quests ({allQuests?.Length ?? 0})", EditorStyles.boldLabel);
101
102 "keyword">if (allQuests == null) "keyword">return;
103
104 EditorGUILayout.BeginHorizontal();
105 "keyword">if (GUILayout.Button("Create New Quest"))
106 {
107 CreateNewQuest();
108 }
109 EditorGUILayout.EndHorizontal();
110
111 EditorGUILayout.Space();
112
113 "comment">// Quest list
114 "keyword">foreach (var quest in GetFilteredQuests())
115 {
116 DrawQuestEntry(quest);
117 }
118 }
119
120 "keyword">void DrawQuestEntry(Quest quest)
121 {
122 EditorGUILayout.BeginVertical("box");
123
124 EditorGUILayout.BeginHorizontal();
125
126 "comment">// Quest info
127 EditorGUILayout.BeginVertical();
128 EditorGUILayout.LabelField(quest.questName, EditorStyles.boldLabel);
129 EditorGUILayout.LabelField($"ID: {quest.questId}", EditorStyles.miniLabel);
130 EditorGUILayout.LabelField($"Type: {quest.questType}", EditorStyles.miniLabel);
131 EditorGUILayout.EndVertical();
132
133 GUILayout.FlexibleSpace();
134
135 "comment">// Action buttons
136 "keyword">if (GUILayout.Button("Edit", GUILayout.Width(60)))
137 {
138 Selection.activeObject = quest;
139 }
140
141 "keyword">if (GUILayout.Button("Duplicate", GUILayout.Width(70)))
142 {
143 DuplicateQuest(quest);
144 }
145
146 GUI.backgroundColor = Color.red;
147 "keyword">if (GUILayout.Button("Delete", GUILayout.Width(60)))
148 {
149 "keyword">if (EditorUtility.DisplayDialog("Delete Quest", $"Are you sure you want to delete {quest.questName}?", "Yes", "No"))
150 {
151 DeleteQuest(quest);
152 }
153 }
154 GUI.backgroundColor = Color.white;
155
156 EditorGUILayout.EndHorizontal();
157
158 EditorGUILayout.EndVertical();
159 }
160
161 "keyword">void DrawItemsTab()
162 {
163 EditorGUILayout.LabelField($"Items ({allItems?.Length ?? 0})", EditorStyles.boldLabel);
164
165 "keyword">if (allItems == null) "keyword">return;
166
167 "comment">// Item type filter
168 DrawItemTypeFilter();
169
170 EditorGUILayout.Space();
171
172 EditorGUILayout.BeginHorizontal();
173 "keyword">if (GUILayout.Button("Create New Item"))
174 {
175 CreateNewItem();
176 }
177 EditorGUILayout.EndHorizontal();
178
179 EditorGUILayout.Space();
180
181 "comment">// Items grid
182 DrawItemsGrid();
183 }
184
185 "keyword">void DrawItemsGrid()
186 {
187 "keyword">int itemsPerRow = Mathf.FloorToInt(position.width / 150f);
188 var filteredItems = GetFilteredItems();
189
190 "keyword">for ("keyword">int i = 0; i < filteredItems.Length; i += itemsPerRow)
191 {
192 EditorGUILayout.BeginHorizontal();
193
194 "keyword">for ("keyword">int j = 0; j < itemsPerRow && i + j < filteredItems.Length; j++)
195 {
196 DrawItemCard(filteredItems[i + j]);
197 }
198
199 EditorGUILayout.EndHorizontal();
200 }
201 }
202
203 "keyword">void DrawItemCard(Item item)
204 {
205 EditorGUILayout.BeginVertical("box", GUILayout.Width(140), GUILayout.Height(160));
206
207 "comment">// Item icon
208 "keyword">if (item.icon != null)
209 {
210 var iconRect = GUILayoutUtility.GetRect(64, 64, GUILayout.ExpandWidth(false));
211 iconRect.x += (140 - 64) / 2; "comment">// Center the icon
212 GUI.DrawTexture(iconRect, item.icon.texture);
213 }
214 "keyword">else
215 {
216 GUILayout.Label("No Icon", GUILayout.Height(64));
217 }
218
219 "comment">// Item name
220 EditorGUILayout.LabelField(item.itemName, EditorStyles.boldLabel);
221
222 "comment">// Item details
223 EditorGUILayout.LabelField($"{item.type}", EditorStyles.miniLabel);
224 EditorGUILayout.LabelField($"{item.value} gold", EditorStyles.miniLabel);
225
226 "comment">// Edit button
227 "keyword">if (GUILayout.Button("Edit"))
228 {
229 Selection.activeObject = item;
230 }
231
232 EditorGUILayout.EndVertical();
233 }
234
235 "keyword">void DrawStatusBar()
236 {
237 EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
238
239 "comment">// Status information
240 switch (selectedTab)
241 {
242 case 0:
243 EditorGUILayout.LabelField($"Quests: {allQuests?.Length ?? 0}", EditorStyles.miniLabel);
244 break;
245 case 1:
246 EditorGUILayout.LabelField($"Items: {allItems?.Length ?? 0}", EditorStyles.miniLabel);
247 break;
248 }
249
250 GUILayout.FlexibleSpace();
251
252 EditorGUILayout.LabelField($"Last Updated: {System.DateTime.Now:HH:mm:ss}", EditorStyles.miniLabel);
253
254 EditorGUILayout.EndHorizontal();
255 }
256
257 "keyword">void RefreshDatabase()
258 {
259 allQuests = Resources.FindObjectsOfTypeAll<Quest>();
260 allItems = Resources.FindObjectsOfTypeAll<Item>();
261 allNPCs = Resources.FindObjectsOfTypeAll<NPC>();
262 allEnemies = Resources.FindObjectsOfTypeAll<Enemy>();
263
264 Repaint();
265 }
266
267 Quest[] GetFilteredQuests()
268 {
269 "keyword">if ("keyword">string.IsNullOrEmpty(searchFilter))
270 "keyword">return allQuests;
271
272 "keyword">return allQuests.Where(q =>
273 q.questName.ToLower().Contains(searchFilter.ToLower()) ||
274 q.questId.ToLower().Contains(searchFilter.ToLower())
275 ).ToArray();
276 }
277
278 Item[] GetFilteredItems()
279 {
280 "keyword">if ("keyword">string.IsNullOrEmpty(searchFilter))
281 "keyword">return allItems;
282
283 "keyword">return allItems.Where(i =>
284 i.itemName.ToLower().Contains(searchFilter.ToLower()) ||
285 i.type.ToString().ToLower().Contains(searchFilter.ToLower())
286 ).ToArray();
287 }
288
289 "keyword">void CreateNewQuest()
290 {
291 "keyword">string path = EditorUtility.SaveFilePanelInProject(
292 "Create New Quest",
293 "NewQuest",
294 "asset",
295 "Create a ">new quest asset"
296 );
297
298 "keyword">if (!"keyword">string.IsNullOrEmpty(path))
299 {
300 Quest newQuest = CreateInstance<Quest>();
301 newQuest.questId = "new_quest_" + System.Guid.NewGuid().ToString("N")[..8];
302 newQuest.questName = "New Quest";
303
304 AssetDatabase.CreateAsset(newQuest, path);
305 AssetDatabase.SaveAssets();
306 AssetDatabase.Refresh();
307
308 Selection.activeObject = newQuest;
309 RefreshDatabase();
310 }
311 }
312
313 "keyword">void CreateNewItem()
314 {
315 ItemCreatorWindow.ShowWindow();
316 }
317
318 "keyword">void DuplicateQuest(Quest quest)
319 {
320 Quest duplicate = Instantiate(quest);
321 duplicate.questId = quest.questId + "_copy";
322 duplicate.questName = quest.questName + " (Copy)";
323
324 "keyword">string path = AssetDatabase.GetAssetPath(quest);
325 "keyword">string directory = System.IO.Path.GetDirectoryName(path);
326 "keyword">string newPath = AssetDatabase.GenerateUniqueAssetPath($"{directory}/{duplicate.questId}.asset");
327
328 AssetDatabase.CreateAsset(duplicate, newPath);
329 AssetDatabase.SaveAssets();
330 RefreshDatabase();
331 }
332
333 "keyword">void DeleteQuest(Quest quest)
334 {
335 "keyword">string path = AssetDatabase.GetAssetPath(quest);
336 AssetDatabase.DeleteAsset(path);
337 AssetDatabase.Refresh();
338 RefreshDatabase();
339 }
340
341 "keyword">void ExportDatabase()
342 {
343 "keyword">string exportPath = EditorUtility.SaveFolderPanel("Export Database", "", "");
344 "keyword">if (!"keyword">string.IsNullOrEmpty(exportPath))
345 {
346 ExportQuestsToJSON(exportPath);
347 ExportItemsToJSON(exportPath);
348
349 EditorUtility.DisplayDialog("Export Complete", "Database exported successfully!", "OK");
350 }
351 }
352
353 "keyword">void ExportQuestsToJSON("keyword">string basePath)
354 {
355 var questData = allQuests.Select(q => "keyword">new {
356 id = q.questId,
357 name = q.questName,
358 description = q.description,
359 type = q.questType.ToString(),
360 objectives = q.objectives?.Select(obj => "keyword">new {
361 type = obj.type.ToString(),
362 description = obj.description,
363 targetCount = obj.targetCount
364 })
365 });
366
367 "keyword">string json = JsonConvert.SerializeObject(questData, Formatting.Indented);
368 System.IO.File.WriteAllText(System.IO.Path.Combine(basePath, "quests.json"), json);
369 }
370
371 "keyword">void ExportItemsToJSON("keyword">string basePath)
372 {
373 var itemData = allItems.Select(i => "keyword">new {
374 id = i.name,
375 name = i.itemName,
376 description = i.description,
377 type = i.type.ToString(),
378 value = i.value,
379 stackSize = i.stackSize
380 });
381
382 "keyword">string json = JsonConvert.SerializeObject(itemData, Formatting.Indented);
383 System.IO.File.WriteAllText(System.IO.Path.Combine(basePath, "items.json"), json);
384 }
385}
386#endif

Property Drawers

Enhanced Property Drawers

Create custom property drawers for better inspector experience:

1#"keyword">if UNITY_EDITOR
2"comment">// Custom drawer "keyword">for stat modifiers
3[CustomPropertyDrawer(typeof(StatModifier))]
4"keyword">public "keyword">class StatModifierDrawer : PropertyDrawer
5{
6 "keyword">public "keyword">override "keyword">void OnGUI(Rect position, SerializedProperty property, GUIContent label)
7 {
8 EditorGUI.BeginProperty(position, label, property);
9
10 "comment">// Calculate rects
11 var statTypeRect = "keyword">new Rect(position.x, position.y, position.width * 0.4f, EditorGUIUtility.singleLineHeight);
12 var valueRect = "keyword">new Rect(position.x + position.width * 0.42f, position.y, position.width * 0.3f, EditorGUIUtility.singleLineHeight);
13 var typeRect = "keyword">new Rect(position.x + position.width * 0.74f, position.y, position.width * 0.24f, EditorGUIUtility.singleLineHeight);
14
15 "comment">// Draw fields
16 EditorGUI.PropertyField(statTypeRect, property.FindPropertyRelative("statType"), GUIContent.none);
17 EditorGUI.PropertyField(valueRect, property.FindPropertyRelative("value"), GUIContent.none);
18 EditorGUI.PropertyField(typeRect, property.FindPropertyRelative("type"), GUIContent.none);
19
20 EditorGUI.EndProperty();
21 }
22
23 "keyword">public "keyword">override "keyword">float GetPropertyHeight(SerializedProperty property, GUIContent label)
24 {
25 "keyword">return EditorGUIUtility.singleLineHeight;
26 }
27}
28
29"comment">// Custom drawer "keyword">for dialogue choices
30[CustomPropertyDrawer(typeof(DialogueChoice))]
31"keyword">public "keyword">class DialogueChoiceDrawer : PropertyDrawer
32{
33 "keyword">public "keyword">override "keyword">void OnGUI(Rect position, SerializedProperty property, GUIContent label)
34 {
35 EditorGUI.BeginProperty(position, label, property);
36
37 var foldoutRect = "keyword">new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
38 property.isExpanded = EditorGUI.Foldout(foldoutRect, property.isExpanded, label, true);
39
40 "keyword">if (property.isExpanded)
41 {
42 EditorGUI.indentLevel++;
43
44 "keyword">float yPos = position.y + EditorGUIUtility.singleLineHeight + 2;
45
46 "comment">// Choice text
47 var choiceTextRect = "keyword">new Rect(position.x, yPos, position.width, EditorGUIUtility.singleLineHeight);
48 EditorGUI.PropertyField(choiceTextRect, property.FindPropertyRelative("choiceText"));
49 yPos += EditorGUIUtility.singleLineHeight + 2;
50
51 "comment">// Next node
52 var nextNodeRect = "keyword">new Rect(position.x, yPos, position.width, EditorGUIUtility.singleLineHeight);
53 EditorGUI.PropertyField(nextNodeRect, property.FindPropertyRelative("nextNodeIndex"));
54 yPos += EditorGUIUtility.singleLineHeight + 2;
55
56 "comment">// Condition
57 var conditionRect = "keyword">new Rect(position.x, yPos, position.width, EditorGUIUtility.singleLineHeight);
58 EditorGUI.PropertyField(conditionRect, property.FindPropertyRelative("condition"));
59 yPos += EditorGUIUtility.singleLineHeight + 2;
60
61 "comment">// Action
62 var actionRect = "keyword">new Rect(position.x, yPos, position.width, EditorGUIUtility.singleLineHeight);
63 EditorGUI.PropertyField(actionRect, property.FindPropertyRelative("action"));
64
65 EditorGUI.indentLevel--;
66 }
67
68 EditorGUI.EndProperty();
69 }
70
71 "keyword">public "keyword">override "keyword">float GetPropertyHeight(SerializedProperty property, GUIContent label)
72 {
73 "keyword">if (!property.isExpanded)
74 "keyword">return EditorGUIUtility.singleLineHeight;
75
76 "keyword">return EditorGUIUtility.singleLineHeight * 5 + 8; "comment">// 5 lines + spacing
77 }
78}
79
80"comment">// Range attribute with custom drawer
81"keyword">public "keyword">class MinMaxRangeAttribute : PropertyAttribute
82{
83 "keyword">public "keyword">float min;
84 "keyword">public "keyword">float max;
85
86 "keyword">public MinMaxRangeAttribute("keyword">float min, "keyword">float max)
87 {
88 "keyword">this.min = min;
89 "keyword">this.max = max;
90 }
91}
92
93[CustomPropertyDrawer(typeof(MinMaxRangeAttribute))]
94"keyword">public "keyword">class MinMaxRangeDrawer : PropertyDrawer
95{
96 "keyword">public "keyword">override "keyword">void OnGUI(Rect position, SerializedProperty property, GUIContent label)
97 {
98 var minMaxAttribute = (MinMaxRangeAttribute)attribute;
99
100 "keyword">if (property.propertyType == SerializedPropertyType.Vector2)
101 {
102 EditorGUI.BeginProperty(position, label, property);
103
104 Vector2 range = property.vector2Value;
105 "keyword">float minValue = range.x;
106 "keyword">float maxValue = range.y;
107
108 EditorGUI.LabelField("keyword">new Rect(position.x, position.y, EditorGUIUtility.labelWidth, position.height), label);
109
110 EditorGUI.MinMaxSlider(
111 "keyword">new Rect(position.x + EditorGUIUtility.labelWidth, position.y, position.width - EditorGUIUtility.labelWidth - 60, position.height),
112 ref minValue, ref maxValue, minMaxAttribute.min, minMaxAttribute.max);
113
114 range.x = minValue;
115 range.y = maxValue;
116 property.vector2Value = range;
117
118 EditorGUI.LabelField(
119 "keyword">new Rect(position.x + position.width - 55, position.y, 55, position.height),
120 $"{minValue:F1}-{maxValue:F1}");
121
122 EditorGUI.EndProperty();
123 }
124 "keyword">else
125 {
126 EditorGUI.LabelField(position, label.text, "Use MinMaxRange with Vector2.");
127 }
128 }
129}
130
131"comment">// ReadOnly attribute "keyword">for inspector
132"keyword">public "keyword">class ReadOnlyAttribute : PropertyAttribute { }
133
134[CustomPropertyDrawer(typeof(ReadOnlyAttribute))]
135"keyword">public "keyword">class ReadOnlyDrawer : PropertyDrawer
136{
137 "keyword">public "keyword">override "keyword">void OnGUI(Rect position, SerializedProperty property, GUIContent label)
138 {
139 GUI.enabled = false;
140 EditorGUI.PropertyField(position, property, label, true);
141 GUI.enabled = true;
142 }
143}
144
145"comment">// Conditional field attribute
146"keyword">public "keyword">class ConditionalFieldAttribute : PropertyAttribute
147{
148 "keyword">public "keyword">string conditionalSourceField;
149 "keyword">public object conditionalValue;
150
151 "keyword">public ConditionalFieldAttribute("keyword">string conditionalSourceField, object conditionalValue)
152 {
153 "keyword">this.conditionalSourceField = conditionalSourceField;
154 "keyword">this.conditionalValue = conditionalValue;
155 }
156}
157
158[CustomPropertyDrawer(typeof(ConditionalFieldAttribute))]
159"keyword">public "keyword">class ConditionalFieldDrawer : PropertyDrawer
160{
161 "keyword">public "keyword">override "keyword">void OnGUI(Rect position, SerializedProperty property, GUIContent label)
162 {
163 var conditionalAttribute = (ConditionalFieldAttribute)attribute;
164
165 "comment">// Get the conditional property
166 SerializedProperty conditionalProperty = property.serializedObject.FindProperty(conditionalAttribute.conditionalSourceField);
167
168 "keyword">if (conditionalProperty != null)
169 {
170 "keyword">bool shouldShow = false;
171
172 "comment">// Check condition based on property type
173 switch (conditionalProperty.propertyType)
174 {
175 case SerializedPropertyType.Boolean:
176 shouldShow = conditionalProperty.boolValue.Equals(conditionalAttribute.conditionalValue);
177 break;
178 case SerializedPropertyType.Enum:
179 shouldShow = conditionalProperty.enumValueIndex.Equals(("keyword">int)conditionalAttribute.conditionalValue);
180 break;
181 case SerializedPropertyType.Integer:
182 shouldShow = conditionalProperty.intValue.Equals(conditionalAttribute.conditionalValue);
183 break;
184 case SerializedPropertyType.String:
185 shouldShow = conditionalProperty.stringValue.Equals(conditionalAttribute.conditionalValue);
186 break;
187 }
188
189 "keyword">if (shouldShow)
190 {
191 EditorGUI.PropertyField(position, property, label, true);
192 }
193 }
194 "keyword">else
195 {
196 EditorGUI.PropertyField(position, property, label, true);
197 }
198 }
199
200 "keyword">public "keyword">override "keyword">float GetPropertyHeight(SerializedProperty property, GUIContent label)
201 {
202 var conditionalAttribute = (ConditionalFieldAttribute)attribute;
203 SerializedProperty conditionalProperty = property.serializedObject.FindProperty(conditionalAttribute.conditionalSourceField);
204
205 "keyword">if (conditionalProperty != null)
206 {
207 "keyword">bool shouldShow = false;
208
209 switch (conditionalProperty.propertyType)
210 {
211 case SerializedPropertyType.Boolean:
212 shouldShow = conditionalProperty.boolValue.Equals(conditionalAttribute.conditionalValue);
213 break;
214 case SerializedPropertyType.Enum:
215 shouldShow = conditionalProperty.enumValueIndex.Equals(("keyword">int)conditionalAttribute.conditionalValue);
216 break;
217 }
218
219 "keyword">if (!shouldShow)
220 "keyword">return 0;
221 }
222
223 "keyword">return EditorGUI.GetPropertyHeight(property, label, true);
224 }
225}
226
227"comment">// Usage examples:
228"keyword">public "keyword">class ExampleUsage : MonoBehaviour
229{
230 [MinMaxRange(0f, 100f)]
231 "keyword">public Vector2 damageRange = "keyword">new Vector2(10f, 20f);
232
233 [ReadOnly]
234 "keyword">public "keyword">float calculatedValue;
235
236 "keyword">public "keyword">bool enableAdvancedOptions;
237
238 [ConditionalField("enableAdvancedOptions", true)]
239 "keyword">public "keyword">float advancedSetting;
240}
241#endif

Scene Tools

Scene Validation Tool

Create tools to validate scene setup:

1#"keyword">if UNITY_EDITOR
2"keyword">public "keyword">class SceneValidator : EditorWindow
3{
4 "keyword">private Vector2 scrollPosition;
5 "keyword">private List<ValidationResult> validationResults = "keyword">new List<ValidationResult>();
6
7 [MenuItem("RPG Tools/Scene Validator")]
8 "keyword">public "keyword">static "keyword">void ShowWindow()
9 {
10 GetWindow<SceneValidator>("Scene Validator").Show();
11 }
12
13 "keyword">void OnGUI()
14 {
15 EditorGUILayout.LabelField("Scene Validation", EditorStyles.boldLabel);
16
17 EditorGUILayout.Space();
18
19 "keyword">if (GUILayout.Button("Validate Current Scene", GUILayout.Height(30)))
20 {
21 ValidateScene();
22 }
23
24 EditorGUILayout.Space();
25
26 "keyword">if (validationResults.Count > 0)
27 {
28 DrawValidationResults();
29 }
30 "keyword">else
31 {
32 EditorGUILayout.HelpBox("Click Validate Current Scene to check ">for issues", MessageType.Info);
33 }
34 }
35
36 "keyword">void ValidateScene()
37 {
38 validationResults.Clear();
39
40 "comment">// Check "keyword">for required components
41 ValidateGameManager();
42 ValidatePlayer();
43 ValidateUI();
44 ValidateCamera();
45 ValidateAudio();
46 ValidateNPCs();
47
48 Repaint();
49 }
50
51 "keyword">void ValidateGameManager()
52 {
53 var gameManager = FindObjectOfType<GameManager>();
54
55 "keyword">if (gameManager == null)
56 {
57 validationResults.Add("keyword">new ValidationResult
58 {
59 severity = ValidationSeverity.Error,
60 category = "Core Systems",
61 message = "GameManager not found in scene",
62 solution = "Add GameManager prefab to scene"
63 });
64 }
65 "keyword">else
66 {
67 "comment">// Check GameManager configuration
68 "keyword">if (gameManager.playerStats == null)
69 {
70 validationResults.Add("keyword">new ValidationResult
71 {
72 severity = ValidationSeverity.Warning,
73 category = "Core Systems",
74 message = "GameManager.playerStats is not assigned",
75 solution = "Assign PlayerStats reference in GameManager"
76 });
77 }
78 }
79 }
80
81 "keyword">void ValidatePlayer()
82 {
83 var player = GameObject.FindWithTag("Player");
84
85 "keyword">if (player == null)
86 {
87 validationResults.Add("keyword">new ValidationResult
88 {
89 severity = ValidationSeverity.Error,
90 category = "Player",
91 message = "No GameObject with Player tag found",
92 solution = "Create player GameObject and assign Player tag
93 });
94 ">return;
95 }
96
97 ">// Check required components
98 var requiredComponents = ">new System.Type[]
99 {
100 typeof(PlayerController),
101 typeof(PlayerStats),
102 typeof(Rigidbody2D),
103 typeof(Collider2D)
104 };
105
106 ">foreach (var componentType in requiredComponents)
107 {
108 ">if (player.GetComponent(componentType) == null)
109 {
110 validationResults.Add(">new ValidationResult
111 {
112 severity = ValidationSeverity.Warning,
113 category = "Player",
114 message = $"Player missing {componentType.Name} component",
115 solution = $"Add {componentType.Name} to player GameObject"
116 });
117 }
118 }
119 }
120
121 ">void ValidateUI()
122 {
123 var canvas = FindObjectOfType<Canvas>();
124
125 ">if (canvas == null)
126 {
127 validationResults.Add(">new ValidationResult
128 {
129 severity = ValidationSeverity.Error,
130 category = "UI",
131 message = "No Canvas found in scene",
132 solution = "Add Canvas "keyword">for UI elements"
133 });
134 ">return;
135 }
136
137 ">// Check ">for UI Manager
138 var uiManager = FindObjectOfType<UIManager>();
139 ">if (uiManager == null)
140 {
141 validationResults.Add(">new ValidationResult
142 {
143 severity = ValidationSeverity.Warning,
144 category = "UI",
145 message = "UIManager not found",
146 solution = "Add UIManager component to manage UI"
147 });
148 }
149
150 ">// Check ">for EventSystem
151 var eventSystem = FindObjectOfType<UnityEngine.EventSystems.EventSystem>();
152 ">if (eventSystem == null)
153 {
154 validationResults.Add(">new ValidationResult
155 {
156 severity = ValidationSeverity.Error,
157 category = "UI",
158 message = "EventSystem not found",
159 solution = "Add EventSystem "keyword">for UI interaction"
160 });
161 }
162 }
163
164 ">void ValidateCamera()
165 {
166 var mainCamera = Camera.main;
167
168 ">if (mainCamera == null)
169 {
170 validationResults.Add(">new ValidationResult
171 {
172 severity = ValidationSeverity.Error,
173 category = "Camera",
174 message = "No main camera found",
175 solution = "Add camera with MainCamera tag
176 });
177 "keyword">return;
178 }
179
180 "comment">// Check "keyword">for camera follow script
181 var cameraFollow = mainCamera.GetComponent<CameraFollow>();
182 "keyword">if (cameraFollow == null)
183 {
184 validationResults.Add("keyword">new ValidationResult
185 {
186 severity = ValidationSeverity.Info,
187 category = "Camera",
188 message = "Camera doesnt have CameraFollow component",
189 solution = "Add CameraFollow ">for smooth camera movement"
190 });
191 }
192 }
193
194 "keyword">void ValidateAudio()
195 {
196 var audioManager = FindObjectOfType<AudioManager>();
197
198 "keyword">if (audioManager == null)
199 {
200 validationResults.Add("keyword">new ValidationResult
201 {
202 severity = ValidationSeverity.Warning,
203 category = "Audio",
204 message = "AudioManager not found",
205 solution = "Add AudioManager ">for sound effects and music"
206 });
207 }
208
209 "comment">// Check "keyword">for audio listener
210 var audioListener = FindObjectOfType<AudioListener>();
211 "keyword">if (audioListener == null)
212 {
213 validationResults.Add("keyword">new ValidationResult
214 {
215 severity = ValidationSeverity.Error,
216 category = "Audio",
217 message = "No AudioListener found",
218 solution = "Add AudioListener (usually on main camera)"
219 });
220 }
221 }
222
223 "keyword">void ValidateNPCs()
224 {
225 var npcs = FindObjectsOfType<NPC>();
226
227 "keyword">foreach (var npc in npcs)
228 {
229 "comment">// Check "keyword">if NPC has required components
230 "keyword">if (npc.GetComponent<Collider2D>() == null)
231 {
232 validationResults.Add("keyword">new ValidationResult
233 {
234 severity = ValidationSeverity.Warning,
235 category = "NPCs",
236 message = $"NPC {npc.name} missing Collider2D",
237 solution = "Add Collider2D ">for interaction detection",
238 targetObject = npc.gameObject
239 });
240 }
241
242 "comment">// Check "keyword">if NPC has dialogue or other interaction
243 "keyword">if (npc.dialogueTree == null && npc.shop == null && npc.questToGive == null)
244 {
245 validationResults.Add("keyword">new ValidationResult
246 {
247 severity = ValidationSeverity.Info,
248 category = "NPCs",
249 message = $"NPC {npc.name} has no assigned interactions",
250 solution = "Assign dialogue, shop, or quest to NPC",
251 targetObject = npc.gameObject
252 });
253 }
254 }
255 }
256
257 "keyword">void DrawValidationResults()
258 {
259 EditorGUILayout.LabelField($"Validation Results ({validationResults.Count} issues found)", EditorStyles.boldLabel);
260
261 scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
262
263 var groupedResults = validationResults.GroupBy(r => r.category);
264
265 "keyword">foreach (var group in groupedResults)
266 {
267 EditorGUILayout.LabelField(group.Key, EditorStyles.boldLabel);
268
269 "keyword">foreach (var result in group)
270 {
271 DrawValidationResult(result);
272 }
273
274 EditorGUILayout.Space();
275 }
276
277 EditorGUILayout.EndScrollView();
278 }
279
280 "keyword">void DrawValidationResult(ValidationResult result)
281 {
282 Color originalColor = GUI.backgroundColor;
283
284 switch (result.severity)
285 {
286 case ValidationSeverity.Error:
287 GUI.backgroundColor = Color.red;
288 break;
289 case ValidationSeverity.Warning:
290 GUI.backgroundColor = Color.yellow;
291 break;
292 case ValidationSeverity.Info:
293 GUI.backgroundColor = Color.cyan;
294 break;
295 }
296
297 EditorGUILayout.BeginVertical("box");
298 GUI.backgroundColor = originalColor;
299
300 EditorGUILayout.BeginHorizontal();
301 EditorGUILayout.LabelField($"[{result.severity}] {result.message}", EditorStyles.wordWrappedLabel);
302
303 "keyword">if (result.targetObject != null)
304 {
305 "keyword">if (GUILayout.Button("Select", GUILayout.Width(60)))
306 {
307 Selection.activeGameObject = result.targetObject;
308 EditorGUIUtility.PingObject(result.targetObject);
309 }
310 }
311
312 EditorGUILayout.EndHorizontal();
313
314 "keyword">if (!"keyword">string.IsNullOrEmpty(result.solution))
315 {
316 EditorGUILayout.LabelField($"Solution: {result.solution}", EditorStyles.miniLabel);
317 }
318
319 EditorGUILayout.EndVertical();
320 }
321}
322
323"keyword">public "keyword">class ValidationResult
324{
325 "keyword">public ValidationSeverity severity;
326 "keyword">public "keyword">string category;
327 "keyword">public "keyword">string message;
328 "keyword">public "keyword">string solution;
329 "keyword">public GameObject targetObject;
330}
331
332"keyword">public "keyword">enum ValidationSeverity
333{
334 Info,
335 Warning,
336 Error
337}
338#endif

Automation Tools

Asset Organization Tool

Automate project organization and cleanup:


Fatal error: Uncaught TypeError: Unsupported operand types: string / string in /srv/disk8/4664669/www/2drpgbydaikage.atwebpages.com/tutorial-editor-tools.php:1747 Stack trace: #0 {main} thrown in /srv/disk8/4664669/www/2drpgbydaikage.atwebpages.com/tutorial-editor-tools.php on line 1747