版本记录
| 版本号 | 时间 |
|---|---|
| V1.0 | 2019.11.24 星期日 |
前言
Unity是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。Unity类似于Director,Blender game engine, Virtools 或 Torque Game Builder等利用交互的图型化开发环境为首要方式的软件。其编辑器运行在Windows 和Mac OS X下,可发布游戏至Windows、Mac、Wii、iPhone、WebGL(需要HTML5)、Windows phone 8和Android平台。也可以利用Unity web player插件发布网页游戏,支持Mac和Windows的网页浏览。它的网页播放器也被Mac 所支持。网页游戏 坦克英雄和手机游戏王者荣耀都是基于它的开发。感兴趣的看下面几篇文章。
1. Unity强化篇(一) —— 如何使用Vuforia制作AR游戏(一)
2. Unity强化篇(二) —— 适用于Unity的HTC Vive教程(一)
3. Unity强化篇(三) —— 适用于Unity的HTC Vive教程(二)
4. Unity强化篇(四) —— Unity 和 Ethereum(一)
5. Unity强化篇(五) —— Unity 和 Ethereum(二)
6. Unity强化篇(六) —— 使用Unity和Photon进行多人游戏简介(一)
7. Unity强化篇(七) —— Unity Sprite Shapes简介(一)
开始
主要内容:将Unity用作游戏开发平台的好处之一是其强大的3D引擎。 在本教程中,您将介绍3D对象和网格处理的世界。
下面看下写作环境
C# 7.3, Unity 2019.1, Unity
欢迎来到3D对象和网格处理的世界! 将Unity用作游戏开发平台的好处之一是其强大的3D引擎。 3D引擎以及Unity使用自定义编辑器的能力,使3D游戏和应用程序的开发变得非常容易。
随着虚拟现实和增强现实(VR / AR)技术的发展,大多数开发人员会无意间发现自己在3D概念的坚韧不拔中挣扎。 因此,以本教程为起点。 不用担心,这里不会有复杂的3D数学运算-只有很多的心,图纸,箭头和无穷的乐趣!
1. Understanding Meshes
现在开始介绍3D渲染的基本词汇。 3D对象的形状由其网格定义。 网格就像点或顶点的网。 连接这些顶点的不可见线形成三角形,三角形定义了对象的基本形状。

但是除了形状之外,引擎还需要知道如何绘制对象的表面。因此,网格的数据还包括其法线,法线是确定特定三角形朝向的方式以及光线如何从其反射的向量。最后,UV Map将材质映射到对象,指定纹理如何环绕形状。
在Unity中,有两个主要的渲染组件:“网格过滤器”(Mesh Filter)(用于存储模型的网格数据)和“网格渲染器”(Mesh Renderer),其将网格数据与材质组合以在场景中渲染对象。
知道了吗?以下是备忘单,以方便参考:
-
Vertices - 顶点:顶点是3D空间中的一个点。通常缩写为
“vert”。 - Lines/Edges - 线/边:将顶点相互连接的不可见线。
- Triangles - 三角形:当边连接三个顶点时形成。
- UV Map - UV贴图:将材质映射到对象,指定纹理如何环绕对象的形状。
- Normals - 法线:顶点或曲面的方向向量。这典型地指向外部,垂直于网格表面,并有助于确定光从对象反弹的方式。
- Mesh - 网格:包含模型的所有顶点,边,三角形,法线和UV数据。
以下是创建3D网格的基本步骤(采用伪代码):
- 创建一个名为
“myMesh”的新网格。 - 将数据添加到
myMesh的顶点和三角形属性。 - 创建一个名为
“myMeshFilter”的新网格过滤器。 - 将
myMesh分配给myMeshFilter的mesh属性。
2. Setting Up the Project
现在您已经掌握了基础知识,在Unity中打开starter项目。 在Project视图中看下文件夹结构:

