Introduction

SRDebugger is a suite of tools to help you fix bugs and iterate gameplay ideas while on your target platform. Having access to the Unity Console in any build of your game while on-device allows you to investigate bugs without deploying a debug build tethered to your computer. The options tab can help you find the perfect parameters for gameplay features by tweaking them in realtime on the destination platform (for example: drag-sensitivity, zoom speed, smoothing factor).

Getting Started

Quick setup and easy configuration were important goals when designing SRDebugger.

After importing the package from the Unity Asset Store the debugger will be accessible in all scenes of your game automatically. See configuration if this behaviour is undesirable and you wish to manually start SRDebugger.

Opening the Debug Panel

SRDebugger includes a simple, non-obstrusive trigger button to provide access to the debug panel on mobile devices. By default, the trigger is placed in the top-left corner of the game window and can be triple-tapped to open the panel. You can disable the trigger or change the position in the settings window (see configuration).

On platforms with a keyboard, you can setup keyboard shortcuts to open and close the debug panel and control various functions of SRDebugger.

You can also open the debug panel from code. See the API section.

Default Shortcuts

Key Combination Action
Ctrl-Shift-F1 Open System Tab
Ctrl-Shift-F2 Open Console Tab
Ctrl-Shift-F3 Open Options Tab
Ctrl-Shift-F4 Open Profiler Tab
Escape Close Debug Panel

Configuration

The SRDebugger Settings window can be accessed in the menubar (Window/SRDebugger Settings).

General

Loading

Disabled SRDebugger will not load until a manual call to the SRDebug.Init() API method.
Automatic SRDebugger will load automatically when your game starts (default)

Panel Access

Trigger Mode Always: Trigger will always be available, Mobile Only: Trigger will only appear on mobile platforms, Off: Trigger will never appear.
Trigger Behaviour Select how the trigger is activated, either Triple-Tap or Tap-and-Hold.
Default Tab Select the tab that will appear first when the debug panel is opened.
Require Entry Code Check to require the user to enter a code before accessing the debug panel for the first time (check every time to require the pin every time).

Layout

Use the dropdown menus to rearrange the positions of the docked tools and trigger.

Bug Reporter

See Bug Reporter.

Advanced

Console

Collapse Log Entries Duplicate log entries will be grouped in the same row in the console, with a badge indicating how many times it occured. Similar to the Unity Editor console Collapse mode.
Rich Text in Console Enable rich text support in console messages.

Display

Background Transparency If enabled the background of the debug panel will be semi-transparent.
Collapse Log Entries Duplicate log entries will be grouped in the same row in the console, with a badge indicating how many times it occured. Similar to the Unity Editor console Collapse mode.
Rich Text in Console Enable rich text support in console messages.
Layer Select the layer that the UI should be placed in. Defaults to the build-in UI layer.
Use Debug Camera By default, SRDebugger uses the Overlay render mode. If this option is selected, SRDebugger will create a camera with the depth provided. You can use this to control what renders on top of the SRDebugger UI.

Console

The console tab hooks into the default Unity Log system. Any calls to Debug.Log, Debug.LogWarning, Debug.LogError will appear here.

Select a log entry to view the full message and stack trace (if available on the target platform).

You can filter the visible log types with the buttons on the toolbar at the top of the screen. Press the Trash icon to clear the log. The Pin button will dock the console to the game view, press again to undock.

Options Tab

Note: If you are using assembly definitions, please use the API to add your own custom objects to the options tab.

It can be incredible useful to be able to modify gameplay parameters while on the target device. The Options Tab enables this by scanning for properties and methods in the built-in SROptions class.

SROptions is declared as a partial class, which allows you to split the class over multiple files. We advise creating a new file for each category of option, for example: SROptions.Gameplay.cs, SROptions.Debug.cs, SROptions.Cheats.cs.

Inside these files you should declare a class with the following signature:

public partial class SROptions { }

We will populate this class with properties and methods to build up the options menu.

Example SROptions class

Let’s begin with a full example of how an options class might look.

public partial class SROptions {

	// Default Value for property
	private float _myProperty = 0.5f;
	
	// Options will be grouped by category
	[Category("My Category")] 
	public float MyProperty {
		get { return _myProperty; }
		set { _myProperty = value; }
	}
	
	private float _myRangeProperty = 0f;
	
	// The NumberRange attribute will ensure that the value never leaves the range 0-10
	[NumberRange(0, 10)]
	[Category("My Category")]
	public float MyRangeProperty {
		get { return _myRangeProperty; }
		set { _myRangeProperty = value; }
	}
	
}

