Unity强化篇(八) —— 使用Unity运行时网格操作(一)

版本记录

版本号 时间
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对象的形状由其网格定义。 网格就像点或顶点的网。 连接这些顶点的不可见线形成三角形,三角形定义了对象的基本形状。

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第1张图片

但是除了形状之外,引擎还需要知道如何绘制对象的表面。因此,网格的数据还包括其法线,法线是确定特定三角形朝向的方式以及光线如何从其反射的向量。最后,UV Map将材质映射到对象,指定纹理如何环绕形状。

在Unity中,有两个主要的渲染组件:“网格过滤器”(Mesh Filter)(用于存储模型的网格数据)和“网格渲染器”(Mesh Renderer),其将网格数据与材质组合以在场景中渲染对象。

知道了吗?以下是备忘单,以方便参考:

  • Vertices - 顶点:顶点是3D空间中的一个点。通常缩写为“vert”
  • Lines/Edges - 线/边:将顶点相互连接的不可见线。
  • Triangles - 三角形:当边连接三个顶点时形成。
  • UV Map - UV贴图:将材质映射到对象,指定纹理如何环绕对象的形状。
  • Normals - 法线:顶点或曲面的方向向量。这典型地指向外部,垂直于网格表面,并有助于确定光从对象反弹的方式。
  • Mesh - 网格:包含模型的所有顶点,边,三角形,法线和UV数据。

以下是创建3D网格的基本步骤(采用伪代码):

  • 创建一个名为“myMesh”的新网格。
  • 将数据添加到myMesh的顶点和三角形属性。
  • 创建一个名为“myMeshFilter”的新网格过滤器。
  • myMesh分配给myMeshFiltermesh属性。

2. Setting Up the Project

现在您已经掌握了基础知识,在Unity中打开starter项目。 在Project视图中看下文件夹结构:

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第2张图片
  • 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视图中,您将看到一个不起眼的立方体:

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第3张图片

您将要建立一个自定义编辑器,以将这个可怜的立方体拆开! (然后,您将学习如何将其保持为一体。)

1. Customizing the Editor Script

Project视图中选择Editor文件夹。 这个特殊文件夹中的脚本修改了Unity编辑器的工作方式。 它们不会成为内置游戏的一部分。

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第4张图片

打开MeshInspector.cs并查看源代码。 请注意,该类继承自Unity的基本Editor类-这就是让Unity了解这是自定义编辑器而不是游戏脚本的原因。

第一步是告诉Unity这个特殊的编辑器应该绘制什么样的对象。 在MeshInspector类声明上方的行上添加以下属性:

[CustomEditor(typeof(MeshStudy))]

现在,当任何附加了Mesh Study组件的GameObjectScene视图中可见时,此类将处理其绘制。 但是现在,您不知道这种情况是否正在发生。

OnSceneGUIUnity每次在编辑器中渲染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上,以将组件附加到该对象。

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第5张图片

在控制台中查看并确保您的代码正在运行。 然后继续删除Debug.Log行,以免淹没您的控制台。

2. Cloning a Mesh

Edit模式下使用自定义编辑器处理3D网格时,很容易意外覆盖Unity的默认网格,即内置的Sphere,Cube,Cylinder等。 如果发生这种情况,则需要重新启动Unity

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第6张图片

为避免这种情况,请在Edit模式下克隆网格,然后再对其进行任何更改。

打开MeshStudy.cs。 该脚本继承自MonoBehaviour,因此其Start不会在Edit模式下运行。 幸运的是,这很容易解决!

MeshStudy的类声明上方,添加以下内容:

[ExecuteInEditMode]

当一个类具有此属性时,其Start将在Play modeEdit 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的网格。 很好! 这意味着您已经成功克隆了网格。

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第7张图片

但是请注意,您的项目中没有新的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按钮将其还原。

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第8张图片

4. Understanding Vertices and Triangles With Unity

如您先前所见,网格由边连接的顶点组成三角形。 三角形定义了对象的基本形状。

注意:Unity的Mesh类使用两个数组跟踪顶点和三角形:

  • 它将顶点存储为Vector3的数组。
  • 它将三角形存储为整数数组。 每个整数是verts数组中一个顶点的索引,并且每组三个连续的整数代表一个三角形。
    例如,组triangles[0], triangles[1], triangles[2]代表一个三角形,组triangles[3], triangles[4], triangles[5]代表下一个三角形,依此类推。

因此,在由四个顶点和两个三角形组成的简单四边形网格中,四边形的网格数据为:

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第9张图片

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对象,然后亲自查看结果!

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第10张图片

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上的点之一。

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第11张图片

似乎有些顶点共享相同的位置,因此,当您仅拉一个顶点时,其他顶点会留在后面,并且网格会断开。 您将很快学习如何解决此问题。

7. Looking at the Vertices Array

在外观上,一个立方体网格由八个顶点,六个边和12个三角形组成。 是时候看看Unity是否一致了。

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第12张图片

转到MeshStudy.cs,然后在Start之前查找名为vertices的变量。 您将看到它具有[HideInInspector]属性。

