Unity強(qiáng)化篇(八) —— 使用Unity運(yùn)行時網(wǎng)格操作(一)

版本記錄

版本號 時間
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ā)布游戲至WindowsMac、Wii、iPhoneWebGL(需要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分配給myMeshFiltermesh屬性。

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組件的GameObjectScene視圖中可見時,此類將處理其繪制。 但是現(xiàn)在,您不知道這種情況是否正在發(fā)生。

OnSceneGUIUnity每次在編輯器中渲染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 modeEdit 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)前索引。

Initif語句之前,添加:

currentIndex = 0;

這將在游戲開始時將currentIndexselectedIndices列表的第一個索引)設(shè)置為0。

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

StartDisplacement();

StartDisplacement是實(shí)際移動頂點(diǎn)的地方。 它僅在isEditModefalse時運(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) 如果isAnimatefalse,則不會執(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 ValueDuration設(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)容,這將生成不同的曲線。

DisplaceVerticesfor循環(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 IndicesRemove 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)注~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容