版本記錄
| 版本號 | 時間 |
|---|---|
| V1.0 | 2019.11.24 星期日 |
前言
Unity是由Unity Technologies開發(fā)的一個讓玩家輕松創(chuàng)建諸如三維視頻游戲、建筑可視化、實(shí)時三維動畫等類型互動內(nèi)容的多平臺的綜合型游戲開發(fā)工具,是一個全面整合的專業(yè)游戲引擎。Unity類似于Director,Blender game engine, Virtools 或 Torque Game Builder等利用交互的圖型化開發(fā)環(huán)境為首要方式的軟件。其編輯器運(yùn)行在Windows 和Mac OS X下,可發(fā)布游戲至Windows、Mac、Wii、iPhone、WebGL(需要HTML5)、Windows phone 8和Android平臺。也可以利用Unity web player插件發(fā)布網(wǎng)頁游戲,支持Mac和Windows的網(wǎng)頁瀏覽。它的網(wǎng)頁播放器也被Mac 所支持。網(wǎng)頁游戲 坦克英雄和手機(jī)游戲王者榮耀都是基于它的開發(fā)。感興趣的看下面幾篇文章。
1. Unity強(qiáng)化篇(一) —— 如何使用Vuforia制作AR游戲(一)
2. Unity強(qiáng)化篇(二) —— 適用于Unity的HTC Vive教程(一)
3. Unity強(qiáng)化篇(三) —— 適用于Unity的HTC Vive教程(二)
4. Unity強(qiáng)化篇(四) —— Unity 和 Ethereum(一)
5. Unity強(qiáng)化篇(五) —— Unity 和 Ethereum(二)
6. Unity強(qiáng)化篇(六) —— 使用Unity和Photon進(jìn)行多人游戲簡介(一)
7. Unity強(qiáng)化篇(七) —— Unity Sprite Shapes簡介(一)
開始
主要內(nèi)容:將Unity用作游戲開發(fā)平臺的好處之一是其強(qiáng)大的3D引擎。 在本教程中,您將介紹3D對象和網(wǎng)格處理的世界。
下面看下寫作環(huán)境
C# 7.3, Unity 2019.1, Unity
歡迎來到3D對象和網(wǎng)格處理的世界! 將Unity用作游戲開發(fā)平臺的好處之一是其強(qiáng)大的3D引擎。 3D引擎以及Unity使用自定義編輯器的能力,使3D游戲和應(yīng)用程序的開發(fā)變得非常容易。
隨著虛擬現(xiàn)實(shí)和增強(qiáng)現(xiàn)實(shí)(VR / AR)技術(shù)的發(fā)展,大多數(shù)開發(fā)人員會無意間發(fā)現(xiàn)自己在3D概念的堅(jiān)韌不拔中掙扎。 因此,以本教程為起點(diǎn)。 不用擔(dān)心,這里不會有復(fù)雜的3D數(shù)學(xué)運(yùn)算-只有很多的心,圖紙,箭頭和無窮的樂趣!
1. Understanding Meshes
現(xiàn)在開始介紹3D渲染的基本詞匯。 3D對象的形狀由其網(wǎng)格定義。 網(wǎng)格就像點(diǎn)或頂點(diǎn)的網(wǎng)。 連接這些頂點(diǎn)的不可見線形成三角形,三角形定義了對象的基本形狀。

