日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

用Unity盖房子(一):《勇者斗恶龙:建造者2》游戏功能的猜想

發布時間:2024/8/26 编程问答 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 用Unity盖房子(一):《勇者斗恶龙:建造者2》游戏功能的猜想 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.



前言

前段時間一直忙著研究CatLikeCoding的HexMap系列教程,好長時間沒有新開坑寫點小工程了,這次又有了些新點子,與大家分享一下。

現在輪到本期主角出場了:《勇者斗惡龍:建造者2》(以下簡稱DQB2)
?


游戲類型是大家都不陌生的開放世界方塊建造類。這類游戲之前也玩過不少,比如《七日殺》、《傳送門騎士》,當然還有大名鼎鼎的《Minecraft》,但就個人感覺而言,DQB2在可玩性上要高很多,可以說是此類游戲的集大成之作。并且還融合了一些經營模擬養成,RPG戰斗的元素到其中,僅主線任務就讓我玩得不亦樂乎。

簡而言之就是:我沉迷了。
?


單就建造而論,DQB2里的工具就設計的非常實用,比如一次性更換多個同種類型方塊的刷子,一次獲取大量素材的大錘子等,此外還發現了一個十分貼心的功能。

大家都知道建造類游戲有一個問題,就是玩家上下限差距太大。例如《Minecraft》還有一個別稱叫"別人的世界"。好不容易自己搭建了一個火柴盒,突然看到視頻里大佬搭建的世界奇觀,突然就失去玩下去的動力了。即便是想仿照大佬的建筑復制一遍,所需要的工作量也是驚人的,大多數咸魚(比如我)就直接放棄了。而在DQB2中這個問題得到極大改善,你可以直接聯機到大佬的島上閑逛參觀,看見喜歡的建筑樣式可以直接把設計圖拷貝回來,甚至搭建都不用自己動手,在圖紙規劃地旁邊放上一個裝有材料的箱子,NPC就會自動幫忙建造。這簡直是建造游戲愛好者的福音,極大的提升了游戲體驗,同時也讓我對此功能的實現方式產生興趣。
?


那么關于安利部分就此打住,進入正題。

下面用Unity來對自動建造功能做一個探索,預計內容分為兩篇。第一篇是關于方塊建造游戲基礎功能在Unity內的實現,第二篇是NPC自動建造系統功能實現方式的猜想。

另外,由于難度直線升高,HexMap系列教程的翻譯進度會稍微放緩,但肯定會繼續更新下去直到完結,這一點可以放心。

1搭建方塊場景

要實現方塊場景的搭建編輯功能,最簡單粗暴的方法是每一個方塊都視為一個單獨的GameObject,每次點擊時實例化一個方塊。簡單歸簡單,但這種方式在效率上肯定會有問題,特別是當在較大的地圖上計算物理碰撞而每個方塊都有自己的碰撞器時。我不知道好點的電腦運行起來如何,反正我的老爺機肯定就卡逑了。

剛好這個問題可以參考之前六邊形地圖教程里的思路,把每一次的編輯看做是對一整塊mesh里頂點的修改。

(1)獲取方塊放置坐標

首先要做的是在鼠標指向一個位置時,獲取這個位置的方塊坐標。即使是粗暴方法這一步也是省不掉的。