You can access these properties in code via the SROptions.Current static property.

transform.position = new Vector3(0, 0, SROptions.Current.MyProperty);

Properties

SRDebugger will scan the SROptions class for compatible properties automatically and include them in the Options Tab.

There are two ways of tying these properties into your own game code.

  1. Reference the property directly in script. e.g. transform.Translate(SROptions.Current.MySpeedProperty * Time.deltaTime, 0, 0);

  2. Implement custom object lookup the property get/set methods, to find and modify objects in the scene.

    // Simplified example - it is a good idea to handle cases where MyComponent is not found.
    public float MyCustomProperty {
     get { return GameObject.FindObjectOfType<MyComponent>().SomeField; }
     set { GameObject.FindObjectOfType<MyComponent>().SomeField = value; }
    }
    

For most cases, #1 would be the most appropriate approach. #2 is useful when you wish to affect code that you do not control, for example a library contained in a .dll file.

For more examples, look in StompyRobot/SRDebugger/Scripts/SROptions.Test.cs

Supported Types

Properties of the following types are supported: Boolean, Enum, int, uint, short, ushort, byte, sbyte, float, double, and string.

Read Only

Read only properties (missing or non-public setter method) will appear in the options tab, but are not able to be changed. This can be used to display debug information to the user. The property value will be rechecked whenever the options tab is opened, but you can use OnPropertyChanged to manually refresh the value.

OnPropertyChanged

The OnPropertyChanged callback will cause SRDebugger to check for new values for properties. If you don’t implement this callback, updates to SROptions properties will only be reflected in the options tab each time it is opened. With this callback implemented any updates to properties will appear instantly.

private int _netBytesIn;
 
[Category("Net")]
public int NetBytesIn {
    get { return _netBytesIn; }
    set {
        _netBytesIn = value;
        OnPropertyChanged("NetBytesIn");
    }
}

Methods

Any public methods in the SROptions class with no parameters and a void return type will appear in the options panel as buttons. When pressed, the method will be invoked dynamically. Category attributes can be applied to methods to group them as with properties.

using System.ComponentModel;
using UnityEngine;

public partial class SROptions {
	[Category("Utilities")] // Options will be grouped by category
	public void ClearPlayerPrefs() {
		Debug.Log("Clearing PlayerPrefs");
		PlayerPrefs.DeleteAll();
	}
}

Pinning

Options can be pinned to the game view by pressing the Pin button when on the options tab and selecting the options you wish to appear. These options can then be modified without entering the debug panel. A thick border will appear to indicate an options Pinned state.

To unpin an option, press the Pin button and tap the option. The border will disappear, indicating that the option is no longer pinned.

It is also possible to pin/unpin options via the API.

Attributes

You can use the built-in attributes to change how the user can interact with your options.

