-
Notifications
You must be signed in to change notification settings - Fork 7
Features Inspector Inspector Button
Execute methods from the inspector with one click.
The [WButton] attribute exposes methods as clickable buttons in the Unity inspector, complete with result history, async support, cancellation, custom styling, and automatic grouping. Test gameplay features, debug systems, and prototype rapidly without writing custom editors.
- Basic Usage
- Parameters
- Execution Types
- Result History
- Draw Order & Positioning
- Grouping
- Color Theming
- Configuration
- Best Practices
- Examples
- Using WButton with Custom Editors
- Troubleshooting
using UnityEngine;
using WallstopStudios.UnityHelpers.Core.Attributes;
public class PlayerController : MonoBehaviour
{
public int health = 100;
[WButton("Heal Player")]
private void Heal()
{
health = 100;
Debug.Log("Player healed!");
}
[WButton("Take Damage")]
private void TakeDamage()
{
health -= 10;
Debug.Log($"Player took damage! Health: {health}");
}
}The [WButton] attribute accepts several optional parameters to customize button appearance, behavior, and organization.
[WButton(
string displayName = null,
int drawOrder = 0,
int historyCapacity = WButtonAttribute.UseGlobalHistory,
string colorKey = null,
string groupName = null,
int groupPriority = WButtonAttribute.NoGroupPriority,
WButtonGroupPlacement groupPlacement = WButtonGroupPlacement.UseGlobalSetting
)]Controls the text shown on the button in the inspector.
-
Default: Uses the method name (e.g.,
"RollDice"forRollDice()) - When to use: Make buttons more readable or add context
// Without displayName - shows "SpawnEnemy"
[WButton]
private void SpawnEnemy() { }
// With displayName - shows "🔫 Spawn Enemy"
[WButton("🔫 Spawn Enemy")]
private void SpawnEnemy1() { }
// More descriptive labels
[WButton("Reset Player to Checkpoint")]
private void ResetPlayer() { }
[WButton("Clear All Save Data")]
private void ClearSaveData() { }
Controls the sort order of buttons within their placement section (top or bottom).
- Lower values render first within the same placement section
- Buttons are sorted by drawOrder, then by declaration order for buttons with the same drawOrder
-
Does NOT control placement — use
groupPlacementto control whether buttons appear above or below properties
public class PlayerController1 : MonoBehaviour
{
public int health = 100;
public float speed = 5f;
// These buttons render in order: Initialize, Validate, Debug Info
// Placement (above/below properties) is controlled by groupPlacement or global settings
[WButton("Initialize", drawOrder: -1)]
private void Initialize() { }
[WButton("Validate", drawOrder: 0)]
private void Validate() { }
[WButton("Debug Info", drawOrder: 1)]
private void ShowDebugInfo() { }
}Visual layout:

Controls how many previous results are stored for methods that return values.
-
Default:
WButtonAttribute.UseGlobalHistory(uses project setting, typically 5) -
Range:
1to10results, or-1for global default - Applies to: Methods returning values, async tasks, or coroutines
-
Does not affect:
voidmethods (no history stored)
// Use global setting (default: 5 results)
[WButton("Roll Dice")]
private int RollDice() => Random.Range(1, 7);
// Store only last result
[WButton("Get Timestamp", historyCapacity: 1)]
private string GetTimestamp() => DateTime.Now.ToString();
// Store up to 10 results for detailed history
[WButton("Measure Frame Time", historyCapacity: 10)]
private float MeasureFrameTime() => Time.deltaTime * 1000f;
// Disable history completely (1 = minimum)
[WButton("Ping Server", historyCapacity: 1)]
private async Task<string> PingServerAsync(CancellationToken ct)
{
// ... ping logic
return "Pong!";
}
Performance tip: Use lower values (1-3) for methods called frequently to reduce memory usage.
Applies custom color themes to buttons using predefined color keys.
-
Default:
null(uses default button color) -
Common values:
"Default","Default-Light","Default-Dark", or custom keys - Configure in: Edit → Project Settings → Unity Helpers → WButton Color Palettes
// Default blue button
[WButton("Standard Action")]
private void StandardAction() { }
// Custom themed button (requires setup in project settings)
[WButton("Dangerous Action", colorKey: "Danger")]
private void DangerousAction() { }
[WButton("Success Action", colorKey: "Success")]
private void SuccessAction() { }
[WButton("Warning Action", colorKey: "Warning")]
private void WarningAction() { }
Setting up custom colors:
- Open Edit → Project Settings → Unity Helpers
- Navigate to WButton Color Palettes
- Add a new color key (e.g.,
"Danger") - Set background/text colors
- Use the key in your
[WButton]attribute


