如何使编辑器句柄可选以显示属性检查器窗口

时间:2018-07-09 04:44:26

标签: unity3d unity3d-editor

这是来自How to make individual anchor points of bezier continuous or non-continuous的后续问题。请参考已接受答案中的相关代码(请注意,由于相关代码很长,我这样做是为了保持问题的清晰性。)

我正在努力实现以下目标:

  1. 以如下方式选择贝塞尔曲线手柄/控制点,即在选中时在检查器窗口中显示单个手柄的属性(例如连续性)。请注意,我希望在不为手柄/控制点创建游戏对象的情况下完成此操作

  2. 保留用于处理每个点的移动的单个方法,而不是为每个点的移动使用单独的方法。

2 个答案:

答案 0 :(得分:2)

我要晚参加聚会吗?

  

以如下方式选择贝塞尔曲线手柄/控制点:选中时,在检查器窗口中显示单个手柄的属性(例如连续性)。请注意,我希望在不为手柄/控制点创建游戏对象的情况下完成此操作

除了一件事,我总体上喜欢@jour的解决方案:使用Handles.Button必须单击一个点以将其选中,然后单击然后单击并拖动以移动控制点。 / p>

我提出了另一种方法。使用相同的Handles.FreeMoveHandle,但有一个变量可以记住上次单击的句柄的ID,因此我可以识别它。

通常,内置的Handle不会为您提供比其设计目的更多的信息。例如,FreeMoveHandle返回其翻译的增量,仅此而已。问题是:您想捕获一个简单的单击,但是如果您只是单击但没有拖动,则返回值为Vector3.zero,这与您根本没有单击一样。

好消息:在任何Handle的重载中,有一些调用带有名为controlID的参数-它是每个可交互GUI对象的标识符。如果您抑制它,引擎将选择一个,而您将永远不会知道。但是,如果您传递一个int,则该值将成为该句柄的ID。 但是,如果我传递一个int值并且碰巧与我看不到的其他任何id发生冲突?那么,您可以致电GUIUtility.GetControlID以获得安全的ID。

然后,它很简单。如果Handle的ID与EditorGUIUtility.hotControl相同(即被单击或具有键盘焦点的控件),那么我将此点的索引保存在selectedControlPointId中并用它来显示Inspector中的自定义属性。

  

保留用于处理每个点的移动的单个方法,而不是为每个点的移动使用单独的方法。

嗯...在这里引起争议。如果我理解正确,则您希望使用一个代码来绘制节点和切线。问题是:这两件事本质上是不同的。当然,如果保持简洁明了,它们是场景中机器人可移动的点。但是,当您引入约束(连续性或smooth)和选择之类的东西时,它们变成具有不同逻辑的不同野兽。即使您要将ControlPoint设为struct(就像我现在所做的那样)并将其作为一个整体传递,您仍然需要指出要更新的组件,因此约束将应用于其他对象-您将始终需要一个“主”字段来避免循环更新(您更改tangentBack,这会使tangentFront进行更新,从而触发tangentBack进行更新,依此类推上)。

这就是为什么即使我以某种方式重组了ControlPoint方法并使其成为struct的原因,我也无法用单一的逻辑来绘制节点和切线。


这是一些代码。我从上一个问题中的my answer上的代码开始。

ControlPoint.cs

using System;
using UnityEngine;

[Serializable]
public struct ControlPoint
{
  public Vector2 position;
  public Vector2 tangentBack;
  public Vector2 tangentFront;
  public bool smooth;

  static public ControlPoint MovePosition(ControlPoint pt, Vector2 newPos)
  {
    var newPt = pt;
    newPt.position = newPos;
    return newPt;
  }

  static public ControlPoint MoveTangentBack(ControlPoint pt, Vector2 newTanBack)
  {
    var newPt = pt;
    newPt.tangentBack = newTanBack;
    if (pt.smooth) newPt.tangentFront = pt.tangentFront.magnitude * -newTanBack.normalized;
    return newPt;
  }
  static public ControlPoint MoveTangentFront(ControlPoint pt, Vector2 newTanFront)
  {
    var newPt = pt;
    newPt.tangentFront = newTanFront;
    if (pt.smooth) newPt.tangentBack = pt.tangentBack.magnitude * -newTanFront.normalized;
    return newPt;
  }

