unity3D编辑器扩展
编辑器扩展只是在编辑项目中运行,发布出来是不会运行的。
固定创建一个文件夹Editor:所有的资源或者代码都不会被打包进去。
01、使用MenuItem添加菜单栏按钮
脚本不需要作为组件存在,可以不用继承MonoBehaviour
如何删除引用(每个函数调用的次数):工具-文本编辑器
如果需要显示行号:
工具-选项-文本编辑器-C#-行号
引用命名空间:using UnityEditor;
[MenuItem("Tools/test/test1")]:在菜单栏会有Tools这一栏,点击显示test,点击test显示test1
1 public class Tools { 2 [MenuItem("Tools/test/test1")] 3 static void Test()//要是静态方法,不然会出现警告 4 { 5 Debug.Log("Test");//UnityEngine里面的 6 } 7 }
[MenuItem("Tools/test/test2")]:test下会有两个子按钮test1、test2(但是不能完全一样,方法名也不能重复)
1 [MenuItem("Tools/test/test1")] 2 static void Test() 3 { 4 Debug.Log("Test");//UnityEngine 5 } 6 [MenuItem("Tools/test/test2")] 7 static void Test2() 8 { 9 Debug.Log("Test"); 10 }
也可以在现有的菜单路径下添加:
1 [MenuItem("Window/mytool/test1")] 2 static void Test3() 3 { 4 Debug.Log("Test"); 5 }
每个菜单栏里面都有一根横线进行分类:
[MenuItem("GameObject/my tool")]//会添加到最下面即第四类
为自己添加的菜单栏进行分组(即设置第三个参数,优先级越小越显示在上面,相邻的两个菜单栏(下面是test3 与show info相邻)的优先级相差11(或者大于11)就会多一个分割线):
unity里面认为优先级相差小就在同一组里面
1 //每一个菜单栏的priority优先级默认为1000,第三个参数 2 [MenuItem("Tools/show info",false,1)] 3 static void Test1() 4 { 5 //Debug.Log(Selection.activeGameObject.name );//是我们第一个选择的游戏物体 6 //Debug.Log(Selection.objects.Length); 7 8 } 9 //%=ctrl #=shift &=alt 10 [MenuItem("Tools/test2 %q",false,100)] 11 static void Test2() 12 { 13 Debug.Log("Test2"); 14 } 15 [MenuItem("Tools/test3 %t",false,0)] 16 static void Test3() 17 { 18 Debug.Log("Test3"); 19 }
要放在现有菜单栏中间某一组中,要知道每个一组有多少个,一般都猜想。
如果要放在hierarchy或者project等面板右键的的弹出框菜单栏里面,也是需要自己根据位置去猜想,第一级菜单看应该放在哪个,比如project对应的菜单是assets。
02、给组件的右键菜单栏添加按钮
注意强转的两种方式不同:
as强转时如果后面的类型和要强转的类型不一样的时候不会报错,但是会但会一个空值给前者,使用其就会报空指针的错误
而直接加类型强转就会报错
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEditor; public class PlayerEditor { [MenuItem("CONTEXT/PlayerHealth/InitHealthAndSpeed")]// CONTEXT 组件名 按钮名 系统自动调用下面的函数,编辑器大部分不需要运行时执行 static void InitHealthAndSpeed( MenuCommand cmd )//menucommand是当前正在操作的组件(context,userData两个参数) { //Debug.Log(cmd.context.GetType().FullName);//得到context完整类型名 //Debug.Log(cmd.context.name);//输出的是组件所在游戏物体的名字 CompleteProject.PlayerHealth health = cmd.context as CompleteProject.PlayerHealth;//强制转化得到当前操作的对象,注意不能在前面加类型强制转型 health.startingHealth = 200; health.flashSpeed = 10; Debug.Log("Init"); } //添加清除刚体的的阻力和重力 1e-07约等于1 [MenuItem("CONTEXT/Rigidbody/Clear")]//CONTEXT 是固定的 static void ClearMassAndGravity( MenuCommand cmd ) { Rigidbody rgd = cmd.context as Rigidbody; rgd.mass = 0; rgd.useGravity = false; } }
03、学习使用selection获取选择的游戏物体(成员为静态,直接用类名访问,如果没有选中物体则为空,active开头的是选择场景中的游戏物体(在inspector面板上显示的))
点击自己添加的按钮实现将选中的所有游戏物体执行该按钮对应的功能函数。
objects参数:获得当前选择的游戏物体数组,可以获得所有选中的游戏物体(包括project)
activeObject参数:在inspector面板上显示的游戏物体,多选的就会显示第一个(包括预制体)
activetransform:通过得到的activeobject物体上的transform组件,只能获取一个
删除功能实现:
[MenuItem("GameObject/my delete", false, 11)] static void Mydelete() { foreach (Object o in Selection.objects)//不选择返回的是空数组 { //GameObject.DestroyImmediate(o);//编辑器模式下不能用destory,能用GameObject.DestroyImmediate,删除之后无法用ctrl+Z无法撤销 Undo.DestroyObjectImmediate(o);//利用Undo进行的删除操作 是可以撤销的 } //想要有撤销功能首先需要把删除操作注册到 操作记录里面 }
给菜单项添加快捷键:
[MenuItem("Tools/test3 _t",false,0)]//表示test3的快捷键为T,一定要有空格和快捷键 static void Test3() { Debug.Log("Test3"); }
设置组合快捷键:
//%代表ctrl #代表shift &代表alt [MenuItem("Tools/test2 %q",false,100)] static void Test2() { Debug.Log("Test2"); }
04、控制菜单项是否启用的功能(第二个参数,比如实现没有选择游戏物体的时候设置上面的mydelete删除键为不能按(不启用)的状态)
true表示按钮对应的这个调用函数是验证函数。点击game object会先执行上面的验证方法,不能按为灰色(不可点击状态)
问题:右键弹出菜单与game object菜单下的按钮点击状态显示不一样?
[MenuItem("GameObject/my delete", true, 11)]//路径保持一致,第二个参数为true表示是给下面的响应函数作为验证的,必须要有一个返回值,根据返回值判断是否能判断 static bool MyDeleteValidate() { if (Selection.objects.Length > 0) return true;//选择了游戏物体返回true,判定下面的方法能够执行 else return false; } [MenuItem("GameObject/my delete", false, 11)] static void Mydelete() { foreach (Object o in Selection.objects) { //GameObject.DestroyImmediate(o); Undo.DestroyObjectImmediate(o);//利用Undo进行的删除操作 是可以撤销的 } //需要把删除操作注册到 操作记录里面 }
05、contextmenu和contextmenuitem的使用(给组件添加右键,可以直接在组件中使用,不需要引用editor命名空间,在unityEngine命名空间下)
对于系统内置组件无法修改脚本代码,只能通过context方式添加右键功能,对于自己写的脚本组件即可用以下方法
要给那个组件添加右键,就在该组件里面添加方法
[ContextMenu("setColor")] void setColor() { flashColour = setColor.green; }
API中特性一般是attribute结尾,使用的时候不需要attribute
contextmenuitem给脚本的属性添加功能。
右键添加给脚本组件的变量血量属性添加增加方法(点击startingHealth右键才有按钮出现):
[ContextMenuItem("按钮的名字","执行的方法(必须要存在)")] [ContextMenuItem("AddHp","addHp") public int startingHealth = 100; void addHp() {
startingHealth +=20;
}
07、创建对话框(有批量修改游戏物体的需求,比如后期游戏对敌人prefabs修改血量什么的属性且更改的值是不确定的,可以用方法06,可是一个个点也很麻烦,如果能全选之后弹出对话框批量修改属性)
可以在弹出的对话框输入值。创建对话框的类后要创建一个对话框,对话框默认有一个close的按钮
对话框类:
1 using System.Collections; 2 using System.Collections.Generic; 3 using UnityEngine; 4 using UnityEditor; 5 6 public class EnemyChange : ScriptableWizard {//对话框类要继承ScriptableWizard 类,定义的字段都会显示在对话框中 7 //实现统一修改敌人 8 [MenuItem("Tools/CreateWizard")]//创建对话框按钮,menuItem可以在任何脚本都可以放,最好将其与对应的修改属性放在一个脚本里 9 static void CreateWizard() 10 { 11 ScriptableWizard.DisplayWizard<EnemyChange>("统一修改敌人","Change And Close","Change");//第二个参数是对话框左下角按钮的名字,第三个参数是otherButtonName按钮 12 } 13 //继承了对话框类脚本中的属性是会显示在对话框上的(公有的) 14 public int changeStartHealthValue = 10; 15 public int changeSinkSpeedValue = 1; 16 17 const string changeStartHealthValueKey = "EnemyChange.changeStartHealthValue"; 18 const string changeSinkSpeedValueKey = "EnemyChange.changeSinkSpeedValue"; 19 //当窗口被创建出来的时候调用的 20 void OnEnable() 21 {
//第二个参数是传默认值,因为第一次没有保存值 22 changeStartHealthValue = EditorPrefs.GetInt(changeStartHealthValueKey, changeStartHealthValue); 23 changeSinkSpeedValue = EditorPrefs.GetInt(changeSinkSpeedValueKey, changeSinkSpeedValue); 24 } 25 //检测create按钮的点击,对话框上面的create(不一定是create,左下角的按钮)按钮(点击会自动调用此函数并关闭对话框) 26 void OnWizardCreate() 27 { 28 GameObject[] enemyPrefabs = Selection.gameObjects;//取得所有选择的方法,gameobjects包括预制体 29 EditorUtility.DisplayProgressBar("进度", "0/" + enemyPrefabs.Length + " 完成修改值", 0); 30 int count = 0; 31 foreach (GameObject go in enemyPrefabs)//遍历所有选择的object 32 { 33 CompleteProject.EnemyHealth hp = go.GetComponent<CompleteProject.EnemyHealth>();//得到选中物体上的要修改属性所属的属性,注意加上正确的命名空间 34 Undo.RecordObject(hp, "change health and speed");//记录做了哪些更改的函数(第二个参数是记录撤销这个步骤的名字,可以随意更改),撤销实现,要注意要放在更改对应属性之前,不然没有用
//修改成我们设置的值 35 hp.startingHealth += changeStartHealthValue; 36 hp.sinkSpeed += changeSinkSpeedValue;//即不能把记录操作函数(undo)放在这之后 37 count++;
//进度条 38 EditorUtility.DisplayProgressBar("进度", count+"/" + enemyPrefabs.Length + " 完成修改值", (float)count/enemyPrefabs.Length); 39 }
//代码清除进度条 40 EditorUtility.ClearProgressBar();
//显示提示信息,对话框被关闭也会关闭 41 ShowNotification(new GUIContent(Selection.gameObjects.Length + "个游戏物体的值被修改了")); 42 }
//otherButtonName按钮响应函数 43 void OnWizardOtherButton() 44 { 45 OnWizardCreate(); 46 }
47 //当前字段值修改的时候会被调用 48 void OnWizardUpdate() 49 {
//设置为空的原因:保证每次两个提示信息都要实时更新(因为原先没有选择敌人的时候会出现错误提示而没有帮助提示,选择一个之后会显示帮助提示而错误提示没有更新) 50 errorString = null; 51 helpString = null; 52 if (Selection.gameObjects.Length > 0) 53 { 54 helpString = "您当前选择了" + Selection.gameObjects.Length + "个敌人";//设置帮助提示,如果想要实时显示当前选择的敌人人数 55 } 56 else 57 { 58 errorString = "请选择至少一个敌人"; 59 } 60 //本地保存对话框修改的值 61 EditorPrefs.SetInt(changeStartHealthValueKey, changeStartHealthValue); 62 EditorPrefs.SetInt(changeSinkSpeedValueKey, changeSinkSpeedValue); 63 }
//当选择的物体发生改变调用的函数 64 void OnSelectionChange() 65 { 66 OnWizardUpdate(); 67 } 69 }
对话框中一些有用的方法属性:
三个事件方法(untiy会自动帮我们调用):
void OnWizardCreate() :
检测create按钮的点击,对话框上面的create(不一定是create,左下角的按钮)按钮(点击会自动调用此函数并关闭对话框)
void OnWizardUpdate() :面板被创建(对话框弹出来的时候)会被调用一次,
当前字段值修改的时候会被调用(一修改就调用)
void OnWizardOtherButton() :otherButtonName按钮响应函数
显
示提示信息字段:
errorString :显示错误信息
helpString:显示提示信息
对话框默认只有一个按钮,如果想要设置多个按钮:
ScriptableWizard DisplayWizard (title : string, klass : System.Type, createButtonName : string = "Create", otherButtonName : string = "") :创建一个按钮
显示进度条:
DisplayCancelableProgressBar:能够点击取消的进度条
DisplayProgressBar:只能由代码清除的进度条
08.创建自定义窗口
1 using System.Collections; 2 using System.Collections.Generic; 3 using UnityEngine; 4 using UnityEditor; 5 //要继承editorWindow 6 public class MyWindow : EditorWindow { 7 8 [MenuItem("Window/show mywindow")] 9 static void ShowMyWindow() 10 { 11 MyWindow window= EditorWindow.GetWindow<MyWindow>(); 12 window.Show(); 13 }
14 //如果想要在窗口中添加东西,使用onGUI绘制 15 private string name=""; 16 void OnGUI() 17 { 18 GUILayout.Label("这是我的窗口"); 19 name = GUILayout.TextField(name); 20 if (GUILayout.Button("创建")) 21 { 22 GameObject go = new GameObject(name);//创建游戏物体 23 Undo.RegisterCreatedObjectUndo(go, "create gameobject");//添加到unity操作记录,方便撤销 24 } 25 } 26 27 }
平常使用的(持续添加):
1.RangeAttribute
在int或者float类型上使用,限制输入值的范围
public class TestRange : MonoBehaviour { [Range(0, 100)] public int HP; }
2.[SerializeField,Tooltip("前景动画视频名字需加后缀")]:序列化并在Inspector面板上添加注释(鼠标放上去会显示Tooltip文字)
3.HeaderAttribute:在一些字段前面加上注释,显示在inspector面板上该属性的上面
1 using UnityEngine; 2 using System.Collections; 3 4 public class ExampleClass : MonoBehaviour { 5 [Header("Health Settings")] 6 public int health = 0; 7 public int maxHealth = 100; 8 [Header("Shield Settings")] 9 public int shield = 0; 10 public int maxShield = 0; 11 }
4.[HideInInspector] :在ispector面板上隐藏
5.RequireComponent:当你添加一个脚本,使用requirecomponent到一个游戏对象,所需的组件将自动被添加到游戏对象。
using UnityEngine; // PlayerScript requires the GameObject to have a Rigidbody component [RequireComponent(typeof(Rigidbody))] public class PlayerScript : MonoBehaviour { Rigidbody rb; void Start() { rb = GetComponent<Rigidbody>(); } void FixedUpdate() { rb.AddForce(Vector3.up); } }
6.TextAreaAttribute:属性使一个字符串被编辑以高度灵活和可滚动文本区域。
using UnityEngine; public class TextAreaExample : MonoBehaviour { [TextArea] public string MyTextArea; }
7.RPC:同步