Treasure chest of Unity developer tools. Professional inspector tooling, high-performance utilities, spatial queries, and 20+ editor tools.
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.
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.
"RollDice" for RollDice())// 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).
groupPlacement to control whether buttons appear above or below propertiespublic 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.
WButtonAttribute.UseGlobalHistory (uses project setting, typically 5)1 to 10 results, or -1 for global defaultvoid methods (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.
null (uses default button color)"Default", "Default-Light", "Default-Dark", or custom keys// 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:
"Danger")[WButton] attribute

Organizes buttons under labeled group headers.
null (no group header)groupName are merged into a single groupgroupPlacement and groupPriority to control where groups appearpublic 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:
Controls the render order of button groups within a placement section.
WButtonAttribute.NoGroupPriority (renders after groups with explicit priorities)groupName; ungrouped buttons ignore this valuepublic 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.
WButtonGroupPlacement.UseGlobalSettingUseGlobalSetting — Respects the global setting in Project SettingsTop — Always render above inspector propertiesBottom — Always render below inspector propertiesgroupName; ungrouped buttons ignore this valuepublic 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:
groupPriority):
groupPriority):
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:
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:
CancellationToken injection (optional parameter)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:
// 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:
drawOrder values render first within a placement sectiongroupPlacement or the global WButtonPlacement setting, not by drawOrderdrawOrder, 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:
UnityHelpersSettings.WButtonPageSize (default: 6)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:
groupNamedrawOrderUnityHelpersSettings.WButtonFoldoutBehavior)Global Setting: UnityHelpersSettings.WButtonFoldoutBehavior
Options:
Always - Always show group foldout trianglesStartExpanded - Collapsible, starts openStartCollapsed - Collapsible, starts closedAnimation:
UnityHelpersSettings.WButtonFoldoutTweenEnabledUnityHelpersSettings.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 blueUnityHelpersSettings.WButtonCustomColorsDefine Custom Colors:
ProjectSettings/UnityHelpersSettings.assetWButtonCustomColors dictionary
All buttons respect project-wide settings defined in UnityHelpersSettings:
Location: ProjectSettings/UnityHelpersSettings.asset
Settings:
WButtonHistorySize (default: 5, range: 1-10) - Results to keep per methodWButtonPlacement (Top or Bottom) - Default button positionWButtonFoldoutBehavior (Always, StartExpanded, StartCollapsed) - Group collapsibilityWButtonFoldoutTweenEnabled (bool) - Enable group animationsWButtonFoldoutSpeed (default: 2.0, range: 2-12) - Animation speedWButtonPageSize (default: 6) - Buttons per page for paginationWButtonCustomColors - 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:
MonoBehaviour, ScriptableObject, etc.)SerializedMonoBehaviour and SerializedScriptableObject (when ODIN_INSPECTOR is 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);
}
}
#endif
For 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);
}
}
#endif
The 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:
WButtonEditorHelper instance per editor (typically in OnEnable)DrawButtonsAtTop before your inspector contentDrawButtonsAtBottomAndProcessInvocations after your inspector contentProcessInvocations() after all button drawing is completeIf 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:
private or protected (public methods may conflict)Problem: Method returns a value but no history appears
Solutions:
historyCapacity - make sure it’s > 0UnityHelpersSettings.WButtonHistorySize if using global settingProblem: Cancel button doesn’t stop an async method
Solutions:
CancellationToken parameterct.ThrowIfCancellationRequested()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
}
}
Next Steps:
groupName to organize buttonsCancellationToken supportUnityHelpersSettings.asset