临时注释掉该属性,以便快速浏览一下数组:

//[HideInInspector]
public Vector3[] vertices;

注意:更复杂的3D网格可以具有数千个顶点。 如果Unity试图在Inspector中显示所有这些值,它将冻结,因此通常,您将使用[HideInInspector]隐藏该数组。 你只是在偷看!

保存文件,返回Unity并查看您的cube。 现在,您可以在Mesh Study中看到vertices属性。 单击其旁边的箭头图标以显示Vector3元素的数组。

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第13张图片

您会看到数组大小为24,这意味着肯定有顶点共享相同的位置! 花点时间考虑一下为什么同一位置可能有多个顶点。

最简单的答案是:
一个立方体有六个边,每个边都有四个形成一个平面的顶点。 6×4 = 24个顶点。

如果很难掌握,还有其他方法可以考虑。 但是现在,只知道某些网格物体的顶点共享相同的位置。

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第14张图片

由于已经完成了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。 单击并拖动任何一个顶点; 网格现在应保持其形状不变。

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第15张图片

保存场景。 您已迈出成为网状魔术师的第一步!


Manipulating Meshes

在Unity中编辑网格很有趣,但是如果您可以通过在运行时变形网格来向游戏中添加一些“squish”呢? 接下来,您将以最基本的形式进行尝试-推和拉一些预定义的顶点。

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第16张图片

1. Collecting the Selected Indices

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

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第17张图片

在“层次结构”中选择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按钮,以确保其正确清除了所有值。

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第18张图片

注意:您可以选择使用自定义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列表的当前索引。

Initif语句之前,添加:

currentIndex = 0;

这将在游戏开始时将currentIndexselectedIndices列表的第一个索引)设置为0。

仍然在Init中,在else语句的右括号之前,添加:

StartDisplacement();

StartDisplacement是实际移动顶点的地方。 它仅在isEditModefalse时运行。

现在,此方法不起作用,因此将以下内容添加到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) 如果isAnimatefalse,则不会执行任何操作。
  • 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以预览您的作品。

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第19张图片

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

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第20张图片

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

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第21张图片

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

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第22张图片

4. Saving Your Mesh in Real Time

现在,每按一次Play按钮,您的心就会动静。 如果您想要持久的爱,则需要一种将网格物体写入文件的方法。

一种简单的方法是设置一个以3D对象作为其子对象的占位符预制件,然后通过脚本将其网格物体资源替换为您的心脏(... er,您的Heart mesh)。

在项目视图中,找到Prefabs / CustomHeart。 双击预制件以在Prefab Editing模式下将其打开。

单击箭头图标以在层次结构中展开其内容,然后选择子级。 您将在此处存储生成的网格。

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第23张图片

退出预制编辑模式,然后打开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模式下将其打开。 您会看到一个预制的心形网状品牌已保存在您的预制件中!

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第24张图片
Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第25张图片

Putting It All Together

在上一节中,您学习了如何通过选择单个顶点来修改网格。 虽然这很酷,但是如果您知道如何按程序选择顶点,则可以做更多有趣的事情。

在上一场景中,DisplaceVertices使用高斯衰减公式来确定在效果半径内“拉”每个顶点的量。 但是,您还可以使用其他数学函数来计算“下降”点。 也就是说,拉力pull开始衰减。 每个函数产生不同的形状:

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第26张图片
Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第27张图片

在本部分中,您将学习如何使用计算出的曲线来操纵顶点。

基于速度等于距离除以时间(v =(d / t))的原理,可以通过将向量的距离除以时间因子来确定向量的位置。

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第28张图片

Using the Curve Method

保存当前场景,然后从Scenes文件夹中打开03 Customize Heart Mesh

在层次结构中找到CustomHeart预制实例,然后单击其旁边的箭头图标以扩展其内容。 选择Child对象。

在检查器中查看其属性。 您将看到带有Heart Mesh资源的Mesh Filter。 将Custom Heart附加到Child上。 资产现在应该从HeartMesh更改为clone

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第29张图片

打开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的内容,这将生成不同的曲线。

DisplaceVerticesfor循环内,在右花括号之前,添加以下内容:

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 IndicesRemove Indices来更新顶点列表。 选择None退出编辑模式,然后单击Play查看结果。 试用不同的设置和顶点选择。

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第30张图片

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

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第31张图片

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

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第32张图片

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

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第33张图片
Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第34张图片

就这样! 您可以单击Clear Selected Vertices以重置Selected Indices并尝试使用自己的样式。 不要忘记,有几个因素会影响网格的最终形状:

  • 半径的大小。
  • 顶点在区域内的散布。
  • 选定顶点的图案位置。
  • 您选择的位移方法。

后记

本篇主要讲述了使用Unity运行时网格操作,感兴趣的给个赞或者关注~~~

Unity强化篇(八) —— 使用Unity运行时网格操作(一)_第35张图片

你可能感兴趣的:(Unity强化篇(八) —— 使用Unity运行时网格操作(一))