-
Prefabs - 预制件:其中包含
CustomHeart预制件,可用于在运行时保存3D网格。 - Scenes - 场景:这包含您将在本教程的不同部分使用的三个场景。
- Editor - 编辑器:在开发过程中,该文件夹中的脚本为您提供了编辑器中的特殊功能。
-
Scripts - 脚本:包含运行时脚本或组件。 将这些组件附加到
GameObject时,单击Play即可执行。 - Materials - 材质:此文件夹包含您要使用的网格物体的材质。
在下一部分中,您将创建一个自定义编辑器以可视化3D网格的各个部分。
Poking and Prodding Meshes With a Custom Editor
在RW/Scenes中打开01 Mesh Study Demo。 在Scene视图中,您将看到一个不起眼的立方体:

您将要建立一个自定义编辑器,以将这个可怜的立方体拆开! (然后,您将学习如何将其保持为一体。)
1. Customizing the Editor Script
在Project视图中选择Editor文件夹。 这个特殊文件夹中的脚本修改了Unity编辑器的工作方式。 它们不会成为内置游戏的一部分。

打开MeshInspector.cs并查看源代码。 请注意,该类继承自Unity的基本Editor类-这就是让Unity了解这是自定义编辑器而不是游戏脚本的原因。
第一步是告诉Unity这个特殊的编辑器应该绘制什么样的对象。 在MeshInspector类声明上方的行上添加以下属性:
[CustomEditor(typeof(MeshStudy))]
现在,当任何附加了Mesh Study组件的GameObject在Scene视图中可见时,此类将处理其绘制。 但是现在,您不知道这种情况是否正在发生。
OnSceneGUI是Unity每次在编辑器中渲染Scene视图时都会调用的事件方法。 您有机会修改Unity在场景中绘制对象的方式。 在OnSceneGUI的开头添加以下内容:
mesh = target as MeshStudy;
Debug.Log("Custom editor is running");
基本的Editor类提供了对您在target变量中具有类型Object的自定义对象的引用。 对于普通的vanilla对象,您无法做很多有用的事情,因此此代码将target强制转换为MeshStudy类型。 记录消息后,您可以在控制台中看到自定义编辑器确实正在运行。
保存文件并返回到Unity。 转到RW / Scripts文件夹,然后将MeshStudy.cs拖到层次结构中的Cube GameObject上,以将组件附加到该对象。

在控制台中查看并确保您的代码正在运行。 然后继续删除Debug.Log行,以免淹没您的控制台。
2. Cloning a Mesh
在Edit模式下使用自定义编辑器处理3D网格时,很容易意外覆盖Unity的默认网格,即内置的Sphere,Cube,Cylinder等。 如果发生这种情况,则需要重新启动Unity。

为避免这种情况,请在Edit模式下克隆网格,然后再对其进行任何更改。
打开MeshStudy.cs。 该脚本继承自MonoBehaviour,因此其Start不会在Edit模式下运行。 幸运的是,这很容易解决!
在MeshStudy的类声明上方,添加以下内容:
[ExecuteInEditMode]
当一个类具有此属性时,其Start将在Play mode和Edit mode下触发。 添加完之后,您可以在更改任何内容之前实例化和克隆网格对象。
将以下代码添加到InitMesh:
meshFilter = GetComponent<MeshFilter>();
originalMesh = meshFilter.sharedMesh; //1
clonedMesh = new Mesh(); //2
clonedMesh.name = "clone";
clonedMesh.vertices = originalMesh.vertices;
clonedMesh.triangles = originalMesh.triangles;
clonedMesh.normals = originalMesh.normals;
clonedMesh.uv = originalMesh.uv;
meshFilter.mesh = clonedMesh; //3
vertices = clonedMesh.vertices; //4
triangles = clonedMesh.triangles;
isCloned = true; //5
Debug.Log("Init & Cloned");
这是正在做的事情:
- 1) 抓取您最初在
MeshFilter中分配的任何网格。 - 2) 创建一个名为
clonedMesh的新网格实例,并通过复制第一个网格设置其属性。 - 3) 将复制的网格分配回网格过滤器。
- 4) 更新局部变量,稍后将需要。
- 5) 将
isCloned设置为true; 您稍后会参考。
保存文件并返回到Unity。 控制台应显示消息“ Init&Cloned”。
在层次结构中选择Cube,然后查看检查器。 网格过滤器将显示一个名为clone的网格。 很好! 这意味着您已经成功克隆了网格。