Attribute Description
NumberRange Limits a number property to the range provided.
Increment Changes how much a number property is changed by pressing the up/down buttons on the options tab.
DisplayName Manually control what name the option will use on the options tab.
Sort Provide a sorting index that is used to control the order in which options appear.
Category Group options by category name. (Note: The System.ComponentModel namespace must be imported in your file to use this attribute.

Option Containers

It is possible to add your own “Option Container” objects to the Options tab. See the API section for more information.

Profiler

The profiler provides an approximation of how long each step of a frame took to execute. Note, this is not as accurate or as detailed as the Unity Editor profiler. It is only intended as a tool for identifying areas of your game that are CPU or GPU heavy.

The frame graph can be viewed on the profiler tab or docked to the in-game view. The profiler docked-state can be toggled with the Pin icon on the toolbar or via the API.

Also available is an indicator of how much memory your Unity application has allocated and in use. You can manually trigger garbage collector runs or memory cleanups from the profiler tab.

Bug Reporter

The Bug Reporter is a web service that we host, provided free to all owners of SRDebugger. User bug reports will be forwarded directly to your email address as they arrive.

A bug report consists of the console log, system information, a screen capture, and (optionally) the user’s email and message. Once setup, the bug reporter tab will be visible in the debug panel.

If you wish to use the bug reporter without providing user access to the debug panel, you can use the API to show a bug report popover sheet.

Setup

To use the bug reporter you must acquire a free API key. In the SRDebugger Settings window (Window/SRDebugger Settings), switch to the Bug Reporter tab. The form will acquire an API key for you. You will need to provide your Invoice Number from the Asset Store receipt and a valid email address that bug reports will be sent to. You will need to click the link sent to the verify the email address before you can receive any bug reports.

Custom Handlers

See the Bug Report Handler section.

API

The SRDebugger API is accessed via the SRDebug.Instance static property.

Available Methods

// Current settings being used by the debugger
Settings Settings { get; }

// True if the debug panel is currently being shown
bool IsDebugPanelVisible { get; }

// True if the trigger is currently enabled
bool IsTriggerEnabled { get; set; }

// Show the debug panel
void ShowDebugPanel(bool requireEntryCode = true);

// Show the debug panel and open a certain tab
void ShowDebugPanel(DefaultTabs tab, bool requireEntryCode = true);

// Hide the debug panel
void HideDebugPanel();

// Hide the debug panel, then remove it from the scene to save memory.
void DestroyDebugPanel();

// Open a bug report popover sheet.
void ShowBugReportSheet(ActionCompleteCallback onComplete = null, bool takeScreenshot = true, string descriptionContent = null);

// Event invoked whenever the debug panel opens or closes
event VisibilityChangedDelegate PanelVisibilityChanged;

// Methods related to docking/undocking the console
IDockConsoleService DockConsole { get; }

// True if the profiler is currently docked.
bool IsProfilerDocked { get; set; }

// Add a piece of information to the System tab (also included with bug reports).
void AddSystemInfo(InfoEntry entry, string category = "Default");

// Add all the properties/methods in "container" to the options tab.
void AddOptionContainer(object container);

// Remove all the properties/methods added by "container" from the options tab.
void RemoveOptionContainer(object container);

// Add a dynamic option to the options panel.
void AddOption(OptionDefinition option);

// Remove a dynamic option from the options panel.
bool RemoveOption(OptionDefinition option);

// Pin all the options in "category"
void PinAllOptions(string category);

// Unpin all the options in "category"
void UnpinAllOptions(string category);

// Pin all the options with "name"
void PinOption(string name);

// Unpin all the options with "name"
void UnpinOption(string name);

// Unpin all options
void ClearPinnedOptions();

// ADVANCED. Will transform the debug panel canvas into a world space object and return the RectTransform.
// This can break things so only use it if you know what you're doing. It can be useful for VR, for example, attaching the panel above the user controller.
RectTransform EnableWorldSpaceMode(); // (Added in 1.6.0)

// Set a custom bug reporter handler. (see Bug Report Handler section below)
void SetBugReporterHandler(IBugReporterHandler bugReporterHandler);

Options Containers

An Options Container is simply a normal C# object. All the usual SROptions supported types and restrictions apply, except that you handle the creation of the object. Options containers can be added or removed at any time. If the container object implements the INotifyPropertyChanged interface, and properties call the OnPropertyChanged method, any changes you make via code will be reflected in the SRDebugger Options panel.

Example

Adding option containers for specific levels

Some options might only be relevent to certain levels of your game. You can use the “Option Containers” API to add/remove options at runtime.

// sceneOptions represents your own management system for the options objects.
var sceneOptions = new Dictionary<Scene, object>();

SceneManager.sceneLoaded += (scene, mode) =>
{
    if (sceneOptions.ContainsKey(scene))
    {
        SRDebug.Instance.AddOptionContainer(sceneOptions[scene]);
    }
};

SceneManager.sceneUnloaded += scene =>
{
    if (sceneOptions.ContainsKey(scene))
    {
        SRDebug.Instance.RemoveOptionContainer(sceneOptions[scene]);
    }
};

Options containers contained in a custom .dll)

If using your own DLLs for game logic, it is not possible to use the same partial class system the default SROptions object uses. Instead, you can create your own options object and register it with the API.

// In MyGameCode.dll
class MyOptions {
	public static readonly MyOptions Instance = new MyOptions();

	public void SomeAction() { /* ... */ }
	public float SomeProperty { get; set; }
}

// In Unity
SRDebug.Instance.AddOptionContainer(MyOptions.Instance);

Dynamic Options

A dynamic options can be used to add or remove options on the fly without requiring an underlying C# class member for each option. This is advantagious when there is no 1-1 correlation between a options and your C# class structure.

For example, you could create a ‘speed’ option for each enemy that spawns in your game as they appear.

There are a few approaches available:

  • The Add/RemoveOption API
  • The Add/RemoveOptionsContainer API

Add and remove options manually via the API

Options are created by using the OptionDefinition.Create() static method. NOTE: Options must be removed manually whenever the associated object that created them is destroyed. There is no automatic cleanup.

OptionDefinition.Create(string name, Func<T> getter, Action<T> setter = null, string category = "Default", int sortPriority = 0)
Read-only property
class MyBehaviour : MonoBehaviour
{
    private int _someInternalField = 10;