  static public ControlPoint WithSmooth(ControlPoint pt, bool smooth)
  {
    var newPt = pt;
    if (smooth != pt.smooth) newPt.tangentBack = -pt.tangentFront;
    return newPt;

  }

  public ControlPoint(Vector2 position, Vector2 tanBack, Vector2 tanFront, bool smooth = false)
  {
    this.position = position;
    this.tangentBack = tanBack;
    this.tangentFront = tanFront;
    this.smooth = smooth;
  }
}

我删除了ControlPointDrawer,因此您添加到其中的其他属性不会隐藏在检查器中。

Path.cs

using System;
using UnityEngine;
using System.Collections.Generic;

[Serializable]
public class Path
{
  [SerializeField] List<ControlPoint> _points;

  [SerializeField] bool _loop = false;

  public Path(Vector2 position)
  {
    _points = new List<ControlPoint>
    {
      new ControlPoint(position, -Vector2.one, Vector2.one),
      new ControlPoint(position + Vector2.right, -Vector2.one, Vector2.one)
    };
  }

  public bool loop { get { return _loop; } set { _loop = value; } }

  public ControlPoint this[int i]
  {
    get { return _points[(_loop && i == _points.Count) ? 0 : i]; }
    set { _points[(_loop && i == _points.Count) ? 0 : i] = value; }
  }

  public int NumPoints { get { return _points.Count; } }

  public int NumSegments { get { return _points.Count - (_loop ? 0 : 1); } }

  public ControlPoint InsertPoint(int i, Vector2 position)
  {
    _points.Insert(i, new ControlPoint(position, -Vector2.one, Vector2.one));
    return this[i];
  }
  public ControlPoint RemovePoint(int i)
  {
    var item = this[i];
    _points.RemoveAt(i);
    return item;
  }
  public Vector2[] GetBezierPointsInSegment(int i)
  {
    var pointBack = this[i];
    var pointFront = this[i + 1];
    return new Vector2[4]
    {
      pointBack.position,
      pointBack.position + pointBack.tangentFront,
      pointFront.position + pointFront.tangentBack,
      pointFront.position
    };
  }

  public ControlPoint MovePoint(int i, Vector2 position)
  {
    this[i] = ControlPoint.MovePosition(this[i], position);
    return this[i];
  }

  public ControlPoint MoveTangentBack(int i, Vector2 position)
  {
    this[i] = ControlPoint.MoveTangentBack(this[i], position);
    return this[i];
  }

  public ControlPoint MoveTangentFront(int i, Vector2 position)
  {
    this[i] = ControlPoint.MoveTangentFront(this[i], position);
    return this[i];
  }
}

PathCreator.cs 相同

PathCreatorEditor.cs

using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(PathCreator))]
public class PathCreatorEditor : Editor
{
  PathCreator creator;
  Path path;
  SerializedProperty property;
  static int selectedControlPointId = -1;

  public override void OnInspectorGUI()
  {
    serializedObject.Update();
    var loopProp = property.FindPropertyRelative("_loop");
    EditorGUI.BeginChangeCheck();
    EditorGUILayout.PropertyField(loopProp);
    var ptsProp = property.FindPropertyRelative("_points");
    var msg = "Total points in path: " + ptsProp.arraySize + "\n";
    if (selectedControlPointId >= 0 && ptsProp.arraySize > 0)
    {
      EditorGUILayout.HelpBox(msg + "Selected control point: " + selectedControlPointId, MessageType.Info);
      EditorGUILayout.Separator();
      EditorGUILayout.PropertyField(ptsProp.GetArrayElementAtIndex(selectedControlPointId), true);
    }
    else
    {
      EditorGUILayout.HelpBox(msg + "No control points selected", MessageType.Info);
    }
    if (EditorGUI.EndChangeCheck()) serializedObject.ApplyModifiedProperties();
  }