但是请注意,您的项目中没有新的Mesh资源-克隆的Mesh现在仅存在于Unity的内存中,如果关闭场景,它将消失。 稍后,您将学习如何保存网格。
3. Resetting a Mesh
目前,您想给自己一个简单的方法来重置网格,以便您可以放心地玩。 返回到MeshInspector.cs。
OnInspectorGUI使您可以使用额外的GUI元素和逻辑为对象自定义Inspector。 在OnInspectorGUI中,找到注释//draw reset button并将其替换为以下内容:
if (GUILayout.Button("Reset")) //1
{
mesh.Reset(); //2
}
- 1) 此代码在检查器中绘制一个“重置”
(Reset)按钮。 按下时,绘制函数会返回true。 - 2) 按下后,该按钮将调用
MeshStudy.cs中的Reset。
保存文件并返回到MeshStudy.cs。 添加以下内容以到Reset:
if (clonedMesh != null && originalMesh != null) //1
{
clonedMesh.vertices = originalMesh.vertices; //2
clonedMesh.triangles = originalMesh.triangles;
clonedMesh.normals = originalMesh.normals;
clonedMesh.uv = originalMesh.uv;
meshFilter.mesh = clonedMesh; //3
vertices = clonedMesh.vertices; //4
triangles = clonedMesh.triangles;
}
以下是此代码的逐步操作:
- 1) 如果对象的网格过滤器中没有任何数据,请检查原始网格和克隆网格是否都存在。
- 2) 将
clonedMesh的所有属性重置为原始网格的属性。 - 3) 将
clonedMesh分配回Mesh Filter组件。 - 4) 更新局部变量。
保存文件并返回到Unity。
在检查器中,单击Test Edit按钮使立方体的网格混乱,然后按Reset按钮将其还原。

4. Understanding Vertices and Triangles With Unity
如您先前所见,网格由边连接的顶点组成三角形。 三角形定义了对象的基本形状。
注意:Unity的
Mesh类使用两个数组跟踪顶点和三角形:
- 它将顶点存储为
Vector3的数组。- 它将三角形存储为整数数组。 每个整数是
verts数组中一个顶点的索引,并且每组三个连续的整数代表一个三角形。
例如,组triangles[0], triangles[1], triangles[2]代表一个三角形,组triangles[3], triangles[4], triangles[5]代表下一个三角形,依此类推。因此,在由四个顶点和两个三角形组成的简单四边形网格中,四边形的网格数据为:
5. Visualizing Vertices
如果您可以在带有handles的网格上的顶点上绘制和移动,将更容易看到它是如何工作的。 handles是用于在Scene视图中处理对象的工具,例如“旋转”工具的可拖动球体。 现在,您将要编写自己的handle!
在MeshInspector.cs中,查找EditMesh并添加以下内容:
handleTransform = mesh.transform; //1
handleRotation = Tools.pivotRotation == PivotRotation.Local ?
handleTransform.rotation : Quaternion.identity; //2
for (int i = 0; i < mesh.vertices.Length; i++) //3
{
ShowPoint(i);
}
- 1) 获取网格的
transform,您将需要知道在世界空间中绘制顶点的位置。 - 2) 获取当前的轴旋转模式,以与场景中其他所有对象相同的方式绘制手柄。
- 3) 遍历网格的顶点并使用
ShowPoint绘制点。
在ShowPoint中,将// draw dot注释替换为:
Vector3 point = handleTransform.TransformPoint(mesh.vertices[index]); //1
Handles.color = Color.blue;
point = Handles.FreeMoveHandle(point, handleRotation, mesh.handleSize,
Vector3.zero, Handles.DotHandleCap); //2
- 1) 这条线将顶点的局部位置转换为世界空间。
- 2) 使用
Handles实用程序类绘制点。
Handles.FreeMoveHandle制作一个不受限制的运动handle,您将在下一部分中使用它来拖动点。
保存文件并返回到Unity。
检查Cube's MeshInspector,并确保已选中Move Vertex Point。
现在,您应该在屏幕上看到标有蓝点的网格的顶点。 尝试将脚本附加到其他3D对象,然后亲自查看结果!