但是除了形狀之外,引擎還需要知道如何繪制對象的表面。因此,網(wǎng)格的數(shù)據(jù)還包括其法線,法線是確定特定三角形朝向的方式以及光線如何從其反射的向量。最后,UV Map將材質(zhì)映射到對象,指定紋理如何環(huán)繞形狀。
在Unity中,有兩個主要的渲染組件:“網(wǎng)格過濾器”(Mesh Filter)(用于存儲模型的網(wǎng)格數(shù)據(jù))和“網(wǎng)格渲染器”(Mesh Renderer),其將網(wǎng)格數(shù)據(jù)與材質(zhì)組合以在場景中渲染對象。
知道了嗎?以下是備忘單,以方便參考:
-
Vertices - 頂點(diǎn):頂點(diǎn)是3D空間中的一個點(diǎn)。通??s寫為
“vert”。 - Lines/Edges - 線/邊:將頂點(diǎn)相互連接的不可見線。
- Triangles - 三角形:當(dāng)邊連接三個頂點(diǎn)時形成。
- UV Map - UV貼圖:將材質(zhì)映射到對象,指定紋理如何環(huán)繞對象的形狀。
- Normals - 法線:頂點(diǎn)或曲面的方向向量。這典型地指向外部,垂直于網(wǎng)格表面,并有助于確定光從對象反彈的方式。
- Mesh - 網(wǎng)格:包含模型的所有頂點(diǎn),邊,三角形,法線和UV數(shù)據(jù)。
以下是創(chuàng)建3D網(wǎng)格的基本步驟(采用偽代碼):
- 創(chuàng)建一個名為
“myMesh”的新網(wǎng)格。 - 將數(shù)據(jù)添加到
myMesh的頂點(diǎn)和三角形屬性。 - 創(chuàng)建一個名為
“myMeshFilter”的新網(wǎng)格過濾器。 - 將
myMesh分配給myMeshFilter的mesh屬性。
2. Setting Up the Project
現(xiàn)在您已經(jīng)掌握了基礎(chǔ)知識,在Unity中打開starter項(xiàng)目。 在Project視圖中看下文件夾結(jié)構(gòu):

-
Prefabs - 預(yù)制件:其中包含
CustomHeart預(yù)制件,可用于在運(yùn)行時保存3D網(wǎng)格。 - Scenes - 場景:這包含您將在本教程的不同部分使用的三個場景。
- Editor - 編輯器:在開發(fā)過程中,該文件夾中的腳本為您提供了編輯器中的特殊功能。
-
Scripts - 腳本:包含運(yùn)行時腳本或組件。 將這些組件附加到
GameObject時,單擊Play即可執(zhí)行。 - Materials - 材質(zhì):此文件夾包含您要使用的網(wǎng)格物體的材質(zhì)。
在下一部分中,您將創(chuàng)建一個自定義編輯器以可視化3D網(wǎng)格的各個部分。
Poking and Prodding Meshes With a Custom Editor
在RW/Scenes中打開01 Mesh Study Demo。 在Scene視圖中,您將看到一個不起眼的立方體:

您將要建立一個自定義編輯器,以將這個可憐的立方體拆開! (然后,您將學(xué)習(xí)如何將其保持為一體。)
1. Customizing the Editor Script
在Project視圖中選擇Editor文件夾。 這個特殊文件夾中的腳本修改了Unity編輯器的工作方式。 它們不會成為內(nèi)置游戲的一部分。

打開MeshInspector.cs并查看源代碼。 請注意,該類繼承自Unity的基本Editor類-這就是讓Unity了解這是自定義編輯器而不是游戲腳本的原因。
第一步是告訴Unity這個特殊的編輯器應(yīng)該繪制什么樣的對象。 在MeshInspector類聲明上方的行上添加以下屬性:
[CustomEditor(typeof(MeshStudy))]
現(xiàn)在,當(dāng)任何附加了Mesh Study組件的GameObject在Scene視圖中可見時,此類將處理其繪制。 但是現(xiàn)在,您不知道這種情況是否正在發(fā)生。
OnSceneGUI是Unity每次在編輯器中渲染Scene視圖時都會調(diào)用的事件方法。 您有機(jī)會修改Unity在場景中繪制對象的方式。 在OnSceneGUI的開頭添加以下內(nèi)容:
mesh = target as MeshStudy;
Debug.Log("Custom editor is running");
基本的Editor類提供了對您在target變量中具有類型Object的自定義對象的引用。 對于普通的vanilla對象,您無法做很多有用的事情,因此此代碼將target強(qiáng)制轉(zhuǎn)換為MeshStudy類型。 記錄消息后,您可以在控制臺中看到自定義編輯器確實(shí)正在運(yùn)行。
保存文件并返回到Unity。 轉(zhuǎn)到RW / Scripts文件夾,然后將MeshStudy.cs拖到層次結(jié)構(gòu)中的Cube GameObject上,以將組件附加到該對象。

在控制臺中查看并確保您的代碼正在運(yùn)行。 然后繼續(xù)刪除Debug.Log行,以免淹沒您的控制臺。
2. Cloning a Mesh
在Edit模式下使用自定義編輯器處理3D網(wǎng)格時,很容易意外覆蓋Unity的默認(rèn)網(wǎng)格,即內(nèi)置的Sphere,Cube,Cylinder等。 如果發(fā)生這種情況,則需要重新啟動Unity。