  void OnSceneGUI()
  {
    Input();
    Draw();
  }

  void Input()
  {
    Event guiEvent = Event.current;
    Vector2 mousePos = HandleUtility.GUIPointToWorldRay(guiEvent.mousePosition).origin;
    mousePos = creator.transform.InverseTransformPoint(mousePos);
    if (guiEvent.type == EventType.MouseDown && guiEvent.button == 0 && guiEvent.shift)
    {
      Undo.RecordObject(creator, "Insert point");
      path.InsertPoint(path.NumPoints, mousePos);
    }
    else if (guiEvent.type == EventType.MouseDown && guiEvent.button == 0 && guiEvent.control)
    {
      for (int i = 0; i < path.NumPoints; i++)
      {
        if (Vector2.Distance(mousePos, path[i].position) <= .25f)
        {
          Undo.RecordObject(creator, "Remove point");
          path.RemovePoint(i);
          break;
        }
      }
    }
  }

  void Draw()
  {
    Handles.matrix = creator.transform.localToWorldMatrix;
    var rot = Quaternion.Inverse(creator.transform.rotation) * Tools.handleRotation;
    var snap = Vector2.zero;
    Handles.CapFunction cap = Handles.DotHandleCap;
    for (int i = 0; i < path.NumPoints; i++)
    {
      float size;
      var pos = path[i].position;
      size = HandleUtility.GetHandleSize(pos) * .05f;
      Handles.Label(pos, i.ToString());
      Handles.color = i == selectedControlPointId ? Handles.selectedColor : Color.red;
      int ctrlId = GUIUtility.GetControlID(FocusType.Passive);
      Vector2 newPos = Handles.FreeMoveHandle(ctrlId, pos, rot, size, snap, cap);
      if (ctrlId == EditorGUIUtility.hotControl) selectedControlPointId = i;
      if (pos != newPos)
      {
        Undo.RecordObject(creator, "Move point position");
        path.MovePoint(i, newPos);
      }
      pos = newPos;
      Handles.color = Color.black;
      if (path.loop || i != 0)
      {
        var tanBack = pos + path[i].tangentBack;
        Handles.DrawLine(pos, tanBack);
        size = HandleUtility.GetHandleSize(tanBack) * .03f;
        Vector2 newTanBack = Handles.FreeMoveHandle(tanBack, rot, size, snap, cap);
        if (tanBack != newTanBack)
        {
          Undo.RecordObject(creator, "Move point tangent");
          path.MoveTangentBack(i, newTanBack - pos);
        }
      }
      if (path.loop || i != path.NumPoints - 1)
      {
        var tanFront = pos + path[i].tangentFront;
        Handles.DrawLine(pos, tanFront);
        size = HandleUtility.GetHandleSize(tanFront) * .03f;
        Vector2 newTanFront = Handles.FreeMoveHandle(tanFront, rot, size, snap, cap);
        if (tanFront != newTanFront)
        {
          Undo.RecordObject(creator, "Move point tangent");
          path.MoveTangentFront(i, newTanFront - pos);
        }
      }
    }
    Repaint();
  }



  [DrawGizmo(GizmoType.Selected | GizmoType.NonSelected | GizmoType.Pickable)]
  static void DrawGizmo(PathCreator creator, GizmoType gizmoType)
  {
    Gizmos.matrix = creator.transform.localToWorldMatrix;
    var path = creator.path;
    for (int i = 0; i < path.NumSegments; i++)
    {
      Vector2[] points = path.GetBezierPointsInSegment(i);
      var pts = Handles.MakeBezierPoints(points[0], points[3], points[1], points[2], 30);
      Gizmos.color = Color.green;
      for (int j = 0; j < pts.Length - 1; j++)
      {
        Gizmos.DrawLine(pts[j], pts[j + 1]);
      }
    }
  }

  void OnEnable()
  {
    creator = (PathCreator)target;
    path = creator.path ?? creator.CreatePath();
    property = serializedObject.FindProperty("path");
  }
}