6. Moving a Single Vertex
您将从最基本的网格处理类型开始:移动单个顶点。
打开MeshInspector.cs。 在ShowPoint内,用以下内容替换// drag注释:
if (GUI.changed) //3
{
mesh.DoAction(index, handleTransform.InverseTransformPoint(point)); //4
}
- 3)
GUI.changed监视对点所做的任何更改,这与Handles.FreeMoveHandle配合使用可很好地检测拖动动作。 - 4) 拖动顶点时,以顶点索引和顶点位置为参数调用
mesh.DoAction。 这条线还使用InverseTransformPoint将顶点的位置转换回局部空间。
保存MeshInspector.cs并转到MeshStudy.cs。 在DoAction中添加以下内容:
PullOneVertex(index, localPos);
然后将以下内容添加到PullOneVertex:
vertices[index] = newPos; //1
clonedMesh.vertices = vertices; //2
clonedMesh.RecalculateNormals(); //3
- 1) 更新目标顶点的位置。
- 2) 将更新后的顶点数组分配回克隆的网格。
- 3) 告诉
Unity重新绘制网格以反映更改。
保存脚本并返回到Unity。 尝试拖动cube上的点之一。

似乎有些顶点共享相同的位置,因此,当您仅拉一个顶点时,其他顶点会留在后面,并且网格会断开。 您将很快学习如何解决此问题。
7. Looking at the Vertices Array
在外观上,一个立方体网格由八个顶点,六个边和12个三角形组成。 是时候看看Unity是否一致了。

转到MeshStudy.cs,然后在Start之前查找名为vertices的变量。 您将看到它具有[HideInInspector]属性。
临时注释掉该属性,以便快速浏览一下数组:
//[HideInInspector]
public Vector3[] vertices;
注意:更复杂的3D网格可以具有数千个顶点。 如果Unity试图在
Inspector中显示所有这些值,它将冻结,因此通常,您将使用[HideInInspector]隐藏该数组。 你只是在偷看!
保存文件,返回Unity并查看您的cube。 现在,您可以在Mesh Study中看到vertices属性。 单击其旁边的箭头图标以显示Vector3元素的数组。

您会看到数组大小为24,这意味着肯定有顶点共享相同的位置! 花点时间考虑一下为什么同一位置可能有多个顶点。
最简单的答案是:
一个立方体有六个边,每个边都有四个形成一个平面的顶点。6×4 = 24个顶点。如果很难掌握,还有其他方法可以考虑。 但是现在,只知道某些网格物体的顶点共享相同的位置。
由于已经完成了verts数组的浏览,因此请继续注释[HideInInspector]。
8. Finding All Similar Vertices
您可以看到,操作网格不仅需要移动单个顶点,还需要更多的操作—您必须移动空间中特定点的所有顶点才能将网格保持在一起。 因此,您现在就可以动弹了,呃,网状的。
在MeshStudy.cs中,将DoAction内的所有代码替换为:
PullSimilarVertices(index, localPos);
转到PullSimilarVertices并添加以下内容:
Vector3 targetVertexPos = vertices[index]; //1
List<int> relatedVertices = FindRelatedVertices(targetVertexPos, false); //2
foreach (int i in relatedVertices) //3
{
vertices[i] = newPos;
}
clonedMesh.vertices = vertices; //4
clonedMesh.RecalculateNormals();
- 1) 从顶点数组获取目标
vertices位置。 - 2) 查找与目标顶点共享相同位置的所有顶点,并将其索引放入列表中。
- 3) 遍历该列表并更新所有相关顶点的位置。
- 4) 将更新后的
vertices分配回clonedMesh.vertices,然后重新绘制网格。
保存文件并返回到Unity。 单击并拖动任何一个顶点; 网格现在应保持其形状不变。

