A comprehensive collection of debugging utilities for Unity developers that provides Python-like repr() functionality, advanced object representation, scene navigation, and call stack analysis tools.
- Core Features
- Installation
- Quick Start
- Features
- Configuration Options
- Real-World Unity Use Cases
- Performance Considerations
- Troubleshooting
- Unity Version Compatibility
- Dependencies
- Roadmap
- Contributing
- License
🔍 Object Representation (Repr, ReprTree Documentation)
Stop getting useless ToString() output. See actual object contents in Unity.
📍 Call Stack Tracking (CallStack Documentation)
Know exactly where your code is executing and which methods are calling what in your Unity project.
🎮 Scene Navigation (SceneNavigator Documentation)
Navigate Unity scene hierarchies with simple path strings instead of manual GameObject traversal.
- Open Unity Package Manager (Window → Package Manager)
- Click the + button in the top-left corner
- Select "Add package from git URL..."
- Enter:
https://github.com/dj-lumiere/DebugUtils-Unity.git#upm - Click Add
# Once published to OpenUPM
openupm add com.lumi.debugutils.unity- Download the latest release from GitHub Releases
- Extract the package
- Copy the
DebugUtils.Unityfolder to your project'sAssetsfolder
Newtonsoft.Json for Unity (required for ReprTree functionality):
- Open Package Manager (Window → Package Manager)
- Select "Unity Registry"
- Search for "com.unity.nuget.newtonsoft-json"
- Click Install
using DebugUtils.Repr;
using UnityEngine;
// See what's actually in your objects
var player = new { Name = "Alice", Health = 100, Position = new Vector3(1, 0, 3) };
Debug.Log(player.Repr());
// Output: Anonymous(Name: "Alice", Health: 100_i32, Position: Vector3(1, 0, 3))
// Track where your code is executing
using DebugUtils.CallStack;
void OnPlayerDamage(int damage)
{
Debug.Log($"[{CallStack.GetCallerName()}] Player took {damage} damage");
// Output: [Enemy.Attack] Player took 25 damage
}
// Navigate Unity scenes easily
using DebugUtils.Unity.SceneNavigator;
var healthBar = SceneNavigator.FindComponentByPath<Slider>("Canvas/HUD/HealthBar");
if (healthBar != null)
{
healthBar.value = 0.75f;
}using DebugUtils.CallStack;
using DebugUtils.Repr;
using DebugUtils.Unity.SceneNavigator;
using UnityEngine;
using UnityEngine.UI;
public class GameDebugExample : MonoBehaviour
{
private void Start()
{
// 🔍 See your data clearly
var inventory = new[] { "Sword", "Shield", "Potion x3" };
Debug.Log($"Inventory: {inventory.Repr()}");
// Output: Inventory: ["Sword", "Shield", "Potion x3"]
// 📍 Know where you are in the code
InitializeGame();
}
private void InitializeGame()
{
Debug.Log($"[{CallStack.GetCallerName()}] Initializing game systems...");
// Output: [GameDebugExample.Start] Initializing game systems...
// 🎮 Find UI elements easily
var startButton = SceneNavigator.FindComponentByPath<Button>("Canvas/MainMenu/StartButton");
if (startButton != null)
{
Debug.Log($"Found button at: {startButton.gameObject.GetScenePath()}");
}
}
private void OnCollisionEnter(Collision collision)
{
// See complete collision data
Debug.Log($"Collision: {collision.gameObject.Repr()}");
Debug.Log($"At: {collision.contacts[0].point.Repr()}");
}
}
## Features
### 🔍 Object Representation (`.Repr()`)
Works with any type - see actual data instead of useless type names, including Unity-specific types.
### Unity Objects and Components
```csharp
// GameObjects and Components
GameObject player = GameObject.Find("Player");
player.Repr() // GameObject(Player, active: true, layer: 0, tag: Player)
Transform transform = player.transform;
transform.Repr() // Transform(position: Vector3(0, 1, 0), rotation: Quaternion(0, 0, 0, 1))
// Unity Vector types
Vector3 position = new Vector3(1.5f, 2.0f, 3.7f);
position.Repr() // Vector3(1.5, 2, 3.7)
// Component arrays
Component[] components = player.GetComponents<Component>();
components.Repr() // [Transform(...), Rigidbody(...), PlayerController(...)]// Arrays (1D, 2D, jagged)
new[] {1, 2, 3}.Repr() // 1DArray([1_i32, 2_i32, 3_i32])
new[,] {{1, 2}, {3, 4}}.Repr() // 2DArray([[1_i32, 2_i32], [3_i32, 4_i32]])
new[][] {{1, 2}, {3, 4, 5}}.Repr() // JaggedArray([[1_i32, 2_i32], [3_i32, 4_i32, 5_i32]])
// Lists, Sets, Dictionaries
new List<int> {1, 2, 3}.Repr() // [1_i32, 2_i32, 3_i32]
new HashSet<string> {"a", "b"}.Repr() // {"a", "b"}
new Dictionary<string, int> {{"x", 1}}.Repr() // {"x": 1_i32}// Integers with different representations (now with Rust-style type suffixes)
42.Repr() // 42_i32
42.Repr(new ReprConfig(IntFormatString: "X")) // 0x2A_i32
42.Repr(new ReprConfig(IntFormatString: "O")) // 0o52_i32 (octal)
42.Repr(new ReprConfig(IntFormatString: "B")) // 0b101010_i32
((byte)255).Repr() // 255_u8
((long)-1000).Repr() // -1000_i64
// Floating point with exact representation
// You can now recognize the real floating point value
// and find what went wrong when doing arithmetics!
(0.1 + 0.2).Repr()
// 3.00000000000000444089209850062616169452667236328125E-001_f64
0.3.Repr()
// 2.99999999999999988897769753748434595763683319091796875E-001_f64
(0.1 + 0.2).Repr(new ReprConfig(FloatFormatString: "G"))
// 0.30000000000000004_f64
// Special formatting modes
3.14f.Repr(new ReprConfig(FloatFormatString: "HP")) // 0x1.91EB86p+001_f32 (hex power)
3.14f.Repr(new ReprConfig(FloatFormatString: "EX")) // 3.14000010490417480468750E+000_f32 (exact)Perfect for Unity logging, error tracking, and debugging call flows:
// PlayerController.cs
public class PlayerController : MonoBehaviour
{
public void ProcessInput(Vector3 inputVector)
{
var caller = CallStack.GetCallerName();
Debug.Log($"[{caller}] Processing input: {inputVector.Repr()}");
if (inputVector.magnitude > 1.0f)
{
Debug.LogWarning($"[{caller}] Input vector too large, normalizing");
inputVector = inputVector.normalized;
}
try
{
MovePlayer(inputVector);
}
catch (Exception ex)
{
Debug.LogError($"[{caller}] Movement failed: {ex.Message}");
throw;
}
}
private void MovePlayer(Vector3 direction)
{
var caller = CallStack.GetCallerName();
Debug.Log($"[{caller}] Moving player in direction: {direction.Repr()}");
transform.Translate(direction * Time.deltaTime);
}
}
// Unity Console Output: [PlayerController.ProcessInput] Processing input: Vector3(0.8, 0, 0.6)
// Unity Console Output: [PlayerController.MovePlayer] Moving player in direction: Vector3(0.8, 0, 0.6)Get the file, line, and column number for even more precise Unity debugging.
// GameManager.cs
public class GameManager : MonoBehaviour
{
public void StartLevel(int levelIndex)
{
var caller = CallStack.GetCallerInfo();
Debug.Log($"[{caller}] Starting level: {levelIndex}");
if (levelIndex < 0 || levelIndex >= totalLevels)
{
Debug.LogError($"[{caller}] Invalid level index: {levelIndex}");
return;
}
LoadLevel(levelIndex);
}
}
// Unity Console Output: [GameManager.StartLevel@GameManager.cs:12:8] Starting level: 3Navigate Unity scenes with simple path strings:
public class UIController : MonoBehaviour
{
private void Start()
{
// Find UI elements with readable paths
Button playBtn = SceneNavigator.FindComponentByPath<Button>("Canvas/MainMenu/PlayButton");
Text scoreText = SceneNavigator.FindComponentByPath<Text>("Canvas/HUD/ScoreText");
Slider volumeSlider = SceneNavigator.FindComponentByPath<Slider>("Canvas/Settings/Audio/VolumeSlider");
if (playBtn != null)
{
Debug.Log($"Found play button at: {playBtn.gameObject.GetScenePath()}");
playBtn.onClick.AddListener(StartGame);
}
}
private void OnTriggerEnter(Collider other)
{
var caller = CallStack.GetCallerName();
string otherPath = other.gameObject.GetScenePath();
Debug.Log($"[{caller}] Collision with object at: {otherPath}");
}
}
// Output: Found play button at: SampleScene/Canvas/MainMenu/PlayButton
// Output: [UIController.OnTriggerEnter] Collision with object at: SampleScene/Level/Player/PlayerControllerUse cases:
- Component debugging - Track MonoBehaviour method execution
- Unity event debugging - Know exactly which UI event fired
- Physics debugging - Trace collision and trigger events
- Animation debugging - Track animation event callbacks
- State machine debugging - Monitor state transitions
For object representation, DebugUtils uses deterministic alphabetical ordering within member categories:
- Public fields (alphabetical by name)
- Public auto-properties (alphabetical by name)
- Private fields (alphabetical by name, prefixed with "private_")
- Private auto-properties (alphabetical by name, prefixed with "private_")
public class ClassifiedData
{
public long Id = 5; // Category 1: Public field
public int Age = 10; // Category 1: Public field
public string Writer { get; set; } // Category 2: Public auto-property
public string Name { get; set; } // Category 2: Public auto-property
private DateTime Date = DateTime.Now; // Category 3: Private field
private string Password = "secret"; // Category 3: Private field
private string Data { get; set; } // Category 4: Private auto-property
private Guid Key { get; set; } // Category 4: Private auto-property
}
// Output with ShowNonPublicProperties: true
// ClassifiedData(Age: 10_i32, Id: 5_i64, Name: "Alice", Writer: "Bob",
// private_Date: DateTime(...), private_Password: "secret",
// private_Data: "info", private_Key: Guid(...))// Format strings for floats
var exact = new ReprConfig(FloatFormatString: "EX");
3.14159.Repr(exact); // 3.14158999999999988261834005243051797151565551757812500E+000_f64
var scientific = new ReprConfig(FloatFormatString: "E5");
3.14159.Repr(scientific); // 3.14159E+000_f64
var rounded = new ReprConfig(FloatFormatString: "F2");
3.14159.Repr(rounded); // 3.14_f64
// Special debugging modes
var hexPower = new ReprConfig(FloatFormatString: "HP");
3.14f.Repr(hexPower); // 0x1.91EB86p+001_f32 (hexadecimal floating-point)
// Decimal type also supports HP format (but differently)
var dec = 3.14159m;
dec.Repr(); // 3.14159_m
dec.Repr(new ReprConfig(FloatFormatString: "HP")); // 0x00000000_00000000_0004CB2Fp10-005_m// Format strings for integers
var hex = new ReprConfig(IntFormatString: "X");
255.Repr(hex); // 0xFF_i32
var octal = new ReprConfig(IntFormatString: "O");
255.Repr(octal); // 0o377_i32
var quaternary = new ReprConfig(IntFormatString: "Q");
255.Repr(quaternary); // 0q3333_i32
var binary = new ReprConfig(IntFormatString: "B");
255.Repr(binary); // 0b11111111_i32
// Width specifiers for padding
42.Repr(new ReprConfig(IntFormatString: "X8")); // 0x0000002A_i32
42.Repr(new ReprConfig(IntFormatString: "B16")); // 0b0000000000101010_i32var hideTypes = new ReprConfig(
TypeMode: TypeReprMode.AlwaysHide,
ContainerReprMode: ContainerReprMode.UseParentConfig
);
playerInventory.ToArray().Repr(hideTypes); // ["sword", "potion", "key"] (no type prefixes)
var showTypes = new ReprConfig(TypeMode: TypeReprMode.AlwaysShow);
playerStats.Repr(showTypes); // PlayerStats(Health: 100_i32, Mana: 50_i32, Level: 15_i32)
// Note: Numeric types always show their bit-width suffix (_i32, _f64, etc.)
// regardless of TypeMode setting for clarity about precisionpublic class PlayerData : MonoBehaviour
{
public string PlayerName { get; set; }
public int Level { get; set; }
public Vector3 SpawnPosition { get; set; }
public PlayerData(string name, int level, Vector3 spawn)
{
PlayerName = name;
Level = level;
SpawnPosition = spawn;
}
}
var playerData = GetComponent<PlayerData>();
Debug.Log(playerData.ReprTree());
// Output: {
// "type": "PlayerData",
// "kind": "class",
// "hashCode": "0xAAAAAAAA",
// "PlayerName": {"type": "string", "kind": "class", "hashCode": "0xBBBBBBBB", "length": 4, "value": "Alice"},
// "Level": {"type": "int", "kind": "struct", "value": "15"},
// "SpawnPosition": {
// "type": "Vector3",
// "kind": "struct",
// "x": {"type": "float", "kind": "struct", "value": "0"},
// "y": {"type": "float", "kind": "struct", "value": "1"},
// "z": {"type": "float", "kind": "struct", "value": "0"}
// }
// } (hashCode may vary)Debug game mechanics and state instantly:
public class GameManager : MonoBehaviour
{
public List<Player> activePlayers;
public Dictionary<string, int> levelScores;
private void Update()
{
if (Input.GetKeyDown(KeyCode.F1))
{
var caller = CallStack.GetCallerInfo();
Debug.Log($"[{caller}] Active players: {activePlayers.Repr()}");
Debug.Log($"[{caller}] Level scores: {levelScores.Repr()}");
}
}
public void ProcessGameTick()
{
var caller = CallStack.GetCallerName();
var gameState = GetCurrentGameState();
Debug.Log($"[{caller}] Game state: {gameState.ReprTree()}"); // Full structure
}
}public class CollisionHandler : MonoBehaviour
{
private void OnCollisionEnter(Collision collision)
{
var caller = CallStack.GetCallerInfo();
Debug.Log($"[{caller}] Collision with: {collision.gameObject.Repr()}");
Debug.Log($"[{caller}] Contact points: {collision.contacts.Repr()}");
Debug.Log($"[{caller}] Object path: {collision.gameObject.GetScenePath()}");
}
}[Test]
public void TestPlayerMovement()
{
var player = CreateTestPlayer();
var initialPos = player.transform.position;
var inputVector = new Vector3(1, 0, 0);
player.ProcessInput(inputVector);
var finalPos = player.transform.position;
// Amazing error messages when tests fail
var caller = CallStack.GetCallerInfo();
Debug.Log($"[{caller}] Initial: {initialPos.Repr()}");
Debug.Log($"[{caller}] Input: {inputVector.Repr()}");
Debug.Log($"[{caller}] Final: {finalPos.Repr()}");
Assert.AreNotEqual(initialPos, finalPos);
}- Unity 2021.3 LTS and higher
- Compatible with all Unity render pipelines (Built-in, URP, HDRP)
- Works in both Editor and Runtime
- Unity 2021.3 LTS+
- Newtonsoft.Json for Unity (com.unity.nuget.newtonsoft-json) - Required for
.ReprTree()functionality
Current Features:
✅ .Repr() - Comprehensive object representation with Unity types
✅ .ReprTree() - Structured JSON tree output using Newtonsoft.Json
✅ .FormatAsJsonNode() - Custom formatter building blocks
✅ GetCallerName() - Simple call stack tracking
✅ GetCallerInfo() - Detailed call stack tracking
✅ SceneNavigator - Unity scene hierarchy navigation
✅ Unity 2021.3+ support
✅ Zero additional dependencies (except Newtonsoft.Json)
✅ Circular reference detection
✅ Custom formatter system for Unity types
Planned Features:
- 🔄 Unity Profiler integration
- 🔄 Component dependency visualization
- 🔄 Scene hierarchy debugging tools
- 🔄 Animation state debugging utilities
- 🔄 Physics debugging helpers
- 🔄 Custom Unity Console window integration
This library started as a solution for Unity debugging pain points and is growing into a comprehensive Unity debugging toolkit.
Built out of frustration with Unity debugging challenges. If you have ideas for additional Unity debugging utilities or find bugs, contributions are welcome!
Ideas for new features:
- Unity-specific performance profiling
- GameObject lifecycle tracking
- Component state change monitoring
- Unity event system debugging
- Custom Unity inspector integration
This project follows MIT license.
Stop debugging blind in Unity. See your actual data with crystal clarity and know exactly where your code executes. 🎮