版本记录
版本号 | 时间 |
---|---|
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();
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 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();
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().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().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运行时网格操作,感兴趣的给个赞或者关注~~~