保存场景。 您已迈出成为网状魔术师的第一步!
Manipulating Meshes
在Unity中编辑网格很有趣,但是如果您可以通过在运行时变形网格来向游戏中添加一些“squish”呢? 接下来,您将以最基本的形式进行尝试-推和拉一些预定义的顶点。

1. Collecting the Selected Indices
首先,创建一个自定义编辑器,使您可以选择要实时移动的顶点。 在RW/Scenes中打开02 Create Heart Mesh场景。 您将在Scene视图中看到一个红色的球体。

在“层次结构”中选择Sphere,然后查看Heart Mesh组件。 这是将存储您选择的顶点的脚本。
但是现在,场景中没有显示任何变体。 因此,接下来,您将解决此问题!
打开RW / Editor / HeartMeshInspector.cs。 在ShowHandle中的if语句内,添加以下代码:
Handles.color = Color.blue;
if (Handles.Button(point, handleRotation, mesh.pickSize, mesh.pickSize,
Handles.DotHandleCap)) //1
{
mesh.selectedIndices.Add(index); //2
}
- 1) 这使
Unity可以将网格的顶点绘制为按钮,因此可以单击它们。 - 2) 单击按钮时,它将选定的索引添加到
mesh.selectedIndices列表。
在现有的if语句之后,在OnInspectorGUI的末尾添加以下代码:
if (GUILayout.Button("Clear Selected Vertices"))
{
mesh.ClearAllData();
}
这会在检查器中添加一个自定义的Reset按钮。 接下来,您将编写代码以清除选择。
保存文件并打开RW / Scripts / HeartMesh.cs。 在ClearAllData中,添加以下内容:
selectedIndices = new List<int>();
targetIndex = 0;
targetVertex = Vector3.zero;
这将清除selectedIndices列表中的值,并将targetIndex设置为零。 它还会重置targetVertex位置。
保存文件并返回到Unity。 选择Sphere并查看其HeartMesh组件。 确保已选中Is Edit Mode,以便可以在Scene视图中查看网格的顶点。 然后单击Selected Indices旁边的箭头图标以显示该数组。
单击一些蓝点,然后观看新条目出现在Selected Indices中。 试用您的Clear Selected Vertices按钮,以确保其正确清除了所有值。