    void Start()
    {
        // Create a read-only option
        var option = OptionDefinition.Create("My Option", () => _someInternalField);
        SRDebug.Instance.AddOption(option);
    }
}
Mutable Property
class MyBehaviour : MonoBehaviour
{
    private int _someInternalField;

    void Start()
    {
        // Create a mutable option
        var option = OptionDefinition.Create(
            "My Option", 
            () => _someInternalField, 
            (newValue) => _someInternalField = newValue
        );

        SRDebug.Instance.AddOption(option);
    }
}

Method Option (from a class method)

class MyBehaviour : MonoBehaviour
{
    void Start()
    {
        OptionDefinition.FromMethod("My Class Method", MyMethod);
    }

    void MyMethod()
    {
        Debug.Log("My Class Method!");

    }
}

Method Option (from a lambda)

void Start()
{
    OptionDefinition.FromMethod("My Dynamic Method", () => 
    {
        // Write your code in here.
        Debug.Log("My Dynamic Method!");
    });
}

DynamicOptionContainer

If you have a collection of options that you would like to add or remove as a group, you can use a DynamicOptionsContainer.

// Create the container and add it to the debug api
var container = new DynamicOptionContainer();
SRDebug.Instance.AddOptionContainer(container);

// Populate the container with options
container.AddOption(OptionDefinition.Create(/* etc */));
container.AddOption(OptionDefinition.Create(/* etc */));
container.AddOption(OptionDefinition.Create(/* etc */));

// Options can be removed.
container.RemoveOption(/* some option */);

// The container can be removed from the options panel
SRDebug.Instance.RemoveOptionContainer(container);

Custom IOptionContainer

For more advanced integration you can implement the IOptionContainer interface.

public interface IOptionContainer
{
    /// <summary>
    /// Get the initial set of options contained in this object.
    /// </summary>
    IEnumerable<OptionDefinition> GetOptions();

    bool IsDynamic { get; }

    event Action<OptionDefinition> OptionAdded;
    event Action<OptionDefinition> OptionRemoved;
}

GetOptions() should return the current set of options from this container.

If IsDynamic is true when the container is added to the SRDebug api, options can be added and removed via the OptionAdded and OptionRemoved events. If false, the events will be ignored.

Note: GetOptions() will only be called when the container is added via the api. Changes to the collection must be provided via events.

Bug Report Handler

Since 1.12 it is possible to set your own bug report handler that is called when a user submits a bug report.

public interface IBugReporterHandler
{
    /// <summary>
    /// Returns true if this bug reporter handler is able to be used.
    /// If false, the bug reporter tab will be hidden.
    /// </summary>
    bool IsUsable { get; }

    /// <summary>
    /// Submit a new bug report to the handler.
    /// </summary>
    /// <param name="report">The report to be submitted.</param>
    /// <param name="onComplete">Action to be invoked when the bug report is completed.</param>
    /// <param name="progress">Callback to set the current submit progress.</param>
    void Submit(BugReport report, Action<BugReportSubmitResult> onComplete, IProgress<float> progress);
}

class MyBugReportHandler : IBugReporterHandler
{
    public bool IsUsable => /* if false, the bug reporter tab will be disabled/hidden */

    public void Submit(BugReport report, Action<BugReportSubmitResult> onComplete, IProgress<float> progress)
    {
        // Read the data from report and submit to your own API

        // If the report is successful:
        onComplete(BugReportSubmitResult.Success);

        // If the report is unsuccessful:
        onComplete(BugReportSubmitResult.Error("Error message to display to the user"));

        // Optionally, you can report the current progress (e.g. upload progress) to update the progress bar:
        progress.Report(0.3); // 30% complete
    }
}

Once you have implemented a bug report handler, it can be enabled via the API:

SRDebug.Instance.SetBugReporterHandler(new MyBugReportHandler());

Examples

Opening debug panel on options tab

SRDebug.Instance.ShowDebugPanel(DefaultTabs.Options);

Hiding debug panel

if(SRDebug.Instance.IsDebugPanelVisible)
	SRDebug.Instance.HideDebugPanel();

Showing bug report popover

SRDebug.Instance.ShowBugReportSheet(); 

Showing bug report popover with callback

SRDebug.Instance.ShowBugReportSheet(success => Debug.Log("Bug Report Complete, Success: " + success));

Showing bug report popover with default description

SRDebug.Instance.ShowBugReportSheet(descriptionContent: "This will be placed in the description field.");

