unity初级——Inspector检视器面板开发
目录
一、背景知识:
1、C#特性:
[System.Serializable]
2、Unity中特殊目录:
Resources:存储资源目录。
Plugins:需要跨语言条用的逻辑代码目录,三方插件,sdk等。
StreamingAssets:只读,存储更游戏包的资源目录,热更新资源目录。
Editor:编辑器目录,存放于编辑器相关的资源和逻辑代码。
3、*注意:
1、多目录是可以同时存在的。
2、在打包的时候不会生成到项目中
3、UnityEditor命名空间不要出现在游戏发布的代码中
二、编辑器检视面板扩展属性
//隐藏公共成员变量,防止Inspector的值影响到它
//同时保证脚本中变量的可访问度
[HideInInspector]
//私有变量,检视面板可见
//Unity会将对象进行序列化存储,所以即使是私有的,那么标记为可序列化后,就会显示,共有默认是可序列化的
[SerializeField]
//初始化 System.SerializableAttribute 类的新实例。
//表示类可以被序列化,此类不能被继承
[Serializable]
//给数值设定范围(最小min,最大max)
[Range(min, max)]
//添加变量悬浮提示文字
//一个成员变量可以添加多个特性
[Tooltip("这是一个提示")]
//指定输入框,拥有line行
[Multiline(int line)]
//默认显示5行,最多显示10行内容,再多用滚动条控制显示区域
[TextArea(5, 10)]
//给一个成员变量添加右键菜单
//第一个参数是菜单的名称
//第二个参数是右键点击的回调函数
[ContextMenuItem(string name, string function)]
//给小齿轮增加一个回调函数
[ContextMenu(string itemName)]
//使生命周期函数,在编辑器状态下可以执行,游戏中也可以正常使用
//Update()在场景中对象发生变化或项目组织发生变化时会在编辑器下执行
[ExecuteInEditMode]
//当前组件依赖于盒子碰撞体
//当前组件挂载在对象上时,被依赖组件会一起被添加上去
//当依赖组件没有被移除时,被依赖组件不能被删除
[RequireComponent(Type requiredComponent)]
//将组件添加到AddComponent下
//第一个参数:分类名/组件名
//第二个参数:列表中显示的顺序
[AddComponentMenu(string menuName,int order)]
//将组件添加到顶部菜单栏下
[MenuItem(string itemName)]
//将当前编辑器扩展脚本和需要扩展的组件,建立关联关系(当前脚本的功能就可以扩展到对应的组件上了)
[CustomEditor(Type inspectedType)]
二、通过Editor脚本扩展组件(检视器外挂式开发)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor; //步骤1:检视器的扩展开发属于编辑器范畴,所以使用 UnityEditor 命名空间
[CustomEditor(typeof(Player))] //步骤3:将当前编辑器扩展脚本和需要扩展的组件,建立关联关系(当前脚本的功能就可以扩展到对应的组件上了)
public class PlayerEditor : Editor //步骤2:继承Editor,使用声明周期函数及成员变量,参考 MonoBehavior
{
//步骤5:选中或挂载时,需要获取被编辑的组件,只有获取到组件,才能对检视器的显示做处理
private Player _Player;
//步骤4:确定,什么时候显示内容在监视器面板中,即使用生命周期函数
//关联组件被挂载在场景中时,或选中挂载对象时被调用
private void OnEnable()
{
//步骤5:通过父类获取当前选中或挂载的脚本组件
_Player = target as Player;
Debug.Log("Enable");
}
//关联组件在场景中被移除时,或挂载对象失去焦点时被调用
private void OnDisable()
{
Debug.Log("Disable");
}
//已经获得了需要扩展显示的组件,现在需要一种方式修改检视器内部的内容
//重写OnInspectorGUI,内部可以实现重新绘制检视器内部的内容
public override void OnInspectorGUI()
{
//所有API都是处理检视器
//实现文字显示
EditorGUILayout.LabelField("人物相关属性");
//简单数据类型绘制//
//使用绘制方法,重新绘制ID字段(获取原始ID,绘制后,再对原始ID赋值)
_Player.ID = EditorGUILayout.IntField("玩家ID", _Player.ID);
//文本
_Player.Name = EditorGUILayout.TextField("玩家名称", _Player.Name);
//浮点数
_Player.Atk = EditorGUILayout.FloatField("玩家攻击力", _Player.Atk);
//布尔
_Player.isMan = EditorGUILayout.Toggle("是否为男性", _Player.isMan);
//向量
_Player.HeadDir = EditorGUILayout.Vector3Field("头部方向", _Player.HeadDir);
//颜色
_Player.Hair = EditorGUILayout.ColorField("头发颜色", _Player.Hair);
对象数据类型绘制-绘制对象类型//
//参数1:标题
//参数2:原始组件的值
//参数3:成员变量的类型
//参数4:是否可以将场景中的对象拖给这个成员变量
_Player.Weapon = EditorGUILayout.ObjectField("持有武器", _Player.Weapon, typeof(GameObject), true) as GameObject;
//纹理
_Player.Cloth = EditorGUILayout.ObjectField("衣服材质贴图", _Player.Cloth, typeof(Texture), false) as Texture;
枚举数据类型绘制
//整数转枚举
//int id = 0;
//PLAYER_PROFESSION p = (PLAYER_PROFESSION)id;
//单选枚举(标题, 组件上的原始值)
_Player.Profession = (PLAYER_PROFESSION)EditorGUILayout.EnumPopup("玩家职业", _Player.Profession);
//多选枚举(标题, 组件上的原始值)
_Player.Talent = (PLAYER_TALENT)EditorGUILayout.EnumFlagsField("玩家天赋", _Player.Talent);
终极数据类型绘制
//更新可序列化数据
serializedObject.Update();
//通过成员变量名找到组件上的成员变量
SerializedProperty sp = serializedObject.FindProperty("Items");
//可序列化数据绘制(取到的数据,标题,是否将所有获得的序列化数据显示出来)
EditorGUILayout.PropertyField(sp, new GUIContent("道具信息"), true);
//将修改的数据,写入到可序列化的原始数据中
serializedObject.ApplyModifiedProperties();
滑动条绘制
//滑动条显示(1.标题,2.原始变量,最小值,最大值)
_Player.Atk = EditorGUILayout.Slider(new GUIContent("玩家攻击力"), _Player.Atk, 0, 100);
if(_Player.Atk > 80)
{
//显示消息框(红色)
EditorGUILayout.HelpBox("攻击力太高了", MessageType.Error);
}
if(_Player.Atk < 20)
{
//显示消息框(黄色)
EditorGUILayout.HelpBox("攻击力太低了", MessageType.Warning);
}
//按钮显示和元素排列
//(按钮是否被按下)显示按钮(按钮名称)
GUILayout.Button("来个按钮");
GUILayout.Button("来个按钮");
//根据按钮返回值,判定按钮是否被按下
if(GUILayout.Button("测试"))
{
Debug.Log("测试");
}
//开始横向排列绘制
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("检查"))
{
Debug.Log("检查");
}
if (GUILayout.Button("排查"))
{
Debug.Log("排查");
}
//结束横向排列绘制
EditorGUILayout.EndHorizontal();
}
}
三、检视器窗口开发
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor; //使用编辑器命名空间
//继承EditorWindow,需要使用内部声明周期函数和成员变量
public class TestWindow : EditorWindow
{
[MenuItem("工具/创建窗口")]
static void OpenWin()
{
//创建窗口
//泛型:窗口类对象类型
//是否为工具窗口
//窗口的标签名
//创建窗口后,是否马上让窗口获得焦点
TestWindow window = GetWindow<TestWindow>(false, "测试窗口", true);
//给窗口最小尺寸赋值
window.minSize = new Vector2(400, 300);
//给窗口最大尺寸赋值
window.maxSize = new Vector2(800, 600);
}
//开启窗口时,被调用
private void OnEnable()
{
Debug.Log("窗口Enable");
}
//窗口关闭时,被调用
private void OnDisable()
{
Debug.Log("窗口Disable");
}
//只要窗口存在时,Update每帧都会被调用
public void Update()
{
//Debug.Log("窗口Update");
}
//场景内部结构有变化时,回调生命周期函数(变一次调一次)
private void OnHierarchyChange()
{
Debug.Log("窗口Hierarchy");
}
//项目有变化时,回调生命周期函数(变一次调一次)
private void OnProjectChange()
{
Debug.Log("窗口Project");
}
//当选中物体发生变化时调用
private void OnSelectionChange()
{
//获得当前选中的GameObject名称
Debug.Log("窗口SelectionChange: " + Selection.activeGameObject.name);
}
//窗口开启时,每帧会调用 OnGUI 绘制窗口内容
private void OnGUI()
{
if(GUILayout.Button("测试"))
{
ShowNotification(new GUIContent("测试窗口按钮"));
}
}
}
四、完成点击生成方块的工具
1、Editor类:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
public class NodeWindow : EditorWindow
{
static NodeWindow window;
static GameObject nodeManager;
public static void OpenWindow(GameObject manager)
{
nodeManager = manager;
//真正开启了一个窗口
window = EditorWindow.GetWindow<NodeWindow>();
}
void Update()
{
//通过窗口的Update,每帧执行一次,当前被选中的对象为板子
Selection.activeGameObject = nodeManager;
}
public static void CloseWindow()
{
window.Close();
}
}
//外挂式关联NodeManager
[CustomEditor(typeof(NodeManager))]
public class NodeManagerEditor : Editor
{
NodeManager manager;
bool isEditor = false;//是否是编辑的状态
//当选中带有NodeManager组件对象的时候,获得组件
void OnEnable()
{
manager = (NodeManager)target;
Debug.Log("manager:" + manager);
}
//绘制组件的生命周期函数
public override void OnInspectorGUI()
{
//通过终极的数据获取方法,显示列表中的数据
serializedObject.Update();
SerializedProperty nodes = serializedObject.FindProperty("nodes");
EditorGUILayout.PropertyField(nodes, new GUIContent("路径"), true);
serializedObject.ApplyModifiedProperties();
//开始编辑的开关
if (!isEditor && GUILayout.Button("开始编辑"))
{
NodeWindow.OpenWindow(manager.gameObject);//调用打开界面的方法
isEditor = true;//改变状态变成编辑模式
}
//结束编辑的开关
else if (isEditor && GUILayout.Button("结束编辑"))
{
NodeWindow.CloseWindow();//调用关闭界面的方法
isEditor = false;//改变状态变成非编辑模式
}
//删除按钮
if (GUILayout.Button("删除最后一个节点"))
{
RemoveAtLast();
}
//删除所有按钮
else if (GUILayout.Button("删除所有节点"))
{
RemoveAll();
}
}
RaycastHit hit;
//有点类似前期Update函数,发送射线
//当选中关联的脚本挂载的物体
//当鼠标在Scene视图下发生变化时,执行该方法,比如鼠标移动,比如鼠标的点击
void OnSceneGUI()
{
if (!isEditor)//非编辑状态下不能生成路点
{
return;
}
//当鼠标按下左键时发射一条射线
//非运行时,使用Event类
//Event.current.button 判断鼠标是哪个按键的(0是鼠标左键)
//Event.current.type 判断鼠标的事件方式的(鼠标按下)
if (Event.current.button == 0 && Event.current.type == EventType.MouseDown)
{
//从鼠标的位置需要发射射线了
//因为是从Scene视图下发射射线,跟场景中的摄像机并没有关系,所以不能使用相机发射射线的方法
//从编辑器GUI中的一个点向世界定义一条射线, 参数一般都是鼠标的坐标
Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
if (Physics.Raycast(ray, out hit, 100, 1 << 9))
{
//需要在检测到的点实例化,路点
InstancePathNode(hit.point + Vector3.up * 0.1f);
}
}
}
/// <summary>
/// 生成节点
/// </summary>
/// <param name="position"></param>
void InstancePathNode(Vector3 position)
{
//点预制体
GameObject prefab = Resources.Load<GameObject>("PathNode");
//点对象,生成到Plane的子物体下
GameObject pathNode= Instantiate<GameObject>(prefab, position, Quaternion.identity, manager.transform);
//把生成的路点添加到列表里
manager.nodes.Add(pathNode);
}
/// <summary>
/// 删除最后一个节点
/// </summary>
void RemoveAtLast()
{
//保证有节点才能删节点
if (manager.nodes.Count > 0)
{
//从场景中删除游戏物体
DestroyImmediate(manager.nodes[manager.nodes.Count - 1]);
//把该节点从列表中移除
manager.nodes.RemoveAt(manager.nodes.Count - 1);
}
}
/// <summary>
/// 删除所有的节点
/// </summary>
void RemoveAll()
{
///遍历删除所有的节点物体
for (int i = 0; i < manager.nodes.Count; i++)
{
if (manager.nodes[i] != null)
{
DestroyImmediate(manager.nodes[i]);
}
}
manager.nodes.Clear();//清空列表
}
}
2、脚本类:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[ExecuteInEditMode]//在编辑模式下可以执行一些生命周期函数
public class NodeManager : MonoBehaviour {
//存储了所有编辑器下点击生成的点,并使用预制体显示
public List<GameObject> nodes;
// Use this for initialization
void Start () {
}
void Update () {
if (nodes != null&&nodes.Count>1)
{
for (int i = 0; i < nodes.Count - 1; i++)
{
Debug.DrawLine(nodes[i].transform.position,
nodes[i + 1].transform.position,
Color.red,
Time.deltaTime);
}
}
}
}