您好,登錄后才能下訂單哦!
前言
這幾天琢磨著開發(fā)個(gè)個(gè)人作品的時(shí)候,發(fā)現(xiàn)原來Unity3D官方?jīng)]有提供圓錐體的創(chuàng)建功能,就自己做了個(gè)編輯器擴(kuò)展。鑒于之前搜索Mesh編程的時(shí)候很少有博客把自己的算法講清楚,這里我拋磚引玉,盡我所能為一些初學(xué)者提供參考,當(dāng)然,算法未必優(yōu),如有更好的算法并樂意知會(huì)我則不勝感激,我是大齡轉(zhuǎn)行Unity3D開發(fā),一路行來都是自己琢磨,比較辛苦,先行謝過。
軟件環(huán)境
Win10 + Unity3D 2017.3.0f3
正文
基本思路是以原點(diǎn)為圓錐體底部圓的中心點(diǎn),以其正上方1單元處的點(diǎn)為圓錐體錐尖頂點(diǎn),其他點(diǎn)參照Cylinder為分布在半徑為0.5單元的圓上,每20度一個(gè)點(diǎn),這樣總共加起來的頂點(diǎn)數(shù)量是38個(gè),三角形索引數(shù)組數(shù)量是108個(gè)(錐體可以看作底部圓心上移,所以這兩部分的三角形數(shù)量是相等的,而底部每20度一個(gè)點(diǎn),那么就有18個(gè)三角形,所以結(jié)果就是18∗3∗2=10818∗3∗2=108)。
下面開始逐步分解實(shí)現(xiàn)。
編輯器擴(kuò)展
首先,擴(kuò)展編輯器,在GameObject/3D Object下新建一個(gè)Cone菜單,為了假裝是親生的,就和Cube等原生菜單放在一起好了。
[MenuItem("GameObject/3D Object/Cone",false,priority = 7)] public static void CreateCone() { SpawnConeInHierarchy(); }
這里主要就是利用MenuItem特性來實(shí)現(xiàn)的,其中false表示該菜單不需要有效性驗(yàn)證,priority=7控制菜單顯示的位置,可以參考這里: Unity擴(kuò)展Hierachry的右鍵菜單
方便起見,我把圖貼下面:
接下來實(shí)現(xiàn)SpawnConeInHierarchy方法:
private static void SpawnConeInHierarchy() { Transform[] selections = Selection.GetTransforms(SelectionMode.TopLevel | SelectionMode.ExcludePrefab); if (selections.Length <= 0) { GameObject cone = new GameObject("Cone"); cone.transform.position = Vector3.zero; cone.transform.rotation = Quaternion.identity; cone.transform.localScale = Vector3.one; //SetMesh(cone); return; } foreach (Transform selection in selections) { GameObject cone = new GameObject("Cone"); cone.transform.SetParent(selection); cone.transform.localPosition = Vector3.zero; cone.transform.localRotation = Quaternion.identity; cone.transform.localScale = Vector3.one; //SetMesh(cone); } }
這里分兩種情況,如果沒有在Hierarchy面板選中任何物體,那么就在根目錄下生成一個(gè)名字為”Cone”的GameObject,如果有選中物體,則生成的”Cone”會(huì)變?yōu)檫x中項(xiàng)的子物體。
PS:這里有個(gè)Bug,如果同時(shí)選中了多個(gè)物體,又是采用的Hierarchy面板右鍵菜單的方式,那么會(huì)在每個(gè)選中物體下都生成與選中物體數(shù)量相同的子物體,見下圖。這個(gè)Bug應(yīng)該不僅限于版本2017.3,因?yàn)槲以诰W(wǎng)上有搜到一個(gè)同情況的帖子,時(shí)間是2016年8月。
目前這個(gè)Bug我已經(jīng)提交給官方確認(rèn)了,他們已轉(zhuǎn)交給QA,不過不影響使用,避免辦法就是不用右鍵菜單,而是點(diǎn)擊菜單欄”GameObject”下的菜單。
到此為止,我們已經(jīng)擴(kuò)展了編輯器菜單,但是生成出來的是空物體,接下來我們實(shí)現(xiàn)SetMesh方法以創(chuàng)建Mesh,讓圓錐體顯示出來。
創(chuàng)建Mesh
分兩部,首先繪出底部的圓。
繪制圓形底部
圓心已經(jīng)確定為原點(diǎn),半徑為0.5f,圓上分布共20個(gè)點(diǎn),那么每個(gè)點(diǎn)的坐標(biāo)就可以用三角函數(shù)算出。
private static void SetMesh(GameObject go) { if (null == go) return; //仿Cylinder參數(shù) float myRadius = 0.5f; int myAngleStep = 20; Vector3 myTopCenter = new Vector3(0, 1, 0); Vector3 myBottomCenter = Vector3.zero; //構(gòu)建頂點(diǎn)數(shù)組和UV數(shù)組 //每20度一個(gè)頂點(diǎn),再加上圓心,得出頂點(diǎn)數(shù)組長(zhǎng)度 Vector3[] myVertices = new Vector3[360 / myAngleStep + 1]; //因?yàn)閡v數(shù)組和頂點(diǎn)數(shù)組是一一對(duì)應(yīng)的,所以這里同時(shí)計(jì)算uv數(shù)組 Vector2[] myUV = new Vector2[myVertices.Length]; //將圓心作為第一個(gè)頂點(diǎn),對(duì)應(yīng)的uv設(shè)置為貼圖正中 myVertices[0] = myBottomCenter; myUV[0] = new Vector2(0.5f, 0.5f); //循環(huán)計(jì)算其他頂點(diǎn)坐標(biāo) for (int i = 1; i <= myVertices.Length / 2; i++) { float curAngle = i * myAngleStep * Mathf.Deg2Rad; float curX = myRadius * Mathf.Cos(curAngle); float curZ = myRadius * Mathf.Sin(curAngle); myVertices[i] = new Vector3(curX, 0, curZ); //頂點(diǎn)坐標(biāo)范圍是[-0.5,0.5],而uv坐標(biāo)范圍是[0,1],所以要進(jìn)行轉(zhuǎn)換 myUV[i] = new Vector2(curX + 0.5f, curZ + 0.5f); }
接下來,構(gòu)建三角形索引數(shù)組,19個(gè)頂點(diǎn),共18個(gè)三角形,所以數(shù)組長(zhǎng)度是18 * 3 = 54。
int[] myTriangle = new int[(myVertices.Length - 1) * 3]; //每三個(gè)索引(即頂點(diǎn)數(shù)組中的頂點(diǎn)索引值)為一個(gè)三角形索引組 for (int i = 0; i <= myTriangle.Length - 3; i = i+3) { //每組都以圓心起始 myTriangle[i] = 0; //為能從圓錐底部看見物體,這里按逆時(shí)針順序排列,也就是(0 1 2 0 2 3...) myTriangle[i + 1] = i / 3 + 1; //最后一個(gè)三角形時(shí)終點(diǎn)索引應(yīng)為1 myTriangle[i + 2] = i + 2 == myTriangle.Length / 2 - 1 ? 1 : i / 3 + 2; } }
最后,分配mesh,賦值材質(zhì)后就可以看到一個(gè)圓形物體了。
//構(gòu)建mesh Mesh myMesh = new Mesh(); myMesh.name = "Cone"; myMesh.vertices = myVertices; myMesh.triangles = myTriangle; myMesh.uv = myUV; myMesh.RecalculateBounds(); myMesh.RecalculateNormals(); myMesh.RecalculateTangents(); //分配mesh MeshFilter mf = go.AddComponent<MeshFilter>(); mf.mesh = myMesh; //分配材質(zhì) MeshRenderer mr = go.AddComponent<MeshRenderer>(); Material myMat = new Material(Shader.Find("Standard")); mr.sharedMaterial = myMat;
因?yàn)榈撞繘]光照,所以看起來是黑的,另外,上面的代碼是我從最終代碼中手動(dòng)修改得到的,可能有錯(cuò)誤,只是用于理解思路,完整代碼會(huì)在最后給出。
完善錐體
底部圓既然已經(jīng)繪制成功,錐體可以理解為將圓心上移即可,在頂點(diǎn)數(shù)量上,三角形索引數(shù)組上都相當(dāng)于double了一份即可。
這里有個(gè)情況說明一下,我本來是想共用圓上頂點(diǎn)的,這樣整個(gè)錐體的頂點(diǎn)數(shù)就是20,但經(jīng)過測(cè)試是不可以的,我參考了Cube,頂點(diǎn)數(shù)是24,說明不同面的頂點(diǎn)是不能共用的,可能是因?yàn)榉ň€方向等因素吧。
修改后的完整代碼如下:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEditor; using System; public class ConeCreatorEditor { [MenuItem("GameObject/3D Object/Cone",false,priority = 7)] public static void CreateCone() { SpawnConeInHierarchy(); } private static void SetMesh(GameObject go) { if (null == go) return; //仿Cylinder參數(shù) float myRadius = 0.5f; int myAngleStep = 20; Vector3 myTopCenter = new Vector3(0, 1, 0); Vector3 myBottomCenter = Vector3.zero; //構(gòu)建頂點(diǎn)數(shù)組和UV數(shù)組 Vector3[] myVertices = new Vector3[360 / myAngleStep * 2 + 2]; // Vector2[] myUV = new Vector2[myVertices.Length]; //這里我把錐尖頂點(diǎn)放在了頂點(diǎn)數(shù)組最后一個(gè) myVertices[0] = myBottomCenter; myVertices[myVertices.Length - 1] = myTopCenter; myUV[0] = new Vector2(0.5f, 0.5f); myUV[myVertices.Length - 1] = new Vector2(0.5f,0.5f); //因?yàn)閳A上頂點(diǎn)坐標(biāo)相同,只是索引不同,所以這里循環(huán)一般長(zhǎng)度即可 for (int i = 1; i <= (myVertices.Length -2) / 2; i++) { float curAngle = i * myAngleStep * Mathf.Deg2Rad; float curX = myRadius * Mathf.Cos(curAngle); float curZ = myRadius * Mathf.Sin(curAngle); myVertices[i] = myVertices[i + (myVertices.Length - 2) / 2] = new Vector3(curX, 0, curZ); myUV[i] = myUV[i + (myVertices.Length - 2) / 2] = new Vector2(curX + 0.5f, curZ + 0.5f); } //構(gòu)建三角形數(shù)組 int[] myTriangle = new int[(myVertices.Length - 2) * 3]; for (int i = 0; i <= myTriangle.Length - 3; i = i+3) { if (i + 2 < myTriangle.Length / 2) { myTriangle[i] = 0; myTriangle[i + 1] = i / 3 + 1; myTriangle[i + 2] = i + 2 == myTriangle.Length / 2 - 1 ? 1 : i / 3 + 2; } else { //繪制錐體部分,索引組起始點(diǎn)都為錐尖 myTriangle[i] = myVertices.Length - 1; //錐體最后一個(gè)三角形的中間頂點(diǎn)索引值為19 myTriangle[i + 1] = i == myTriangle.Length - 3 ? 19 : i / 3 + 2; myTriangle[i + 2] = i / 3 + 1; } } //構(gòu)建mesh Mesh myMesh = new Mesh(); myMesh.name = "Cone"; myMesh.vertices = myVertices; myMesh.triangles = myTriangle; myMesh.uv = myUV; myMesh.RecalculateBounds(); myMesh.RecalculateNormals(); myMesh.RecalculateTangents(); //分配mesh MeshFilter mf = go.AddComponent<MeshFilter>(); mf.mesh = myMesh; //分配材質(zhì) MeshRenderer mr = go.AddComponent<MeshRenderer>(); Material myMat = new Material(Shader.Find("Standard")); mr.sharedMaterial = myMat; } private static void SpawnConeInHierarchy() { Transform[] selections = Selection.GetTransforms(SelectionMode.TopLevel | SelectionMode.ExcludePrefab); if (selections.Length <= 0) { GameObject cone = new GameObject("Cone"); cone.transform.position = Vector3.zero; cone.transform.rotation = Quaternion.identity; cone.transform.localScale = Vector3.one; //設(shè)置創(chuàng)建操作可撤銷 Undo.RegisterCreatedObjectUndo(cone, "Undo Creating Cone"); SetMesh(cone); return; } foreach (Transform selection in selections) { GameObject cone = new GameObject("Cone"); cone.transform.SetParent(selection); cone.transform.localPosition = Vector3.zero; cone.transform.localRotation = Quaternion.identity; cone.transform.localScale = Vector3.one; //設(shè)置創(chuàng)建操作可撤銷 Undo.RegisterCreatedObjectUndo(cone, "Undo Creating Cone"); SetMesh(cone); } } }
PS:這里的uv設(shè)置比較簡(jiǎn)單,所以對(duì)貼圖也特定要求,不然圖片會(huì)比較扭曲,需要的朋友可以自行修改。
結(jié)果
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。