為避免這種情況,請?jiān)?code>Edit模式下克隆網(wǎng)格,然后再對其進(jìn)行任何更改。
打開MeshStudy.cs。 該腳本繼承自MonoBehaviour,因此其Start不會在Edit模式下運(yùn)行。 幸運(yùn)的是,這很容易解決!
在MeshStudy的類聲明上方,添加以下內(nèi)容:
[ExecuteInEditMode]
當(dāng)一個類具有此屬性時,其Start將在Play mode和Edit mode下觸發(fā)。 添加完之后,您可以在更改任何內(nèi)容之前實(shí)例化和克隆網(wǎng)格對象。
將以下代碼添加到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中分配的任何網(wǎng)格。 - 2) 創(chuàng)建一個名為
clonedMesh的新網(wǎng)格實(shí)例,并通過復(fù)制第一個網(wǎng)格設(shè)置其屬性。 - 3) 將復(fù)制的網(wǎng)格分配回網(wǎng)格過濾器。
- 4) 更新局部變量,稍后將需要。
- 5) 將
isCloned設(shè)置為true; 您稍后會參考。
保存文件并返回到Unity。 控制臺應(yīng)顯示消息“ Init&Cloned”。
在層次結(jié)構(gòu)中選擇Cube,然后查看檢查器。 網(wǎng)格過濾器將顯示一個名為clone的網(wǎng)格。 很好! 這意味著您已經(jīng)成功克隆了網(wǎng)格。

但是請注意,您的項(xiàng)目中沒有新的Mesh資源-克隆的Mesh現(xiàn)在僅存在于Unity的內(nèi)存中,如果關(guān)閉場景,它將消失。 稍后,您將學(xué)習(xí)如何保存網(wǎng)格。
3. Resetting a Mesh
目前,您想給自己一個簡單的方法來重置網(wǎng)格,以便您可以放心地玩。 返回到MeshInspector.cs。
OnInspectorGUI使您可以使用額外的GUI元素和邏輯為對象自定義Inspector。 在OnInspectorGUI中,找到注釋//draw reset button并將其替換為以下內(nèi)容:
if (GUILayout.Button("Reset")) //1
{
mesh.Reset(); //2
}
- 1) 此代碼在檢查器中繪制一個“重置”
(Reset)按鈕。 按下時,繪制函數(shù)會返回true。 - 2) 按下后,該按鈕將調(diào)用
MeshStudy.cs中的Reset。
保存文件并返回到MeshStudy.cs。 添加以下內(nèi)容以到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) 如果對象的網(wǎng)格過濾器中沒有任何數(shù)據(jù),請檢查原始網(wǎng)格和克隆網(wǎng)格是否都存在。
- 2) 將
clonedMesh的所有屬性重置為原始網(wǎng)格的屬性。 - 3) 將
clonedMesh分配回Mesh Filter組件。 - 4) 更新局部變量。
保存文件并返回到Unity。
在檢查器中,單擊Test Edit按鈕使立方體的網(wǎng)格混亂,然后按Reset按鈕將其還原。

