版本記錄
版本號(hào) | 時(shí)間 |
---|---|
V1.0 | 2019.11.24 星期日 |
前言
Unity是由Unity Technologies開(kāi)發(fā)的一個(gè)讓玩家輕松創(chuàng)建諸如三維視頻游戲萨蚕、建筑可視化乍钻、實(shí)時(shí)三維動(dòng)畫(huà)等類(lèi)型互動(dòng)內(nèi)容的多平臺(tái)的綜合型游戲開(kāi)發(fā)工具完丽,是一個(gè)全面整合的專(zhuān)業(yè)游戲引擎姥敛。Unity類(lèi)似于Director,Blender game engine, Virtools 或 Torque Game Builder等利用交互的圖型化開(kāi)發(fā)環(huán)境為首要方式的軟件砂沛。其編輯器運(yùn)行在Windows 和Mac OS X下,可發(fā)布游戲至Windows哀澈、Mac牌借、Wii、iPhone割按、WebGL(需要HTML5)膨报、Windows phone 8和Android平臺(tái)。也可以利用Unity web player插件發(fā)布網(wǎng)頁(yè)游戲适荣,支持Mac和Windows的網(wǎng)頁(yè)瀏覽现柠。它的網(wǎng)頁(yè)播放器也被Mac 所支持。網(wǎng)頁(yè)游戲 坦克英雄和手機(jī)游戲王者榮耀都是基于它的開(kāi)發(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)行多人游戲簡(jiǎn)介(一)
7. Unity強(qiáng)化篇(七) —— Unity Sprite Shapes簡(jiǎn)介(一)
開(kāi)始
主要內(nèi)容:將Unity用作游戲開(kāi)發(fā)平臺(tái)的好處之一是其強(qiáng)大的3D引擎。 在本教程中丈氓,您將介紹3D對(duì)象和網(wǎng)格處理的世界周循。
下面看下寫(xiě)作環(huán)境
C# 7.3, Unity 2019.1, Unity
歡迎來(lái)到3D對(duì)象和網(wǎng)格處理的世界! 將Unity用作游戲開(kāi)發(fā)平臺(tái)的好處之一是其強(qiáng)大的3D引擎万俗。 3D引擎以及Unity使用自定義編輯器的能力湾笛,使3D游戲和應(yīng)用程序的開(kāi)發(fā)變得非常容易。
隨著虛擬現(xiàn)實(shí)和增強(qiáng)現(xiàn)實(shí)(VR / AR)
技術(shù)的發(fā)展闰歪,大多數(shù)開(kāi)發(fā)人員會(huì)無(wú)意間發(fā)現(xiàn)自己在3D概念的堅(jiān)韌不拔中掙扎嚎研。 因此,以本教程為起點(diǎn)库倘。 不用擔(dān)心嘉赎,這里不會(huì)有復(fù)雜的3D數(shù)學(xué)運(yùn)算-只有很多的心置媳,圖紙,箭頭和無(wú)窮的樂(lè)趣公条!
1. Understanding Meshes
現(xiàn)在開(kāi)始介紹3D渲染的基本詞匯拇囊。 3D對(duì)象的形狀由其網(wǎng)格定義。 網(wǎng)格就像點(diǎn)或頂點(diǎn)的網(wǎng)靶橱。 連接這些頂點(diǎn)的不可見(jiàn)線形成三角形寥袭,三角形定義了對(duì)象的基本形狀。
但是除了形狀之外关霸,引擎還需要知道如何繪制對(duì)象的表面传黄。因此,網(wǎng)格的數(shù)據(jù)還包括其法線队寇,法線是確定特定三角形朝向的方式以及光線如何從其反射的向量膘掰。最后,UV Map
將材質(zhì)映射到對(duì)象佳遣,指定紋理如何環(huán)繞形狀识埋。
在Unity中,有兩個(gè)主要的渲染組件:“網(wǎng)格過(guò)濾器”(Mesh Filter)
(用于存儲(chǔ)模型的網(wǎng)格數(shù)據(jù))和“網(wǎng)格渲染器”(Mesh Renderer)
零渐,其將網(wǎng)格數(shù)據(jù)與材質(zhì)組合以在場(chǎng)景中渲染對(duì)象窒舟。
知道了嗎?以下是備忘單诵盼,以方便參考:
-
Vertices - 頂點(diǎn):頂點(diǎn)是3D空間中的一個(gè)點(diǎn)惠豺。通常縮寫(xiě)為
“vert”
风宁。 - Lines/Edges - 線/邊:將頂點(diǎn)相互連接的不可見(jiàn)線洁墙。
- Triangles - 三角形:當(dāng)邊連接三個(gè)頂點(diǎn)時(shí)形成。
- UV Map - UV貼圖:將材質(zhì)映射到對(duì)象戒财,指定紋理如何環(huán)繞對(duì)象的形狀扫俺。
- Normals - 法線:頂點(diǎn)或曲面的方向向量。這典型地指向外部固翰,垂直于網(wǎng)格表面狼纬,并有助于確定光從對(duì)象反彈的方式。
- Mesh - 網(wǎng)格:包含模型的所有頂點(diǎn)骂际,邊疗琉,三角形,法線和UV數(shù)據(jù)歉铝。
以下是創(chuàng)建3D網(wǎng)格的基本步驟(采用偽代碼):
- 創(chuàng)建一個(gè)名為
“myMesh”
的新網(wǎng)格盈简。 - 將數(shù)據(jù)添加到
myMesh
的頂點(diǎn)和三角形屬性。 - 創(chuàng)建一個(gè)名為
“myMeshFilter”
的新網(wǎng)格過(guò)濾器。 - 將
myMesh
分配給myMeshFilter
的mesh
屬性柠贤。
2. Setting Up the Project
現(xiàn)在您已經(jīng)掌握了基礎(chǔ)知識(shí)香浩,在Unity中打開(kāi)starter項(xiàng)目。 在Project
視圖中看下文件夾結(jié)構(gòu):
-
Prefabs - 預(yù)制件:其中包含
CustomHeart
預(yù)制件臼勉,可用于在運(yùn)行時(shí)保存3D網(wǎng)格邻吭。 - Scenes - 場(chǎng)景:這包含您將在本教程的不同部分使用的三個(gè)場(chǎng)景。
- Editor - 編輯器:在開(kāi)發(fā)過(guò)程中宴霸,該文件夾中的腳本為您提供了編輯器中的特殊功能囱晴。
-
Scripts - 腳本:包含運(yùn)行時(shí)腳本或組件。 將這些組件附加到
GameObject
時(shí)瓢谢,單擊Play
即可執(zhí)行畸写。 - Materials - 材質(zhì):此文件夾包含您要使用的網(wǎng)格物體的材質(zhì)。
在下一部分中氓扛,您將創(chuàng)建一個(gè)自定義編輯器以可視化3D網(wǎng)格的各個(gè)部分枯芬。
Poking and Prodding Meshes With a Custom Editor
在RW/Scenes
中打開(kāi)01 Mesh Study Demo
。 在Scene
視圖中采郎,您將看到一個(gè)不起眼的立方體:
您將要建立一個(gè)自定義編輯器千所,以將這個(gè)可憐的立方體拆開(kāi)! (然后尉剩,您將學(xué)習(xí)如何將其保持為一體真慢。)
1. Customizing the Editor Script
在Project
視圖中選擇Editor
文件夾毅臊。 這個(gè)特殊文件夾中的腳本修改了Unity
編輯器的工作方式理茎。 它們不會(huì)成為內(nèi)置游戲的一部分。
打開(kāi)MeshInspector.cs
并查看源代碼管嬉。 請(qǐng)注意皂林,該類(lèi)繼承自Unity
的基本Editor
類(lèi)-這就是讓Unity
了解這是自定義編輯器而不是游戲腳本的原因。
第一步是告訴Unity
這個(gè)特殊的編輯器應(yīng)該繪制什么樣的對(duì)象蚯撩。 在MeshInspector
類(lèi)聲明上方的行上添加以下屬性:
[CustomEditor(typeof(MeshStudy))]
現(xiàn)在础倍,當(dāng)任何附加了Mesh Study
組件的GameObject
在Scene
視圖中可見(jiàn)時(shí),此類(lèi)將處理其繪制胎挎。 但是現(xiàn)在沟启,您不知道這種情況是否正在發(fā)生。
OnSceneGUI
是Unity
每次在編輯器中渲染Scene
視圖時(shí)都會(huì)調(diào)用的事件方法犹菇。 您有機(jī)會(huì)修改Unity在場(chǎng)景中繪制對(duì)象的方式德迹。 在OnSceneGUI
的開(kāi)頭添加以下內(nèi)容:
mesh = target as MeshStudy;
Debug.Log("Custom editor is running");
基本的Editor
類(lèi)提供了對(duì)您在target
變量中具有類(lèi)型Object
的自定義對(duì)象的引用。 對(duì)于普通的vanilla
對(duì)象揭芍,您無(wú)法做很多有用的事情胳搞,因此此代碼將target
強(qiáng)制轉(zhuǎn)換為MeshStudy
類(lèi)型。 記錄消息后,您可以在控制臺(tái)中看到自定義編輯器確實(shí)正在運(yùn)行肌毅。
保存文件并返回到Unity
筷转。 轉(zhuǎn)到RW / Scripts
文件夾,然后將MeshStudy.cs
拖到層次結(jié)構(gòu)中的Cube GameObject
上悬而,以將組件附加到該對(duì)象呜舒。
在控制臺(tái)中查看并確保您的代碼正在運(yùn)行。 然后繼續(xù)刪除Debug.Log
行摊滔,以免淹沒(méi)您的控制臺(tái)阴绢。
2. Cloning a Mesh
在Edit
模式下使用自定義編輯器處理3D網(wǎng)格時(shí),很容易意外覆蓋Unity
的默認(rèn)網(wǎng)格艰躺,即內(nèi)置的Sphere呻袭,Cube,Cylinder
等腺兴。 如果發(fā)生這種情況左电,則需要重新啟動(dòng)Unity
。
為避免這種情況页响,請(qǐng)?jiān)?code>Edit模式下克隆網(wǎng)格篓足,然后再對(duì)其進(jìn)行任何更改。
打開(kāi)MeshStudy.cs
闰蚕。 該腳本繼承自MonoBehaviour
栈拖,因此其Start
不會(huì)在Edit
模式下運(yùn)行。 幸運(yùn)的是没陡,這很容易解決涩哟!
在MeshStudy
的類(lèi)聲明上方,添加以下內(nèi)容:
[ExecuteInEditMode]
當(dāng)一個(gè)類(lèi)具有此屬性時(shí)盼玄,其Start
將在Play mode
和Edit mode
下觸發(fā)贴彼。 添加完之后,您可以在更改任何內(nèi)容之前實(shí)例化和克隆網(wǎng)格對(duì)象埃儿。
將以下代碼添加到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)建一個(gè)名為
clonedMesh
的新網(wǎng)格實(shí)例,并通過(guò)復(fù)制第一個(gè)網(wǎng)格設(shè)置其屬性童番。 - 3) 將復(fù)制的網(wǎng)格分配回網(wǎng)格過(guò)濾器精钮。
- 4) 更新局部變量,稍后將需要剃斧。
- 5) 將
isCloned
設(shè)置為true
轨香; 您稍后會(huì)參考。
保存文件并返回到Unity
悯衬。 控制臺(tái)應(yīng)顯示消息“ Init&Cloned”
弹沽。
在層次結(jié)構(gòu)中選擇Cube
檀夹,然后查看檢查器。 網(wǎng)格過(guò)濾器將顯示一個(gè)名為clone
的網(wǎng)格策橘。 很好炸渡! 這意味著您已經(jīng)成功克隆了網(wǎng)格。
但是請(qǐng)注意丽已,您的項(xiàng)目中沒(méi)有新的Mesh
資源-克隆的Mesh
現(xiàn)在僅存在于Unity
的內(nèi)存中蚌堵,如果關(guān)閉場(chǎng)景,它將消失沛婴。 稍后吼畏,您將學(xué)習(xí)如何保存網(wǎng)格。
3. Resetting a Mesh
目前嘁灯,您想給自己一個(gè)簡(jiǎn)單的方法來(lái)重置網(wǎng)格泻蚊,以便您可以放心地玩。 返回到MeshInspector.cs
丑婿。
OnInspectorGUI
使您可以使用額外的GUI元素和邏輯為對(duì)象自定義Inspector
性雄。 在OnInspectorGUI
中,找到注釋//draw reset button
并將其替換為以下內(nèi)容:
if (GUILayout.Button("Reset")) //1
{
mesh.Reset(); //2
}
- 1) 此代碼在檢查器中繪制一個(gè)“重置”
(Reset)
按鈕羹奉。 按下時(shí)秒旋,繪制函數(shù)會(huì)返回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) 如果對(duì)象的網(wǎng)格過(guò)濾器中沒(méi)有任何數(shù)據(jù)耕挨,請(qǐng)檢查原始網(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
如您先前所見(jiàn)忽妒,網(wǎng)格由邊連接的頂點(diǎn)組成三角形季春。 三角形定義了對(duì)象的基本形狀。
注意:Unity的
Mesh
類(lèi)使用兩個(gè)數(shù)組跟蹤頂點(diǎn)和三角形:
- 它將頂點(diǎn)存儲(chǔ)為
Vector3
的數(shù)組。- 它將三角形存儲(chǔ)為整數(shù)數(shù)組拢操。 每個(gè)整數(shù)是
verts
數(shù)組中一個(gè)頂點(diǎn)的索引,并且每組三個(gè)連續(xù)的整數(shù)代表一個(gè)三角形恳蹲。
例如嫌变,組triangles[0], triangles[1], triangles[2]
代表一個(gè)三角形,組triangles[3], triangles[4], triangles[5]
代表下一個(gè)三角形,依此類(lèi)推巍实。因此滓技,在由四個(gè)頂點(diǎn)和兩個(gè)三角形組成的簡(jiǎn)單四邊形網(wǎng)格中,四邊形的網(wǎng)格數(shù)據(jù)為:
5. Visualizing Vertices
如果您可以在帶有handles
的網(wǎng)格上的頂點(diǎn)上繪制和移動(dòng)棚潦,將更容易看到它是如何工作的令漂。 handles
是用于在Scene
視圖中處理對(duì)象的工具,例如“旋轉(zhuǎn)”工具的可拖動(dòng)球體丸边。 現(xiàn)在叠必,您將要編寫(xiě)自己的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)模式骄呼,以與場(chǎng)景中其他所有對(duì)象相同的方式繪制手柄玄组。
- 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í)用程序類(lèi)繪制點(diǎn)。
Handles.FreeMoveHandle
制作一個(gè)不受限制的運(yùn)動(dòng)handle
绕德,您將在下一部分中使用它來(lái)拖動(dòng)點(diǎn)患膛。
保存文件并返回到Unity
。
檢查Cube's MeshInspector
耻蛇,并確保已選中Move Vertex Point
踪蹬。
現(xiàn)在,您應(yīng)該在屏幕上看到標(biāo)有藍(lán)點(diǎn)的網(wǎng)格的頂點(diǎn)臣咖。 嘗試將腳本附加到其他3D對(duì)象跃捣,然后親自查看結(jié)果!
6. Moving a Single Vertex
您將從最基本的網(wǎng)格處理類(lèi)型開(kāi)始:移動(dòng)單個(gè)頂點(diǎn)夺蛇。
打開(kāi)MeshInspector.cs
疚漆。 在ShowPoint
內(nèi),用以下內(nèi)容替換// drag
注釋?zhuān)?/p>
if (GUI.changed) //3
{
mesh.DoAction(index, handleTransform.InverseTransformPoint(point)); //4
}
- 3)
GUI.changed
監(jiān)視對(duì)點(diǎn)所做的任何更改刁赦,這與Handles.FreeMoveHandle
配合使用可很好地檢測(cè)拖動(dòng)動(dòng)作娶聘。 - 4) 拖動(dòng)頂點(diǎn)時(shí),以頂點(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
。 嘗試拖動(dòng)cube
上的點(diǎn)之一夷狰。
似乎有些頂點(diǎn)共享相同的位置涛碑,因此,當(dāng)您僅拉一個(gè)頂點(diǎn)時(shí)孵淘,其他頂點(diǎn)會(huì)留在后面蒲障,并且網(wǎng)格會(huì)斷開(kāi)。 您將很快學(xué)習(xí)如何解決此問(wèn)題瘫证。
7. Looking at the Vertices Array
在外觀上揉阎,一個(gè)立方體網(wǎng)格由八個(gè)頂點(diǎn),六個(gè)邊和12個(gè)三角形組成背捌。 是時(shí)候看看Unity是否一致了毙籽。
轉(zhuǎn)到MeshStudy.cs
,然后在Start
之前查找名為vertices
的變量毡庆。 您將看到它具有[HideInInspector]
屬性坑赡。
臨時(shí)注釋掉該屬性,以便快速瀏覽一下數(shù)組:
//[HideInInspector]
public Vector3[] vertices;
注意:更復(fù)雜的3D網(wǎng)格可以具有數(shù)千個(gè)頂點(diǎn)么抗。 如果Unity試圖在
Inspector
中顯示所有這些值毅否,它將凍結(jié),因此通常蝇刀,您將使用[HideInInspector]
隱藏該數(shù)組螟加。 你只是在偷看!
保存文件吞琐,返回Unity并查看您的cube
捆探。 現(xiàn)在,您可以在Mesh Study
中看到vertices
屬性站粟。 單擊其旁邊的箭頭圖標(biāo)以顯示Vector3
元素的數(shù)組黍图。
您會(huì)看到數(shù)組大小為24
,這意味著肯定有頂點(diǎn)共享相同的位置奴烙! 花點(diǎn)時(shí)間考慮一下為什么同一位置可能有多個(gè)頂點(diǎn)助被。
最簡(jiǎn)單的答案是:
一個(gè)立方體有六個(gè)邊,每個(gè)邊都有四個(gè)形成一個(gè)平面的頂點(diǎn)缸沃。6×4 = 24
個(gè)頂點(diǎn)恰起。如果很難掌握修械,還有其他方法可以考慮趾牧。 但是現(xiàn)在,只知道某些網(wǎng)格物體的頂點(diǎn)共享相同的位置肯污。
由于已經(jīng)完成了verts
數(shù)組的瀏覽翘单,因此請(qǐng)繼續(xù)注釋[HideInInspector]
吨枉。
8. Finding All Similar Vertices
您可以看到,操作網(wǎng)格不僅需要移動(dòng)單個(gè)頂點(diǎn)哄芜,還需要更多的操作—您必須移動(dòng)空間中特定點(diǎn)的所有頂點(diǎn)才能將網(wǎng)格保持在一起貌亭。 因此,您現(xiàn)在就可以動(dòng)彈了认臊,呃圃庭,網(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
儒旬。 單擊并拖動(dòng)任何一個(gè)頂點(diǎn); 網(wǎng)格現(xiàn)在應(yīng)保持其形狀不變帖族。
保存場(chǎng)景栈源。 您已邁出成為網(wǎng)狀魔術(shù)師的第一步!
Manipulating Meshes
在Unity中編輯網(wǎng)格很有趣竖般,但是如果您可以通過(guò)在運(yùn)行時(shí)變形網(wǎng)格來(lái)向游戲中添加一些“squish”
呢凉翻? 接下來(lái),您將以最基本的形式進(jìn)行嘗試-推和拉一些預(yù)定義的頂點(diǎn)捻激。
1. Collecting the Selected Indices
首先制轰,創(chuàng)建一個(gè)自定義編輯器,使您可以選擇要實(shí)時(shí)移動(dòng)的頂點(diǎn)胞谭。 在RW/Scenes
中打開(kāi)02 Create Heart Mesh
場(chǎng)景垃杖。 您將在Scene
視圖中看到一個(gè)紅色的球體。
在“層次結(jié)構(gòu)”中選擇Sphere
丈屹,然后查看Heart Mesh
組件调俘。 這是將存儲(chǔ)您選擇的頂點(diǎn)的腳本。
但是現(xiàn)在旺垒,場(chǎng)景中沒(méi)有顯示任何變體彩库。 因此,接下來(lái)先蒋,您將解決此問(wèn)題骇钦!
打開(kāi)RW / Editor / HeartMeshInspector.cs
。 在ShowHandle
中的if
語(yǔ)句內(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) 單擊按鈕時(shí),它將選定的索引添加到
mesh.selectedIndices
列表鳞仙。
在現(xiàn)有的if
語(yǔ)句之后寇蚊,在OnInspectorGUI
的末尾添加以下代碼:
if (GUILayout.Button("Clear Selected Vertices"))
{
mesh.ClearAllData();
}
這會(huì)在檢查器中添加一個(gè)自定義的Reset
按鈕。 接下來(lái)棍好,您將編寫(xiě)代碼以清除選擇仗岸。
保存文件并打開(kāi)RW / Scripts / HeartMesh.cs
。 在ClearAllData
中借笙,添加以下內(nèi)容:
selectedIndices = new List<int>();
targetIndex = 0;
targetVertex = Vector3.zero;
這將清除selectedIndices
列表中的值爹梁,并將targetIndex
設(shè)置為零。 它還會(huì)重置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
來(lái)顯示/隱藏變換手柄川慌,因?yàn)樗梢苑恋K選擇頂點(diǎn)。 只要記住當(dāng)您發(fā)現(xiàn)其他場(chǎng)景中缺少Transform handle
時(shí)不要驚慌祠乃! 退出之前梦重,請(qǐng)務(wù)必將其重新打開(kāi)。
2. Deforming the Sphere Into a Heart Shape
實(shí)時(shí)更新網(wǎng)格頂點(diǎn)需要三個(gè)步驟:
- 1) 將當(dāng)前的網(wǎng)格頂點(diǎn)(在動(dòng)畫(huà)之前)復(fù)制到
ModifyedVertices
亮瓷。 - 2) 計(jì)算并更新
modifiedVertices
上的值琴拧。 - 3) 每次更改步驟時(shí),將
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;
移動(dòng)頂點(diǎn)應(yīng)該對(duì)其周?chē)捻旤c(diǎn)產(chǎn)生一些影響,以保持平滑的形狀除师。 這些變量控制效果沛膳。
- 1) 受目標(biāo)頂點(diǎn)影響的區(qū)域半徑。
- 2)
pull
的長(zhǎng)度汛聚。 - 3) 動(dòng)畫(huà)將運(yùn)行多長(zhǎng)時(shí)間锹安。
- 4)
selectedIndices
列表的當(dāng)前索引。
在Init
的if
語(yǔ)句之前,添加:
currentIndex = 0;
這將在游戲開(kāi)始時(shí)將currentIndex
(selectedIndices
列表的第一個(gè)索引)設(shè)置為0八毯。
仍然在Init
中搓侄,在else
語(yǔ)句的右括號(hào)之前瞄桨,添加:
StartDisplacement();
StartDisplacement
是實(shí)際移動(dòng)頂點(diǎn)的地方话速。 它僅在isEditMode
為false
時(shí)運(yùn)行。
現(xiàn)在芯侥,此方法不起作用泊交,因此將以下內(nèi)容添加到StartDisplacement
中:
targetVertex = originalVertices[selectedIndices[currentIndex]]; //1
startTime = Time.time; //2
isAnimate = true;
- 1) 從
originalVertices
數(shù)組中選擇targetVertex
以啟動(dòng)動(dòng)畫(huà)。 請(qǐng)記住柱查,每個(gè)數(shù)組項(xiàng)都是一個(gè)整數(shù)值列表廓俭。 - 2) 將開(kāi)始時(shí)間設(shè)置為當(dāng)前時(shí)間,并將
isAnimate
更改為true
唉工。
在StartDisplacement
之后研乒,使用以下代碼創(chuàng)建一個(gè)名為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)行,這意味著它與幀速率無(wú)關(guān)淋硝。 在此處了解更多信息雹熬。 - 2) 如果
isAnimate
為false
,則不會(huì)執(zhí)行任何操作谣膳。 - 3) 跟蹤動(dòng)畫(huà)運(yùn)行了多長(zhǎng)時(shí)間竿报。
- 4) 如果動(dòng)畫(huà)沒(méi)有運(yùn)行太長(zhǎng)時(shí)間,它將通過(guò)獲取
targetVertex
的世界空間坐標(biāo)并調(diào)用DisplaceVertices
來(lái)繼續(xù)動(dòng)畫(huà)继谚。 - 5) 否則烈菌,時(shí)間到了! 向
currentIndex
添加一個(gè)以開(kāi)始處理下一個(gè)選定頂點(diǎn)的動(dòng)畫(huà)花履。 - 6) 檢查是否所有選定的頂點(diǎn)都已處理芽世。 如果不是,請(qǐng)使用最新的頂點(diǎn)調(diào)用
StartDisplacement
诡壁。 - 7) 否則捂襟,您已到達(dá)所選頂點(diǎn)列表的末尾。 該行將復(fù)制當(dāng)前網(wǎng)格欢峰,并將
isAnimate
設(shè)置為false
以停止動(dòng)畫(huà)葬荷。
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)格中的每個(gè)頂點(diǎn)纽帖,并替換與您在編輯器中選擇的頂點(diǎn)接近的頂點(diǎn)宠漩。它通過(guò)一些數(shù)學(xué)技巧來(lái)創(chuàng)建平滑的效果,例如將拇指推入粘土中懊直。稍后扒吁,您將了解有關(guān)此內(nèi)容的更多信息。
下面是這段代碼的詳細(xì)信息:
- 1) 獲取半徑的平方室囊。
- 2) 遍歷網(wǎng)格中的每個(gè)頂點(diǎn)雕崩。
- 3) 查找當(dāng)前頂點(diǎn)和目標(biāo)頂點(diǎn)之間的距離并將其平方魁索。
- 4) 如果此頂點(diǎn)不在效果范圍內(nèi),請(qǐng)盡早退出循環(huán)并繼續(xù)到下一個(gè)頂點(diǎn)盼铁。
- 5) 否則粗蔚,根據(jù)距離計(jì)算衰減
falloff
值。高斯函數(shù)可創(chuàng)建平滑的鐘形曲線饶火。 - 6) 根據(jù)距離計(jì)算要移動(dòng)多遠(yuǎn)鹏控,然后根據(jù)結(jié)果設(shè)置旋轉(zhuǎn)(位移方向)。這使頂點(diǎn)“向外”移動(dòng)肤寝,即直接遠(yuǎn)離
targetVertex
当辐,使其看起來(lái)像從中心噴出。 - 7) 退出循環(huán)后鲤看,將更新后的
ModifyedVertices
存儲(chǔ)在原始網(wǎng)格中缘揪,并讓Unity重新計(jì)算法線。
保存文件并返回到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)備就緒后陆赋,請(qǐng)按照以下屏幕截圖更新設(shè)置。
點(diǎn)擊Play
嚷闭,您的球體氣球變成了心臟形狀嗎攒岛?
恭喜你! 在下一部分中胞锰,您將學(xué)習(xí)如何保存網(wǎng)格以備將來(lái)使用灾锯。
4. Saving Your Mesh in Real Time
現(xiàn)在,每按一次Play
按鈕嗅榕,您的心就會(huì)動(dòng)靜顺饮。 如果您想要持久的愛(ài),則需要一種將網(wǎng)格物體寫(xiě)入文件的方法凌那。
一種簡(jiǎn)單的方法是設(shè)置一個(gè)以3D對(duì)象作為其子對(duì)象的占位符預(yù)制件兼雄,然后通過(guò)腳本將其網(wǎng)格物體資源替換為您的心臟(... er,您的Heart mesh
)帽蝶。
在項(xiàng)目視圖中赦肋,找到Prefabs / CustomHeart
。 雙擊預(yù)制件以在Prefab Editing
模式下將其打開(kāi)。
單擊箭頭圖標(biāo)以在層次結(jié)構(gòu)中展開(kāi)其內(nèi)容佃乘,然后選擇子級(jí)囱井。 您將在此處存儲(chǔ)生成的網(wǎng)格。
退出預(yù)制編輯模式趣避,然后打開(kāi)HeartMeshInspector.cs
庞呕。 在OnInspectorGUI
的結(jié)尾,大括號(hào)之前鹅巍,添加以下內(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) 存儲(chǔ)
CustomHeart
預(yù)制對(duì)象資產(chǎn)路徑千扶,您需要能夠?qū)⒃撀窂綄?xiě)入文件料祠。 - 2) 從
CustomHeart
預(yù)制中創(chuàng)建兩個(gè)對(duì)象骆捧,一個(gè)作為GameObject
,另一個(gè)作為參考髓绽。 - 3) 從
CustomHeart
創(chuàng)建網(wǎng)格資產(chǎn)prefabMesh
的實(shí)例敛苇。 如果找到資產(chǎn),則清除其數(shù)據(jù)顺呕; 否則枫攀,它將創(chuàng)建一個(gè)新的空網(wǎng)格。 - 4) 用新的網(wǎng)格數(shù)據(jù)更新
prefabMesh
并將其與CustomHeart
資產(chǎn)關(guān)聯(lián)株茶。 - 5) 使用
prefabMesh
更新GameObject
的網(wǎng)格資源来涨。 - 6) 在給定
gameObj
的給定路徑上創(chuàng)建一個(gè)Prefab Asset
,包括場(chǎng)景中的所有子代启盛。 這將替代CustomHeart
預(yù)制件中的任何東西蹦掐。 - 7) 立即銷(xiāo)毀
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
社裆。 動(dòng)畫(huà)結(jié)束時(shí),Save Mesh
按鈕將出現(xiàn)在檢查器中向图。 單擊按鈕保存新的網(wǎng)格泳秀,然后停止播放器。
在“項(xiàng)目”視圖中再次找到Prefabs / CustomHeart
榄攀,然后Prefab Editing
模式下將其打開(kāi)嗜傅。 您會(huì)看到一個(gè)預(yù)制的心形網(wǎng)狀品牌已保存在您的預(yù)制件中!
Putting It All Together
在上一節(jié)中航攒,您學(xué)習(xí)了如何通過(guò)選擇單個(gè)頂點(diǎn)來(lái)修改網(wǎng)格磺陡。 雖然這很酷,但是如果您知道如何按程序選擇頂點(diǎn),則可以做更多有趣的事情币他。
在上一場(chǎng)景中坞靶,DisplaceVertices
使用高斯衰減公式來(lái)確定在效果半徑內(nèi)“拉”每個(gè)頂點(diǎn)的量。 但是蝴悉,您還可以使用其他數(shù)學(xué)函數(shù)來(lái)計(jì)算“下降”點(diǎn)彰阴。 也就是說(shuō),拉力pull
開(kāi)始衰減拍冠。 每個(gè)函數(shù)產(chǎn)生不同的形狀:
在本部分中尿这,您將學(xué)習(xí)如何使用計(jì)算出的曲線來(lái)操縱頂點(diǎn)。
基于速度等于距離除以時(shí)間(v =(d / t))
的原理庆杜,可以通過(guò)將向量的距離除以時(shí)間因子來(lái)確定向量的位置射众。
Using the Curve Method
保存當(dāng)前場(chǎng)景,然后從Scenes
文件夾中打開(kāi)03 Customize Heart Mesh
晃财。
在層次結(jié)構(gòu)中找到CustomHeart
預(yù)制實(shí)例叨橱,然后單擊其旁邊的箭頭圖標(biāo)以擴(kuò)展其內(nèi)容。 選擇Child
對(duì)象断盛。
在檢查器中查看其屬性罗洗。 您將看到帶有Heart Mesh
資源的Mesh Filter
。 將Custom Heart
附加到Child
上钢猛。 資產(chǎn)現(xiàn)在應(yīng)該從HeartMesh
更改為clone
伙菜。
打開(kāi)CustomHeart.cs
并在Start
上方添加以下內(nèi)容:
public enum CurveType
{
Curve1, Curve2
}
public CurveType curveType;
Curve curve;
這將創(chuàng)建一個(gè)名為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) 基本曲線由三個(gè)點(diǎn)組成。 該代碼設(shè)置并繪制第一條曲線的點(diǎn)躺翻。
- 2) 使用
Curve
生成第一條曲線并將其值分配給Curve
丧叽。 您可以將最后一個(gè)參數(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
中,在右括號(hào)之前剪芥,添加以下內(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)税肪,在右花括號(hào)之前溉躲,添加以下內(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]);
這看起來(lái)很熟悉-就像您添加到HeartMesh
的代碼一樣榜田。
- 1) 獲取給定距離
distance
處的曲線位置,并將其y
值乘以force
即可獲得增量increment
锻梳。 - 2) 創(chuàng)建一個(gè)新的
Vector3
箭券,稱(chēng)為translate
,以存儲(chǔ)當(dāng)前頂點(diǎn)的新位置并相應(yīng)地應(yīng)用其Transform
疑枯。
保存文件并返回到Unity辩块。 在子GameObject
上檢查Custom Heart
中的屬性。
現(xiàn)在荆永,在Edit Type
下拉菜單中废亭,可以選擇Add Indices
或Remove Indices
來(lái)更新頂點(diǎn)列表。 選擇None
退出編輯模式具钥,然后單擊Play
查看結(jié)果豆村。 試用不同的設(shè)置和頂點(diǎn)選擇。
要查看不同曲線類(lèi)型的示例氓拼,請(qǐng)輸入以下值:
將 Curve Type 設(shè)置為Curve1
你画,檢查Edit Type
是否設(shè)置為None
抵碟,然后按Play
桃漾。
您應(yīng)該看到網(wǎng)格如何呈扇形展開(kāi)。 將模型移至其側(cè)視圖拟逮,以便可以看到該曲線產(chǎn)生的形狀撬统。 Exit Play
,然后使用Curve 2
再次嘗試比較兩種曲線類(lèi)型的結(jié)果:
就這樣敦迄! 您可以單擊Clear Selected Vertices
以重置Selected Indices
并嘗試使用自己的樣式恋追。 不要忘記,有幾個(gè)因素會(huì)影響網(wǎng)格的最終形狀:
- 半徑的大小罚屋。
- 頂點(diǎn)在區(qū)域內(nèi)的散布苦囱。
- 選定頂點(diǎn)的圖案位置。
- 您選擇的位移方法脾猛。
后記
本篇主要講述了使用Unity運(yùn)行時(shí)網(wǎng)格操作撕彤,感興趣的給個(gè)贊或者關(guān)注~~~