Organizes buttons under labeled group headers.
-
Default:
null(no group header) -
Behavior: All buttons with the same
groupNameare merged into a single group -
Best practice: Use with
groupPlacementandgroupPriorityto control where groups appear
public class GameManager : MonoBehaviour
{
// "Debug Tools" group - will appear based on groupPlacement or global settings
[WButton("Log State", groupName: "Debug Tools", groupPlacement: WButtonGroupPlacement.Top)]
private void LogState() { }
[WButton("Clear Console", groupName: "Debug Tools")]
private void ClearConsole() { }
// Inspector properties here
public int currentLevel = 1;
public bool debugMode = false;
// "Save System" group - explicitly placed at bottom
[WButton("Save Game", groupName: "Save System", groupPlacement: WButtonGroupPlacement.Bottom)]
private void SaveGame() { }
[WButton("Load Game", groupName: "Save System")]
private void LoadGame() { }
[WButton("Delete Save", groupName: "Save System")]
private void DeleteSave() { }
}
Notes:
- Group headers are collapsible (click the arrow to expand/collapse)
- Groups can be configured to start expanded or collapsed in project settings
- The first button in a group determines the group's canonical properties (groupPlacement, groupPriority, drawOrder)
Controls the render order of button groups within a placement section.
-
Default:
WButtonAttribute.NoGroupPriority(renders after groups with explicit priorities) - Lower values render first within the same placement (top or bottom)
-
Only applies to buttons with a
groupName; ungrouped buttons ignore this value
public class ActionPanel : MonoBehaviour
{
// This group renders FIRST (priority 0)
[WButton("Quick Save", groupName: "Primary", groupPriority: 0)]
private void QuickSave() { }
[WButton("Quick Load", groupName: "Primary", groupPriority: 0)]
private void QuickLoad() { }
// This group renders SECOND (priority 10)
[WButton("Debug Info", groupName: "Debug", groupPriority: 10)]
private void ShowDebugInfo() { }
// This group renders LAST (no explicit priority)
[WButton("Reset", groupName: "Misc")]
private void Reset() { }
}
Important: The first declared button in a group sets the canonical priority for the entire group. If other buttons in the same group specify different priorities, they are ignored and a warning is displayed in the inspector.
// ⚠️ WARNING: Conflicting priorities in the same group
[WButton("Action A", groupName: "Tools", groupPriority: 0)] // This priority is used
private void ActionA() { }
[WButton("Action B", groupName: "Tools", groupPriority: 10)] // Ignored! Warning shown
private void ActionB() { }Controls where a button group renders, overriding the global Unity Helpers setting.
-
Default:
WButtonGroupPlacement.UseGlobalSetting -
Options:
-
UseGlobalSetting— Respects the global setting in Project Settings -
Top— Always render above inspector properties -
Bottom— Always render below inspector properties
-
-
Only applies to buttons with a
groupName; ungrouped buttons ignore this value
public class MixedPlacementExample : MonoBehaviour
{
public int health = 100;
public float speed = 5f;
// This group ALWAYS renders at the top, regardless of global setting
[WButton("Initialize", groupName: "Setup", groupPlacement: WButtonGroupPlacement.Top)]
private void Initialize() { }
[WButton("Validate", groupName: "Setup", groupPlacement: WButtonGroupPlacement.Top)]
private void Validate() { }
// Properties appear here (health, speed)
// This group ALWAYS renders at the bottom, regardless of global setting
[WButton("Cleanup", groupName: "Maintenance", groupPlacement: WButtonGroupPlacement.Bottom)]
private void Cleanup() { }
[WButton("Reset All", groupName: "Maintenance", groupPlacement: WButtonGroupPlacement.Bottom)]
private void ResetAll() { }
}
Important: Like groupPriority, the first declared button in a group sets the canonical placement for the entire group. Conflicting values from other buttons in the same group are ignored with a warning.
Use both parameters together for fine-grained control:
using UnityEngine;
using WallstopStudios.UnityHelpers.Core.Attributes;
[CreateAssetMenu(
fileName = "AdvancedButtonLayout",
menuName = "Wallstop Studios/Advanced Button Layout"
)]
public class AdvancedButtonLayout : ScriptableObject
{
// TOP SECTION - ordered by priority
[WButton(
"Validate Data",
groupName: "Validation",
groupPriority: 1,
groupPlacement: WButtonGroupPlacement.Top
)]
private void ValidateData() { }
[WButton(
"Generate IDs",
groupName: "Authoring",
groupPriority: 0,
groupPlacement: WButtonGroupPlacement.Top
)]
private void GenerateIds() { }
// Properties appear here
public int property1;
public string property2;
// BOTTOM SECTION - ordered by priority
[WButton(
"Submit to Server",
groupName: "Network",
groupPriority: 10,
groupPlacement: WButtonGroupPlacement.Bottom
)]
private void Submit() { }
[WButton(
"Export",
groupName: "IO",
groupPriority: 0,
groupPlacement: WButtonGroupPlacement.Bottom
)]
private void Export() { }
[WButton(
"Import",
groupName: "IO",
groupPriority: 0,
groupPlacement: WButtonGroupPlacement.Bottom
)]
private void Import() { }
}
Rendering Order:
-
Top Section (sorted by
groupPriority):- Authoring (priority 0)
- Validation (priority 1)
- Default Inspector Properties
-
Bottom Section (sorted by
groupPriority):- IO (priority 0)
- Network (priority 10)
using UnityEngine;
using WallstopStudios.UnityHelpers.Core.Attributes;
public class LevelManager : MonoBehaviour
{
public int currentLevel = 1;
public bool debugMode = false;
// Setup group - explicitly placed at top, renders first due to groupPriority: 0
[WButton("Initialize Level", groupName: "Setup", groupPriority: 0, groupPlacement: WButtonGroupPlacement.Top)]
private void Initialize()
{
Debug.Log("Level initialized!");
}
[WButton("✓ Validate Configuration", groupName: "Setup")]
private void ValidateConfig()
{
Debug.Log("Configuration valid!");
}
// Debug group - explicitly placed at top, renders second due to groupPriority: 1
[WButton("Roll Dice", historyCapacity: 10, groupName: "Debug", groupPriority: 1, groupPlacement: WButtonGroupPlacement.Top)]
private int RollDice() => Random.Range(1, 7);
[WButton("🎯 Spawn Test Enemy", colorKey: "Warning", groupName: "Debug")]
private void SpawnTestEnemy()
{
// Spawn logic here
}
// Properties appear here in the inspector
// Actions group - explicitly placed at bottom, renders first in bottom section due to groupPriority: 0
[WButton("▶ Start Level", colorKey: "Success", groupName: "Actions", groupPriority: 0, groupPlacement: WButtonGroupPlacement.Bottom)]
private void StartLevel()
{
Debug.Log($"Starting level {currentLevel}...");
}
[WButton("⏸ Pause Game", groupName: "Actions")]
private void PauseGame()
{
Time.timeScale = 0f;
}
[WButton("🔄 Restart Level", colorKey: "Danger", groupName: "Actions")]
private void RestartLevel()
{
// Restart logic here
}
// Maintenance group - explicitly placed at bottom, renders last in bottom section due to groupPriority: 10
[WButton("Clear Cache", historyCapacity: 1, groupName: "Maintenance", groupPriority: 10, groupPlacement: WButtonGroupPlacement.Bottom)]
private string ClearCache()
{
return $"Cache cleared at {System.DateTime.Now:HH:mm:ss}";
}
}
WButton supports four method signatures:
[WButton("Log Message")]
private void LogMessage()
{
Debug.Log("Button clicked!");
}Behavior: Executes immediately, no return value shown
[WButton("Roll Dice", historyCapacity: 10, groupName: "Debug")]
private int RollDice()
{
return Random.Range(1, 7);
}
[WButton("Get Position")]
private Vector3 GetPlayerPosition()
{
return transform.position;
}

