赞
踩
本文前置知识:CustomData与CustomList
创建一个名为Star的普通脚本,定义以下字段,然后将这个脚本挂在一个空对象上
//网格
private Mesh mesh;
//顶点
private Vector3[] vertices;
//颜色
private Color[] colors;
//三角形(顶点索引)
private int[] triangles;
首先,我们要绘制顶点网格。以vertices[0]
为中心点。其余所有的顶点围绕中心点绘制。
按照定义顺序绘制三角形,例如:
第一个三角形的索引顶点为:0-1-2
第二个三角形的索引顶点为:0-2-3
第三个三角形的索引顶点为:0-3-4
请注意,我们还没有定义控制点,也就是距离中心点一定距离的初始定义点Points
。
所有后续的顶点会按照初始定义的点按照均分角度依次旋转。其次每一个顶点都应该拥有他的颜色。
这里我们使用上篇文章使用的ColorPoint
,现在我们继续定义
//中心点
public ColorPoint center;
//顶点
public ColorPoint[] points;
//迭代次数
public int frequency = 1;
首先初始化网格,注意所有代码我们暂时写在Start里
void Start()
{
GetComponent<MeshFilter>().mesh = mesh = new Mesh();
mesh.name = "Star Mesh";
}
下限判断
//迭代次数至少为1
if (frequency < 1)
{
frequency = 1;
}
//顶点数字初始化
if (points == null)
{
points = new ColorPoint[0];
}
确定顶点总数
顶点总数为迭代次数与控制点长度的乘积,你可能还不太明白,但是这没有关系,读下去你会明白的。
int numberOfPoints = frequency * points.Length;
初始化顶点数组,颜色数组,索引数组
vertices = new Vector3[numberOfPoints + 1];
colors = new Color[numberOfPoints + 1];
triangles = new int[numberOfPoints * 3];
注意,这里顶点与颜色数组都进行了加1,这是因为中心点,而我们计算的顶点总数是不包含中心点的。
顶点总数就等于三角形总数,因此索引数组是三倍的顶点总数。
请注意,顶点总数有他的下限,也就是3个顶点,如果仅有两个顶点时无法绘制三角形的,因为一个顶点在最上面一个在最下面,他们与中心点构成了直线而不是三角形。
代码如下:
if (numberOfPoints >= 3) { //中心点 vertices[0] = center.position; colors[0] = center.color; //均分角度 float angle = -360f / numberOfPoints; //迭代次数 for (int repetitions = 0, v = 1, t = 1; repetitions < frequency; repetitions++) { //每次迭代,遍历所有控制点 for (int p = 0; p < points.Length; p += 1, v += 1, t += 3) { //顶点旋转 vertices[v] = Quaternion.Euler(0f, 0f, angle * (v - 1)) * points[p].position; colors[v] = points[p].color; //索引赋值 triangles[t] = v; triangles[t + 1] = v + 1; } } //最后一个三角形循环到第一个顶点 triangles[triangles.Length - 1] = 1; }
最后将各数组赋予给网格
mesh.vertices = vertices;
mesh.colors = colors;
mesh.triangles = triangles;
让我们看看效果:
呈现紫色,是因为还没有赋予材质,没有着色器绘制。
创建一个材质并将材质赋予对象,然后创建一个Shader并使用下面的Shader代码
Shader "MyShader/Star" { SubShader { Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" } Blend SrcAlpha OneMinusSrcAlpha Cull Off Lighting Off ZWrite Off Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag struct data { float4 vertex : POSITION; fixed4 color: COLOR; }; data vert (data v) { v.vertex = UnityObjectToClipPos(v.vertex); return v; } fixed4 frag(data f) : COLOR { return f.color; } ENDCG } } }
现在,让我们来看看效果:
数据参考:
[CustomEditor(typeof(Star)), CanEditMultipleObjects] public class StarInspector : Editor { public override void OnInspectorGUI() { SerializedProperty points = serializedObject.FindProperty("points"), frequency = serializedObject.FindProperty("frequency"); serializedObject.Update(); EditorGUILayout.PropertyField(serializedObject.FindProperty("center")); EditorList.Show(points,EditorListOption.Buttons | EditorListOption.ListLabel); EditorGUILayout.IntSlider(frequency, 1, 20); int totalPoints = frequency.intValue * points.arraySize; if (totalPoints < 3) { EditorGUILayout.HelpBox("At least three points are needed.", MessageType.Warning); } else { EditorGUILayout.HelpBox(totalPoints + " points in total.", MessageType.Info); } serializedObject.ApplyModifiedProperties(); } }
如果你不明白上述代码,我建议你先阅读CustomList
到目前为止,我们要查看效果必须要运行才可以查看,并且不可更改,这十分麻烦。接下来,我们将介绍如何编写编辑器模式
我们需要做的第一件事是告诉Unity,我们的组件应该在编辑模式下处于活动状态。我们通过添加ExecuteInEditMode
类属性来表明这一点。从现在开始,只要编辑器中出现星号,就会调用Start方法。
因为我们在开始时创建了一个网格,所以它将在编辑模式下创建。当我们将它分配给一个MeshFilter
时,它将持久化并保存在场景中。我们不希望这种情况发生,因为我们是动态生成网格的。我们可以通过设置适当的HideFlags来阻止Unity保存网格。
using System.Collections; using System.Collections.Generic; using UnityEngine; [ExecuteInEditMode,RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))] public class Star : MonoBehaviour { public int numberOfPoints = 10; private Mesh mesh; private Vector3[] vertices; private Color[] colors; private int[] triangles; public ColorPoint center; public ColorPoint[] points; public int frequency = 1; void Start() { GetComponent<MeshFilter>().mesh = mesh = new Mesh(); mesh.name = "Star Mesh"; mesh.hideFlags = HideFlags.HideAndDontSave; if (frequency < 1) { frequency = 1; } if (points == null) { points = new ColorPoint[0]; } int numberOfPoints = frequency * points.Length; vertices = new Vector3[numberOfPoints + 1]; colors = new Color[numberOfPoints + 1]; triangles = new int[numberOfPoints * 3]; if (numberOfPoints >= 3) { vertices[0] = center.position; colors[0] = center.color; float angle = -360f / numberOfPoints; for (int repetitions = 0, v = 1, t = 1; repetitions < frequency; repetitions++) { for (int p = 0; p < points.Length; p += 1, v += 1, t += 3) { vertices[v] = Quaternion.Euler(0f, 0f, angle * (v - 1)) * points[p].position; colors[v] = points[p].color; triangles[t] = v; triangles[t + 1] = v + 1; } } triangles[triangles.Length - 1] = 1; } mesh.vertices = vertices; mesh.colors = colors; mesh.triangles = triangles; } }
我们先将网格更新封装为一个方法,并添加reset
重置也要调用网格更新
using System.Collections; using System.Collections.Generic; using UnityEngine; [ExecuteInEditMode,RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))] public class Star : MonoBehaviour { public int numberOfPoints = 10; private Mesh mesh; private Vector3[] vertices; private Color[] colors; private int[] triangles; public ColorPoint center; public ColorPoint[] points; public int frequency = 1; void Start() { UpdateMesh(); } void Reset() { UpdateMesh(); } private void OnEnable() { UpdateMesh(); } public void UpdateMesh() { if (mesh == null) { GetComponent<MeshFilter>().mesh = mesh = new Mesh(); mesh.name = "Star Mesh"; mesh.hideFlags = HideFlags.HideAndDontSave; } if (frequency < 1) { frequency = 1; } if (points == null) { points = new ColorPoint[0]; } int numberOfPoints = frequency * points.Length; if (vertices == null || vertices.Length != numberOfPoints + 1) { vertices = new Vector3[numberOfPoints + 1]; colors = new Color[numberOfPoints + 1]; triangles = new int[numberOfPoints * 3]; mesh.Clear(); } if (numberOfPoints >= 3) { vertices[0] = center.position; colors[0] = center.color; float angle = -360f / numberOfPoints; for (int repetitions = 0, v = 1, t = 1; repetitions < frequency; repetitions++) { for (int p = 0; p < points.Length; p += 1, v += 1, t += 3) { vertices[v] = Quaternion.Euler(0f, 0f, angle * (v - 1)) * points[p].position; colors[v] = points[p].color; triangles[t] = v; triangles[t + 1] = v + 1; } } triangles[triangles.Length - 1] = 1; } mesh.vertices = vertices; mesh.colors = colors; mesh.triangles = triangles; } }
编辑器属性改变即更新网格,其中撤销也要更新网格
if (serializedObject.ApplyModifiedProperties() ||
Event.current.commandName == "UndoRedoPerformed")
{
foreach (Star s in targets)
{
s.UpdateMesh();
}
}
if (PrefabUtility.GetPrefabType(s) != PrefabType.Prefab) {
s.UpdateMesh();
}
if (!serializedObject.isEditingMultipleObjects)
{
int totalPoints = frequency.intValue * points.arraySize;
if (totalPoints < 3)
{
EditorGUILayout.HelpBox("At least three points are needed.", MessageType.Warning);
}
else
{
EditorGUILayout.HelpBox(totalPoints + " points in total.", MessageType.Info);
}
}
//句柄在所有轴上的单元增量 private static Vector3 pointSnap = Vector3.one * 0.1f; void OnSceneGUI() { //获得目标对象 Star star = target as Star; Transform starTransform = star.transform; float angle = -360f / (star.frequency * star.points.Length); for (int i = 0; i < star.points.Length; i++) { //偏移量 Quaternion rotation = Quaternion.Euler(0f, 0f, angle * i); //原偏移量 Vector3 oldPoint = starTransform.TransformPoint(rotation * star.points[i].position), //创造一个句柄,传入位置和旋转 newPoint = Handles.FreeMoveHandle( oldPoint, Quaternion.identity, 0.02f, pointSnap, Handles.DotHandleCap); //判断位置是否相同 if (oldPoint != newPoint) { Undo.RecordObject(star, "Move"); //这里要注意,所有网格的位置都是局部空间,因此,而通过句柄返回的newPoint为世界空间位置 //因此,变化到局部空间后的位置再叠加偏移量,然后将偏移量叠加回去,因为对象数据需要的是没有偏移量的点,这里要叠加逆旋转去消除偏移量 star.points[i].position = Quaternion.Inverse(rotation) * starTransform.InverseTransformPoint(newPoint); star.UpdateMesh(); } } }
最终效果:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。