Debugger Extensions for Unity in Rider 2020.2
Rider 2020.2 has a lot of great new features for Unity, and debugging has certainly had its fair share of attention in this release.
We’ve already introduced “pausepoints”, a new kind of breakpoint that puts the Unity editor into pause mode when a condition is met in code, allowing you to use the Unity Inspector to see what’s going on at that point. And in the last post, we saw how Rider can help you debug players, either standalone on the desktop, or on mobile devices and consoles, including USB support for Apple devices. Today, we’re going to look at a set of small features that will make a big difference to how you debug your Unity projects.
One of the problems with debugging a Unity game is understanding your current context – if you’re debugging a
MonoBehaviour script, which game object is it attached to? Where is it in the object hierarchy? What other components are attached to the game object? What’s the position of this
All of these questions and more can be answered through the debugger, but that often requires drilling down into objects, or evaluating expressions and calling methods, making you work to find these answers, rather than having the most useful information quickly and easily accessible.
Rider has made a whole load of little tweaks to how we display Unity values in the debugger, all designed to give you a better view of the current state of your project, and to make it easier to see this much needed context.
Rider does this in a number of ways. It will add extra items to the Watches and Variables tab, showing you the currently active scene, for example. It also adds extra child items to expanded values – the "Active Scene" item will show root game objects, and each
GameObject shows child game objects and attached components, giving a view of the whole scene hierarchy. Rider has also updated the summary information displayed for some Unity types, pulling useful information up so it’s always visible, such as showing the position, rotation and scale of a
Transform without having to expand the value. Let’s take a look at more details, and see some examples.
Active scene and the game object hierarchy
Whenever you hit a breakpoint, Rider will add an “Active Scene” item to the Variables and Watches tab, very much like an automatically created watch variable. This shows the active
Scene object, as returned by
SceneManager.GetActiveScene. The idea here is to give you the context you need for your Unity game, and the active scene is a great entry point for this. Collapsed, it will show the scene name and file path, and when expanded, you’ll not only see the normal debugger view of fields and properties, but you’ll also see a new group called “Game objects”. Expanding this item will list the root game objects in the scene.
Rider adds new items to
GameObject instances, too. The “Children” group shows the child
GameObjects of this instance, very much like how the “Game objects” group works for
Scene. You can use this to get a debugger view of the entire object hierarchy of your scene. And don’t worry about performance – Rider collects these values on demand, so they’re not calculated unless they’re shown, and Rider will group results larger than one hundred game objects, so it will handle very large hierarchies, too.
You might notice a couple of other new values under
GameObject. There’s a new “Components” group that shows all
Component instances attached to the
GameObject, such as
Transform or any attached
MonoBehaviour derived scripts. And there’s a new “Scene path” value that shows the location of the
GameObject in the object hierarchy, as a path from the root object. This is also shown for
Component instances, making it a little easier to see where a
Component is being used in the object hierarchy.
Finally, if the current object being debugged – i.e.
this – is a
Component such as a
MonoBehaviour script, then Rider will add another “automatic watch variable”,
this.gameObject. This does exactly what the name implies and shows the
this.gameObject property. It would be very easy to expand
this and find the
gameObject value, but this simple addition pulls some useful context up to the top level to make it quicker and easier to find.
Improving summary info
So far, we’ve looked at only one way the debugger has to display values – the Variables and Watches tree view. This can show complex objects such as a class or struct by expanding the value to show fields and properties. But when collapsed, the debugger shows a brief summary, hopefully showing the most relevant information. You can also see this summary info in tooltips, and drawn on the editor surface.
This content comes from the object itself, either as the output of
ToString() (which by default is simply the name of the object’s type, but can be overridden in derived objects), or from the
[DebuggerDisplay(...)] attribute, which specifies field or property names in a string.
Unity doesn’t make much use of
[DebuggerDisplay], but it does override
ToString() in a number of places. Unfortunately, the output of
ToString() is sometimes more appropriate to user facing output than it is to debugging. For example, the implementation of
Vector3.ToString() defaults to showing the float values of
z with the
F1 format specifier. This means that each float value is shown to one decimal place. While this makes for a nice looking output, it’s not helpful when the summary and expanded view of an object are so different.
So Rider provides alternative summary information for a number of Unity types. This includes the
Vector types, as well as
Ray and so on. Any Unity type that outputs float values at
F1 has been overridden to show the field values at full precision, making it much easier to see, at a glance, what the value actually is.
There are other types that Rider overrides like this.
Transform is a great example.
Transform derives from
UnityEngine.Object, which provides an implementation of
ToString that shows the name of the object and its type. The name of a component is always the name of the game object it’s attached to, so for a transform, this quite often shows something like
"Cube (UnityEngine.Transform)". Wouldn’t it be better to show the local position, rotation and scale without having to expand the object?
Rider provides overrides for a few other
Object based types – the
MeshFilter component quickly shows the vertex count and
Scene will show the scene name and file path.
GameObject shows its name and the
layer properties. Objects deriving from
Behaviour show the
enabled flag, plus the name of the
GameObject they’re attached to, surfacing the most important details to tooltips and summary information.
And don’t forget that you can add debugging information for you own types too – override
ToString() or use
[DebuggerDisplay(...)] to provide summary information, or use
[DebuggerTypeProxy(typeof(...))] to specify an object to provide different fields and properties when you expand an object. And the
[DebuggerBrowsable(...)] attribute can be used to hide a type member. See the docs for more information.
Of course, many of Rider’s other debugger features work just fine with Unity projects, too. Things like Pin to Top and setting the next statement, conditional and dependent breakpoints (these work great with pausepoints, too!), debugging decompiled code, and also the new Pin to Frame (although sadly, not the Data Breakpoints in that blog post, as that requires an API that is only available in .NET Core on Windows).
And finally, the best kind of new feature – we’ve removed something! Both
Component have a number of obsolete properties that do nothing but throw exceptions. They’re hidden from code completion with the
[EditorBrowsable(EditorBrowsableState.Never)] attribute, but not hidden from the debugger with
[DebuggerBrowsable(DebuggerBrowsableState.Never)]. You might have seen this in older versions of Rider – the exceptions aren’t causing any problems, but they do add a lot of noise. Rider now filters them out. Much cleaner!
Before we finish, it’s important to point out that all of these features are available when debugging your game in the editor, but not everything is available when debugging players. This is because the debugger can’t always get the information – some features require APIs that only exist when the application is running in the Unity editor, and others require fields or properties that are stripped from player builds. If it can get the data, the debugger will show it. If not, it will gracefully fall back, and you’ll see the standard
ToString representation of the value.
Oh, and if you don’t want to see this extra information, you can disable it in the Preferences | Languages & Frameworks | Unity Engine settings page.
In the next few posts on Unity, we’ll take a look at analysis of Burst enabled code, the long awaited shader support, and perhaps we’ll have a quick round up of other changes. In the mean time, download Rider and try out the updated debugger on your own projects. We’re looking forward to your feedback!