注意:您可以选择使用自定义
Inspector中的Show Transform Handle来显示/隐藏变换手柄,因为它可以妨碍选择顶点。 只要记住当您发现其他场景中缺少Transform handle时不要惊慌! 退出之前,请务必将其重新打开。
2. Deforming the Sphere Into a Heart Shape
实时更新网格顶点需要三个步骤:
- 1) 将当前的网格顶点(在动画之前)复制到
ModifyedVertices。 - 2) 计算并更新
modifiedVertices上的值。 - 3) 每次更改步骤时,将
ModifyedVertices复制到当前网格,并让Unity重新绘制网格。
转到HeartMesh.cs并在Start之前添加以下变量:
public float radiusOfEffect = 0.3f; //1
public float pullValue = 0.3f; //2
public float duration = 1.2f; //3
int currentIndex = 0; //4
bool isAnimate = false;
float startTime = 0f;
float runTime = 0f;
移动顶点应该对其周围的顶点产生一些影响,以保持平滑的形状。 这些变量控制效果。
- 1) 受目标顶点影响的区域半径。
- 2)
pull的长度。 - 3) 动画将运行多长时间。
- 4)
selectedIndices列表的当前索引。
在Init的if语句之前,添加:
currentIndex = 0;
这将在游戏开始时将currentIndex(selectedIndices列表的第一个索引)设置为0。
仍然在Init中,在else语句的右括号之前,添加:
StartDisplacement();
StartDisplacement是实际移动顶点的地方。 它仅在isEditMode为false时运行。
现在,此方法不起作用,因此将以下内容添加到StartDisplacement中:
targetVertex = originalVertices[selectedIndices[currentIndex]]; //1
startTime = Time.time; //2
isAnimate = true;
- 1) 从
originalVertices数组中选择targetVertex以启动动画。 请记住,每个数组项都是一个整数值列表。 - 2) 将开始时间设置为当前时间,并将
isAnimate更改为true。
在StartDisplacement之后,使用以下代码创建一个名为FixedUpdate的新方法:
protected void FixedUpdate() //1
{
if (!isAnimate) //2
{
return;
}
runTime = Time.time - startTime; //3
if (runTime < duration) //4
{
Vector3 targetVertexPos =
meshFilter.transform.InverseTransformPoint(targetVertex);
DisplaceVertices(targetVertexPos, pullValue, radiusOfEffect);
}
else //5
{
currentIndex++;
if (currentIndex < selectedIndices.Count) //6
{
StartDisplacement();
}
else //7
{
originalMesh = GetComponent<MeshFilter>().mesh;
isAnimate = false;
isMeshReady = true;
}
}
}
代码正在执行以下操作:
- 1)
FixedUpdate方法以固定的间隔运行,这意味着它与帧速率无关。 在此处了解更多信息。 - 2) 如果
isAnimate为false,则不会执行任何操作。 - 3) 跟踪动画运行了多长时间。
- 4) 如果动画没有运行太长时间,它将通过获取
targetVertex的世界空间坐标并调用DisplaceVertices来继续动画。 - 5) 否则,时间到了! 向
currentIndex添加一个以开始处理下一个选定顶点的动画。 - 6) 检查是否所有选定的顶点都已处理。 如果不是,请使用最新的顶点调用
StartDisplacement。 - 7) 否则,您已到达所选顶点列表的末尾。 该行将复制当前网格,并将
isAnimate设置为false以停止动画。
3. Making the Vertices Move Smoothly
在DisplaceVertices中,添加以下内容:
Vector3 currentVertexPos = Vector3.zero;
float sqrRadius = radius * radius; //1
for (int i = 0; i < modifiedVertices.Length; i++) //2
{
currentVertexPos = modifiedVertices[i];
float sqrMagnitude = (currentVertexPos - targetVertexPos).sqrMagnitude; //3
if (sqrMagnitude > sqrRadius)
{
continue; //4
}
float distance = Mathf.Sqrt(sqrMagnitude); //5
float falloff = GaussFalloff(distance, radius);
Vector3 translate = (currentVertexPos * force) * falloff; //6
translate.z = 0f;
Quaternion rotation = Quaternion.Euler(translate);
Matrix4x4 m = Matrix4x4.TRS(translate, rotation, Vector3.one);
modifiedVertices[i] = m.MultiplyPoint3x4(currentVertexPos);
}
originalMesh.vertices = modifiedVertices; //7
originalMesh.RecalculateNormals();
此代码循环遍历网格中的每个顶点,并替换与您在编辑器中选择的顶点接近的顶点。它通过一些数学技巧来创建平滑的效果,例如将拇指推入粘土中。稍后,您将了解有关此内容的更多信息。
下面是这段代码的详细信息:
- 1) 获取半径的平方。
- 2) 遍历网格中的每个顶点。
- 3) 查找当前顶点和目标顶点之间的距离并将其平方。
- 4) 如果此顶点不在效果范围内,请尽早退出循环并继续到下一个顶点。
- 5) 否则,根据距离计算衰减
falloff值。高斯函数可创建平滑的钟形曲线。 - 6) 根据距离计算要移动多远,然后根据结果设置旋转(位移方向)。这使顶点“向外”移动,即直接远离
targetVertex,使其看起来像从中心喷出。 - 7) 退出循环后,将更新后的
ModifyedVertices存储在原始网格中,并让Unity重新计算法线。
保存文件并返回到Unity。选择Sphere,转到HeartMesh组件,然后尝试将一些顶点添加到Selected Indices属性中。关闭Is Edit mode模式,然后按Play以预览您的作品。

尝试使用Radius Of Effect, Pull Value和Duration设置以查看不同的结果。 准备就绪后,请按照以下屏幕截图更新设置。