4. Understanding Vertices and Triangles With Unity
如您先前所見,網(wǎng)格由邊連接的頂點(diǎn)組成三角形。 三角形定義了對象的基本形狀。
注意:Unity的
Mesh類使用兩個數(shù)組跟蹤頂點(diǎn)和三角形:
- 它將頂點(diǎn)存儲為
Vector3的數(shù)組。- 它將三角形存儲為整數(shù)數(shù)組。 每個整數(shù)是
verts數(shù)組中一個頂點(diǎn)的索引,并且每組三個連續(xù)的整數(shù)代表一個三角形。
例如,組triangles[0], triangles[1], triangles[2]代表一個三角形,組triangles[3], triangles[4], triangles[5]代表下一個三角形,依此類推。因此,在由四個頂點(diǎn)和兩個三角形組成的簡單四邊形網(wǎng)格中,四邊形的網(wǎng)格數(shù)據(jù)為:
5. Visualizing Vertices
如果您可以在帶有handles的網(wǎng)格上的頂點(diǎn)上繪制和移動,將更容易看到它是如何工作的。 handles是用于在Scene視圖中處理對象的工具,例如“旋轉(zhuǎn)”工具的可拖動球體。 現(xiàn)在,您將要編寫自己的handle!
在MeshInspector.cs中,查找EditMesh并添加以下內(nèi)容:
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) 獲取網(wǎng)格的
transform,您將需要知道在世界空間中繪制頂點(diǎn)的位置。 - 2) 獲取當(dāng)前的軸旋轉(zhuǎn)模式,以與場景中其他所有對象相同的方式繪制手柄。
- 3) 遍歷網(wǎng)格的頂點(diǎn)并使用
ShowPoint繪制點(diǎn)。
在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) 這條線將頂點(diǎn)的局部位置轉(zhuǎn)換為世界空間。
- 2) 使用
Handles實(shí)用程序類繪制點(diǎn)。
Handles.FreeMoveHandle制作一個不受限制的運(yùn)動handle,您將在下一部分中使用它來拖動點(diǎn)。
保存文件并返回到Unity。
檢查Cube's MeshInspector,并確保已選中Move Vertex Point。
現(xiàn)在,您應(yīng)該在屏幕上看到標(biāo)有藍(lán)點(diǎn)的網(wǎng)格的頂點(diǎn)。 嘗試將腳本附加到其他3D對象,然后親自查看結(jié)果!

6. Moving a Single Vertex
您將從最基本的網(wǎng)格處理類型開始:移動單個頂點(diǎn)。
打開MeshInspector.cs。 在ShowPoint內(nèi),用以下內(nèi)容替換// drag注釋:
if (GUI.changed) //3
{
mesh.DoAction(index, handleTransform.InverseTransformPoint(point)); //4
}
- 3)
GUI.changed監(jiān)視對點(diǎn)所做的任何更改,這與Handles.FreeMoveHandle配合使用可很好地檢測拖動動作。 - 4) 拖動頂點(diǎn)時,以頂點(diǎn)索引和頂點(diǎn)位置為參數(shù)調(diào)用
mesh.DoAction。 這條線還使用InverseTransformPoint將頂點(diǎn)的位置轉(zhuǎn)換回局部空間。
保存MeshInspector.cs并轉(zhuǎn)到MeshStudy.cs。 在DoAction中添加以下內(nèi)容:
PullOneVertex(index, localPos);
然后將以下內(nèi)容添加到PullOneVertex:
vertices[index] = newPos; //1
clonedMesh.vertices = vertices; //2
clonedMesh.RecalculateNormals(); //3
- 1) 更新目標(biāo)頂點(diǎn)的位置。
- 2) 將更新后的頂點(diǎn)數(shù)組分配回克隆的網(wǎng)格。
- 3) 告訴
Unity重新繪制網(wǎng)格以反映更改。
保存腳本并返回到Unity。 嘗試拖動cube上的點(diǎn)之一。

似乎有些頂點(diǎn)共享相同的位置,因此,當(dāng)您僅拉一個頂點(diǎn)時,其他頂點(diǎn)會留在后面,并且網(wǎng)格會斷開。 您將很快學(xué)習(xí)如何解決此問題。
7. Looking at the Vertices Array
在外觀上,一個立方體網(wǎng)格由八個頂點(diǎn),六個邊和12個三角形組成。 是時候看看Unity是否一致了。

轉(zhuǎn)到MeshStudy.cs,然后在Start之前查找名為vertices的變量。 您將看到它具有[HideInInspector]屬性。
臨時注釋掉該屬性,以便快速瀏覽一下數(shù)組:
//[HideInInspector]
public Vector3[] vertices;
注意:更復(fù)雜的3D網(wǎng)格可以具有數(shù)千個頂點(diǎn)。 如果Unity試圖在
Inspector中顯示所有這些值,它將凍結(jié),因此通常,您將使用[HideInInspector]隱藏該數(shù)組。 你只是在偷看!
保存文件,返回Unity并查看您的cube。 現(xiàn)在,您可以在Mesh Study中看到vertices屬性。 單擊其旁邊的箭頭圖標(biāo)以顯示Vector3元素的數(shù)組。

