溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Unity3D自定義創建圓錐體

發布時間:2020-10-08 20:41:14 來源:腳本之家 閱讀:254 作者:hellemic 欄目:編程語言

前言

這幾天琢磨著開發個個人作品的時候,發現原來Unity3D官方沒有提供圓錐體的創建功能,就自己做了個編輯器擴展。鑒于之前搜索Mesh編程的時候很少有博客把自己的算法講清楚,這里我拋磚引玉,盡我所能為一些初學者提供參考,當然,算法未必優,如有更好的算法并樂意知會我則不勝感激,我是大齡轉行Unity3D開發,一路行來都是自己琢磨,比較辛苦,先行謝過。

軟件環境

Win10 + Unity3D 2017.3.0f3

正文

基本思路是以原點為圓錐體底部圓的中心點,以其正上方1單元處的點為圓錐體錐尖頂點,其他點參照Cylinder為分布在半徑為0.5單元的圓上,每20度一個點,這樣總共加起來的頂點數量是38個,三角形索引數組數量是108個(錐體可以看作底部圓心上移,所以這兩部分的三角形數量是相等的,而底部每20度一個點,那么就有18個三角形,所以結果就是18∗3∗2=10818∗3∗2=108)。

下面開始逐步分解實現。

編輯器擴展

首先,擴展編輯器,在GameObject/3D Object下新建一個Cone菜單,為了假裝是親生的,就和Cube等原生菜單放在一起好了。

[MenuItem("GameObject/3D Object/Cone",false,priority = 7)]
public static void CreateCone()
{
 SpawnConeInHierarchy();
}

這里主要就是利用MenuItem特性來實現的,其中false表示該菜單不需要有效性驗證,priority=7控制菜單顯示的位置,可以參考這里: Unity擴展Hierachry的右鍵菜單

方便起見,我把圖貼下面:

Unity3D自定義創建圓錐體

Unity3D自定義創建圓錐體

接下來實現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面板選中任何物體,那么就在根目錄下生成一個名字為”Cone”的GameObject,如果有選中物體,則生成的”Cone”會變為選中項的子物體。

PS:這里有個Bug,如果同時選中了多個物體,又是采用的Hierarchy面板右鍵菜單的方式,那么會在每個選中物體下都生成與選中物體數量相同的子物體,見下圖。這個Bug應該不僅限于版本2017.3,因為我在網上有搜到一個同情況的帖子,時間是2016年8月。

Unity3D自定義創建圓錐體

目前這個Bug我已經提交給官方確認了,他們已轉交給QA,不過不影響使用,避免辦法就是不用右鍵菜單,而是點擊菜單欄”GameObject”下的菜單。

到此為止,我們已經擴展了編輯器菜單,但是生成出來的是空物體,接下來我們實現SetMesh方法以創建Mesh,讓圓錐體顯示出來。

創建Mesh

分兩部,首先繪出底部的圓。

繪制圓形底部

圓心已經確定為原點,半徑為0.5f,圓上分布共20個點,那么每個點的坐標就可以用三角函數算出。

private static void SetMesh(GameObject go)
 {
 if (null == go)
  return;
 //仿Cylinder參數
 float myRadius = 0.5f;
 int myAngleStep = 20;
 Vector3 myTopCenter = new Vector3(0, 1, 0);
 Vector3 myBottomCenter = Vector3.zero;
 //構建頂點數組和UV數組
 //每20度一個頂點,再加上圓心,得出頂點數組長度
 Vector3[] myVertices = new Vector3[360 / myAngleStep + 1];
 //因為uv數組和頂點數組是一一對應的,所以這里同時計算uv數組
 Vector2[] myUV = new Vector2[myVertices.Length];
 //將圓心作為第一個頂點,對應的uv設置為貼圖正中
 myVertices[0] = myBottomCenter;
 myUV[0] = new Vector2(0.5f, 0.5f);
 //循環計算其他頂點坐標
 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);
  //頂點坐標范圍是[-0.5,0.5],而uv坐標范圍是[0,1],所以要進行轉換
  myUV[i] = new Vector2(curX + 0.5f, curZ + 0.5f);
 }

接下來,構建三角形索引數組,19個頂點,共18個三角形,所以數組長度是18 * 3 = 54。

int[] myTriangle = new int[(myVertices.Length - 1) * 3]; 
 //每三個索引(即頂點數組中的頂點索引值)為一個三角形索引組
 for (int i = 0; i <= myTriangle.Length - 3; i = i+3)
 {
  //每組都以圓心起始
  myTriangle[i] = 0;
  //為能從圓錐底部看見物體,這里按逆時針順序排列,也就是(0 1 2 0 2 3...)
  myTriangle[i + 1] = i / 3 + 1;
  //最后一個三角形時終點索引應為1
  myTriangle[i + 2] = i + 2 == myTriangle.Length / 2 - 1 ? 1 : i / 3 + 2;
  }
 }

最后,分配mesh,賦值材質后就可以看到一個圓形物體了。

//構建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;
 //分配材質
 MeshRenderer mr = go.AddComponent<MeshRenderer>();
 Material myMat = new Material(Shader.Find("Standard"));
 mr.sharedMaterial = myMat;

Unity3D自定義創建圓錐體

因為底部沒光照,所以看起來是黑的,另外,上面的代碼是我從最終代碼中手動修改得到的,可能有錯誤,只是用于理解思路,完整代碼會在最后給出。

完善錐體

底部圓既然已經繪制成功,錐體可以理解為將圓心上移即可,在頂點數量上,三角形索引數組上都相當于double了一份即可。

這里有個情況說明一下,我本來是想共用圓上頂點的,這樣整個錐體的頂點數就是20,但經過測試是不可以的,我參考了Cube,頂點數是24,說明不同面的頂點是不能共用的,可能是因為法線方向等因素吧。
修改后的完整代碼如下:

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參數
 float myRadius = 0.5f;
 int myAngleStep = 20;
 Vector3 myTopCenter = new Vector3(0, 1, 0);
 Vector3 myBottomCenter = Vector3.zero;
 //構建頂點數組和UV數組
 Vector3[] myVertices = new Vector3[360 / myAngleStep * 2 + 2];
 //
 Vector2[] myUV = new Vector2[myVertices.Length];
 //這里我把錐尖頂點放在了頂點數組最后一個
 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);
 //因為圓上頂點坐標相同,只是索引不同,所以這里循環一般長度即可
 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);

 }
 //構建三角形數組
 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
  {
  //繪制錐體部分,索引組起始點都為錐尖
  myTriangle[i] = myVertices.Length - 1;
  //錐體最后一個三角形的中間頂點索引值為19
  myTriangle[i + 1] = i == myTriangle.Length - 3 ? 19 : i / 3 + 2;
  myTriangle[i + 2] = i / 3 + 1;
  }
 }

 //構建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;
 //分配材質
 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;
  //設置創建操作可撤銷
  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;
  //設置創建操作可撤銷
  Undo.RegisterCreatedObjectUndo(cone, "Undo Creating Cone");
  SetMesh(cone);
 }
 }
}

PS:這里的uv設置比較簡單,所以對貼圖也特定要求,不然圖片會比較扭曲,需要的朋友可以自行修改。

結果

Unity3D自定義創建圓錐體

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女