Behavior: Shows return value in a collapsible history panel
[WButton("Fade Out")]
private IEnumerator FadeOut()
{
SpriteRenderer sprite = GetComponent<SpriteRenderer>();
Color color = sprite.color;
for (float t = 1f; t >= 0f; t -= Time.deltaTime)
{
color.a = t;
sprite.color = color;
yield return null;
}
Debug.Log("Fade complete!");
}
Behavior:
- Shows "Running..." status
- Spinner animation during execution
- "Complete" message when finished
using System.Threading;
using System.Threading.Tasks;
[WButton("Load Data")]
private async Task<string> LoadDataAsync(CancellationToken ct)
{
Debug.Log("Loading...");
await Task.Delay(2000, ct); // Simulate async work
return "Data loaded successfully!";
}
[WButton("Download Asset")]
private async ValueTask<Texture2D> DownloadAssetAsync(CancellationToken ct)
{
Debug.Log("Downloading...");
await Task.Delay(1000, ct);
// Simulate download
return new Texture2D(256, 256);
}
Behavior:
- Automatic
CancellationTokeninjection (optional parameter) - "Cancel" button appears during execution
- Result shown in history when complete
- Exceptions logged to console
Cancellation Example:
[WButton("Long Operation")]
private async Task LongOperationAsync(CancellationToken ct)
{
for (int i = 0; i < 10; i++)
{
ct.ThrowIfCancellationRequested(); // Check cancellation
Debug.Log($"Step {i + 1}/10");
await Task.Delay(500, ct);
}
return Task.CompletedTask;
}Supported Signatures:
-
Task(void async) -
Task<T>(async with a result) -
ValueTask(void async, no heap allocation) -
ValueTask<T>(async with a result, no heap allocation)
[WButton("Generate ID", historyCapacity: 10)]
private string GenerateId()
{
return System.Guid.NewGuid().ToString().Substring(0, 8);
}
Features:
- Per-method, per-target buffering (history survives inspector refresh)
- Collapsible foldout for each method
- Chronological order (newest first)
- Pagination when history exceeds the display threshold
// Use global setting (default: 5, configurable in UnityHelpersSettings)
[WButton("Use Global")]
private int UseGlobal() => Random.Range(1, 100);
// Custom capacity per method
[WButton("Keep 20 Results", historyCapacity: 20)]
private float KeepMany() => Random.value;
// Disable history (0 capacity)
[WButton("No History", historyCapacity: 0)]
private void NoHistory() => Debug.Log("No history stored");Global Setting: UnityHelpersSettings.WButtonHistorySize (default: 5, range: 1-10)
Control the sort order of buttons within their placement section:
public class ButtonPositioning : MonoBehaviour
{
// Buttons are sorted by drawOrder (lower values first)
[WButton("Top Button", drawOrder: -1)]
private void TopButton() => Debug.Log("Renders first (lowest drawOrder)");
[WButton("Middle Button", drawOrder: 0)]
private void MiddleButton() => Debug.Log("Renders second");
[WButton("Bottom Button", drawOrder: 1)]
private void BottomButton() => Debug.Log("Renders last (highest drawOrder)");
// Inspector fields - buttons appear above or below based on groupPlacement/global settings
public int someField = 10;
}
Positioning Rules:
-
Lower
drawOrdervalues render first within a placement section -
Placement (above/below properties) is controlled by
groupPlacementor the globalWButtonPlacementsetting, not bydrawOrder -
Within the same
drawOrder, buttons render in declaration order (source code order)
[WButton(drawOrder: 0)]
private void Action1() {}
[WButton( drawOrder: 0)]
private void Action2() {}
// ... 10 more buttons with drawOrder: 0 ...
[WButton(drawOrder: 0)]
private void Action12() {}
Pagination Settings:
- Page size controlled by
UnityHelpersSettings.WButtonPageSize(default: 6) - Pagination only applies within each draw order group
- Navigation: First, Previous, Next, Last buttons
Organize buttons into named sections:
[WButton("Spawn Enemy", groupName: "Combat")]
private void SpawnEnemy() => Debug.Log("Enemy spawned");
[WButton("Clear Enemies", groupName: "Combat")]
private void ClearEnemies() => Debug.Log("Enemies cleared");
[WButton("Save Game", groupName: "Persistence")]
private void SaveGame() => Debug.Log("Game saved");
[WButton("Load Game", groupName: "Persistence")]
private void LoadGame() => Debug.Log("Game loaded");
Grouping Behavior:
- Groups are created automatically based on
groupName - Buttons within a group are organized by
drawOrder - Groups can be collapsible (controlled by
UnityHelpersSettings.WButtonFoldoutBehavior)
Global Setting: UnityHelpersSettings.WButtonFoldoutBehavior
Options:
-
Always- Always show group foldout triangles -
StartExpanded- Collapsible, starts open -
StartCollapsed- Collapsible, starts closed
Animation:
- Enable/disable via
UnityHelpersSettings.WButtonFoldoutTweenEnabled - Speed controlled by
UnityHelpersSettings.WButtonFoldoutSpeed(default: 2.0, range: 2-12)