您會看到數(shù)組大小為24,這意味著肯定有頂點(diǎn)共享相同的位置! 花點(diǎn)時間考慮一下為什么同一位置可能有多個頂點(diǎn)。
最簡單的答案是:
一個立方體有六個邊,每個邊都有四個形成一個平面的頂點(diǎn)。6×4 = 24個頂點(diǎn)。如果很難掌握,還有其他方法可以考慮。 但是現(xiàn)在,只知道某些網(wǎng)格物體的頂點(diǎn)共享相同的位置。
由于已經(jīng)完成了verts數(shù)組的瀏覽,因此請繼續(xù)注釋[HideInInspector]。
8. Finding All Similar Vertices
您可以看到,操作網(wǎng)格不僅需要移動單個頂點(diǎn),還需要更多的操作—您必須移動空間中特定點(diǎn)的所有頂點(diǎn)才能將網(wǎng)格保持在一起。 因此,您現(xiàn)在就可以動彈了,呃,網(wǎng)狀的。
在MeshStudy.cs中,將DoAction內(nèi)的所有代碼替換為:
PullSimilarVertices(index, localPos);
轉(zhuǎn)到PullSimilarVertices并添加以下內(nèi)容:
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) 從頂點(diǎn)數(shù)組獲取目標(biāo)
vertices位置。 - 2) 查找與目標(biāo)頂點(diǎn)共享相同位置的所有頂點(diǎn),并將其索引放入列表中。
- 3) 遍歷該列表并更新所有相關(guān)頂點(diǎn)的位置。
- 4) 將更新后的
vertices分配回clonedMesh.vertices,然后重新繪制網(wǎng)格。
保存文件并返回到Unity。 單擊并拖動任何一個頂點(diǎn); 網(wǎng)格現(xiàn)在應(yīng)保持其形狀不變。

保存場景。 您已邁出成為網(wǎng)狀魔術(shù)師的第一步!
Manipulating Meshes
在Unity中編輯網(wǎng)格很有趣,但是如果您可以通過在運(yùn)行時變形網(wǎng)格來向游戲中添加一些“squish”呢? 接下來,您將以最基本的形式進(jìn)行嘗試-推和拉一些預(yù)定義的頂點(diǎn)。

1. Collecting the Selected Indices
首先,創(chuàng)建一個自定義編輯器,使您可以選擇要實(shí)時移動的頂點(diǎn)。 在RW/Scenes中打開02 Create Heart Mesh場景。 您將在Scene視圖中看到一個紅色的球體。

在“層次結(jié)構(gòu)”中選擇Sphere,然后查看Heart Mesh組件。 這是將存儲您選擇的頂點(diǎn)的腳本。
但是現(xiàn)在,場景中沒有顯示任何變體。 因此,接下來,您將解決此問題!
打開RW / Editor / HeartMeshInspector.cs。 在ShowHandle中的if語句內(nèi),添加以下代碼:
Handles.color = Color.blue;
if (Handles.Button(point, handleRotation, mesh.pickSize, mesh.pickSize,
Handles.DotHandleCap)) //1
{
mesh.selectedIndices.Add(index); //2
}
- 1) 這使
Unity可以將網(wǎng)格的頂點(diǎn)繪制為按鈕,因此可以單擊它們。 - 2) 單擊按鈕時,它將選定的索引添加到
mesh.selectedIndices列表。
在現(xiàn)有的if語句之后,在OnInspectorGUI的末尾添加以下代碼:
if (GUILayout.Button("Clear Selected Vertices"))
{
mesh.ClearAllData();
}
這會在檢查器中添加一個自定義的Reset按鈕。 接下來,您將編寫代碼以清除選擇。
保存文件并打開RW / Scripts / HeartMesh.cs。 在ClearAllData中,添加以下內(nèi)容:
selectedIndices = new List<int>();
targetIndex = 0;
targetVertex = Vector3.zero;
這將清除selectedIndices列表中的值,并將targetIndex設(shè)置為零。 它還會重置targetVertex位置。
保存文件并返回到Unity。 選擇Sphere并查看其HeartMesh組件。 確保已選中Is Edit Mode,以便可以在Scene視圖中查看網(wǎng)格的頂點(diǎn)。 然后單擊Selected Indices旁邊的箭頭圖標(biāo)以顯示該數(shù)組。
單擊一些藍(lán)點(diǎn),然后觀看新條目出現(xiàn)在Selected Indices中。 試用您的Clear Selected Vertices按鈕,以確保其正確清除了所有值。

