diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
index eef26cdd4e1b..01d91032212b 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
@@ -725,15 +725,7 @@ private static unsafe void GetScriptTypeInfo(Type scriptType, godot_csharp_type_
         {
             Type native = GodotObject.InternalGetClassNativeBase(scriptType);
 
-            string typeName = scriptType.Name;
-            if (scriptType.IsGenericType)
-            {
-                var sb = new StringBuilder();
-                AppendTypeName(sb, scriptType);
-                typeName = sb.ToString();
-            }
-
-            godot_string className = Marshaling.ConvertStringToNative(typeName);
+            godot_string className = Marshaling.ConvertStringToNative(ReflectionUtils.ConstructTypeName(scriptType));
 
             bool isTool = scriptType.IsDefined(typeof(ToolAttribute), inherit: false);
 
@@ -766,24 +758,6 @@ private static unsafe void GetScriptTypeInfo(Type scriptType, godot_csharp_type_
             outTypeInfo->IsGenericTypeDefinition = scriptType.IsGenericTypeDefinition.ToGodotBool();
             outTypeInfo->IsConstructedGenericType = scriptType.IsConstructedGenericType.ToGodotBool();
 
-            static void AppendTypeName(StringBuilder sb, Type type)
-            {
-                sb.Append(type.Name);
-                if (type.IsGenericType)
-                {
-                    sb.Append('<');
-                    for (int i = 0; i < type.GenericTypeArguments.Length; i++)
-                    {
-                        Type typeArg = type.GenericTypeArguments[i];
-                        AppendTypeName(sb, typeArg);
-                        if (i != type.GenericTypeArguments.Length - 1)
-                        {
-                            sb.Append(", ");
-                        }
-                    }
-                    sb.Append('>');
-                }
-            }
         }
 
         [UnmanagedCallersOnly]
@@ -1097,7 +1071,7 @@ private static unsafe void GetPropertyInfoListForType(Type type, IntPtr scriptPt
                         interopProperties[i] = interopProperty;
                     }
 
-                    using godot_string currentClassName = Marshaling.ConvertStringToNative(type.Name);
+                    using godot_string currentClassName = Marshaling.ConvertStringToNative(ReflectionUtils.ConstructTypeName(type));
 
                     addPropInfoFunc(scriptPtr, &currentClassName, interopProperties, length);
 
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/ReflectionUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/ReflectionUtils.cs
index ee605f8d8f27..27989b5c813f 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/ReflectionUtils.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/ReflectionUtils.cs
@@ -1,5 +1,8 @@
 using System;
+using System.Collections.Generic;
+using System.Diagnostics;
 using System.Linq;
+using System.Text;
 
 #nullable enable
 
@@ -7,10 +10,186 @@ namespace Godot;
 
 internal class ReflectionUtils
 {
+    private static readonly HashSet<Type>? _tupleTypeSet;
+    private static readonly Dictionary<Type, string>? _builtinTypeNameDictionary;
+    private static readonly bool _isEditorHintCached;
+
+    static ReflectionUtils()
+    {
+        _isEditorHintCached = Engine.IsEditorHint();
+        if (!_isEditorHintCached)
+        {
+            return;
+        }
+
+        _tupleTypeSet = new HashSet<Type>
+        {
+            // ValueTuple with only one element should be treated as normal generic type.
+            //typeof(ValueTuple<>),
+            typeof(ValueTuple<,>),
+            typeof(ValueTuple<,,>),
+            typeof(ValueTuple<,,,>),
+            typeof(ValueTuple<,,,,>),
+            typeof(ValueTuple<,,,,,>),
+            typeof(ValueTuple<,,,,,,>),
+            typeof(ValueTuple<,,,,,,,>),
+        };
+
+        _builtinTypeNameDictionary ??= new Dictionary<Type, string>
+        {
+            { typeof(sbyte), "sbyte" },
+            { typeof(byte), "byte" },
+            { typeof(short), "short" },
+            { typeof(ushort), "ushort" },
+            { typeof(int), "int" },
+            { typeof(uint), "uint" },
+            { typeof(long), "long" },
+            { typeof(ulong), "ulong" },
+            { typeof(nint), "nint" },
+            { typeof(nuint), "nuint" },
+            { typeof(float), "float" },
+            { typeof(double), "double" },
+            { typeof(decimal), "decimal" },
+            { typeof(bool), "bool" },
+            { typeof(char), "char" },
+            { typeof(string), "string" },
+            { typeof(object), "object" },
+        };
+    }
+
     public static Type? FindTypeInLoadedAssemblies(string assemblyName, string typeFullName)
     {
         return AppDomain.CurrentDomain.GetAssemblies()
             .FirstOrDefault(a => a.GetName().Name == assemblyName)?
             .GetType(typeFullName);
     }
+
+    public static string ConstructTypeName(Type type)
+    {
+        if (!_isEditorHintCached)
+        {
+            return type.Name;
+        }
+
+        if (type is { IsArray: false, IsGenericType: false })
+        {
+            return GetSimpleTypeName(type);
+        }
+
+        var typeNameBuilder = new StringBuilder();
+        AppendType(typeNameBuilder, type);
+        return typeNameBuilder.ToString();
+
+        static void AppendType(StringBuilder sb, Type type)
+        {
+            if (type.IsArray)
+            {
+                AppendArray(sb, type);
+            }
+            else if (type.IsGenericType)
+            {
+                AppendGeneric(sb, type);
+            }
+            else
+            {
+                sb.Append(GetSimpleTypeName(type));
+            }
+        }
+
+        static void AppendArray(StringBuilder sb, Type type)
+        {
+            // Append inner most non-array element.
+            var elementType = type.GetElementType()!;
+            while (elementType.IsArray)
+            {
+                elementType = elementType.GetElementType()!;
+            }
+
+            AppendType(sb, elementType);
+            // Append brackets.
+            AppendArrayBrackets(sb, type);
+
+            static void AppendArrayBrackets(StringBuilder sb, Type type)
+            {
+                while (type != null && type.IsArray)
+                {
+                    int rank = type.GetArrayRank();
+                    sb.Append('[');
+                    sb.Append(',', rank - 1);
+                    sb.Append(']');
+                    type = type.GetElementType();
+                }
+            }
+        }
+
+        static void AppendGeneric(StringBuilder sb, Type type)
+        {
+            var genericArgs = type.GenericTypeArguments;
+            var genericDefinition = type.GetGenericTypeDefinition();
+
+            // Nullable<T>
+            if (genericDefinition == typeof(Nullable<>))
+            {
+                AppendType(sb, genericArgs[0]);
+                sb.Append('?');
+                return;
+            }
+
+            // ValueTuple
+            Debug.Assert(_tupleTypeSet != null);
+            if (_tupleTypeSet.Contains(genericDefinition))
+            {
+                sb.Append('(');
+                while (true)
+                {
+                    // We assume that ValueTuple has 1~8 elements.
+                    // And the 8th element (TRest) is always another ValueTuple.
+
+                    // This is a hard coded tuple element length check.
+                    if (genericArgs.Length != 8)
+                    {
+                        AppendParamTypes(sb, genericArgs);
+                        break;
+                    }
+                    else
+                    {
+                        AppendParamTypes(sb, genericArgs.AsSpan(0, 7));
+                        sb.Append(", ");
+
+                        // TRest should be a ValueTuple!
+                        var nextTuple = genericArgs[7];
+
+                        genericArgs = nextTuple.GenericTypeArguments;
+                    }
+                }
+                sb.Append(')');
+                return;
+            }
+
+            // Normal generic
+            var typeName = type.Name.AsSpan();
+            sb.Append(typeName[..typeName.LastIndexOf('`')]);
+            sb.Append('<');
+            AppendParamTypes(sb, genericArgs);
+            sb.Append('>');
+
+            static void AppendParamTypes(StringBuilder sb, ReadOnlySpan<Type> genericArgs)
+            {
+                int n = genericArgs.Length - 1;
+                for (int i = 0; i < n; i += 1)
+                {
+                    AppendType(sb, genericArgs[i]);
+                    sb.Append(", ");
+                }
+
+                AppendType(sb, genericArgs[n]);
+            }
+        }
+
+        static string GetSimpleTypeName(Type type)
+        {
+            Debug.Assert(_builtinTypeNameDictionary != null);
+            return _builtinTypeNameDictionary.TryGetValue(type, out string? name) ? name : type.Name;
+        }
+    }
 }