Custom Data Visualizer Bindings
DataVisualizerComponent uses a plugin model. Built-in bindings like sphere, circle, axis, and box are just concrete binding types discovered by the editor. You can add your own binding type in runtime code and a matching scene drawer in editor code.
This is useful when you want a visualizer that is specific to your project data, such as a waypoint arrow, attack cone, hearing range, or patrol path marker.
How It Works
There are two extension points:
- A runtime binding type that inherits
DataVisualizerComponent.DataVisualizerBinding. - An editor scene drawer that inherits
DataVisualizerBindingSceneDrawer.
The runtime binding stores serialized settings and field references. The scene drawer reads the current DataRecord values and draws the gizmo in the Scene view.
Runtime Binding Example
This example adds a simple wire capsule visualizer. It uses the shared position, rotation, color, and label support from the base binding, plus custom radius and height fields.
using HutongGames.PlayMaker;
using UnityEngine;
namespace MyGame.PlayMaker
{
[DataVisualizerBinding("Capsule", 200)]
[System.Serializable]
public sealed class CapsuleBinding : DataVisualizerComponent.DataVisualizerBinding
{
[SerializeField]
private SerializableGuid _radiusFieldGuid;
[SerializeField]
private float _radius = 0.5f;
[SerializeField]
private SerializableGuid _heightFieldGuid;
[SerializeField]
private float _height = 2f;
public string RadiusFieldGuid => _radiusFieldGuid;
public float Radius => _radius;
public string HeightFieldGuid => _heightFieldGuid;
public float Height => _height;
}
}
After this class compiles, Capsule appears in the Add Binding menu on DataVisualizerComponent.
Scene Drawer Example
Put the matching drawer in an editor assembly:
using HutongGames.PlayMaker;
using HutongGames.PlayMaker.Editor;
using UnityEditor;
using UnityEngine;
namespace MyGame.PlayMaker.Editor
{
[DataVisualizerBindingSceneDrawer(typeof(CapsuleBinding))]
public sealed class CapsuleBindingSceneDrawer : DataVisualizerBindingSceneDrawer
{
public override void Draw(
in DataVisualizerDrawContext context,
DataVisualizerComponent.DataVisualizerBinding binding)
{
if (binding is not CapsuleBinding capsuleBinding)
{
return;
}
DataVisualizerBindingResolver.TryGetTransform(
context,
binding,
out var worldCenter,
out var rotation,
out var useLocalSpace);
var radius = DataVisualizerBindingResolver.ResolveFloat(
context.DataRecord,
capsuleBinding.RadiusFieldGuid,
capsuleBinding.Radius);
var height = DataVisualizerBindingResolver.ResolveFloat(
context.DataRecord,
capsuleBinding.HeightFieldGuid,
capsuleBinding.Height);
var color = DataVisualizerBindingResolver.ResolveColor(context.DataRecord, binding);
if (useLocalSpace)
{
using var scope = new Handles.DrawingScope(
color,
Matrix4x4.TRS(worldCenter, rotation, context.Owner.transform.lossyScale));
DrawCapsule(radius, height);
return;
}
using var worldScope = new Handles.DrawingScope(color);
Handles.matrix = Matrix4x4.TRS(worldCenter, rotation, Vector3.one);
DrawCapsule(radius, height);
}
private static void DrawCapsule(float radius, float height)
{
var bodyHeight = Mathf.Max(0f, height - (radius * 2f));
var top = Vector3.up * (bodyHeight * 0.5f);
var bottom = Vector3.down * (bodyHeight * 0.5f);
Handles.DrawWireDisc(top, Vector3.up, radius);
Handles.DrawWireDisc(bottom, Vector3.up, radius);
Handles.DrawLine(top + Vector3.forward * radius, bottom + Vector3.forward * radius);
Handles.DrawLine(top - Vector3.forward * radius, bottom - Vector3.forward * radius);
Handles.DrawLine(top + Vector3.right * radius, bottom + Vector3.right * radius);
Handles.DrawLine(top - Vector3.right * radius, bottom - Vector3.right * radius);
}
}
}
Optional Inspector UI
If you want a custom inspector for the new binding, add a PropertyDrawer for the concrete binding type. The built-in bindings use this pattern so they can show DataDefinitionFieldPicker controls instead of raw GUID fields.
If you do not add a custom drawer, Unity will still serialize the binding and show the default managed reference UI for its fields.
Practical Notes
- Put binding types in runtime code, not editor code.
- Put scene drawers and property drawers in editor code.
- Reuse the base binding fields when possible. Position, rotation, color, and label behavior is already handled consistently across built-in bindings.
- Use field GUIDs for values that should come from the
DataRecord. - Keep fallback constants for values that should still draw when the field is empty.
Typical Pattern
- Add a serializable binding class with project-specific fields.
- Mark it with
DataVisualizerBinding. - Add a scene drawer marked with
DataVisualizerBindingSceneDrawer. - Optionally add a property drawer for a nicer inspector.
- Add the binding from the
DataVisualizerComponentinspector and point its fields at yourDataRecordComponentdata.
That keeps the visualizer data-driven. The gizmo is configured by the same data already attached to the GameObject, so it updates as you edit the record in the inspector.