注意:您可以選擇使用自定義
Inspector中的Show Transform Handle來顯示/隱藏變換手柄,因?yàn)樗梢苑恋K選擇頂點(diǎn)。 只要記住當(dāng)您發(fā)現(xiàn)其他場景中缺少Transform handle時不要驚慌! 退出之前,請務(wù)必將其重新打開。
2. Deforming the Sphere Into a Heart Shape
實(shí)時更新網(wǎng)格頂點(diǎn)需要三個步驟:
- 1) 將當(dāng)前的網(wǎng)格頂點(diǎn)(在動畫之前)復(fù)制到
ModifyedVertices。 - 2) 計算并更新
modifiedVertices上的值。 - 3) 每次更改步驟時,將
ModifyedVertices復(fù)制到當(dāng)前網(wǎng)格,并讓Unity重新繪制網(wǎng)格。
轉(zhuǎn)到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;
移動頂點(diǎn)應(yīng)該對其周圍的頂點(diǎn)產(chǎn)生一些影響,以保持平滑的形狀。 這些變量控制效果。
- 1) 受目標(biāo)頂點(diǎn)影響的區(qū)域半徑。
- 2)
pull的長度。 - 3) 動畫將運(yùn)行多長時間。
- 4)
selectedIndices列表的當(dāng)前索引。
在Init的if語句之前,添加:
currentIndex = 0;
這將在游戲開始時將currentIndex(selectedIndices列表的第一個索引)設(shè)置為0。
仍然在Init中,在else語句的右括號之前,添加:
StartDisplacement();
StartDisplacement是實(shí)際移動頂點(diǎn)的地方。 它僅在isEditMode為false時運(yùn)行。
現(xiàn)在,此方法不起作用,因此將以下內(nèi)容添加到StartDisplacement中:
targetVertex = originalVertices[selectedIndices[currentIndex]]; //1
startTime = Time.time; //2
isAnimate = true;
- 1) 從
originalVertices數(shù)組中選擇targetVertex以啟動動畫。 請記住,每個數(shù)組項(xiàng)都是一個整數(shù)值列表。 - 2) 將開始時間設(shè)置為當(dāng)前時間,并將
isAnimate更改為true。
在StartDisplacement之后,使用以下代碼創(chuàng)建一個名為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;
}
}
}
代碼正在執(zhí)行以下操作:
- 1)
FixedUpdate方法以固定的間隔運(yùn)行,這意味著它與幀速率無關(guān)。 在此處了解更多信息。 - 2) 如果
isAnimate為false,則不會執(zhí)行任何操作。 - 3) 跟蹤動畫運(yùn)行了多長時間。
- 4) 如果動畫沒有運(yùn)行太長時間,它將通過獲取
targetVertex的世界空間坐標(biāo)并調(diào)用DisplaceVertices來繼續(xù)動畫。 - 5) 否則,時間到了! 向
currentIndex添加一個以開始處理下一個選定頂點(diǎn)的動畫。 - 6) 檢查是否所有選定的頂點(diǎn)都已處理。 如果不是,請使用最新的頂點(diǎn)調(diào)用
StartDisplacement。 - 7) 否則,您已到達(dá)所選頂點(diǎn)列表的末尾。 該行將復(fù)制當(dāng)前網(wǎng)格,并將
isAnimate設(shè)置為false以停止動畫。
3. Making the Vertices Move Smoothly
在DisplaceVertices中,添加以下內(nèi)容:
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();
此代碼循環(huán)遍歷網(wǎng)格中的每個頂點(diǎn),并替換與您在編輯器中選擇的頂點(diǎn)接近的頂點(diǎn)。它通過一些數(shù)學(xué)技巧來創(chuàng)建平滑的效果,例如將拇指推入粘土中。稍后,您將了解有關(guān)此內(nèi)容的更多信息。
下面是這段代碼的詳細(xì)信息:
- 1) 獲取半徑的平方。
- 2) 遍歷網(wǎng)格中的每個頂點(diǎn)。
- 3) 查找當(dāng)前頂點(diǎn)和目標(biāo)頂點(diǎn)之間的距離并將其平方。
- 4) 如果此頂點(diǎn)不在效果范圍內(nèi),請盡早退出循環(huán)并繼續(xù)到下一個頂點(diǎn)。
- 5) 否則,根據(jù)距離計算衰減
falloff值。高斯函數(shù)可創(chuàng)建平滑的鐘形曲線。 - 6) 根據(jù)距離計算要移動多遠(yuǎn),然后根據(jù)結(jié)果設(shè)置旋轉(zhuǎn)(位移方向)。這使頂點(diǎn)“向外”移動,即直接遠(yuǎn)離
targetVertex,使其看起來像從中心噴出。 - 7) 退出循環(huán)后,將更新后的
ModifyedVertices存儲在原始網(wǎng)格中,并讓Unity重新計算法線。
保存文件并返回到Unity。選擇Sphere,轉(zhuǎn)到HeartMesh組件,然后嘗試將一些頂點(diǎn)添加到Selected Indices屬性中。關(guān)閉Is Edit mode模式,然后按Play以預(yù)覽您的作品。