[WButton("Dangerous Action", colorKey: "Default-Dark")]
private void DangerousAction() => Debug.LogWarning("Dangerous!");
[WButton("Safe Action", colorKey: "Default-Light")]
private void SafeAction() => Debug.Log("Safe operation");
Built-in Priorities (Color Keys):
-
"Default"- Theme-aware (adapts to Unity theme) -
"Default-Dark"- Dark theme colors -
"Default-Light"- Light theme colors -
"WDefault"- Legacy vibrant blue - Custom keys defined in
UnityHelpersSettings.WButtonCustomColors
Define Custom Colors:
- Open
ProjectSettings/UnityHelpersSettings.asset - Add entry to
WButtonCustomColorsdictionary - Set a button background, text color, border

All buttons respect project-wide settings defined in UnityHelpersSettings:
Location: ProjectSettings/UnityHelpersSettings.asset
Settings:
-
WButtonHistorySize(default: 5, range: 1-10) - Results to keep per method -
WButtonPlacement(Top or Bottom) - Default button position -
WButtonFoldoutBehavior(Always, StartExpanded, StartCollapsed) - Group collapsibility -
WButtonFoldoutTweenEnabled(bool) - Enable group animations -
WButtonFoldoutSpeed(default: 2.0, range: 2-12) - Animation speed -
WButtonPageSize(default: 6) - Buttons per page for pagination -
WButtonCustomColors- Custom color palette dictionary