注意:我将Bezier线的图形从OnSceneGui移到了DrawGizmo,所以即使没有切开对象,绿线也将可见,并且可以在“场景编辑器”中选择它,以使其被选中。

最后,我建议对该脚本进行一些进一步的开发。使多点选择成为可能并不是很难。也许使默认手柄(例如位置和旋转)可以分别应用于点。或将用于创建和删除点的方法更改为直观的内容,例如双击或拖动路径线。甚至是用于智能点操作的自定义工具栏,例如对齐,分布,雕刻...不同的约束,例如平滑,对称,尖点或笔直...

答案 1 :(得分:0)

不确定我是否理解了这个问题,但是

  

以这种方式选择贝塞尔曲线手柄/控制点   单个句柄的属性(例如连续性)   选择后,将在检查器窗口中显示。请注意,我会   这样就无需为游戏创建游戏对象   处理/控制点

您需要使用OnSceneGUI选项才能选择手柄,每次选择新点时,只需存储其值即可。

    private void OnSceneGUI ()
    {
        spline = target as Path;
        handleTransform = spline.transform;
        handleRotation = Tools.pivotRotation == PivotRotation.Local
            ? handleTransform.rotation : Quaternion.identity;

        Vector3 p0 = ShowPoint (0);
        Color gg = Color.gray;
        gg.a = 0.25f;
        for (int i = 1; i < spline.ControlPointCount; i += 3)
        {
            Vector3 p1 = ShowPoint (i);
            Vector3 p2 = ShowPoint (i + 1);
            Vector3 p3 = ShowPoint (i + 2);

            Handles.color = gg;
            Handles.DrawLine (p0, p1);
            Handles.DrawLine (p2, p3);

            Handles.DrawBezier (p0, p3, p1, p2, Color.white, null, 2f);
            p0 = p3;
        }
        ShowDirections ();
    }

private Vector3 ShowPoint (int index)
        {
            Vector3 point = handleTransform.TransformPoint (spline.Points[index]);
            float size = HandleUtility.GetHandleSize (point);
            if (index == 0)
            {
                size *= 2f;
            }
            Handles.color = modeColors[(int) spline.GetControlPointMode (index)];
            if (Handles.Button (point, handleRotation, size * handleSize, size * pickSize, Handles.DotCap))
            {
                selectedIndex = index;
                Repaint ();
            }
            if (selectedIndex == index)
            {
                EditorGUI.BeginChangeCheck ();
                point = Handles.DoPositionHandle (point, handleRotation);
                if (EditorGUI.EndChangeCheck ())
                {
                    Undo.RecordObject (spline, "Move Point");
                    EditorUtility.SetDirty (spline);
                    spline.SetControlPoint (handleTransform.InverseTransformPoint (point), index);
                }
            }
            return point;
        }

enter image description here

并用于在检查器GUI上显示该点

    private void DrawSelectedPointInspector ()
    {
        GUILayout.Label ("Selected Point");
        EditorGUI.BeginChangeCheck ();
        Vector3 point = EditorGUILayout.Vector3Field ("Position", spline.Points[selectedIndex]);
        if (EditorGUI.EndChangeCheck ())
        {
            Undo.RecordObject (spline, "Move Point");
            EditorUtility.SetDirty (spline);
            spline.SetControlPoint (point, selectedIndex);
        }
        EditorGUI.BeginChangeCheck ();
        BezierControlPointModes mode = (BezierControlPointModes) EditorGUILayout.EnumPopup ("Mode", spline.GetControlPointMode (selectedIndex));
        if (EditorGUI.EndChangeCheck ())
        {
            Undo.RecordObject (spline, "Change Point Mode");
            spline.SetControlPointMode (selectedIndex, mode);
            EditorUtility.SetDirty (spline);
        }
    }

并在InspectorGUI上调用它

    if (selectedIndex >= 0 && selectedIndex < spline.points.Count)
    {
        DrawSelectedPointInspector ();
    }
  

保留一个单独的方法来处理每个点的移动   每个点都有单独的移动方法。   为此,您只需要保留