嘗試使用Radius Of Effect, Pull Value和Duration設(shè)置以查看不同的結(jié)果。 準(zhǔn)備就緒后,請按照以下屏幕截圖更新設(shè)置。

點(diǎn)擊Play,您的球體氣球變成了心臟形狀嗎?

恭喜你! 在下一部分中,您將學(xué)習(xí)如何保存網(wǎng)格以備將來使用。

4. Saving Your Mesh in Real Time
現(xiàn)在,每按一次Play按鈕,您的心就會動靜。 如果您想要持久的愛,則需要一種將網(wǎng)格物體寫入文件的方法。
一種簡單的方法是設(shè)置一個以3D對象作為其子對象的占位符預(yù)制件,然后通過腳本將其網(wǎng)格物體資源替換為您的心臟(... er,您的Heart mesh)。
在項(xiàng)目視圖中,找到Prefabs / CustomHeart。 雙擊預(yù)制件以在Prefab Editing模式下將其打開。
單擊箭頭圖標(biāo)以在層次結(jié)構(gòu)中展開其內(nèi)容,然后選擇子級。 您將在此處存儲生成的網(wǎng)格。

退出預(yù)制編輯模式,然后打開HeartMeshInspector.cs。 在OnInspectorGUI的結(jié)尾,大括號之前,添加以下內(nèi)容:
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預(yù)制對象資產(chǎn)路徑,您需要能夠?qū)⒃撀窂綄懭胛募?/li> - 2) 從
CustomHeart預(yù)制中創(chuàng)建兩個對象,一個作為GameObject,另一個作為參考。 - 3) 從
CustomHeart創(chuàng)建網(wǎng)格資產(chǎn)prefabMesh的實(shí)例。 如果找到資產(chǎn),則清除其數(shù)據(jù); 否則,它將創(chuàng)建一個新的空網(wǎng)格。 - 4) 用新的網(wǎng)格數(shù)據(jù)更新
prefabMesh并將其與CustomHeart資產(chǎn)關(guān)聯(lián)。 - 5) 使用
prefabMesh更新GameObject的網(wǎng)格資源。 - 6) 在給定
gameObj的給定路徑上創(chuàng)建一個Prefab Asset,包括場景中的所有子代。 這將替代CustomHeart預(yù)制件中的任何東西。 - 7) 立即銷毀
gameObj。
保存文件,然后轉(zhuǎn)到HeartMesh.cs。 將SaveMesh的主體替換為以下內(nèi)容:
meshToSave.name = "HeartMesh";
meshToSave.vertices = originalMesh.vertices;
meshToSave.triangles = originalMesh.triangles;
meshToSave.normals = originalMesh.normals;
return meshToSave;
這將返回基于心形網(wǎng)格的網(wǎng)格資產(chǎn)。
保存文件并返回到Unity。 按Play。 動畫結(jié)束時,Save Mesh按鈕將出現(xiàn)在檢查器中。 單擊按鈕保存新的網(wǎng)格,然后停止播放器。
在“項(xiàng)目”視圖中再次找到Prefabs / CustomHeart,然后Prefab Editing模式下將其打開。 您會看到一個預(yù)制的心形網(wǎng)狀品牌已保存在您的預(yù)制件中!


