Prefab Drawer 基本逻辑
扩展编辑器窗口部分中已经完成了一个简单的 Prefab 管理工具,可以对工程下的 Prefab 进行简单的操作。现在通过对 Scene View 的扩展,实现一个简单的 Prefab 绘制工具,主要包含以下功能:
- 没有任何操作的基本展示
- 以类似画笔的方式将 Prefab 添加到场景中
- 以类似橡皮的方式将场景中的 Prefab 实例删除
实现之前,首先需要把与 Prefab Center 相关的资源、脚本等导入工程。然后新建脚本 Assets/Tools/PrefabDrawer/Editor/PrefabDrawerSceneView.cs
。
Prefab Drawer 的多种模式
首先通过工具栏实现多种模式的选择切换:
private void DrawToolbar () {
_mode = (Mode)GUI.Toolbar (
new Rect (5, 5, 300, 40),
(int)_mode,
new string [] { "View", "Paint", "Erase" }
);
}
其中 Mode
为自定义的枚举类型,表示当前的模式:
public enum Mode {
View = 0,
Paint,
Erase
}
后续的具体逻辑中,只需依据 _mode
的值分别进行处理即可。
Unity 默认工具的预设
Unity 内置有多种工具,最基本的工具栏位于 Unity 面板的顶部:
其中最左侧的工具栏对应 Scene 视图中的基本操作,例如视图工具、移动工具、放缩工具、旋转工具等。为避免 Unity 的默认工具和 Prefab Drawer 的操作冲突,我们需要通过代码预设默认工具:
private void HandleUnityTools () {
switch (_mode) {
case Mode.View:
Tools.current = Tool.View;
break;
case Mode.Paint:
case Mode.Erase:
Tools.current = Tool.None;
break;
}
}
绘制模式
在绘制模式下,用户可以在 Scene 视图中通过单击鼠标快速添加物体。应该实现这样一个函数:将使用者鼠标点击的位置传入,然后便可以在场景的对应位置中生成一个确定 Prefab 的实例。需要实现的核心步骤如下:
- 预先确定好一个 Prefab
- 将鼠标位置转换为场景中的位置
- 在场景中示例化 Prefab
首先在 Prefab Drawer 的 Inspector 视图中设置一个控件,用于设定要绘制的 Prefab。新建脚本 Assets/Scripts/PrefabDrawer/PrefabDrawer.cs
并添加代码:
using UnityEngine;
namespace SceneViewExtension.PrefabDrawer {
public class PrefabDrawer : MonoBehaviour {
[SerializeField]
GameObject _selectedPrefab;
public GameObject SelectedPrefab {
get { return _selectedPrefab; }
set { _selectedPrefab = value; }
}
}
}
则可以通过设置控件来设定想要绘制的 Prefab:
然后代码实现屏幕左边转换为 2D 场景坐标。由于这里的坐标转换参照的 Camera 并不是新建场景中的默认照相机,而是 Scene 视图对应的 SceneView.currentDrawingSceneView.camera
,所以在具体转换中需要进行一些额外的计算:
private Vector3 ScreenCoordsToSceneViewCoords (Vector3 screen) {
Camera camera = SceneView.currentDrawingSceneView.camera;
Vector3 newPos = camera.ScreenToWorldPoint (new Vector3(screen.x * 2, (camera.pixelHeight / 2 - screen.y) * 2, 0));
return new Vector3 (newPos.x, newPos.y, 0);
}
最后将选定好的 SelectedPrefab
实例化,并移动到转换后的坐标位置上:
private void Paint (Vector3 pos) {
if (_target.SelectedPrefab) {
GameObject newObj = (GameObject)PrefabUtility.InstantiatePrefab (_target.SelectedPrefab);
newObj.transform.position = ScreenCoordsToSceneViewCoords (pos);
newObj.AddComponent<PrefabDrawerItem> ();
_objs.Add (newObj);
} else {
Debug.LogError ("No selected prefab.");
}
}
擦除模式
在擦除模式下,用户可以在 Scene 视图中通过单击或者拖动鼠标删除之前绘制的 Prefab 实例。一个不考虑性能的简单实现方式为:在单击或拖动鼠标时,将屏幕坐标转换为场景坐标,然后判断这个坐标是否被某个物体的包围盒所包含,如果是则将这个物体删除。
首先需要知道哪些物体是之前由 Prefab Drawer 绘制的物体。为了找到这些物体,现通过代码在绘制时给新建的物体挂一个脚本 PrefabDrawerItem
,并把这些物体都收集到一个列表中,从而之后便于寻找:
private void Paint (Vector3 pos) {
if (_target.SelectedPrefab) {
// ...
newObj.AddComponent<PrefabDrawerItem> ();
_objs.Add (newObj);
} else {
// ...
}
}
然后只需简单的坐标转换和遍历判断即可:
private void Erase (Vector3 pos) {
Camera camera = SceneView.currentDrawingSceneView.camera;
Vector3 worldCoord = ScreenCoordsToSceneViewCoords (pos);
foreach (GameObject obj in _objs) {
if (obj.GetComponent<Collider2D>().bounds.Contains(worldCoord)) {
_objs.Remove (obj);
DestroyImmediate (obj);
break;
}
}
}