点击Play,您的球体气球变成了心脏形状吗?

恭喜你! 在下一部分中,您将学习如何保存网格以备将来使用。

4. Saving Your Mesh in Real Time
现在,每按一次Play按钮,您的心就会动静。 如果您想要持久的爱,则需要一种将网格物体写入文件的方法。
一种简单的方法是设置一个以3D对象作为其子对象的占位符预制件,然后通过脚本将其网格物体资源替换为您的心脏(... er,您的Heart mesh)。
在项目视图中,找到Prefabs / CustomHeart。 双击预制件以在Prefab Editing模式下将其打开。
单击箭头图标以在层次结构中展开其内容,然后选择子级。 您将在此处存储生成的网格。

退出预制编辑模式,然后打开HeartMeshInspector.cs。 在OnInspectorGUI的结尾,大括号之前,添加以下内容:
if (!mesh.isEditMode && mesh.isMeshReady)
{
string path = "Assets/RW/Prefabs/CustomHeart.prefab"; //1
if (GUILayout.Button("Save Mesh"))
{
mesh.isMeshReady = false;
Object prefabToInstantiate =
AssetDatabase.LoadAssetAtPath(path, typeof(GameObject)); //2
Object referencePrefab =
AssetDatabase.LoadAssetAtPath (path, typeof(GameObject));
GameObject gameObj =
(GameObject)PrefabUtility.InstantiatePrefab(prefabToInstantiate);
Mesh prefabMesh = (Mesh)AssetDatabase.LoadAssetAtPath(path,
typeof(Mesh)); //3
if (!prefabMesh)
{
prefabMesh = new Mesh();
AssetDatabase.AddObjectToAsset(prefabMesh, path);
}
else
{
prefabMesh.Clear();
}
prefabMesh = mesh.SaveMesh(prefabMesh); //4
gameObj.GetComponentInChildren<MeshFilter>().mesh = prefabMesh; //5
PrefabUtility.SaveAsPrefabAsset(gameObj, path); //6
Object.DestroyImmediate(gameObj); //7
}
}
代码是这样的:
- 1) 存储
CustomHeart预制对象资产路径,您需要能够将该路径写入文件。 - 2) 从
CustomHeart预制中创建两个对象,一个作为GameObject,另一个作为参考。 - 3) 从
CustomHeart创建网格资产prefabMesh的实例。 如果找到资产,则清除其数据; 否则,它将创建一个新的空网格。 - 4) 用新的网格数据更新
prefabMesh并将其与CustomHeart资产关联。 - 5) 使用
prefabMesh更新GameObject的网格资源。 - 6) 在给定
gameObj的给定路径上创建一个Prefab Asset,包括场景中的所有子代。 这将替代CustomHeart预制件中的任何东西。 - 7) 立即销毁
gameObj。
保存文件,然后转到HeartMesh.cs。 将SaveMesh的主体替换为以下内容:
meshToSave.name = "HeartMesh";
meshToSave.vertices = originalMesh.vertices;
meshToSave.triangles = originalMesh.triangles;
meshToSave.normals = originalMesh.normals;
return meshToSave;
这将返回基于心形网格的网格资产。
保存文件并返回到Unity。 按Play。 动画结束时,Save Mesh按钮将出现在检查器中。 单击按钮保存新的网格,然后停止播放器。
在“项目”视图中再次找到Prefabs / CustomHeart,然后Prefab Editing模式下将其打开。 您会看到一个预制的心形网状品牌已保存在您的预制件中!


Putting It All Together
在上一节中,您学习了如何通过选择单个顶点来修改网格。 虽然这很酷,但是如果您知道如何按程序选择顶点,则可以做更多有趣的事情。
在上一场景中,DisplaceVertices使用高斯衰减公式来确定在效果半径内“拉”每个顶点的量。 但是,您还可以使用其他数学函数来计算“下降”点。 也就是说,拉力pull开始衰减。 每个函数产生不同的形状:


在本部分中,您将学习如何使用计算出的曲线来操纵顶点。
基于速度等于距离除以时间(v =(d / t))的原理,可以通过将向量的距离除以时间因子来确定向量的位置。

