RuntimeTransformGizmo:位移、旋转、缩放插件

写在前面:本文主要讲述插件的API,相关的基础操作,本站有相关的帖子,请自行搜索学习;

PS:本文中的所有示例均来自插件的官方文档,如有不清楚或者不理解的地方可以去翻一下文档

插件下载地址:点此下载

废话不多说直接开始!

1.IRTEditorEventListener

        在Unity的脚本里面继承该接口,可以在选择物体的不同阶段进行代码控制(可以参考Unity碰撞框的三个方法来理解OnColiderEnter/Stay/Exit),以实现自己的功能或效果。

        如果场景中需要操作的模型比较多的时候,可以把该脚本挂载到一个物体上,然后把该物体制作成预制体。在场景运行的时候,只需要实例化该预制体即可。

        继承该接口可以实现的方法:

                bool OnCanBeSelected(ObjectSelectEventArgs selectEventArgs):当前物体即将被选中的状态。如果物体可以被选中就返回true,否则返回false;

                 void OnSelected(ObjectSelectEventArgs selectEventArgs):选中了当前的物体之后调用该方法;

                void OnDeselected(ObjectSelectEventArgs selectEventArgs):取消选择当前物体时调用该方法;

                以上三个方法的参数类型:ObjectSelectEventArgs/ObjectDeselectEventArgs

                两个类都有的属性:GiamoType:选择当前物体时的Gizmo(位移旋转还是缩放);

                                                IsGizmoActive:当前的Gizmo是否被激活;

                ObjectSelectEventArgs的属性:SelectActionType:指定物体被选择的方式;

                 ObjectDeselectEventArgs的属性:DeselectActionType:指定物体被取消的方式;

一个栗子:

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

public class MyRTEditorEventListener : MonoBehaviour,IRTEditorEventListener
{
    public void OnAlteredByTransformGizmo(Gizmo gizmo)
    {
        Debug.Log("改变");
    }

    public bool OnCanBeSelected(ObjectSelectEventArgs selectEventArgs)
    {
        return true;
    }

    public void OnDeselected(ObjectDeselectEventArgs deselectEventArgs)
    {
        Debug.Log("取消选中");
    }

    public void OnSelected(ObjectSelectEventArgs selectEventArgs)
    {
        Debug.Log("选中");
    }

}

 1.1 Useful GameObject Extensions(有用的对象扩展)

        使用IRTEditorEventListener接口,可以自定义选择对象的方式。比如:场景中一个空的游戏对象下有多个子节点,如果你在EditorObjectSelection Inspector中设置了Can Select Empty Object(即允许选择空的游戏对象),那么你就可以把接口的实现给这个空的游戏对象,这样就可以实现一次操作所有该空节点下所有的对象。这只是个例,有很多方法可以自定义选择对象的方式;

        物体判空的规则:(只有满足以下条件,该物体才会被认定为是空物体)

        1. 该物体上没有网格组件:

        2.该物体上没有地形组件;

        3.该物体上没有灯光组件:

        4.该物体上没有粒子系统组件:

        5.该物体上没有带有sprite render的sprite;

        要实现自定义选择对象的方式,可以使用以下方法对游戏对象的层次结构进行处理。

        下列方法包含在静态类GameObjectExtensions中:

  • GameObject GetFirstEmptyParent(this GameObject gameObject):从指定对象gameObject开始向上进行查找,直到它找到一个空的父对象。它可能是一个直接的父对象,也可以是一个更高层的父对象。
  • GameObject GetFirstParentOfType<T>(this GameObject gameObject):从“gameObject”开始向上进行查找,返回第一个类型为T的父对象。它可以是直接的父对象,也可以是层次结构更上层的父对象。
  • List<GameObject> GetRootObjectsFromObjectCollection(List<GameObject> gameObjects):给定一个游戏对象列表,函数将返回这个对象列表里面所有对象所属层次结构的根节点。例如:有两个层次:A(以B和C为子级)和D(以E和F为子级)。如果输入列表包含B和F,那么输出列表将包含A和D,因为A是B所在层次结构的根节点,D是F所在层次结构的根节点。该方法有一个重载,可以接受HashSet作为输入,但方法的工作方式相同。
  • List<GameObject> GetAllRootsAndChildren(List<GameObject>gameObjects):与上面几个方法一样,但是这个方法会返回包含根节点和子节点在内的所有对象。同样的例子,对于两个根节点A和D,函数将返回一个包含A, B, C, D, E和F的列表,A和D是根节点B,C, E和F是子节点。
  • List<GameObject> GetAllChildren(this GameObject gameObject):该方法返回“gameObject”的所有子对象(直接或间接)。
  • List<GameObject> GetAllChildrenIncludingSelf(this GameObject gameObject):该方法返回所有子对象(直接和间接)+对象本身的列表。

        以上方法的使用方法:由于是静态类,都可以通过类名直接使用:GameObjectExtensions.GetRootObjectsFromObjectCollection(collection);其中参数collection为传入的游戏对象或者列表。

