一,MonoBehaviour类及其生命周期
1.1创建脚本
新建脚本文件后,文件内会有一段默认代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class test : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
它源自基类MonoBehaviour,以确保此脚本将在游戏循环中运行,并具有对某些事件作出反应的附加功能。
1.2生命周期
- 当脚本运行时,unity会按照预定顺序依次调用以下方法,完成生命周期:
[所有脚本的顺序是并行的]
- 初始化:
Awake():仅执行一次,游戏物体创建时调用一次(不管脚本激没激活),常用于初始化
OnEnable():可调用多次,当组件设置enable = true、游戏物体设置active = true或脚本激活第一次run时调用
Start():仅执行一次,在脚本被激活并完成初始化时调用一次(在第一次Update之前),与组件相关 - 更新:
FixedUpdate():固定时间更新,默认50fps,默认时间通过Edit->Project Settings->Time面板中的Fixed Timestep设置(与物理引擎相关的更新在这里)
OnTrigger……():触发,与碰撞互斥,Collider或Rigidbody接触时调用「Enter表示接触瞬间执行一次;Stay表示物体一直接触持续执行;Exit表示物体分开瞬间执行一次。」
OnCollision……():物理碰撞,与触发互斥,Collider或Rigidbody接触时调用
Update():当游戏运行且脚本启用时,每帧触发一次(时间不固定,与物理引擎无关的更新在这里)[由于不同设备帧率不同,比如人物移动等需乘上Time.deltaTime----每帧的时间增量]
LateUpdate():每帧执行,在其他更新执行完之后执行。(一般用于命令脚本执行,比如物体移动位置之后的相机跟随) - 渲染:
OnPreCull(): 在相机剔除cull场景之前调用。剔除过程决定相机的可见对象。
OnBecameVisible/OnBecameInvisible(): 在对象对于相机可见/不可见时调用。
OnWillRenderObject(): 如果对象可见,则为每个相机调用一次。
OnPreRender(): 在相机开始渲染场景之前调用。
OnRenderObject():在完成所有常规场景渲染后调用此函数。此时,可使用 GL 类或 Graphics.DrawMeshNow 绘制自定义几何图形。
OnPostRender(): 在相机完成场景渲染后调用此函数。
OnRenderImage(): 在完成场景渲染后调用此函数,以便对屏幕图像进行后处理。
OnGUI(): 响应 GUI 事件,可多次调用。程序首先将处理 Layout 和 Repaint 事件,然后再处理每个输入事件的 Layout 和 keyboard/鼠标事件。
OnDrawGizmos(): 场景可视化绘制。 - 结束:
OnApplicationQuit():应用程序退出时调用
OnDisable():OnApplicationQuit调用时同时调用,并且行为被禁用enable = false 、对象被销毁active = false时(由激活转向禁用,一直是禁用不管)调用,加载完后调用OnEnable
OnDestroy():OnApplicationQuit调用时同时调用,并且在Destroy游戏物体时被调用
1.3MonoBehaviour类与普通类的区别
- MonoBehaviour类不用创建实例或new,unity会自动完成。但可以通过添加组件的方式替代。
- 只有继承了MonoBehaviour才能用Invoke,Coroutine,print以及生命周期函数等
- 只有继承了MonoBehaviour才能在面板上看到,挂在物体上
二,拓展菜单和面板
- 小tips:Unity目录下的Editor文件夹是专门用来进行编辑器拓展的,尽在编辑模式下有效;可以放在Assets下,也可以放在任意目录下;这个文件夹的内容打包是会被自动忽略。
2.1 Inspector面板参数
Inspector面板参数与shader类似:
- [Header(" ")]----总标题
- [Tooltip(" ")]----给紧挨着的下一行注释,在unity里鼠标放在下一行的变量上时出现该注释
- [HideInInspector]----不想在面板上出现的public变量
- [SerializeField]----可以在面板上出现的private变量
- [Range( , )]----表示下一行变量的滑竿范围
- [Serializable]----表示声明的类或结构体在unity面板上以折叠的方式展示
- [System.Serializable]----序列化,unity里自定义数据类型无法显示在inspectior面板里,需要加上[System.Serializable]使其序列化以显示自定义数据或结构体
2.2 顶部菜单栏
[MenuItem("Tools/菜单选项")]
public static void ToolsTest()
{
//设置菜单选中状态:
string path = "Tools/菜单选项"; //菜单路径
bool beChosen = Menu.GetChecked(path); //获取菜单的选中状态
Menu.SetChecked(path, !beChosen); //设置新的选中状态
}
通过AddItem()添加列表元素,效果如下图
- MenuItem属性
//itemName:菜单项的名称
//isValidateFunction:表示是否为验证函数
//priority:菜单项的排序权重,越大越下边
public MenuItem(string itemName, bool isValidateFunction=false, int priority=1000);
2.3 拓展Assets右键菜单
[MenuItem("Assets/Test", false, 1)]
static void Test()
2.4 拓展Inspector组件右键菜单
//CONTEXT路径下
[MenuItem("CONTEXT/组件名/Test5")]
static void Test()
2.5 拓展Hierarchy右键菜单
//GameObject路径下
[MenuItem("GameObject/Create Other/XXX", priority = 0)]
static void Test()
效果如下图
2.6 CustomEditor自定义编辑器
自定义编辑器可以定义序列化字段的外观, 同时在字段发生变化时做一些对应的操作,使自己的功能像购买的那些nb的插件一样好用!
[CustomEditor(typeof(MonoTest), true)]
public class MonoTestEditor : Editor
{
public override void OnInspectorGUI()
{
MonoTest _target = target as MonoTest;
//base.OnInspectorGUI();//按照Editor默认的的方式绘制所有序列化属性
serializedObject.Update();//刷新serializedObject
var param = serializedObject.FindProperty("Params");
//获取参数重绘等等…………//
serializedObject.ApplyModifiedProperties();//应用serializedObject,返回值能够表明序列化字段是否有修改
}
}
- 第一行[CustomEditor(typeof(MonoTest), true)]用来描述要自定义的是哪个类, 第二个参数代表是否对其子类起效.
- 将类继承父类Editor(需要using UnityEditor;)
- CustomEditor两个关键属性:
- target:代表自己类的实例对象,默认是Object类型
- serializedObject:封装了对应类上的「序列化」字段及一些方法,序列化可以用SerializedProperty(单个属性)、[System.Serializable](类似Param的类属性);可以通过serializedObject.FindProperty()查找。
- 重写OnInspectorGUI,需要用到GUI, GUILayout, EditorGUILayout等,接下来详细介绍
三,GUI/GUILayout
GUI:运行时(game视图)和编辑器里都可以绘制UI,需要自己算Rect
GUILayout:基于GUI实现的自动排版的绘制功能
3.1按钮
- 普通Button
GUI.backgroundColor = Color.red;
if (GUILayout.Button("Create"))
{
CreateComponent(scr);
}
- button网格
private int index;
private string[] buttonNames = new string[] { "1", "2", "3" };
index = GUILayout.SelectionGrid(index, buttonNames, 2); //第三个参数是指一行有几个按钮
//或者
index = GUILayout.Toolbar(index, buttonNames);//“工具栏”功能,都在一行了
- 打勾Toggle
private bool toggleValue;
toggleValue = GUILayout.Toggle(toggleValue, "ues XXX"); //第二个参数是Toggle文本信息
3.2文本
- 普通文本标签
GUILayout.Label("FPS:");//屏幕左上显示一行字的最简单那种
- 输入文本区域
private string areaText = "multi-line text";
private string singleText = "single-line text..";
areaText = GUILayout.TextArea(areaText); //TextArea 支持多行编辑,回车换行
singleText = GUILayout.TextField(singleText); //Textfield 不支持回车换行,只能用\n的换行符
3.3滑条
- 滑条Slider–用于改变值
private float hValue = 0;
private float vValue = 0;
//横向、纵向滑条
hValue = GUILayout.HorizontalSlider(hValue, 0, 100, GUILayout.Width(300));
vValue = GUILayout.VerticalSlider(vValue, 0, 100);
- 滚动条ScrollBar–用于改变百分比
private float blockSize = 10; //滑动块大小
private float leftValue = 0; //最左侧/最上侧数值
private float rightValue = 100; //最右侧/最下侧数值
//横向、纵向滚动条
hValue = GUILayout.HorizontalScrollbar(hValue, blockSize, leftValue, rightValue, GUILayout.Width(300));
vValue = GUILayout.VerticalScrollbar(vValue, blockSize, leftValue, rightValue);
3.4布局
- Box区域
GUILayout.Box("Box Area", GUILayout.Width(200), GUILayout.Height(200));
- 可视化Area–可用于自定义可视化内容
GUILayout.BeginArea(new Rect(0, 0, 300, 700));
GUILayout.Button("Button in Area");
GUILayout.EndArea();
- 水平/垂直布局–一半成对使用
GUILayout.BeginHorizontal();
GUILayout.Button("Horizontal Button No.1");
GUILayout.Button("Horizontal Button No.2");
GUILayout.EndHorizontal();
GUILayout.BeginVertical();
GUILayout.Button("Veritical Button No.1");
GUILayout.Button("Veritical Button No.2");
GUILayout.EndVertical();
- 滑动视图ScrollView
private Vector2 scrollViewRoot;
GUILayout.BeginArea(new Rect(0, 0, 400, 400)); //上边的可视化Area
scrollViewRoot = GUILayout.BeginScrollView(scrollViewRoot);
GUILayout.Button("Buttons", GUILayout.Height(200)); //这里加的组件要越界才能滑动
GUILayout.Button("Buttons", GUILayout.Height(200));
GUILayout.Button("Buttons", GUILayout.Height(200));
GUILayout.EndScrollView();
GUILayout.EndArea();
- 空白间隔Space
GUILayout.FlexibleSpace();//把上下两行代码的组件合理间隔于Area里
GUILayout.Space(100); //手动间隔100个像素
- 控件宽高
//不手动设置则会自动根据Area大小扩展
GUILayout.Width(float width) // 设置控件的宽度
GUILayout.Height(float height) // 设置控件的高度
GUILayout.MinWidth(float width) // 设置控件的最小宽度
GUILayout.MinHeight(float height) // 设置控件的最小高度
GUILayout.MaxWidth(float width)// 设置控件的最大宽度
GUILayout.MaxHeight(float width) // 设置控件的最大高度
GUILayout.ExpandHeight(bool expand) // 是否允许自动扩展高度
GUILayout.ExpandWidth(bool expand) // 是否允许自动扩展宽度--不扩展就是自适应文字
四,EditorGUI/EditorGUILayout
EditorGUI、EditorGUILayout:只能在编辑器里用,需要自己算Rect;属于GUILayoutd1派生,也可以用GUILayout替代
4.1基本面板
4.1.1 浮动窗口EditorWindow
就是这玩意:
using UnityEditor;
public class ClothMonitorMenu : EditorWindow
{
private static ClothMonitorMenu Monitor { get; set; } //窗口实例对象,必须是一个static
[MenuItem("Tools/Magica Cloth/Cloth Monitor", false)] //定义菜单栏位置
public static void InitWindow() //必须是static
{
GetWindow<ClothMonitorMenu>(); //实例化窗口
}
/// 窗口内显示的GUI面板
private void OnGUI()
{
}
}
之后的绘制都是在OnGUI()函数中
4.1.2 检视Inspector面板
也就是重构面板界面
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(MagicaMeshCloth))]typeof(TutorialMono)
public class MagicaMeshClothInspector : ClothEditor
//「ClothEditor内部是public abstract class ClothEditor : Editor」----继承Editor
{
public override void OnInspectorGUI()
{
MagicaMeshCloth scr = target as MagicaMeshCloth;
…………
}
}
之后的绘制都是在OnInspectorGUI()函数中
4.2 普通参数
- 整数、浮点、字符串、向量等
m_intValue = EditorGUILayout.IntField("Int 输入:", m_intValue);
m_floatValue = EditorGUILayout.FloatField("Float 输入:", m_floatValue);
m_textValue = EditorGUILayout.TextField("Text输入:", m_textValue);
m_vec2 = EditorGUILayout.Vector2Field("Vec2输入: ", m_vec2);
m_vec3 = EditorGUILayout.Vector3Field("Vec3输入: ", m_vec3);
m_vec4 = EditorGUILayout.Vector4Field("Vec4输入: ", m_vec4);
m_bounds = EditorGUILayout.BoundsField("Bounds输入: ", m_bounds);
m_boundsInt = EditorGUILayout.BoundsIntField("Bounds输入: ", m_boundsInt);
- 文本、标签层级选项等
EditorGUILayout.LabelField("文本标题", "文本内容");
EditorGUILayout.SelectableLabel("可复制的只读文本内容。");
m_layer = EditorGUILayout.LayerField("层级选择", m_layer);
m_tag = EditorGUILayout.TagField("标签选择", m_tag);//=GameObject Inspector上边的“Tag”“Layer”
- 曲线CurveField
private AnimationCurve m_curve = AnimationCurve.Linear(0, 0, 1, 1);
m_curve = EditorGUILayout.CurveField("动画曲线:", m_curve);
- 颜色
private Color m_color;
private GUIContent colorTitle = new GUIContent("颜色选择");
...
//着重强调一下这个重载方法
//第一个参数: GUIContent,通常拿来作为Title
//第二个参数: Color,目标修改数据
//第三个参数: bool ,是否显示拾色器
//第四个参数: bool ,是否显示透明度通道
//第五个参数: bool ,是否支持HDR。
m_color = EditorGUILayout.ColorField(colorTitle, m_color, true, true, true);
- 选择对象GameObject、Transform、Material
private Transform objectReference;
objectReference = EditorGUILayout.ObjectField("Reference Object", objectReference, typeof(Transform/GameObject), true);//可以最后加一个as GameObject;进行类型转换
- 注意!:所有的“Field”类,如果不把数据放在左边赋值,那么在输入框内的修改将不会保存
4.3 拓展功能–相比自动界面
4.3.1 按钮
- 单个按钮
if (GUILayout.Button("Create"))
{
Undo.RecordObject(scr, "CreateMeshCloth");
if (scr.ClothSelection == null)
InitSelectorData();
}
- 按钮网格
public string[] selectGridStrs = new string[] { "按钮1", "按钮2", "按钮3", "按钮4" };
GUILayout.BeginVertical();
//(1.按钮索引,2.按钮显示文本或图片数组,3.一行多少列)
selectGridIdx = GUILayout.SelectionGrid(selectGridIdx, selectGridStrs, 2);
selectGridIdx = GUILayout.Toolbar(selectGridIdx, selectGridStrs);//“工具栏”功能,都在一行了
GUILayout.EndVertical();
4.3.2 枚举
- 单选枚举
private enum TutorialEnum{One,Two,Three}
private TutorialEnum m_enum;
m_enum = (TutorialEnum)EditorGUILayout.EnumPopup("枚举选择", m_enum);
- 多选枚举
private enum TutorialEnum
{
None = 0,
OneAndTwo = One | Two,
One = 1 << 0,
Two = 1 << 1,
Three = 1 << 2
}
private TutorialEnum m_enum;
m_enum = (TutorialEnum)EditorGUILayout.EnumFlagsField("枚举多选", m_enum);
- 整型单选、多选
private string[] intSelections = new string[] { "整数10", "整数20", "整数30" };
private string[] intMultiSelections = new string[] { "1号", "2号", "3号" };
private int[] intValues = new int[] { 10, 20, 30 };
...
m_singleInt = EditorGUILayout.IntPopup("整数单选框", m_singleInt, intSelections, intValues);
EditorGUILayout.LabelField($"m_singleInt is {m_singleInt}");
m_multiInt = EditorGUILayout.MaskField("整数多选框", m_multiInt, intMultiSelections);
EditorGUILayout.LabelField($"m_multiInt is {m_multiInt}");
4.3.3 折叠
- 单个折叠
private bool foldOut;
foldOut = EditorGUILayout.Foldout(foldOut, "一般折叠栏");
if (foldOut)
{
//折叠的内容
}
- 折叠组
private bool foldOut;
foldOut = EditorGUILayout.BeginFoldoutHeaderGroup(foldOut, "折叠栏组");
if (foldOut)
{
//折叠的内容
}
EditorGUILayout.EndFoldoutHeaderGroup();
4.3.4 滑动条
- 单值滑条
EditorGUILayout.Slider(maxRotationSpeed, 0.0f, 720.0f, "Max Rotation Speed");
EditorGUILayout.IntSlider(bendMaxCount, 1, 6, "Bend Max Connection");
- 范围滑条
EditorGUILayout.MinMaxSlider("双块滑动条", ref m_leftValue, ref m_rightValue, 0.25f, 10.25f);
EditorGUILayout.FloatField("滑动左值:", m_leftValue);
EditorGUILayout.FloatField("滑动右值:", m_rightValue);
4.3.4 开关
- 开关
//下边两个格式不同
toggle = EditorGUILayout.Toggle("Normal Toggle", toggle);//标题 打勾框
toggle = EditorGUILayout.ToggleLeft("Left Toggle", toggle);//打勾框 标题
- 开关组(打开才能调整下边参数)
//下边两个格式不同
toggleGroupValue_1 = EditorGUILayout.BeginToggleGroup("Pos", toggleGroupValue_1);
vector3Filed = EditorGUILayout.Vector3Field("位置修改:", vector3Filed);
EditorGUILayout.EndToggleGroup();
4.3.5 提示警告
EditorGUILayout.HelpBox("一般提示,你应该这样做...", MessageType.Info);
EditorGUILayout.HelpBox("警告提示,你可能需要这样做...", MessageType.Warning);
EditorGUILayout.HelpBox("错误提示,你不能这样做...", MessageType.Error);
4.3.6 其他
- 间隔Space
EditorGUILayout.Space();//出现最频繁
GUILayout.FlexibleSpace();
- 布局,同GUILayout
EditorGUILayout.BeginHorizontal(); //开始水平布局
EditorGUILayout.EndHorizontal();//结束水平布局
EditorGUILayout.BeginVertical();//开始垂直布局
EditorGUILayout.EndVertical();//结束垂直布局
scrollRoot = EditorGUILayout.BeginScrollView(scrollRoot); //开启滚动视图
EditorGUILayout.EndScrollView(); //结束滚动视图
4.4 案例:互斥开关的实现
提个magica cloth选点界面的案例
using (new EditorGUILayout.HorizontalScope())
{
int nowtype = selectPointType;
for(int i = 0; i < tcnt; i++)
{
GUI.backgroundColor = pointTypeList[i].col;
bool ret = GUILayout.Toggle(i == nowtype, pointTypeList[i].label, EditorStyles.miniButtonLeft);
if (ret)
{
nowtype = i;
}
}
if (nowtype != selectPointType)
{
selectPointType = nowtype;
}
}
五,Handles–Scene界面拓展
在Scene界面可视化一些UI空间并且实现用户的交互操作,Handles手柄其实就是Scene里模型的坐标拉杆
5.1 在Scene界面自定义绘制
在SceneView接口中,有一个duringSceneGui,可以实现自定义绘制
void OnEnable()
{
SceneView.duringSceneGui += OnSceneFunc; //onSceneFunc为你自己的绘制方法
}
//我们自己的绘制方法
static void OnSceneFunc(SceneView view)
{
//在这里面实现你想要的绘制效果
}
void OnDisable()
{
SceneView.duringSceneGui -= OnSceneFunc;
}
5.2 Handles常用接口方法
- transform相关
//位置
Vector3 PositionHandle(Vector3 position, Quaternion rotation);
//旋转
Quaternion RotationHandle(Quaternion rotation, Vector3 position);
//大小
Vector3 ScaleHandle(Vector3 scale, Vector3 position, Quaternion rotation, float size);
//变换(可以同时改变位置、旋转、大小)
void TransformHandle(ref Vector3 position, ref Quaternion rotation, ref Vector3 scale);
void TransformHandle(ref Vector3 position, ref Quaternion rotation, ref float uniformScale);
void TransformHandle(ref Vector3 position, ref Quaternion rotation);
- 绘制相关
//实线
void DrawLine(Vector3 p1, Vector3 p2); //实线
void DrawLines(Vector3[] lineSegments); //一系列线段
//多边线
void DrawPolyLine(params Vector3[] points); //多边线(穿过points列表的线)
//虚线
void DrawDottedLine(Vector3 p1, Vector3 p2, float screenSpaceSize);
//一系列虚线
void DrawDottedLines(Vector3[] lineSegments, float screenSpaceSize);
//贝塞尔曲线
void DrawBezier(Vector3 startPosition, Vector3 endPosition, Vector3 startTangent, Vector3 endTangent, Color color, Texture2D texture, float width);
//线形圆弧
void DrawWireArc(Vector3 center, Vector3 normal, Vector3 from, float angle, float radius);
//填充扇形
void DrawSolidArc(Vector3 center, Vector3 normal, Vector3 from, float angle, float radius);
//线形圆形
void DrawWireDisc(Vector3 center, Vector3 normal, float radius);
//填充圆形
void DrawSolidDisc(Vector3 center, Vector3 normal, float radius);
//矩形
void DrawSolidRectangleWithOutline(Vector3[] verts, Color faceColor, Color outlineColor);
//标签、文本
void Label(Vector3 position, string text);
void Label(Vector3 position, Texture image);
//摄像机
void DrawCamera(Rect position, Camera camera);
//缩放滑动条
float ScaleSlider(float scale, Vector3 position, Vector3 direction, Quaternion rotation, float size, float snap);
//绘制2D的UI
void BeginGUI(); //在3D的GUI块中开启2D的GUI块
void EndGUI(); //结束2D的GUI块
5.3 Event简单介绍
在实际中搭配Event可以实现笔刷交互、地形调整高度等等,主要就是交互的一些事件,这里简单介绍下,详情见Unity官方Event API说明文章来源:https://www.toymoban.com/news/detail-767475.html
- 这里截取一段magica cloth的mesh选点操作
if (Event.current.type == EventType.MouseDown && Event.current.button == 0 && !Event.current.alt)
{
hitTest(spos, epos, pointSize * 0.5f);
GUIUtility.hotControl = controlId;
Event.current.Use();
}
if (Event.current.type == EventType.MouseDrag && Event.current.button == 0 && !Event.current.alt)
{
hitTest(spos, epos, pointSize * 0.5f);
Event.current.Use();
}
if (Event.current.type == EventType.MouseUp && Event.current.button == 0 && !Event.current.alt)
{
GUIUtility.hotControl = 0;
Event.current.Use(); // ?
}
if (Event.current.type == EventType.Repaint)
………………
- EventType类型
参考资料
1,Unity’s documentation on MonoBehaviours
2,Unity中的CustomEditor(自定义编辑器)
3,Unity3d Editor 编辑器扩展功能详解一系列文章
4,UnityEditor编辑器扩展
5,Unity编辑器开发(七)Scene界面拓展之Handles文章来源地址https://www.toymoban.com/news/detail-767475.html
到了这里,关于unity脚本基础+编辑器UnityEditor学习的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!