Unity-可编辑的星星特效

本文档介绍了如何在Unity中创建一个自定义的星星特效,包括利用顶点网格、颜色和三角形索引来绘制星星,以及通过编辑器模式实时更新网格。此外,还提供了自定义编辑器的实现,允许在Unity编辑器中直接调整星星的形状和颜色。文章涵盖了CustomData和CustomList的概念,并提供了Shader和编辑器扩展的代码示例。
摘要由CSDN通过智能技术生成

Unity-可编辑的星星特效


阅前须知

本文前置知识: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();
    }
  }
}

最终效果:

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值