// ✅ GOOD: Action-oriented, descriptive
[WButton("Heal to Full")]
private void HealToFull() { ... }
[WButton("Spawn 10 Enemies")]
private void SpawnEnemies() { ... }
// ❌ BAD: Vague or technical
[WButton("DoStuff")]
private void DoStuff() { ... }
[WButton] // Defaults to method name "HandlePlayerDeath"
private void HandlePlayerDeath() { ... }// ✅ GOOD: Grouped by feature
[WButton("Spawn Enemy", groupName: "Combat Testing")]
private void SpawnEnemy() { ... }
[WButton("Kill All Enemies", groupName: "Combat Testing")]
private void KillAll() { ... }
[WButton("Save Progress", groupName: "Persistence")]
private void Save() { ... }
// ❌ BAD: No grouping, cluttered inspector
[WButton("Spawn Enemy")]
private void SpawnEnemy() { ... }
[WButton("Save Progress")]
private void Save() { ... }
[WButton("Kill All Enemies")]
private void KillAll() { ... }// ✅ GOOD: History helps track random values
[WButton("Roll Loot", historyCapacity: 10)]
private string RollLoot()
{
return lootTable[PRNG.Instance.Next(0, lootTable.Length)];
}
// ✅ GOOD: No history needed for fixed actions
[WButton("Reset Position", historyCapacity: 0)]
private void ResetPosition()
{
transform.position = Vector3.zero;
}// ✅ GOOD: Accept CancellationToken, check it
[WButton("Long Task")]
private async Task LongTaskAsync(CancellationToken ct)
{
for (int i = 0; i < 100; i++)
{
ct.ThrowIfCancellationRequested();
await Task.Delay(100, ct);
}
}
// ✅ GOOD: Handle exceptions gracefully
[WButton("Risky Operation")]
private async Task RiskyOperationAsync()
{
try
{
await SomeRiskyApiCall();
}
catch (Exception ex)
{
Debug.LogError($"Operation failed: {ex.Message}");
}
}
// ❌ BAD: Long operation with no cancellation support
[WButton("Infinite Loop")]
private async Task InfiniteLoopAsync()
{
while (true) // No way to stop this!
{
await Task.Delay(1000);
}
}// ✅ GOOD: Use colors to indicate risk/importance
[WButton("Delete All Data", colorKey: "Default-Dark")] // Dark = danger
private void DeleteAllData() { ... }
[WButton("Quick Save", colorKey: "Default-Light")] // Light = safe
private void QuickSave() { ... }
// ❌ BAD: Random colors without meaning
[WButton("Log Message", colorKey: "CustomPurple")] // Why purple?
private void LogMessage() { ... }using UnityEngine;
using WallstopStudios.UnityHelpers.Core.Attributes;
public class PlayerDebug : MonoBehaviour
{
public int health = 100;
public int gold = 0;
[WButton("Heal", groupName: "Health", colorKey: "Default-Light")]
private void Heal()
{
health = 100;
Debug.Log("Player healed!");
}
[WButton("Take Damage", groupName: "Health")]
private void TakeDamage()
{
health -= 25;
Debug.Log($"Took damage! Health: {health}");
}
[WButton("Kill Player", groupName: "Health", colorKey: "Default-Dark")]
private void Kill()
{
health = 0;
Debug.LogWarning("Player died!");
}
[WButton("Add Gold", groupName: "Economy")]
private void AddGold()
{
gold += 100;
Debug.Log($"Gold: {gold}");
}
[WButton("Roll Reward", groupName: "Economy", historyCapacity: 10)]
private int RollReward()
{
int amount = PRNG.Instance.Next(10, 100);
gold += amount;
return amount;
}
}
using UnityEngine;
using System.Threading;
using System.Threading.Tasks;
using WallstopStudios.UnityHelpers.Core.Attributes;
public class DataManager : MonoBehaviour
{
[WButton("Load Player Data")]
private async Task<string> LoadPlayerDataAsync(CancellationToken ct)
{
Debug.Log("Loading player data...");
// Simulate network request
await Task.Delay(2000, ct);
string playerName = "TestPlayer_" + Random.Range(1000, 9999);
Debug.Log($"Loaded: {playerName}");
return playerName;
}
[WButton("Batch Load", historyCapacity: 5)]
private async Task<int> BatchLoadAsync(CancellationToken ct)
{
int count = 0;
for (int i = 0; i < 10; i++)
{
ct.ThrowIfCancellationRequested();
await Task.Delay(200, ct);
count++;
Debug.Log($"Loaded item {count}/10");
}
return count;
}
}
using UnityEngine;
using WallstopStudios.UnityHelpers.Core.Attributes;
public class LevelGenerator : MonoBehaviour
{
[WButton("Generate Seed", historyCapacity: 20)]
private int GenerateSeed()
{
return Random.Range(1000, 9999);
}
[WButton("Generate Level")]
private void GenerateLevel()
{
int seed = Random.Range(1000, 9999);
Random.InitState(seed);
Debug.Log($"Generating level with seed: {seed}");
// ... generation logic ...
}
[WButton("Clear Level", colorKey: "Default-Dark")]
private void ClearLevel()
{
// ... cleanup logic ...
Debug.Log("Level cleared");
}
[WButton("Generate with Seed")]
private void GenerateWithSeed(int seed)
{
Random.InitState(seed);
Debug.Log($"Generating with seed: {seed}");
// ... generation logic ...
}
}
using System.Collections;
using UnityEngine;
using WallstopStudios.UnityHelpers.Core.Attributes;
public class AnimationTester : MonoBehaviour
{
public SpriteRenderer spriteRenderer;
private void Awake()
{
spriteRenderer = GetComponent<SpriteRenderer>();
}
[WButton("Fade Out", groupName: "Animations")]
private IEnumerator FadeOutCoroutine()
{
Color color = spriteRenderer.color;
while (color.a > 0f)
{
color.a -= Time.deltaTime;
spriteRenderer.color = color;
yield return null;
}
Debug.Log("Fade out complete");
}
[WButton("Fade In", groupName: "Animations")]
private IEnumerator FadeInCoroutine()
{
Color color = spriteRenderer.color;
while (color.a < 1f)
{
color.a += Time.deltaTime;
spriteRenderer.color = color;
yield return null;
}
Debug.Log("Fade in complete");
}
[WButton("Pulse", groupName: "Animations")]
private IEnumerator PulseCoroutine()
{
Vector3 originalScale = transform.localScale;
for (int i = 0; i < 3; i++)
{
transform.localScale = originalScale * 1.2f;
yield return new WaitForSeconds(0.2f);
transform.localScale = originalScale;
yield return new WaitForSeconds(0.2f);
}
Debug.Log("Pulse complete");
}
}
WButton works automatically with:
- All Unity Objects (
MonoBehaviour,ScriptableObject, etc.) -
Odin Inspector's
SerializedMonoBehaviourandSerializedScriptableObject(whenODIN_INSPECTORis defined)
When do you need WButtonEditorHelper?
Only when you create custom Odin editors that override the default behavior. For example, if you create a CustomEditor that inherits from OdinEditor for a specific type, you'll need to integrate WButton manually.
No setup required! WButton automatically works with Odin's base types:
#if ODIN_INSPECTOR
using Sirenix.OdinInspector;
#endif
using UnityEngine;
using WallstopStudios.UnityHelpers.Core.Attributes;
#if ODIN_INSPECTOR
public class MyComponent : SerializedMonoBehaviour
#else
public class MyComponent : MonoBehaviour
#endif
{
[WButton("Test Button")]
private void TestMethod()
{
Debug.Log("Button clicked!");
}
}WButton will appear automatically in the inspector - no additional code needed!
If you create a custom Odin editor for your type, you need to manually integrate WButton using WButtonEditorHelper:
#if UNITY_EDITOR && ODIN_INSPECTOR
using Sirenix.OdinInspector.Editor;
using UnityEditor;
using WallstopStudios.UnityHelpers.Editor.Utils.WButton;
// Custom editor for a specific type
[CustomEditor(typeof(MyComponent))]
public class MyComponentEditor : OdinEditor
{
private WButtonEditorHelper _wButtonHelper;
protected override void OnEnable()
{
base.OnEnable();
_wButtonHelper = new WButtonEditorHelper();
}
public override void OnInspectorGUI()
{
// Draw WButtons at top (optional - based on your settings)
_wButtonHelper.DrawButtonsAtTop(this);
// Draw Odin inspector
base.OnInspectorGUI();
// Draw WButtons at bottom and process any invocations
_wButtonHelper.DrawButtonsAtBottomAndProcessInvocations(this);
}
}
#endifFor standard Unity custom editors:
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
using WallstopStudios.UnityHelpers.Editor.Utils.WButton;
[CustomEditor(typeof(MyComponent))]
public class MyComponentEditor : Editor
{
private WButtonEditorHelper _wButtonHelper;
private void OnEnable()
{
_wButtonHelper = new WButtonEditorHelper();
}
public override void OnInspectorGUI()
{
serializedObject.Update();
// Draw WButtons at top
_wButtonHelper.DrawButtonsAtTop(this);
// Draw your custom inspector
DrawDefaultInspector();
serializedObject.ApplyModifiedProperties();
// Draw WButtons at bottom and process invocations
_wButtonHelper.DrawButtonsAtBottomAndProcessInvocations(this);
}
}
#endifThe WButtonEditorHelper class provides several methods for different use cases:
| Method | Description |
|---|---|
DrawButtonsAtTop(Editor) |
Draws buttons configured for top placement |
DrawButtonsAtBottom(Editor) |
Draws buttons configured for bottom placement |
ProcessInvocations() |
Processes any triggered button invocations |
DrawButtonsAtBottomAndProcessInvocations(Editor) |
Convenience method combining bottom drawing + processing (most common) |
DrawAllButtonsAndProcessInvocations(Editor) |
Draws all buttons in one location regardless of placement settings |
Key Points:
- Create one
WButtonEditorHelperinstance per editor (typically inOnEnable) - Call
DrawButtonsAtTopbefore your inspector content - Call
DrawButtonsAtBottomAndProcessInvocationsafter your inspector content - Always call
ProcessInvocations()after all button drawing is complete
If you prefer all buttons in one location regardless of placement settings:
public override void OnInspectorGUI()
{
// Your inspector code here
DrawDefaultInspector();
// Draw all buttons at the end
_wButtonHelper.DrawAllButtonsAndProcessInvocations(this);
}Problem: Method has [WButton] but button doesn't show
Solutions:
- Ensure method is
privateorprotected(public methods may conflict) - Verify the component is enabled and active
Problem: Method returns a value but no history appears
Solutions:
- Check
historyCapacity- make sure it's > 0 - Verify that the return type is serializable
- Check
UnityHelpersSettings.WButtonHistorySizeif using global setting
Problem: Cancel button doesn't stop an async method
Solutions:
- Ensure method accepts
CancellationTokenparameter - Check token periodically:
ct.ThrowIfCancellationRequested() - Pass token to async operations:
await Task.Delay(1000, ct)
// ✅ CORRECT: Cancellable
[WButton("Long Task")]
private async Task LongTaskAsync(CancellationToken ct)
{
for (int i = 0; i < 100; i++)
{
ct.ThrowIfCancellationRequested(); // Check token
await Task.Delay(100, ct); // Pass token
}
}- Inspector Overview - Complete inspector features overview
- Inspector Grouping Attributes - WGroup layouts
- Inspector Settings - Configuration reference
- Editor Tools Guide - Other editor utilities
Next Steps:
- Add buttons to your components for quick testing
- Experiment with
groupNameto organize buttons - Try async methods with
CancellationTokensupport - Customize colors in
UnityHelpersSettings.asset
📦 Unity Helpers | 📖 Documentation | 🐛 Issues | 📜 MIT License
- Inspector Button
- Inspector Conditional Display
- Inspector Grouping Attributes
- Inspector Inline Editor
- Inspector Overview
- Inspector Selection Attributes
- Inspector Settings
- Inspector Validation Attributes
- Utility Components
- Visual Components
- Data Structures
- Helper Utilities
- Math And Extensions
- Pooling Guide
- Random Generators
- Reflection Helpers
- Singletons