為方便起見就設定每個方塊的邊長是Unity里的標準單位1,那么無論怎么轉換,方塊坐標都處于方塊的中心位置,坐標的小數部分肯定都是是0.5。
?

  • public static Vector3 FromWorldPositionToCubePosition(Vector3 position)
  • ? ? {
  • ? ?? ???Vector3 resut = Vector3.zero;
  • ? ?? ???resut.x = position.x > 0 ? (int)position.x * 1f + 0.5f : (int)position.x * 1f - 0.5f;
  • ? ?? ???resut.y = position.y > 0 ? (int)position.y * 1f + 0.5f : (int)position.y * 1f - 0.5f;
  • ? ?? ???resut.z = position.z > 0 ? (int)position.z * 1f + 0.5f : (int)position.z * 1f - 0.5f;
  • ? ?? ???return resut;
  • ? ? }
  • 復制代碼


    然后通過屏幕發射射線,換算擊中坐標為方塊坐標,并用Gizmo驗證一下計算是否正確。當然,別忘了新建的測試plane上要有碰撞器,不然射線檢測不會起作用。
    ?

  • ??bool GetMouseRayPoint(out Vector3 addCubePosition)
  • ? ? {
  • ? ?? ???Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
  • ? ?? ???RaycastHit hitInfo;
  • ? ?? ???if (Physics.Raycast(ray, out hitInfo))
  • ? ?? ???{
  • ? ?? ?? ?? ?Debug.DrawRay(hitInfo.point, Vector3.up, Color.red);
  • ? ?? ?? ?? ?addCubePosition = CubeMetrics.FromWorldPositionToCubePosition(hitInfo.point - ray.direction * 0.001f);
  • ? ?? ?? ???
  • ? ?? ?? ?? ?return true;
  • ? ?? ???}
  • ? ?? ???addCubePosition = Vector3.zero;
  • ? ?? ???return false;
  • ? ? }
  • ? ?private void OnDrawGizmos()
  • ? ? {
  • ? ?? ???
  • ? ?? ???if (GetMouseRayPoint(out Vector3 cubePosition)
  • ? ?? ???{
  • ? ?? ?? ?? ?Gizmos.DrawWireCube(cubePosition, Vector3.one);
  • ? ?? ???}
  • ? ?? ???
  • ? ? }
  • 復制代碼

    紅線即鼠標射線擊中位置


    (2)方塊構建

    方塊的位置正確無誤之后,下一步就是添加方塊的操作。之前已經說了,要用修改頂點數據的方式來實現這個功能,所以第一步先定義當正方體中心為零點時八個頂點的相對坐標。
    ?

  • public static Vector3[] cubeVertex =
  • ? ?{
  • ? ?? ???//上面四個頂點
  • ? ?? ???//左上
  • ? ?? ???new Vector3(-0.5f,0.5f,0.5f),
  • ? ?? ???//右上
  • ? ?? ???new Vector3(0.5f,0.5f,0.5f),
  • ? ?? ???//右下
  • ? ?? ???new Vector3 (0.5f,0.5f,-0.5f),
  • ? ?? ???//左下
  • ? ?? ???new Vector3(-0.5f,0.5f,-0.5f),
  • ? ?? ???//下面四個頂點
  • ? ?? ???//左上
  • ? ?? ???new Vector3(-0.5f,-0.5f,0.5f),
  • ? ?? ???//右上
  • ? ?? ???new Vector3(0.5f,-0.5f,0.5f),
  • ? ?? ???//右下
  • ? ?? ???new Vector3(0.5f,-0.5f,-0.5f),
  • ? ?? ???//左下
  • ? ?? ???new Vector3(-0.5f,-0.5f,-0.5f)
  • ? ? };
  • 復制代碼


    然后為整個mesh新建一個類,用來處理方塊的形狀問題。
    ?

  • [RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
  • public class CubeMesh : MonoBehaviour
  • {
  • ? ? Mesh cubeMesh;
  • ? ? MeshCollider meshCollider;
  • ? ? List<Vector3> vertices;
  • ? ? List<int> triangles;
  • private void Awake()
  • {
  • ? ? {
  • ? ?? ???GetComponent<MeshFilter>().mesh = cubeMesh = new Mesh();
  • ? ?? ???meshCollider = gameObject.AddComponent<MeshCollider>();? ?? ?
  • ? ?? ???vertices = new List<Vector3>();
  • ? ?? ???triangles = new List<int>();? ?
  • ? ? }
  • }
  • 復制代碼


    由于是正方體,它的三角剖分非常簡單且有規律,所以可以寫一個較為通用的方法來三角化。這樣能使代碼更易讀,且更方便后續功能的添加。
    ?

  • public void TriaggulateCube(Vector3 p)
  • ? ? {
  • ? ?? ? Vector3 v1 = p + CubeMetrics.cubeVertex[0];
  • ? ?? ? Vector3 v2 = p + CubeMetrics.cubeVertex[1];
  • ? ?? ? Vector3 v3 = p + CubeMetrics.cubeVertex[2];
  • ? ?? ? Vector3 v4 = p + CubeMetrics.cubeVertex[3];
  • ? ?? ? Vector3 v5 = p + CubeMetrics.cubeVertex[4];
  • ? ?? ? Vector3 v6 = p + CubeMetrics.cubeVertex[5];
  • ? ?? ? Vector3 v7 = p + CubeMetrics.cubeVertex[6];
  • ? ?? ? Vector3 v8 = p + CubeMetrics.cubeVertex[7];
  • ? ?? ???for (int i = 0; i < 6; i++)
  • ? ?? ???{? ?? ?? ?
  • ? ?? ?? ? AddCubeSurface(v1, v2, v3, v4,v5, v6, v7, v8,(CubeSurface)i);? ?? ?
  • ? ?? ???}
  • ? ? }
  • void AddCubeSurface(Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4,
  • ? ?? ?? ?? ?? ?? ?? ?Vector3 v5, Vector3 v6, Vector3 v7, Vector3 v8)
  • ? ? {
  • ? ?? ???switch (suface)
  • ? ?? ???{
  • ? ?? ?? ?? ?case CubeSurface.up:? ?? ?? ?
  • ? ?? ?? ?? ?? ? AddSurfaceQuad(v1, v2, v3, v4);
  • ? ?? ?? ?? ?? ? break;
  • ? ?? ?? ?? ?case CubeSurface.down:
  • ? ?? ?? ?? ?? ? AddSurfaceQuad(v6, v5, v8, v7);
  • ? ?? ?? ?? ?? ? break;
  • ? ?? ?? ?? ?case CubeSurface.left:
  • ? ?? ?? ?? ?? ? AddSurfaceQuad(v1, v4, v8, v5);
  • ? ?? ?? ?? ?? ? break;
  • ? ?? ?? ?? ?case CubeSurface.right:
  • ? ?? ?? ?? ?? ? AddSurfaceQuad(v3, v2, v6, v7);
  • ? ?? ?? ?? ?? ? break;
  • ? ?? ?? ?? ?case CubeSurface.front:
  • ? ?? ?? ?? ?? ? AddSurfaceQuad(v2, v1, v5, v6);
  • ? ?? ?? ?? ?? ? break;
  • ? ?? ?? ?? ?case CubeSurface.back:
  • ? ?? ?? ?? ?? ? AddSurfaceQuad(v4, v3, v7, v8);
  • ? ?? ?? ?? ?? ? break;
  • ? ?? ???}
  • ? ? }
  • void AddSurfaceQuad(Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4)
  • ? ? {
  • ? ?? ???int vertexIndex = vertices.Count;
  • ? ?? ???vertices.Add(v1); vertices.Add(v2); vertices.Add(v3); vertices.Add(v4);
  • ? ?? ???triangles.Add(vertexIndex); triangles.Add(vertexIndex + 1); triangles.Add(vertexIndex + 2);
  • ? ?? ???triangles.Add(vertexIndex); triangles.Add(vertexIndex + 2); triangles.Add(vertexIndex + 3);
  • ? ? }
  • public enum CubeSurface
  • {
  • ? ? front, right, back, left, up, down
  • }
  • 復制代碼


    頂點和三角形數據填充進去后再刷新mesh。
    ?

  • public void Apply()
  • ? ? {
  • ? ?? ???cubeMesh.SetVertices(vertices);
  • ? ?? ???cubeMesh.SetTriangles(triangles, 0);
  • ? ?? ???cubeMesh.RecalculateNormals();
  • ? ?? ???meshCollider.sharedMesh = cubeMesh;
  • ??
  • ? ?? ???
  • ? ? }
  • ? ? public void Clear()
  • ? ? {
  • ? ?? ???vertices.Clear();
  • ? ?? ???triangles.Clear();
  • ? ?? ???cubeMesh.Clear();
  • ? ? }
  • 復制代碼


    可以看到方塊雖然是一個一個添加的,但數據是表現在一個mesh上的。

    (3)刪除方塊

    能添加自然就應該能刪除,所以下一步是實現刪除的功能,后續還能擴展成DQB2里的創造師手套搬運功能。

    不知道有沒有同學注意到,之前在寫射線坐標轉換成方塊坐標時,代碼里給了一個射線反方向的微小偏移,這是為了防止方塊坐標在某些角度計算到了錯誤的位置。由于現在所有方塊共用的一個碰撞器,所以沒辦法通過碰撞信息來識別點擊的是哪個方塊。那么反過來考慮這個問題,直接通過給射線正方向的偏移,就能讓換算坐標變為當前鼠標指著的方塊坐標。
    ?

  • bool GetMouseRayPoint(out Vector3 addCubePosition, out Vector3 removeCubePosition)
  • ? ? {
  • ? ?? ???Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
  • ? ?? ???RaycastHit hitInfo;
  • ? ?? ???if (Physics.Raycast(ray, out hitInfo))
  • ? ?? ???{
  • ? ?? ?? ?? ?Debug.DrawRay(hitInfo.point, Vector3.up, Color.red);
  • ? ?? ?? ?? ?addCubePosition = CubeMetrics.FromWorldPositionToCubePosition(hitInfo.point - ray.direction * 0.001f);
  • ? ?? ?? ?? ?removeCubePosition = CubeMetrics.FromWorldPositionToCubePosition(hitInfo.point + ray.direction * 0.001f);
  • ? ?? ?? ?? ?return true;
  • ? ?? ???}
  • ? ?? ???addCubePosition = Vector3.zero;
  • ? ?? ???removeCubePosition = Vector3.zero;
  • ? ?? ???return false;
  • ? ? }
  • 復制代碼


    坐標計算是沒問題,但是該如何告訴mesh刪除這些指定的頂點和三角形呢?

    辦法當然是有,射線的RaycastHit結構體里是可以獲取擊中位置的三角形下標和uv坐標的,憑借這些信息已經足夠計算出要刪除的頂點和三角形下標了。
    ?


    但即使能算出來,用腳指頭想也知道會很復雜,咱們不是來做數學題的,所以換個思路。

    我們可以這么去思考這個問題:用空的GameObject當做信息載體,在添加方塊時添加這些GameObject到mesh腳本新建的容器里,然后遍歷這個容器來完成三角化。同理,刪除方塊時也根據坐標從容器中找到這個GameObject,然后刪除它并更新mesh。
    ?

  • public class CubeInfo : MonoBehaviour
  • {
  • ? ? public string cubeName;? ?
  • ? ? public Vector3 Position
  • ? ? {
  • ? ?? ???get
  • ? ?? ???{
  • ? ?? ?? ?? ?return transform.localPosition;
  • ? ?? ???}
  • ? ? }
  • }
  • 復制代碼


    新建上面的腳本并掛在一個空的GameObject上并拖成預制體,然后在添加方塊的時候實例化這個預制體并加到列表中。
    ?

  • public void AddCube(Vector3 position)
  • ? ? {
  • ? ?? ???CubeInfo cube = Instantiate(CubePrefab, position, Quaternion.identity, transform);
  • ? ?? ???Debug.Log("傳入坐標" + position + "||cube本地坐標" + cube.transform.localPosition+"type:"+(int)type);
  • ? ?? ?
  • ? ?? ???Allcubes.Add(cube);
  • ? ?? ???TriangulateAllCubes();
  • ? ? }
  • void TriangulateAllCubes()
  • ? ? {
  • ? ?? ???Clear();
  • ? ?? ???foreach (var c in Allcubes)
  • ? ?? ???{
  • ? ?? ?? ?? ?TriaggulateCube(c);
  • ? ?? ???}
  • ? ?? ???Apply();
  • ? ? }
  • 復制代碼


    這樣一來刪除方塊的方法也容易寫了。
    ?

  • public void RemoveCube(Vector3 positon)
  • ? ? {
  • ? ?? ???CubeInfo cube;
  • ? ?? ???if (GetCubeByPosition(positon, out cube))
  • ? ?? ???{
  • ? ?? ?? ?? ?Allcubes.Remove(cube);
  • ? ?? ?? ?? ?Destroy(cube.gameObject);
  • ? ?? ?? ?? ?TriangulateAllCubes();
  • ? ?? ???}
  • ? ? }
  • bool GetCubeByPosition(Vector3 position, out CubeInfo resutCube)
  • ? ? {
  • ? ?? ???for (int i = 0; i < Allcubes.Count; i++)
  • ? ?? ???{
  • ? ?? ?? ?? ?if (Allcubes[i].Position == position)
  • ? ?? ?? ?? ?{
  • ? ?? ?? ?? ?? ? resutCube = Allcubes[i];
  • ? ?? ?? ?? ?? ? return true;
  • ? ?? ?? ?? ?}
  • ? ?? ???}
  • ? ?? ???resutCube = null;
  • ? ?? ???return false;
  • ? ? }
  • 復制代碼


    2設置相鄰方塊與頂點優化

    到目前為止添加和刪除方塊都實現了,來考慮一下在兩個方塊相鄰時隱藏接觸面來優化頂點的方法。這一步并不是很必要,優化頂點后并不能帶來明顯的提升,就保持現在這樣也沒問題。但考慮到在后面還要給NPC做尋路功能,獲取方塊之間的相鄰關系是必須的。以此為前提條件的基礎下,那么優化頂點其實就是個順帶的事情。

    (1)獲取方塊之間相鄰關系

    首先自然就是獲取相鄰關系,在CubeInfo腳本里新建一個數組去存放相鄰方塊的引用關系。基于導航的需要,水平面的每個朝向上還要額外存儲斜上斜下兩個方塊,因此最大相鄰方塊的個數就是3X4+2=14個。把把數組的長度設為14,同時把方向用枚舉保存。
    ?

  • public enum CubeNeighborDirection
  • {
  • ? ? front,
  • ? ? frontUp,
  • ? ? frontDown,
  • ? ? back,
  • ? ? backUp,
  • ? ? backDown,
  • ? ? left,
  • ? ? leftUp,
  • ? ? leftDown,
  • ? ? right,
  • ? ? rightUp,
  • ? ? rightDown,
  • ? ? up,
  • ? ? dowm,
  • }
  • 復制代碼


    下一步是寫一個方法,根據當前方塊的坐標和指定方向來推算出這個方向上的方塊坐標。
    ?

  • public static Vector3 GetCubePosByDirection(Vector3 pos,CubeNeighborDirection direction)
  • ? ? {??
  • ? ?? ???switch (direction)
  • ? ?? ???{
  • ? ?? ?? ?? ?case CubeNeighborDirection.front:
  • ? ?? ?? ?? ?? ? pos += Vector3.forward;
  • ? ?? ?? ?? ?? ? break;
  • ? ?? ?? ?? ?case CubeNeighborDirection.frontUp:
  • ? ?? ?? ?? ?? ? pos += Vector3.forward + Vector3.up;
  • ? ?? ?? ?? ?? ? break;
  • ? ?? ?? ?? ?case CubeNeighborDirection.frontDown:
  • ? ?? ?? ?? ?? ? pos += Vector3.forward + Vector3.down;
  • ? ?? ?? ?? ?? ? break;
  • ? ?? ?? ?? ?case CubeNeighborDirection.back:
  • ? ?? ?? ?? ?? ? pos += Vector3.back;
  • ? ?? ?? ?? ?? ? break;
  • ? ?? ?? ?? ?case CubeNeighborDirection.backUp:
  • ? ?? ?? ?? ?? ? pos += Vector3.back + Vector3.up;
  • ? ?? ?? ?? ?? ? break;
  • ? ?? ?? ?? ?case CubeNeighborDirection.backDown:
  • ? ?? ?? ?? ?? ? pos += Vector3.back + Vector3.down;
  • ? ?? ?? ?? ?? ? break;
  • ? ?? ?? ?? ?case CubeNeighborDirection.left:
  • ? ?? ?? ?? ?? ? pos += Vector3.left;
  • ? ?? ?? ?? ?? ? break;
  • ? ?? ?? ?? ?case CubeNeighborDirection.leftUp:
  • ? ?? ?? ?? ?? ? pos += Vector3.left + Vector3.up;
  • ? ?? ?? ?? ?? ? break;
  • ? ?? ?? ?? ?case CubeNeighborDirection.leftDown:
  • ? ?? ?? ?? ?? ? pos += Vector3.left + Vector3.down;
  • ? ?? ?? ?? ?? ? break;
  • ? ?? ?? ?? ?case CubeNeighborDirection.right:
  • ? ?? ?? ?? ?? ? pos += Vector3.right;
  • ? ?? ?? ?? ?? ? break;
  • ? ?? ?? ?? ?case CubeNeighborDirection.rightUp:
  • ? ?? ?? ?? ?? ? pos += Vector3.right + Vector3.up;
  • ? ?? ?? ?? ?? ? break;
  • ? ?? ?? ?? ?case CubeNeighborDirection.rightDown:
  • ? ?? ?? ?? ?? ? pos += Vector3.right + Vector3.down;
  • ? ?? ?? ?? ?? ? break;
  • ? ?? ?? ?? ?case CubeNeighborDirection.up:
  • ? ?? ?? ?? ?? ? pos += Vector3.up;
  • ? ?? ?? ?? ?? ? break;
  • ? ?? ?? ?? ?case CubeNeighborDirection.dowm:
  • ? ?? ?? ?? ?? ? pos += Vector3.down;
  • ? ?? ?? ?? ?? ? break;? ?? ?? ?? ?? ?
  • ? ?? ???}
  • ? ?? ???return pos;
  • ? ? }
  • 復制代碼


    下一步就是根據這個坐標,在之前保存的所有CubeInfo的容器中找到與之對應的方塊。
    ?

  • bool GetCubeByDirection(Vector3 position, CubeNeighborDirection direction, out CubeInfo resutCube)
  • ? ? {
  • ? ?? ???CubeInfo cube;
  • ? ?? ???if (GetCubeByPosition(CubeMetrics.GetCubePosByDirection(position, direction), out cube))
  • ? ?? ???{
  • ? ?? ?? ?? ?resutCube = cube;
  • ? ?? ?? ?? ?return true;
  • ? ?? ???}
  • ? ?? ???resutCube = cube;
  • ? ?? ???return false;
  • ? ? }
  • ? ? bool GetCubeByPosition(Vector3 position, out CubeInfo resutCube)
  • ? ? {
  • ? ?? ???for (int i = 0; i < Allcubes.Count; i++)
  • ? ?? ???{
  • ? ?? ?? ?? ?if (Allcubes[i].Position == position)
  • ? ?? ?? ?? ?{
  • ? ?? ?? ?? ?? ? resutCube = Allcubes[i];
  • ? ?? ?? ?? ?? ? return true;
  • ? ?? ?? ?? ?}
  • ? ?? ???}
  • ? ?? ???resutCube = null;
  • ? ?? ???return false;
  • ? ? }
  • 復制代碼


    然后就可以設置相鄰方塊了,由于方塊的添加有先后,可以在為一個方塊設置其相鄰方塊時在相鄰方塊的相反方向上設置自己為相鄰方塊。但是方向的數量并不對稱,方向的枚舉轉換成int不好找到規律,所以就用笨辦法。
    ?

  • ? ?public void SetNeighbor(CubeNeighborDirection direction,CubeInfo cube)
  • ? ? {
  • ? ?? ???neighbors[(int)direction] = cube;
  • ? ?? ???cube.neighbors[(int)CubeMetrics.GetOppositeDirection(direction)] = this;
  • ? ? }
  • ??public static CubeNeighborDirection GetOppositeDirection(CubeNeighborDirection direction)
  • ? ? {
  • ? ?? ???switch(direction)
  • ? ?? ???{
  • ? ?? ?? ?? ?case CubeNeighborDirection.front:
  • ? ?? ?? ?? ?? ? return CubeNeighborDirection.back;
  • ? ?? ?? ?? ?case CubeNeighborDirection.frontUp:
  • ? ?? ?? ?? ?? ? return CubeNeighborDirection.backDown;
  • ? ?? ?? ?? ?case CubeNeighborDirection.frontDown:
  • ? ?? ?? ?? ?? ? return CubeNeighborDirection.backUp;
  • ? ?? ?? ?? ?case CubeNeighborDirection.back:
  • ? ?? ?? ?? ?? ? return CubeNeighborDirection.front;
  • ? ?? ?? ?? ?case CubeNeighborDirection.backUp:
  • ? ?? ?? ?? ?? ? return CubeNeighborDirection.frontDown;
  • ? ?? ?? ?? ?case CubeNeighborDirection.backDown:
  • ? ?? ?? ?? ?? ? return CubeNeighborDirection.frontUp;
  • ? ?? ?? ?? ?case CubeNeighborDirection.left:
  • ? ?? ?? ?? ?? ? return CubeNeighborDirection.right;
  • ? ?? ?? ?? ?case CubeNeighborDirection.leftUp:
  • ? ?? ?? ?? ?? ? return CubeNeighborDirection.rightDown;
  • ? ?? ?? ?? ?case CubeNeighborDirection.leftDown:
  • ? ?? ?? ?? ?? ? return CubeNeighborDirection.rightUp;
  • ? ?? ?? ?? ?case CubeNeighborDirection.right:
  • ? ?? ?? ?? ?? ? return CubeNeighborDirection.left;
  • ? ?? ?? ?? ?case CubeNeighborDirection.rightUp:
  • ? ?? ?? ?? ?? ? return CubeNeighborDirection.leftDown;
  • ? ?? ?? ?? ?case CubeNeighborDirection.rightDown:
  • ? ?? ?? ?? ?? ? return CubeNeighborDirection.leftUp;
  • ? ?? ?? ?? ?case CubeNeighborDirection.up:
  • ? ?? ?? ?? ?? ? return CubeNeighborDirection.dowm;
  • ? ?? ?? ?? ?case CubeNeighborDirection.dowm:
  • ? ?? ?? ?? ?? ? return CubeNeighborDirection.up;
  • ? ?? ?? ?? ?default:
  • ? ?? ?? ?? ?? ? return direction;
  • ? ?? ???}
  • ? ? }
  • 復制代碼


    當然也別忘了在刪除方塊時把相鄰關系也更新一下。
    ?

  • public void RemoveNeighbor(CubeNeighborDirection direction)
  • ? ? {
  • ? ?? ???neighbors[(int)direction] = null;
  • ? ? }
  • 復制代碼




    現在就能在添加和刪除時設置正確的相鄰關系了,下一步就是優化頂點了。

    (2)頂點優化

    現在能知道方塊之間的相鄰關系,買手機號碼那在相鄰方塊的方向上隱藏當前面就是一句話的事情了。根據之前表示相鄰方向的枚舉可知,下標為0,3,6,9,12,13的相鄰方塊分別對應前,后,左,右,上,下,接下來就是根據當前三角化的面的朝向來檢測相鄰方塊是否為空。

  • public void TriaggulateCube(Vector3 p)
  • ? ? {
  • ? ?? ? Vector3 v1 = p + CubeMetrics.cubeVertex[0];
  • ? ?? ? Vector3 v2 = p + CubeMetrics.cubeVertex[1];
  • ? ?? ? Vector3 v3 = p + CubeMetrics.cubeVertex[2];
  • ? ?? ? Vector3 v4 = p + CubeMetrics.cubeVertex[3];
  • ? ?? ? Vector3 v5 = p + CubeMetrics.cubeVertex[4];
  • ? ?? ? Vector3 v6 = p + CubeMetrics.cubeVertex[5];
  • ? ?? ? Vector3 v7 = p + CubeMetrics.cubeVertex[6];
  • ? ?? ? Vector3 v8 = p + CubeMetrics.cubeVertex[7];
  • ? ?? ???for (int i = 0; i < 6; i++)
  • ? ?? ???{? ?
  • ? ?? ?? ?? ?if (i == 0 && cube.neighbors[0]) { continue; }
  • ? ?? ?? ?? ?else if (i == 1 && cube.neighbors[3]) { continue; }
  • ? ?? ?? ?? ?else if (i == 2 && cube.neighbors[6]) { continue; }
  • ? ?? ?? ?? ?else if (i == 3 && cube.neighbors[9]) { continue; }
  • ? ?? ?? ?? ?else if (i == 4 && cube.neighbors[12]) { continue; }
  • ? ?? ?? ?? ?else if (i == 5 && cube.neighbors[13]) { continue; }? ?? ?
  • ? ?? ?? ? AddCubeSurface(v1, v2, v3, v4,v5, v6, v7, v8,(CubeSurface)i);? ?? ?
  • ? ?? ???}
  • ? ? }
  • 復制代碼

    減肥前

    減肥成功后


    雖然看不出來變化,但表現在數據上還是蠻明顯的。

    3方塊的類型UV設置

    由于所有方塊都用一個Mesh表示,所以直接改其材質球的顏色是無法區分方塊類型的。那么辦法就是把所有的方塊紋理集合在一張紋理圖上,而根據方塊的類型不同傳入不同的UV坐標。所以首先在CubeInfo里定義方塊類型的枚舉字段。
    ?

  • ublic class CubeInfo : MonoBehaviour
  • {
  • ? ? public string cubeName;
  • ? ? public CubeInfo[] neighbors;
  • ? ? public M_CubeType type;
  • }
  • public enum M_CubeType
  • {
  • ? ? test1,
  • ? ? test2,
  • ? ? test3,
  • ? ? test4,
  • ? ? test5,
  • ? ? test6
  • }
  • 復制代碼


    先暫且用test占位,后面再來考慮具體的類型。至于為什么是6個類型,因為正方形有六個面,設置為六種類型剛好紋理圖就是正方形。

    然后就找也好,自己畫也好,搞到一張6乘6正方形的紋理圖,大概就像這樣:
    ?

    隨手畫的,不好看輕噴..


    把紋理圖導入Unity中,由于這是像素圖,所以記得修改圖片的Filter Mode為Point,然后把圖片類型改為Sprite。
    ?


    接下來在添加頂點信息時同時把UV信息也添加進去。這里用了一個易于擴展的寫法,之后擴展方塊類型也可以直接修改TypeCount的值,很方便。
    ?

  • void AddCubeSurface(Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4,
  • ? ?? ?? ?? ?? ?? ?? ?? ?Vector3 v5, Vector3 v6, Vector3 v7, Vector3 v8,
  • ? ?? ?? ?? ?? ?? ?? ?? ?CubeSurface suface, M_CubeType type,int TypeCount)
  • ? ? {
  • ? ?? ???//正方體為六個面,若使UV圖為正方形,則暫設正方體的類型為n種
  • ? ?? ???//v坐標基點:0~5/n
  • ? ?? ???float uCoordinate = ((int)suface * 1.0f) / 6.0f;
  • ? ?? ???float vCoordinate=((int)type*1.0f)/TypeCount*1.0f;
  • ? ???
  • ? ?? ???Vector2 uvBasePoint=new Vector2(uCoordinate,vCoordinate);
  • ? ?? ???switch (suface)
  • ? ?? ???{
  • ? ?? ?? ?? ?case CubeSurface.up:? ?? ?? ?
  • ? ?? ?? ?? ?? ? AddSurfaceQuad(v1, v2, v3, v4,uvBasePoint,TypeCount);
  • ? ?? ?? ?? ?? ? break;
  • ? ?? ?? ?? ?case CubeSurface.down:
  • ? ?? ?? ?? ?? ? AddSurfaceQuad(v6, v5, v8, v7,uvBasePoint, TypeCount);
  • ? ?? ?? ?? ?? ? break;
  • ? ?? ?? ?? ?case CubeSurface.left:
  • ? ?? ?? ?? ?? ? AddSurfaceQuad(v1, v4, v8, v5,uvBasePoint, TypeCount);
  • ? ?? ?? ?? ?? ? break;
  • ? ?? ?? ?? ?case CubeSurface.right:
  • ? ?? ?? ?? ?? ? AddSurfaceQuad(v3, v2, v6, v7,uvBasePoint, TypeCount);
  • ? ?? ?? ?? ?? ? break;
  • ? ?? ?? ?? ?case CubeSurface.front:
  • ? ?? ?? ?? ?? ? AddSurfaceQuad(v2, v1, v5, v6,uvBasePoint, TypeCount);
  • ? ?? ?? ?? ?? ? break;
  • ? ?? ?? ?? ?case CubeSurface.back:
  • ? ?? ?? ?? ?? ? AddSurfaceQuad(v4, v3, v7, v8,uvBasePoint, TypeCount);
  • ? ?? ?? ?? ?? ? break;
  • ? ?? ???}
  • ? ? }
  • void AddSurfaceQuad(Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4, Vector2 uvDp,int uvCount)
  • ? ? {
  • ? ?? ???AddQuad(v1, v2, v3, v4);
  • ? ?? ???AddQuadUV(uvDp,uvCount);
  • ? ? }
  • void AddQuadUV(Vector2 uvBasePoint,int TypeCount)
  • ? ? {
  • ? ?? ???float deltaU = 1f / 6.0f;
  • ? ?? ???float deltaV = 1f / TypeCount*1.0f;
  • ? ?? ???Vector2 uv1 = new Vector2(uvBasePoint.x, uvBasePoint.y + deltaV);
  • ? ?? ???Vector2 uv2 = new Vector2(uvBasePoint.x + deltaU, uvBasePoint.y + deltaV);
  • ? ?? ???Vector2 uv3 = new Vector2(uvBasePoint.x + deltaU, uvBasePoint.y);
  • ? ?? ???Vector2 uv4 = uvBasePoint;
  • ? ?? ???uvs.Add(uv1); uvs.Add(uv2); uvs.Add(uv3); uvs.Add(uv4);
  • ? ? }
  • 復制代碼


    在場景里新建6個toggle作為方塊類型選擇,并關聯至腳本里修改方塊類型的枚舉。
    ?

  • <p>public void TypeSelect(int type)</p><p>
  • </p><p>{</p><p>
  • </p><p>cubeType=(M_CubeType)type;</p><p>
  • </p><p>}</p>
  • 復制代碼


    現在就可以根據選中的類型來方便切換方塊類型了。
    ?


    4方塊旋轉

    我們的項目里現在都是方塊,而且由于我畫的UV圖除了最上面都是一個樣子,方塊能不能旋轉無所謂。但原版游戲中不是所有的建造素材都是方塊形狀,其中可能有階梯或者別的不對稱幾何形狀,我們后續也可以往這方面擴展,所以我們還是有必要去實現這個方塊旋轉功能。還是用枚舉來定義方塊的朝向,為方便起見,我們把旋轉的范圍限制在水平面上。
    ?

  • bool OrientateControl()
  • ? ? {
  • ? ?? ???CubeOrientate temp = Orientate;
  • ? ?? ???if (Input.GetKeyDown(KeyCode.Q))
  • ? ?? ???{
  • ? ?? ?? ?? ?Orientate = (int)Orientate == 0 ? (CubeOrientate)3 : (CubeOrientate)Orientate - 1;
  • ? ?? ???}
  • ? ?? ???else if (Input.GetKeyDown(KeyCode.E))
  • ? ?? ???{
  • ? ?? ?? ?? ?Orientate = (int)Orientate == 3 ? (CubeOrientate)0 : (CubeOrientate)Orientate + 1;
  • ? ?? ???}
  • ? ?? ???if(temp!=Orientate)
  • ? ?? ???{
  • ? ?? ?? ?? ?return true;
  • ? ?? ???}
  • ? ?? ???return false;
  • ? ? }
  • ? ? void Update()
  • ? ? {
  • ? ?? ?
  • ? ?? ???if(OrientateControl())
  • ? ?? ???{
  • ? ?? ?? ?? ?preview.UpdateCube(cubeType, Orientate);
  • ? ?? ???}
  • ? ?? ?
  • ? ? }
  • public enum CubeOrientate
  • {
  • ? ? front, right, back, left
  • }
  • 復制代碼


    然后在CubeInfo里定義方塊的朝向字段,在添加方塊時將當前朝向一并傳入。
    ?

  • ??public void AddCube(Vector3 position, M_CubeType type,CubeOrientate orientate)
  • ? ? {
  • ? ?? ???CubeInfo cube = Instantiate(CubePrefab, position, Quaternion.identity, transform);
  • ? ?? ???cube.type = type;
  • ? ?? ???cube.Orientate = orientate;
  • ? ?? ???Debug.Log("傳入坐標" + position + "||cube本地坐標" + cube.transform.localPosition+"type:"+(int)type);
  • ? ?? ?
  • ? ?? ???SetNeighbors(cube);
  • ? ?? ???TriangulateAllCubes();
  • ? ? }
  • 復制代碼


    使用屬性,在修改方塊朝向枚舉的同時也直接修改實際朝向。
    ?

  • public CubeOrientate Orientate
  • ? ? {
  • ? ?? ???get
  • ? ?? ???{
  • ? ?? ?? ?? ?return orientate;
  • ? ?? ???}
  • ? ?? ???set
  • ? ?? ???{
  • ? ?? ?? ?? ?
  • ? ?? ?? ?? ?switch(value)
  • ? ?? ?? ?? ?{
  • ? ?? ?? ?? ?? ? case CubeOrientate.front:
  • ? ?? ?? ?? ?? ?? ???transform.forward = Vector3.forward;
  • ? ?? ?? ?? ?? ?? ???break;
  • ? ?? ?? ?? ?? ? case CubeOrientate.back:
  • ? ?? ?? ?? ?? ?? ???transform.forward = Vector3.back;
  • ? ?? ?? ?? ?? ?? ???break;
  • ? ?? ?? ?? ?? ? case CubeOrientate.left:
  • ? ?? ?? ?? ?? ?? ???transform.forward = Vector3.left;
  • ? ?? ?? ?? ?? ?? ???break;
  • ? ?? ?? ?? ?? ? case CubeOrientate.right:
  • ? ?? ?? ?? ?? ?? ???transform.forward = Vector3.right;
  • ? ?? ?? ?? ?? ?? ???break;
  • ? ?? ?? ?? ?}
  • ? ?? ?? ?? ?orientate = value;
  • ? ?? ???}
  • ? ? }
  • 復制代碼


    剛才很巧合的畫了一個六面不同的UV,剛好用來檢測旋轉功能是否正確。(怎么可能是巧合,我肯定是故意的)
    ?


    但是還沒完,別忘了我們之前還為mesh做了"減肥",那么現在旋轉了方塊之后對于需要隱藏面的判定就會出問題,所以這個地方需要修正。干脆直接把這個部分抽成一個函數。
    ?

  • public bool CanHideSurface(CubeSurface surface)
  • ? ? {
  • ? ?? ???if((int)surface<4)
  • ? ?? ???{
  • ? ?? ?? ?? ?int temp = (int)surface -(int)orientate;
  • ? ?? ?? ?? ?if(temp<0)
  • ? ?? ?? ?? ?{
  • ? ?? ?? ?? ?? ? temp += 4;
  • ? ?? ?? ?? ?}
  • ? ?? ?? ?? ?switch((CubeOrientate)temp)
  • ? ?? ?? ?? ?{
  • ? ?? ?? ?? ?? ? case CubeOrientate.front:
  • ? ?? ?? ?? ?? ?? ???return neighbors[0];
  • ? ?? ?? ?? ?? ? case CubeOrientate.back:
  • ? ?? ?? ?? ?? ?? ???return neighbors[3];
  • ? ?? ?? ?? ?? ? case CubeOrientate.left:
  • ? ?? ?? ?? ?? ?? ???return neighbors[6];
  • ? ?? ?? ?? ?? ? case CubeOrientate.right:
  • ? ?? ?? ?? ?? ?? ???return neighbors[9];
  • ? ?? ?? ?? ?? ? default:
  • ? ?? ?? ?? ?? ?? ???return false;
  • ? ?? ?? ?? ?}
  • ? ?? ???}
  • ? ?? ???else if((int)surface == 4)
  • ? ?? ???{
  • ? ?? ?? ?? ?return neighbors[12];
  • ? ?? ???}
  • ? ?? ???else
  • ? ?? ???{
  • ? ?? ?? ?? ?return neighbors[13];
  • ? ?? ???}
  • ? ?? ???
  • ? ? }
  • }
  • ? ? void TriaggulateCube(CubeInfo cube)
  • ? ? {
  • ? ?? ???TransformToCubeVertices(cube);
  • ? ?? ???for (int i = 0; i < 6; i++)
  • ? ?? ???{
  • ? ?? ?? ?? ?if (!cube.CanHideSurface((CubeSurface)i))
  • ? ?? ?? ?? ?{
  • ? ?? ?? ?? ?? ? AddCubeSurface(tempCubeVertices[0], tempCubeVertices[1], tempCubeVertices[2], tempCubeVertices[3],
  • ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? tempCubeVertices[4], tempCubeVertices[5], tempCubeVertices[6], tempCubeVertices[7],
  • ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?(CubeSurface)i, cube.type,6);
  • ? ?? ?? ?? ?}
  • ? ?? ???}
  • ? ? }
  • 復制代碼


    然后再檢查一下相鄰時是否會出問題。
    ?


    結束

    這期咱們算是把基本的架子搭出來了,可以看到使用簡單粗暴但耗性能的方式一旦換了個思路,其實還是有點麻煩,但這也正是寫這種小工程有意思的地方。
    ?


    文章的代碼貼地有些亂,有興趣的同學還是下載工程研究吧,感謝觀看至此。

    總結

    以上是生活随笔為你收集整理的用Unity盖房子(一):《勇者斗恶龙:建造者2》游戏功能的猜想的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 蜜臀精品一区二区三区 | 女人被狂躁c到高潮 | 色婷五月天 | 久久久老熟女一区二区三区91 | 在线观看国产成人 | 欧美久久久久久久久中文字幕 | 91视频二区 | 欧美精品一二 | 成年人黄色小视频 | 日韩一道本| 欧美综合亚洲图片综合区 | 婷婷丁香社区 | 精品日韩中文字幕 | 青草福利视频 | 亚洲黄v| 国产激情视频在线播放 | 中文字幕av免费 | 裸体女人a级一片 | 狠狠爱成人 | 噜噜av | 97在线播放| 日韩中文在线视频 | 五月天综合网站 | 假日游船法国满天星 | 五月婷婷综合在线观看 | 亚洲国产精品va在线 | 日韩av影视大全 | 四虎影视成人 | 快色污| 久久久久久久久久久久久久久久久 | 韩国av一区二区三区 | 免费看又黄又无码的网站 | 麻豆69xxnxxporn | 免费看国产片在线观看 | 无码精品在线观看 | 张柏芝亚洲一区二区三区 | 欧美性色黄大片手机版 | 国产精品不卡一区 | 37p粉嫩大胆色噜噜噜 | 青青草97国产精品麻豆 | 国产淫视频 | 免费在线观看黄网站 | 日韩人妻无码精品久久久不卡 | 天天做日日做 | 一区二区影院 | 国产高潮国产高潮久久久 | 国语对白对话在线观看 | se综合| 欧美xxxx喷水| 伊人免费视频 | 欧美夫妻性生活视频 | 久久久一二三四 | 1024久久 | 99国产精品久久久 | 全黄一级裸体片 | 中国一区二区视频 | 午夜视频在线网站 | 伊人最新网址 | 国产99久| 尤物视频网站在线观看 | 关之琳三级做爰 | 色九九视频 | 欧美黑人巨大xxx极品 | 国产麻豆影视 | 久久亚洲av无码精品色午夜麻豆 | 国产微拍精品 | 亚洲福利专区 | 国产精品国产三级国产aⅴ中文 | 麻豆精品国产精华精华液好用吗 | 免费av在 | 日韩视频一 | 国产成人综合欧美精品久久 | 大奶子av| 久久久久久久 | 欧美少妇一区二区三区 | 91久久国产精品 | 色婷婷综合久久久久中文一区二区 | 亚洲经典一区二区 | 亚洲午夜久久久久久久久红桃 | 午夜视频免费看 | 日本r级电影在线观看 | 少妇媚药按摩中文字幕 | 中文字幕第三页 | av黄色一级片 | 黄网在线看 | 国产成人在线观看网站 | 精品在线免费观看视频 | 制服丝袜第二页 | 黄在线免费看 | 国产精品久久久久久久免费看 | 热久久伊人 | 国产一区二区精彩视频 | 国产精品av在线播放 | 日韩免费观看一区二区 | 日本在线视频www色 国产在线视频网址 | 中文字幕亚洲天堂 | 亚洲激情综合网 | 香蕉久久一区二区三区 | 久久伊人婷婷 |