In-game debug menu system for Unity that uses attributes and UI Toolkit to create developer-friendly debug commands.
- Attribute-Based: Simple
[DevMenu]attribute to mark debug methods - Hotkey Support: Assign keyboard shortcuts to debug methods
- UI Toolkit Integration: Modern, responsive UI built with UI Toolkit
- Parameter Support: Full support for all parameter types (int, float, bool, enum, string)
- Editor Integration: Works in both Play Mode and Edit Mode
- Project Agnostic: Designed as a Unity package for easy distribution
- Import into Unity via Package Manager (
Add package from diskβ¦and pick this package'spackage.json). - Mark methods with the
[DevMenu]attribute on anyMonoBehaviourin your scene. - Use it:
- In the editor, open
DevMenu > Open Dev Menufrom the menu bar. - In Play Mode, hotkeys work automatically β a listener is injected on start, no setup required.
- For an in-game UI panel, add a
UIDocumentplus theDevMenuUIControllercomponent (the UXML needs aScrollViewnamedmethod-scroll).
- In the editor, open
In the Package Manager, select this package and click Samples > DevMenu Sample > Import. It adds a single DevMenuSample MonoBehaviour you can drop on an empty GameObject to see actions, typed parameters, validation, and change events in action.
using DevMenuTool.Core.Attributes;
using UnityEngine;
public class GameDebugger : MonoBehaviour
{
[DevMenu(KeyCode.G, "Cheats")]
private void AddGold(int amount = 100)
{
Debug.Log($"Added {amount} gold!");
// This method will appear in the DevMenu with a hotkey
}
[DevMenu(KeyCode.H, "Player")]
private void SetPlayerHealth(int health = 100, bool invincible = false)
{
Debug.Log($"Set health to {health}, invincible: {invincible}");
// Parameters are stored and remembered between sessions
}
}- Runtime: Press the configured hotkey or use the UI
- Editor: Go to
DevMenu > Open Dev Menuin the menu bar
- Parameter Storage: Each
DevMethodstores its own parameter values - Automatic Persistence: Values are saved to EditorPrefs automatically
- Smart Resolution: UI updates stored values, hotkeys use stored values
- Reset Functionality: Easy reset to default values
graph TD
A[DevMethod Created] --> B[Load from EditorPrefs]
B --> C{Values Found?}
C -->|Yes| D[Use Stored Values]
C -->|No| E[Use Default Values]
D --> F[UI Shows Current Values]
E --> F
F --> G[User Modifies Values]
G --> H[Update Stored Parameters]
H --> I[Save to EditorPrefs]
I --> J[Method Invocation]
J --> K[Use Stored Values]
| Action | Parameter Source | Behavior |
|---|---|---|
| UI Button | Current UI field values | Updates stored parameters, then invokes |
| Hotkey | Stored parameter values | Uses last-saved values, no UI interaction |
[DevMenu(KeyCode.F1, "Debug")]
private void LogPlayerPosition()
{
var player = GameObject.FindGameObjectWithTag("Player");
if (player != null)
Debug.Log($"Player position: {player.transform.position}");
}
[DevMenu(KeyCode.F2, "Debug")]
private void ShowGameStats()
{
Debug.Log($"FPS: {1.0f / Time.deltaTime:F1}");
Debug.Log($"Memory: {System.GC.GetTotalMemory(false) / 1024 / 1024}MB");
}[DevMenu(KeyCode.G, "Cheats")]
private void AddGold(int amount = 100)
{
// amount will be stored and remembered
PlayerGold += amount;
Debug.Log($"Added {amount} gold! Total: {PlayerGold}");
}
[DevMenu(KeyCode.H, "Player")]
private void SetPlayerStats(int health = 100, float speed = 5.0f, bool invincible = false)
{
// All parameters are stored independently
playerHealth = health;
playerSpeed = speed;
playerInvincible = invincible;
Debug.Log($"Set stats: Health={health}, Speed={speed}, Invincible={invincible}");
}public enum GameState { Menu, Playing, Paused, GameOver }
[DevMenu(KeyCode.J, "Game")]
private void SetGameState(GameState state = GameState.Playing)
{
currentGameState = state;
Debug.Log($"Game state set to: {state}");
}The DevMenu tool follows SOLID principles and Clean Code practices:
DevMethod: Represents a debug method with parameter storageMethodScanner: Discovers methods with[DevMenu]attributesMethodInvoker: Handles method invocation with parameter resolutionUIBuilder: Creates UI elements for method interactionDevMenuContainer: Composition root that wires and exposes the shared services
- Factory Pattern:
DevMethodFactorycreatesDevMethodinstances and wires their collaborators - Strategy Pattern:
ParameterHandlerRegistryhandles different parameter types - Dependency Injection: collaborators (
IParameterManager,IParameterValidatorManager,IParameterEventManager,IParameterHandlerRegistry) are passed in through constructors;DevMenuContaineris the composition root that wires them together
- Interface Segregation:
IDevMethodis composed from focused role interfaces (IDevMethodInfo,IDevMethodExecutor,IParameterManager,IParameterValidatorManager,IParameterChangeNotifier) rather than a single fat interface. - Dependency Inversion:
DevMethoddepends on collaborator abstractions injected via its constructor instead ofnew-ing concretes, and parameter handling is resolved through theIParameterHandlerRegistryabstraction instead of a static lookup.
Tests are not shipped with the package. They live in the development project (DevMenuTool/Assets/Tests/) so consumers stay clean of test code and NUnit references. To run them, open the development project and use Window > General > Test Runner.
com.routaz.devmenu/
βββ Core/ # Core interfaces and implementations
β βββ Interfaces/ # Interface definitions
β βββ DevMethod.cs # Enhanced DevMethod with parameter storage
β βββ MethodScanner.cs # Method discovery
β βββ DevMenuContainer.cs # Composition root
βββ Runtime/ # Runtime functionality
β βββ MethodInvoker.cs # Method invocation
β βββ Handlers/ # Parameter type handlers
β βββ DevMenuHotkeyListener.cs
βββ UI/ # UI Toolkit components
β βββ UIBuilder.cs # UI construction
β βββ DevMenuUIController.cs
βββ Editor/ # Editor-specific functionality
β βββ DevMenuEditorWindow.cs
βββ Samples~/ # Importable sample (via Package Manager)
βββ DevMenuSample/
βββ DevMenuSample.cs
Parameter parsing/formatting is handled by IParameterHandler strategies. The built-in set
covers string, numeric, bool, and enum types. To support an additional type, implement
IParameterHandler:
public class Vector3ParameterHandler : IParameterHandler
{
public bool CanHandle(Type parameterType) => parameterType == typeof(Vector3);
public object Parse(string input, Type targetType)
{
// Parse "x,y,z" format
var parts = input.Split(',');
return new Vector3(float.Parse(parts[0]), float.Parse(parts[1]), float.Parse(parts[2]));
}
public string ToString(object value)
{
var vector = (Vector3)value;
return $"{vector.x},{vector.y},{vector.z}";
}
}Note: handlers are currently registered in the
ParameterHandlerRegistryconstructor. Add your handler to that list to enable it β there is no runtime registration API yet.
Validation is declarative: annotate parameters with validation attributes and the DevMenu wires them up automatically when the method is discovered. There is no need to hand-write guard clauses inside the method body.
using DevMenuTool.Core.Attributes;
[DevMenu(KeyCode.G, "Cheats")]
private void AddGold([ParameterRange(0, 9999)] int amount = 100)
{
// Only reached with a valid amount; no manual checks required.
PlayerGold += amount;
Debug.Log($"Added {amount} gold!");
}
[DevMenu(KeyCode.N, "Player")]
private void SetPlayerName([StringValidation(minLength: 2, maxLength: 16)] string name = "Hero")
{
playerName = name;
}Built-in attributes:
| Attribute | Applies to | Purpose |
|---|---|---|
[ParameterRange(min, max, inclusive = true)] |
numeric | Value must fall within the range |
[StringValidation(minLength, maxLength, pattern, allowEmpty)] |
string | Length / regex / emptiness rules |
[Required] |
string | Value must be non-null and non-empty |
When an entered value fails validation the edit is rejected and the last valid value is
kept β the parameter is never silently overwritten with the default. Add your own rule by
implementing IParameterValidator and a matching ParameterValidationAttribute.
This project is licensed under the MIT License - see the LICENSE file for details.
Made with β€οΈ for Unity developers