举个栗子:

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

public class MyRTEditorEventListener : MonoBehaviour,IRTEditorEventListener
{
    public void OnAlteredByTransformGizmo(Gizmo gizmo)
    {
        Debug.Log("改变");
    }

    public bool OnCanBeSelected(ObjectSelectEventArgs selectEventArgs)
    {
        return true;
    }

    public void OnDeselected(ObjectDeselectEventArgs deselectEventArgs)
    {
        Debug.Log("取消选中");
    }

    public void OnSelected(ObjectSelectEventArgs selectEventArgs)
    {
        //这里如果不加判断可能会陷入死循环,AddObjectToSelection也可能会触发OnSelected;
        if (!gameObject.GetComponent<Light>())  //如果当前对象不包含Light组件开始检索
        { 
            //创建一个单例
            EditorObjectSelection objSelection = EditorObjectSelection.Instance;
            //从当前对象开始往上进行检索直到遇见第一个带有Light组件的对象,然后返回该对象;
            GameObject firstLightObject = gameObject.GetFirstParentOfType<Light>();
            //把当前对象取消选中,false作用是保证撤销和重做的功能可以正常使用;
            objSelection.RemoveObjectFromSelection(gameObject, false);
            //把包含Light组件的对象变为选中对象,false作用同上;
            objSelection.AddObjectToSelection(firstLightObject, false);
            Debug.Log(firstLightObject.name);
        }

    }

}

        大家可以试一下上面的代码。这只是一个简单的例子,你可以使用这种方法实现更复杂的选择机制。但是要注意,一定要有一个停止条件来退出递归链,并将false作为第二个参数传递给选择修改的方法(AddObjectToSelection, RemoveObjectFromSelection等)允许撤消/重做正常工作。

2. Gizmo

2.1 Gizmo events

        Gizmo对象公开一些事件,你可以添加对这些事件的监听。这些事件包括GizmoDragStart, GizmoDragUpdateGizmoDragEnd

public delegate void GizmoDragStartHandler(Gizmo gizmo);
public delegate void GizmoDragUpdateHandler(Gizmo gizmo);
public delegate void GizmoDragEndHandler(Gizmo gizmo);

public event GizmoDragStartHandler GizmoDragStart;
public event GizmoDragUpdateHandler GizmoDragUpdate;
public event GizmoDragEndHandler GizmoDragEnd;

         监听这些事件只需要创建事件处理程序,将它们注册到Gizmo即可(MyHandler自己写)。

        栗子:

EditorGizmoSystem.Instance.TranslationGizmo.GizmoDragStart += MyHandler;
EditorGizmoSystem.Instance.RotationGizmo.GizmoDragStart += MyHandler;
EditorGizmoSystem.Instance.ScaleGizmo.GizmoDragStart += MyHandler;

        在上面的代码中,对所有Gizmos的拖动启动事件使用了相同的处理程序。这里要注意的是访问Gizmos的方式。你还可以注册另一种类型的事件,它允许你检测Gizmo的类型何时发生了改变,而且它是写在EditorGizmoSystem类里的。

public delegate void ActiveGizmoTypeChangedHandler(GizmoType newGizmoType);
public event ActiveGizmoTypeChangedHandler ActiveGizmoTypeChanged;

        你可以通过下面的代码来添加这个事件的注册(MyHandler自己写):