Putting It All Together
在上一節(jié)中,您學(xué)習(xí)了如何通過選擇單個頂點(diǎn)來修改網(wǎng)格。 雖然這很酷,但是如果您知道如何按程序選擇頂點(diǎn),則可以做更多有趣的事情。
在上一場景中,DisplaceVertices使用高斯衰減公式來確定在效果半徑內(nèi)“拉”每個頂點(diǎn)的量。 但是,您還可以使用其他數(shù)學(xué)函數(shù)來計算“下降”點(diǎn)。 也就是說,拉力pull開始衰減。 每個函數(shù)產(chǎn)生不同的形狀:


在本部分中,您將學(xué)習(xí)如何使用計算出的曲線來操縱頂點(diǎn)。
基于速度等于距離除以時間(v =(d / t))的原理,可以通過將向量的距離除以時間因子來確定向量的位置。

Using the Curve Method
保存當(dāng)前場景,然后從Scenes文件夾中打開03 Customize Heart Mesh。
在層次結(jié)構(gòu)中找到CustomHeart預(yù)制實(shí)例,然后單擊其旁邊的箭頭圖標(biāo)以擴(kuò)展其內(nèi)容。 選擇Child對象。
在檢查器中查看其屬性。 您將看到帶有Heart Mesh資源的Mesh Filter。 將Custom Heart附加到Child上。 資產(chǎn)現(xiàn)在應(yīng)該從HeartMesh更改為clone。

打開CustomHeart.cs并在Start上方添加以下內(nèi)容:
public enum CurveType
{
Curve1, Curve2
}
public CurveType curveType;
Curve curve;
這將創(chuàng)建一個名為CurveType的公共枚舉,并使其在Inspector中可用。
轉(zhuǎn)到CurveType1并添加以下內(nèi)容:
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) 基本曲線由三個點(diǎn)組成。 該代碼設(shè)置并繪制第一條曲線的點(diǎn)。
- 2) 使用
Curve生成第一條曲線并將其值分配給Curve。 您可以將最后一個參數(shù)設(shè)置為true,以繪制曲線作為預(yù)覽。
現(xiàn)在轉(zhuǎn)到CurveType2并添加以下內(nèi)容:
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) 設(shè)置并繪制第二條曲線的點(diǎn)。
- 2) 使用
Curve方法生成第二條曲線,并將其值分配給curve。
在StartDisplacement中,在右括號之前,添加以下內(nèi)容:
if (curveType == CurveType.Curve1)
{
CurveType1();
}
else if (curveType == CurveType.Curve2)
{
CurveType2();
}
根據(jù)在Custom Heart組件中選擇為Curve Type的內(nèi)容,這將生成不同的曲線。
在DisplaceVertices的for循環(huán)內(nèi),在右花括號之前,添加以下內(nèi)容:
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) 創(chuàng)建一個新的
Vector3,稱為translate,以存儲當(dāng)前頂點(diǎn)的新位置并相應(yīng)地應(yīng)用其Transform。
保存文件并返回到Unity。 在子GameObject上檢查Custom Heart中的屬性。
現(xiàn)在,在Edit Type下拉菜單中,可以選擇Add Indices或Remove Indices來更新頂點(diǎn)列表。 選擇None退出編輯模式,然后單擊Play查看結(jié)果。 試用不同的設(shè)置和頂點(diǎn)選擇。

要查看不同曲線類型的示例,請輸入以下值:

將 Curve Type 設(shè)置為Curve1 ,檢查Edit Type是否設(shè)置為None,然后按Play。

您應(yīng)該看到網(wǎng)格如何呈扇形展開。 將模型移至其側(cè)視圖,以便可以看到該曲線產(chǎn)生的形狀。 Exit Play,然后使用Curve 2再次嘗試比較兩種曲線類型的結(jié)果:


就這樣! 您可以單擊Clear Selected Vertices以重置Selected Indices并嘗試使用自己的樣式。 不要忘記,有幾個因素會影響網(wǎng)格的最終形狀:
- 半徑的大小。
- 頂點(diǎn)在區(qū)域內(nèi)的散布。
- 選定頂點(diǎn)的圖案位置。
- 您選擇的位移方法。
后記
本篇主要講述了使用Unity運(yùn)行時網(wǎng)格操作,感興趣的給個贊或者關(guān)注~~~


