工欲善其事必先利其器 ,熟练的使用工具,会使得一些重复的事情变得很容易。
unity编辑器给我们提供了一些交互接口,可以用于助力游戏开发的效率。
需求:需要在创建一个64*64阵列的球体,并且球体两两之间的间隔相同,如果手动右键创建再拖动,这是一件非常费时费力的事情,所以我们通过Unity提供的编辑器功能实现,实现的代码如下,代码也用AI添加了注释,比较详细:
csharp
using System.Collections; // 引入集合迭代相关命名空间,用于支持协程等迭代操作
using System.Collections.Generic; // 引入泛型集合命名空间,提供List<T>等泛型容器
using UnityEditor; // 引入Unity编辑器命名空间,提供EditorWindow、MenuItem等编辑器特有功能
using UnityEngine; // 引入Unity引擎核心命名空间,提供GameObject、Transform、Vector3等基础类
// Start is called before the first frame update
// 这是MonoBehaviour的默认注释模板,但此类继承自EditorWindow,该注释不适用,建议删除
public class copyModelProperty : EditorWindow // 定义公共类,继承自EditorWindow,使其成为编辑器窗口
{
public GameObject targetObject; // 公共字段,用于存储用户在编辑器窗口中选择的目标游戏对象(作为父物体容器)
// MenuItem特性将该方法添加到Unity顶部菜单栏,菜单路径为"Tools/initSphere"
[MenuItem("Tools/initSphere")]
public static void ShowWindow() // 静态方法,用于显示或创建该编辑器窗口的实例
{
// 获取或创建copyModelProperty类型的窗口,并设置窗口标题为"Init Sphere Position"
GetWindow<copyModelProperty>("Init Sphere Position");
}
// OnGUI方法在编辑器窗口每次重绘或刷新时调用,用于绘制窗口中的所有UI控件
private void OnGUI()
{
// 在窗口顶部绘制一个粗体标签,显示"Init Sphere Array"作为标题
GUILayout.Label("Init Sphere Array", EditorStyles.boldLabel);
// 创建一个对象选择字段,允许用户在编辑器中通过拖拽或选择按钮来指定GameObject
// 参数依次为:标签文本、当前值、允许的对象类型、是否允许选择场景中的对象(true表示允许)
targetObject = (GameObject)EditorGUILayout.ObjectField("Target Model", targetObject, typeof(GameObject), true);
// 创建一个按钮,显示文本"run"
if (GUILayout.Button("run"))
{
// 当按钮被点击时,检查目标对象是否已被赋值
if (targetObject != null)
{
run(); // 如果已赋值,调用核心执行方法
}
else
{
// 如果未赋值,在控制台输出错误日志,提示用户必须指定目标对象
Debug.LogError("Target Model objects must be assigned.");
}
}
}
// 私有方法,包含该工具的核心功能逻辑
private void run()
{
// 获取targetObject及其所有子物体的Transform组件(包括targetObject自身的Transform)
var childs = targetObject.GetComponentsInChildren<Transform>();
// 创建一个GameObject列表,用于存储需要删除的子物体
List<GameObject> toDestroy = new List<GameObject>();
// 遍历所有子物体的Transform组件
foreach (Transform child in childs)
{
// 判断当前Transform是否不是targetObject自身的Transform(即只处理子物体,排除父物体自身)
if(child != targetObject.transform)
{
toDestroy.Add(child.gameObject); // 将符合条件的子物体添加到待删除列表
}
}
// 遍历待删除列表中的所有GameObject
foreach (GameObject child in toDestroy)
{
DestroyImmediate(child); // 立即销毁该物体(编辑器模式下必须使用DestroyImmediate而非Destroy)
}
// 双层循环,生成64x64=4096个球体,在XZ平面上排列成网格
for (int i = 0; i < 64; ++i) // 外层循环,控制X轴方向的位置(行索引,范围0-63)
{
for (int j = 0; j < 64; ++j) // 内层循环,控制Z轴方向的位置(列索引,范围0-63)
{
var sphere = initSphere(); // 调用方法实例化一个新的球体
if (sphere == null) continue; // 如果实例化失败(预制体未找到),跳过本次循环,继续生成下一个
string name = "sphere + " + i + "_" + j; // 生成带有行列索引的唯一名称
sphere.name = name; // 设置球体的名称,便于在Hierarchy面板中识别
Vector3 pos = new Vector3(i * 3, 1, j * 3 ); // 计算位置:X和Z轴间隔3单位,Y轴固定为1
sphere.transform.position = pos; // 设置球体的世界坐标位置
sphere.transform.SetParent(targetObject.transform); // 将球体设置为targetObject的子物体,保持层级结构
}
}
Debug.Log("done!"); // 在控制台输出完成信息,提示所有球体已生成完毕
}
// 私有方法,用于初始化并实例化一个球体
GameObject initSphere()
{
// 从Resources文件夹中加载球体预制体,路径为"prefab/sphere"
GameObject prefab = Resources.Load<GameObject>("prefab/sphere");
// 检查预制体是否加载成功
if (prefab == null)
{
// 如果加载失败,在控制台输出错误日志,提示用户检查路径
Debug.LogError("未找到预制体,请检查路径:prefab/sphere");
return null; // 返回null,调用方会进行判空处理
}
// 实例化加载的预制体,在场景中创建实际的游戏对象并返回
return Instantiate(prefab);
}
}
创建的实机示意图如下所示:

我们需要在项目位置创建一个叫做Editor的文件夹,并且将上述脚本放在这个文件夹下面,就可以在编辑器的Tool目录下,找到initSphere菜单了

还有一些比较常见的使用场景,比如我遇到过一种情况,美术给过来一个场景,里面有很多Object的材质球需要替换,此时如果人工一个一个的去找,这是一件事倍功半的事情,此时使用编辑器功能就非常轻松了。