EditorGizmoSystem.Instance.ActiveGizmoTypeChanged += MyHandler;

 2.2 Gizmo Object Masks

        在开发的过程中,我们可能会希望Gizmo忽略某些对象或对象层。例如,你可能想让Gizmo忽略场景中的一些树对象。这时,你可以将树对象分配给Gizmo的遮罩。或者,如果树对象被设置为一个专用层,你可以屏蔽整个对象层。当你使用Volume Scale Gizmo(体积缩放工具,就一个类似于Unity调整碰撞框一样的操作方式),Gizmo的遮罩是不可用的。

        你可以用下面的方法来设置Mask或者取消Mask对象和对象层。这些方法属于Gizmo基类,所以它们可以用于任何Gizmo类型:

  //屏蔽指定的对象,被屏蔽的对象不能被Gizmo操作
• public void MaskObject(GameObject gameObject);
  //解除指定对象的遮罩
• public void UnmaskObject(GameObject gameObject);
  //屏蔽指定的对象集合。被屏蔽的对象不能被gizmo操作
• public void MaskObjectCollection(IEnumerable<GameObject> collection);
  //解除指定对象集合的遮罩
• public void UnmaskObjectCollection(Ienumerable<GameObject> collection);
  //如果指定的对象被屏蔽,则返回true
• public bool IsGameObjectMasked(GameObject gameObject);
  //屏蔽指定的对象层。属于该层的对象不能被Gizmo操作
• public void MaskObjectLayer(int objectLayer);
  //解除指定对象层的遮罩
• public void UnmaskObjectLayer(int objectLayer);
  //用来检查指定的层是否被屏蔽,属于蒙面层的对象不能被小工具操作
• public bool IsObjectLayerMasked(int objectLayer);
  //可以用来检查某个游戏物体是否可以被Gizmo操作
• public bool CanObjectBeManipulated(GameObject gameObject)

举个栗子(这里以移动的Gizmo工具为例,其他的Gizmo同理):

TranslationGizmo trGizmo = EditorGizmoSystem.Instance.TranslationGizmo;
trGizmo.MaskObject(myObject);
trGizmo.MaskObjectLayer(objectLayer);
trGizmo.MaskObjectCollection(myObjectCollection);
trGizmo.UnmaskObjectCollection(myObjectCollection);

 Gizmo Masks and Object Hierarchies(Gizmo蒙版和对象层次结构)

        在使用Gizmo Masks和对象层次结构时,有一个重要的细节需要记住。例如,假设对象B是对象A的子对象,并且在代码的某个地方使用了MaskObject(B)。但是,因为对象A没有被屏蔽,所以当对象A被移动时,B也会受到影响。

2.3 Axes Masks

        所有Gizmo(Volume Scale Gizmo除外)都支持 Axes Masks(轴线遮罩),这些遮罩适用于每个对象。例如,你可以告诉Gizmo它不能沿着Z轴移动一个某个的物体。轴线遮罩函数调用属于Gizmo基类,因此可以适用于Volume Scale Gizmo除外的其他三种Gizmo类型。需要注意的是,遮罩仅在使用某个小部件轴或多轴转换对象时适用。下面是受遮罩影响的小部件的简要总结:

  • Move Gizmo(移动工具) → 轴线,锥体和多轴正方形;
  • Rotation Gizmo(旋转工具) → 旋转圈;
  • Scale Gizmo(缩放工具) → 轴线,盒子和多轴三角形;

 下面是两个方法,让你为不同的对象注册轴线遮罩:

void SetObjectAxisMask(GameObject gameObj, bool[] axisMask) 
void SetObjectAxisMask(GameObject gameObj, int axisIndex, bool isMasked)

 第一个重载:为指定的游戏对象设置一个轴线遮罩。该数组必须包含3个布尔值(每个轴一个),用于指定屏蔽状态。值为true表示轴没有被屏蔽。false表示该对象被屏蔽。

第二个重载:为指定轴设置指定对象的屏蔽状态。

举个栗子:

// 用移动Gizmo为对象注册一个轴线遮罩,对象可以沿X和Y方向移动,但不能沿Z轴移动
TranslationGizmo trGizmo = EditorGizmoSystem.Instance.TranslationGizmo;
trGizmo.SetObjectAxisMask(gameObj, new bool[] {true, true, false};
// 用旋转Gizmo为对象注册一个轴线遮罩,对象只能绕X轴旋转
RotatinoGizmo rotGizmo = EditorGizmoSystem.Instance.RotationGizmo;
rotGizmo.SetAxisMask(gameObj, 0, false);
rotGizmo.SetAxisMask(gameObj, 1, true);
rotGizmo.SetAxisMask(gameObj, 2, true);

        需要注意的是,假设你正在使用移动Gizmo移动单个物体,并且你已经为该物体指定了一个轴线遮罩,该遮罩可以阻止它在任何轴上移动。在这种情况下,当你拖动Gizmo时,Gizmo仍然会移动,但物体不会动。在你结束拖动后,这个Gizmo会弹回它应该在的位置。

3. Actions

        当你执行了一个操作之后,然后对这个操作使用了撤销或者重做操作。这些步骤都会存储在它们自己的类中。他们都实现了两个接口IUndoableAndRedoableActionIAction。下图显示了派生动作类型和它们实现的接口之间的关系:

 这些接口位于以下位置:Assets\Runtime Transform Gizmos\Scripts\Editor Undo Redo System\Actions.

例如,复制对象是使用一个操作类来执行的,该操作类实现了UndoRedoExecute方法。

所有可以撤消/重做的动作必须包装在一个自定义动作类中。

3.1 Defining An Action

        这节主要通过一个栗子来说明,如何自定义一个新的动作类型。假设你现在已经在自己的项目中实现了一个操作界面,这个界面允许用户修改Gizmo的属性(比如修改Gizmo轴的颜色)。而且你还允许用户撤消并重做更改Gizmo轴颜色的操作。当然如果不需要撤销/重做,就不需要实现操作类。Gizmo基类包含两个方法,可以实现这个功能:

public Color GetAxisColor(GizmoAxis axis);
public void SetAxisColor(GizmoAxis axis, Color color);

下面开始演示如何自定义一个新的动作类:

  • 首先需要在EditorActions.cs脚本中,定义一个更改Gizmo轴颜色的新操作类(EditorAction.cs脚本在: Assets\Runtime Transform Gizmos\Scripts\Editor Undo Redo System\Actions\Editor Actions目录下);这个新的操作类实现代码如下:

         

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

public class GizmoAxisColorChangeAction : IUndoableAndRedoableAction, IAction
{
    private Color _oldColor;        //执行更改操作之前Gizmo轴的颜色
    private Color _newColor;        //执行更改操作之后Gizmo轴的颜色
    private GizmoAxis _gizmoAxis;   //记录动作执行时改变颜色的轴
    private Gizmo _gizmo;           //执行动作时,轴颜色改变的Gizmo对象

    public GizmoAxisColorChangeAction(Color oldColor, Color newcolor, 
                                      GizmoAxis gizmoAxis, Gizmo gizmo)
    {
        _oldColor = oldColor;
        _newColor = newcolor;
        _gizmoAxis = gizmoAxis;
        _gizmo = gizmo;
    }
    public void Execute()  //定义该操作需要执行什么内容
    {
        if (_newColor != _oldColor)
        {
            _gizmo.SetAxisColor(_gizmoAxis, _newColor);
            EditorUndoRedoSystem.Instance.RegisterAction(this);
        }
    }

    public void Redo()   //重做
    {
        _gizmo.SetAxisColor(_gizmoAxis, _oldColor);
    }

    public void Undo()   //撤销
    {
        _gizmo.SetAxisColor(_gizmoAxis, _newColor);
    }

    public void OnRemovedFromUndoRedoStack()
    {
        throw new System.NotImplementedException();
    }
}
  • 然后,可以通过下面的代码来改变当前激活的Gizmo的X轴的颜色:
EditorGizmoSystem gizmoSystem = EditorGizmoSystem.Instance;
Gizmo activeGizmo = gizmoSystem.ActiveGizmo;
var action = new GizmoAxisColorChangeAction(activeGizmo.GetAxisColor(GizmoAxis.X), 
                                            newColor, GizmoAxis.X, activeGizmo);
action.Execute();

这样就可以改变Gizmo工具X轴的颜色了,当然也允许撤消/重做。
同样,如果不需要撤消/重做,就不需要实现操作类。直接使用Gizmo属性/方法就可以了。

4 Scene Management

4.1 Game Object Sphere Tree

        这个工具是为了让你能够独立于碰撞器与对象进行交互(当未勾选Use Unity colliders时),该工具使用树型结构来组织场景中的对象。这个工具必须执行的一个操作是,确定对象转换何时发生了更改。系统可以通过两种方式做到这一点:

        1.手动跟踪对象转换数据(默认),效果非常好;

        2.使用Transform.hasChanged属性,用于确定任何转换特定的数据是否已更改。这是最快的,但在默认情况下不会被激活,因为工具会将这个属性设置为false,你可以通过代码自己手动修改这个设置。如果你确定不需要在代码的任何地方手动修改这个值,你可以通过执行以下步骤激活这个功能:

  • 打开Assets\Runtime Transform Gizmos\Scripts\Scene Management\Bounding Volume Hierarchies\Sphere Tree\Game Object Tree\GameObjectSphereTree.cs文件;
  • 取消文件顶部的注释(#define USE_TRANSFORM_HAS_CHANGED)

         方法2的实际效果提升不是会很明显,但是如果你想这样做的话也是可以的。

4.2 Picking Mesh Objects

        系统需要访问对象的网格数据,来允许你选择网格对象。如果一个网格被标记为不可读,系统将执行以下步骤:

  • 它会检查当前对象是否有一个网格碰撞器,有的话会获取并使用它来执行选择操作;
  • 如果没有网格碰撞器,它将使用游戏对象的box volume来挑选。

5 Object Selection

        对象选择机制在EditorObjectSelection.cs脚本中实现(Assets\Runtime Transform Gizmos\Scripts\Editor Object Selection\)。Selection模块是一个单例类,你可以通过EditorObjectSelection.Instance来访问。

5.1 Object Selection Masks

        你可能希望场景中的一些对象被Object Selection忽略。这时,你可以在运行时将对象分配给Object Selection Masks。分配给选择遮罩的对象永远不会被选中。

var objectCollection = new List<GameObject>();//把要忽略的对象添加到List里面
EditorObjectSelection.Instance.AddGameObjectCollectionToSelectionMask(objectCollection);

你也可以把对象从Object Selection Masks 里移除:

var objectCollection = new List<GameObject>();//把要操作的对象添加到List里面
EditorObjectSelection.Instance.RemoveGameObjectCollectionToSelectionMask(objectCollection);

5.2 Manually changing the object selection

        很多时候,你会通过使用鼠标单击或使用选择矩形选择对象的方式,来完成对象选择。但是,有时你可能希望手动更改对象选择。例如,如果你想实现CTRL + A功能来选择场景中的所有对象,你需要一种方法来告诉Object Selection选择什么对象。你可以通过以下方式来实现:

//selectedObjects希望选择的对象集合,allowUndoRedo指定选择操作是否可以撤消/重做
public void SetSelectedObjects(List<GameObject> selectedObjects, bool allowUndoRedo)

        如果你在其他地方设置了某些对象被忽略选择的条件,那么这个方法也会把这些忽略的条件考虑在内,比如说Object Selection Mask、忽略当前层、可以选择网格对象等等条件。被这些限制条件过滤掉的对象不会被选中。

        同样,如果之前被选中的某个物体,如果它不存在于SelectionObjects集合中,那么它就会被取消选中。所以这个方法不会吧当前选择的物体添加到集合中,他会改变当前选中的物体对象。你也可以调用ClearSelection取消对象选择:public bool ClearSelection(bool allowUndoRedo)。参数allowUndoRedo表示清除选择的这个操作是否可以被撤消/重做。

        另外还有两个方法可能会用到:

//将指定对象gameObj追加到当前所选内容里,bool表示是否可以撤销或者重做
bool AddObjectToSelection(GameObject gameObj, bool allowUndoRedo)
//将指定对象gameObj从所选内容中移除,bool表示是否可以撤销或者重做
bool RemoveObjectFromSelection(GameObject gameObj, bool allowUndoRedo) 

5.3 Listening to Selection Changed events

我们可以通过给选择模块注册一个事件的监听方法,来监听选择事件的变化。栗子:

public void AddLinsterEventsFunc()
{
    EditorObjectSelection.Instance.SelectionChanged += MyHandler;
}
public void MyHandler(ObjectSelectionChangedEventArgs args)
{
    throw new NotImplementedException();
}

事件的处理方法仅有的一个参数类型为:ObjectSelectionChangedEventArgs,它包含了几个属性

public ObjectSelectActionType SelectActionType { get { return _selectActionType; } }
public List<GameObject> SelectedObjects { get { return new List<GameObject>(_selectedObjects); } }
public ObjectDeselectActionType DeselectActionType { get { return _deselectActionType; } }
public List<GameObject> DeselectedObjects { get { return new List<GameObject>(_deselectedObjects); } }
public GizmoType GizmoType { get { return _gizmoType; } }
public bool IsGizmoActive { get { return _isGizmoActive; } }

属性SelectActionType为枚举类型:

//定义了物体被选中的方式
public enum ObjectSelectActionType
{
    Click = 0,  //对象是通过鼠标点击选择的
    ClickAppend,//对象是通过鼠标点击+热键来实现选择多个对象的
    MultiSelect,//物体是通过框选的
    MultiSelectAppend,//物体是通过框选+热键的方式选择多个对象的
    Undo,//物体时通过撤销被选中的(执行撤销操作后,当前选择的物体变成了撤销回来的那个对象)
    Redo,//物体时通过重制被选中的(执行重制操作后,当前选择的物体变成了重制回来的那个对象)
    SetSelectedObjectsCall,//对象是通过调用EditorObjectSelection.Instance .SetSelectedObjects被选择的
    AddObjectToSelectionCall,//对象是通过调用EditorObjectSelection.Instance .AddObjectToSelection被选择的
    None//没有对象被选中
}

属性SelectedObjects是一个集合,保存所有被选中的对象。与SelectActionType属性组合起来使用,可以用来找出哪些对象被以何种方式选中。如果SelectActionType属性设置为None,这意味着没有对象被选中,在这种情况下,对象集合为空。

DeselectActionType属性和DeselectedObjects属性与前两个几乎相同,但它们表示的是哪些对象以何种方式被取消选择。

DeselectActionType枚举的值有:

public enum ObjectDeselectActionType
{
    ClearSelectionCall = 0,//对象通过调用EditorObjectSelection.Instance.ClearSelection取消选择
    SetSelectedObjectsCall,//对象通过调用EditorObjectSelection.Instance.SetSelectedObjects取消选择
    RemoveObjectFromSelectionCall,//对象通过调用EditorObjectSelection.Instance.RemoveObjectFromSelection被取消选择
    ClearClickAir,//点击空白处时,物体会被取消选择,这会导致选择模块清除对象选择
    ClickAlreadySelected,//通过按住附加选择热键然后点击一个已经被选择的对象时,该对象将会被取消
    ClickSelectedOther,//通过点击选择其他物体的方式取消选择
    MultiSelectNotInRect,//按住附加热键,然后框选之外的物体会被选择(反选)
    MultiDeselect,//按住附加热键,然后通过框选的方式取消选择
    SelectionDeleted,//对象由于删除被取消
    Undo,//通过撤销操作取消选择对象
    Redo,//通过重做操作取消选择对象
    DeselectInactive,//当前选择的所有物体中不活跃的那些对象,会被取消选择
    None//没有对象被取消选择
}

DeselectActionTypeDeselectedObjects告诉你哪些对象以什么方式被取消选择。如果DeselectActionType设置为None,则表示没有对象被取消选中,DeselectedObjects也会为空。

最后,GizmoType属性表示当选择更改时所使用的Gizmo的类型。IsGizmoActive属性指定该Gizmo是否处于活动状态。当用户通过相应的热键关闭Gizmo时,Gizmo可以处于不活动状态。

猜你喜欢

转载自blog.csdn.net/m0_68256659/article/details/123549600
今日推荐