Toggling the docked console

SRDebug.Instance.DockConsole.IsVisible = !SRDebug.Instance.DockConsole.IsVisible;

Toggling the docked console collapsed state

SRDebug.Instance.DockConsole.IsExpanded = !SRDebug.Instance.DockConsole.IsExpanded;

Toggling the profiler docked state

SRDebug.Instance.IsProfilerDocked = !SRDebug.Instance.IsProfilerDocked;

Custom action when debug panel opens/closes

Some games use input methods which aren’t suitable for navigating the debug panel UI (e.g. VR or 1st-person shooters). In these cases, you can subscribe to the PanelVisibilityChanged event to enable a suitable input method only when the debug panel is visible.

SRDebug.Instance.PanelVisibilityChanged += visible => {
    // Handle panel visibility changing here.
    Debug.Log("IsVisible: " + visible);
};

Entry Code

You can use the same entry code form that SRDebugger uses to control access to parts of your game.

using SRDebugger.Services;
using SRF.Service;
using UnityEngine;

public class PinSample : MonoBehaviour
{
    void Start()
    {
        // Pin must be 4-digits, 0-9
        SRServiceManager.GetService<IPinEntryService>().ShowPinEntry(new [] {3,0,5,6}, "Message to user here", OnComplete);
    }

    private void OnComplete(bool validPinEntered)
    {
        if (validPinEntered)
        {
            // User entered a valid code
        }
        else
        {
            // User entered invalid code
        }
    }
}

Editor API

Starting in SRDebugger 1.10.0 there is an editor API available for performing scripted operations on SRDebugger at edit-time (e.g. during a build process). To access the API you must reference the SRDebugger.Editor.asmdef assembly.

namespace SRDebugger.Editor
{
    public static SRDebugEditor
    {
        public static bool IsEnabled; // Returns whether SRDebugger is currently enabled or disabled.

        // Set whether SRDebugger should be enabled or disabled.
        // If disabled, SRDebugger code will no longer be usable from your scripts
        // and no SRDEbugger resources will be included in your build.
        public static void SetEnabled(bool enable);
    }
}

Disable SRDebugger

SRDebugEditor.SetEnabled(false);

See Disabling SRDebugger for more information on enabling/disabling SRDebugger.

Advanced Features

Disabling SRDebugger

Note: This feature is experimental. Please make sure your project is backed up somewhere secure (e.g. source control) before using this feature.

When it’s time to ship your game, it may be desirable to remove any debug tools so your users cannot abuse them. In these cases you can disable SRDebugger via the option in the settings window or via the API.

The disable feature is available in the “Advanced” tab of the settings window. The UI provides an overview of the operations that will be performed to disable SRDebugger.

SRDebugger can be re-enabled at any time, the operations outlined below will be reversed automatically.

How it works

  • The DISABLE_SRDEBUGGER C# compiler define will be added to all platforms.

Any code you have written that interacts with SRDebugger at runtime must use this compiler define to remove those usages.

#if !DISABLE_SRDEBUGGER
SRDebug.Instance.ShowDebugPanel();
#endif
  • Some SRDebugger folders will be renamed and removed from your project Asset Database.

Resource folders are renamed to <FolderName>_DISABLED~. The ~ at the end of the name informs Unity that the folder should be ignored and not to import any assets contained within.

Not all SRDebugger folders are renamed - only the folders that Unity includes directly in builds without checking for usages are disabled (Resource Folders). Any remaining assets will not be included in your game as there is nothing to reference them.

Disabling SRDebugger & Source Control

It is not recommended that you submit your project to source control while SRDebugger is disabled, as this will result in many rename/move operations. Instead, SRDebugger should be disabled locally before performing a release build and re-enabled again before submitting any changes to your source control.

Updating SRDebugger

You should not import a new version of SRDebugger while SRDebugger is disabled, as this may cause unexpected behaviour due to resource folders not being overwritten correctly.

Support

FAQ

Q: My game responds to input while the debug panel is open. How can I stop this?

A: Use the IsPointerOverGameObject() method to filter input that is blocked by a UI element. (See here for details.)

Q: I am getting a message about the Unity log handler being overriden / my logs aren’t showing up.

A: SRDebugger uses the Application.logMessageReceivedThreaded event to obtain your log messages. If the default Unity log handling is overriden or disabled then SRDebugger will not be aware of any log messages. If you have a custom log handler (Debug.unityLogger.logHandler) then ensure it calls the original log handler.

Direct Support

Email contact@stompyrobot.uk with any support questions, or post in the Unity Forum Thread.