Skip to content

Instantly share code, notes, and snippets.

@kurtdekker
Created August 18, 2025 23:49
Show Gist options
  • Save kurtdekker/f3fad3eaae8937af06fb397b535ac5d4 to your computer and use it in GitHub Desktop.
Save kurtdekker/f3fad3eaae8937af06fb397b535ac5d4 to your computer and use it in GitHub Desktop.
Unity3D: right-click to jump to code containing inspector variable
// found here, tested in Unity2018:
//
// https://discussions.unity.com/t/nice-but-still-no-feature-that-help/1675548
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
using System;
using System.Reflection;
using System.Text.RegularExpressions;
[InitializeOnLoad]
public static class JumpToDeclarationContextMenu
{
static JumpToDeclarationContextMenu()
{
EditorApplication.contextualPropertyMenu += OnPropertyContextMenu;
}
private static void OnPropertyContextMenu(GenericMenu menu, SerializedProperty property)
{
var targetObject = property.serializedObject.targetObject;
var targetType = targetObject.GetType();
FieldInfo fieldInfo = GetFieldFromBaseClasses(targetType, property.name);
if (fieldInfo == null) return;
Type declaringType = fieldInfo.DeclaringType;
if (declaringType == null) return;
MonoScript monoScript = FindMonoScriptForType(declaringType);
if (monoScript == null) return;
int lineNumber = FindVariableDeclarationLine(monoScript.text, property.name);
if (lineNumber == -1) return;
menu.AddSeparator("");
menu.AddItem(new GUIContent("-> Open at Declaration"), false, () => { AssetDatabase.OpenAsset(monoScript, lineNumber); });
}
private static FieldInfo GetFieldFromBaseClasses(Type startType, string fieldName)
{
Type currentType = startType;
while (currentType != null)
{
var field = currentType.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
if (field != null) { return field; }
currentType = currentType.BaseType;
}
return null;
}
private static MonoScript FindMonoScriptForType(Type type)
{
string[] guids = AssetDatabase.FindAssets($"t:script {type.Name}");
if (guids.Length == 0) return null;
foreach (var guid in guids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
MonoScript script = AssetDatabase.LoadAssetAtPath<MonoScript>(path);
if (script != null && script.GetClass() == type) { return script; }
}
string fallbackPath = AssetDatabase.GUIDToAssetPath(guids[0]);
return AssetDatabase.LoadAssetAtPath<MonoScript>(fallbackPath);
}
private static int FindVariableDeclarationLine(string scriptText, string variableName)
{
string[] lines = scriptText.Split(new[]
{ "\r\n","\n" }, StringSplitOptions.None);
string pattern = @"(public|private|protected|internal|\[SerializeField\]).*\s+" + Regex.Escape(variableName) + @"\s*(\;|=)";
for (int i = 0; i < lines.Length; i++)
{
if (Regex.IsMatch(lines[i].Trim(), pattern)) { return i + 1; }
}
return -1;
}
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment