How to make a box editor like what we have on BoxCollider2D?

I’d like to create a game object in which I define a box similar to how I define box collider and then divide that box into regions like this:

This is for visualization and debugging. In this case this box would define the playing area for my game with regions inside it for AI to use. It’s not a collider.

I understand how to draw a box and the sections inside, I can use Debug.DrawLine.

What I don’t understand is how to make it editable in the editor like what BoxCollider2D did. I would like to be able to drag corners and also the sides themselves.

check gizmos, some of them are clickable

Gizmos are only for drawing. I don’t see there any API to be able to drag and edit anything.

oops, i meant Handles,
but then again, i guess you want to do this in build/game view?

1 Like

Ok, here’s what I wrote today using this Handles API:

using System;
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using JetBrains.Annotations;
using static DrawDebugMethods;
using UnityEditor;
using Quaternion = UnityEngine.Quaternion;
using Vector3 = UnityEngine.Vector3;
using static UnityEngine.RuleTile.TilingRuleOutput;

public class PlayingField : MonoBehaviour { 
    public Vector3 TopWall = Vector3.up;
    public Vector3 RightWall = Vector3.right;
    public Vector3 BottomWall = Vector3.down;
    public Vector3 LeftWall = Vector3.left;
}

#if UNITY_EDITOR
[CustomEditor(typeof(PlayingField))]
public class PlayingFieldEditor : Editor {

    public void OnSceneGUI() {
        PlayingField PlayingField = target as PlayingField;

        ref Vector3 LeftWall = ref PlayingField.LeftWall;
        ref Vector3 RightWall = ref PlayingField.RightWall;
        ref Vector3 TopWall = ref PlayingField.TopWall;
        ref Vector3 BottomWall = ref PlayingField.BottomWall;

        ProcessHorizontalPositionHandle(PlayingField, ref LeftWall, ref RightWall);
        ProcessHorizontalPositionHandle(PlayingField, ref RightWall, ref LeftWall);
        ProcessVerticalPositionHandle(PlayingField, ref TopWall, ref BottomWall);
        ProcessVerticalPositionHandle(PlayingField, ref BottomWall, ref TopWall);

        var FromTopToBottomWallCenter = (TopWall - BottomWall) / 2;
        var CenterTopAndBottomWalls = BottomWall + FromTopToBottomWallCenter;
        LeftWall.y = CenterTopAndBottomWalls.y;
        RightWall.y = CenterTopAndBottomWalls.y;

        var FromLeftToRightWallCenter = (RightWall - LeftWall) / 2;
        var CenterLeftToRightWalls = LeftWall + FromLeftToRightWallCenter;
        TopWall.x = CenterLeftToRightWalls.x;
        BottomWall.x = CenterLeftToRightWalls.x;
        Vector3 BasePosition = PlayingField.transform.position;
        var LeftTop = new Vector3(LeftWall.x, TopWall.y) + BasePosition;
        var RightTop = new Vector3(RightWall.x, TopWall.y) + BasePosition;
        var LeftBottom = new Vector3(LeftWall.x, BottomWall.y) + BasePosition;
        var RightBottom = new Vector3(RightWall.x, BottomWall.y) + BasePosition;
        Handles.DrawLine(LeftTop, RightTop, 2);
        Handles.DrawLine(RightTop, RightBottom, 2);
        Handles.DrawLine(RightBottom, LeftBottom, 2);
        Handles.DrawLine(LeftBottom, LeftTop, 2);
    }

    void ProcessHorizontalPositionHandle(PlayingField PlayingField, ref Vector3 CurrentWall, ref Vector3 OppositeWall) {
        Handles.color = Color.green;

        // run before drawing any user interactable handles to detect changes
        EditorGUI.BeginChangeCheck();

        Vector3 BasePosition = PlayingField.transform.position;
        Vector3 NewWall = Handles.PositionHandle(BasePosition + CurrentWall, Quaternion.identity) - BasePosition;

        // if the value was changed then update it and register with the undo system
        if(!EditorGUI.EndChangeCheck()) return;

        Undo.RecordObject(target, "Update box");

        Vector3 WallChange = NewWall - CurrentWall;
        float CurrentWallNewX = BasePosition.x + CurrentWall.x + WallChange.x;
        float OppositeWallNewX = BasePosition.x + OppositeWall.x;
        float NewCenterX = CurrentWallNewX + (OppositeWallNewX - CurrentWallNewX) / 2;

        PlayingField.transform.position = new(NewCenterX, PlayingField.transform.position.y, PlayingField.transform.position.z);
        CurrentWall.x += WallChange.x;
        OppositeWall.x = OppositeWallNewX - PlayingField.transform.position.x;

        var FromCurrentToOppositeWallCenter = (OppositeWall - CurrentWall) / 2;
        var CenterBetweenTwoWalls = CurrentWall + FromCurrentToOppositeWallCenter;
        PlayingField.TopWall.x = CenterBetweenTwoWalls.x;
        PlayingField.BottomWall.x = CenterBetweenTwoWalls.x;
    }

    void ProcessVerticalPositionHandle(PlayingField PlayingField, ref Vector3 CurrentWall, ref Vector3 OppositeWall) {
        Handles.color = Color.green;

        // run before drawing any user interactable handles to detect changes
        EditorGUI.BeginChangeCheck();
        Vector3 BasePosition = PlayingField.transform.position;
        Vector3 NewWall = Handles.PositionHandle(BasePosition + CurrentWall, Quaternion.identity) - BasePosition;

        // if the value was changed then update it and register with the undo system
        if(!EditorGUI.EndChangeCheck()) return;
        Undo.RecordObject(target, "Update box");

        Vector3 WallChange = NewWall - CurrentWall;
        float CurrentWallNewY = BasePosition.y + CurrentWall.y + WallChange.y;
        float OppositeWallNewY = BasePosition.y + OppositeWall.y;
        float NewCenterY = CurrentWallNewY + (OppositeWallNewY - CurrentWallNewY) / 2;

        PlayingField.transform.position = new(PlayingField.transform.position.x, NewCenterY, PlayingField.transform.position.z);
        OppositeWall.y = OppositeWallNewY - PlayingField.transform.position.y;
        CurrentWall.y += WallChange.y;

        CurrentWall = NewWall;
        var FromCurrentToOppositeWallCenter = (OppositeWall - CurrentWall) / 2;
        var CenterBetweenTwoWalls = CurrentWall + FromCurrentToOppositeWallCenter;
        PlayingField.LeftWall.y = CenterBetweenTwoWalls.y;
        PlayingField.RightWall.y = CenterBetweenTwoWalls.y;
    }
}
#endif // UNITY_EDITOR

Very dirty and ugly. But this is what I came up with to draw an editable box. It only supports relative translation but not rotation and not scaling.

I feel like that’s a kind of thing that should be in the engine out of the box, it’s 2025 on the streets. We need all the basic shapes for 2D and 3D and then an easier way to make our own draggable items. The Handle API and other are way too low and very poorly documented and there are no tutorials from what I’ve seen. Like, there is still no documentation on how to make the handle that’s used in the BoxCollider2D: the green dot handle. I haven’t found it in the Handles API.