Using the Curve Method
保存当前场景,然后从Scenes文件夹中打开03 Customize Heart Mesh。
在层次结构中找到CustomHeart预制实例,然后单击其旁边的箭头图标以扩展其内容。 选择Child对象。
在检查器中查看其属性。 您将看到带有Heart Mesh资源的Mesh Filter。 将Custom Heart附加到Child上。 资产现在应该从HeartMesh更改为clone。

打开CustomHeart.cs并在Start上方添加以下内容:
public enum CurveType
{
Curve1, Curve2
}
public CurveType curveType;
Curve curve;
这将创建一个名为CurveType的公共枚举,并使其在Inspector中可用。
转到CurveType1并添加以下内容:
Vector3[] curvepoints = new Vector3[3]; //1
curvepoints[0] = new Vector3(0, 1, 0);
curvepoints[1] = new Vector3(0.5f, 0.5f, 0);
curvepoints[2] = new Vector3(1, 0, 0);
curve = new Curve(curvepoints[0], curvepoints[1], curvepoints[2], false); //2
这里做了什么?
- 1) 基本曲线由三个点组成。 该代码设置并绘制第一条曲线的点。
- 2) 使用
Curve生成第一条曲线并将其值分配给Curve。 您可以将最后一个参数设置为true,以绘制曲线作为预览。
现在转到CurveType2并添加以下内容:
Vector3[] curvepoints = new Vector3[3]; //1
curvepoints[0] = new Vector3(0, 0, 0);
curvepoints[1] = new Vector3(0.5f, 1, 0);
curvepoints[2] = new Vector3(1, 0, 0);
curve = new Curve(curvepoints[0], curvepoints[1], curvepoints[2], false); //2
这与CurveType1非常相似。
- 1) 设置并绘制第二条曲线的点。
- 2) 使用
Curve方法生成第二条曲线,并将其值分配给curve。
在StartDisplacement中,在右括号之前,添加以下内容:
if (curveType == CurveType.Curve1)
{
CurveType1();
}
else if (curveType == CurveType.Curve2)
{
CurveType2();
}
根据在Custom Heart组件中选择为Curve Type的内容,这将生成不同的曲线。
在DisplaceVertices的for循环内,在右花括号之前,添加以下内容:
float increment = curve.GetPoint(distance).y * force; //1
Vector3 translate = (vert * increment) * Time.deltaTime; //2
Quaternion rotation = Quaternion.Euler(translate);
Matrix4x4 m = Matrix4x4.TRS(translate, rotation, Vector3.one);
modifiedVertices[i] = m.MultiplyPoint3x4(modifiedVertices[i]);
这看起来很熟悉-就像您添加到HeartMesh的代码一样。
- 1) 获取给定距离
distance处的曲线位置,并将其y值乘以force即可获得增量increment。 - 2) 创建一个新的
Vector3,称为translate,以存储当前顶点的新位置并相应地应用其Transform。
保存文件并返回到Unity。 在子GameObject上检查Custom Heart中的属性。
现在,在Edit Type下拉菜单中,可以选择Add Indices或Remove Indices来更新顶点列表。 选择None退出编辑模式,然后单击Play查看结果。 试用不同的设置和顶点选择。

要查看不同曲线类型的示例,请输入以下值:

将 Curve Type 设置为Curve1 ,检查Edit Type是否设置为None,然后按Play。

您应该看到网格如何呈扇形展开。 将模型移至其侧视图,以便可以看到该曲线产生的形状。 Exit Play,然后使用Curve 2再次尝试比较两种曲线类型的结果:


就这样! 您可以单击Clear Selected Vertices以重置Selected Indices并尝试使用自己的样式。 不要忘记,有几个因素会影响网格的最终形状:
- 半径的大小。
- 顶点在区域内的散布。
- 选定顶点的图案位置。
- 您选择的位移方法。
后记
本篇主要讲述了使用Unity运行时网格操作,感兴趣的给个赞或者关注~~~


