找回密碼
 注冊帳號

掃一掃,訪問微社區

士郎 用Unity蓋房子(一)——《勇者斗惡龍:建造者2》游戲功能的猜想

13
回復
1561
查看
打印 上一主題 下一主題
[ 復制鏈接 ]
排名
1
昨日變化

8045

主題

8603

帖子

3萬

積分

Rank: 16

UID
1231
好友
186
蠻牛幣
12139
威望
30
注冊時間
2013-7-29
在線時間
4113 小時
最后登錄
2019-8-16

活力之星原創精華達人突出貢獻獎財富之證游戲蠻牛QQ群會員蠻牛妹VIP

馬上注冊,結交更多好友,享用更多功能,讓你輕松玩轉社區。

您需要 登錄 才可以下載或查看,沒有帳號?注冊帳號

x
前言
前段時間一直忙著研究CatLikeCoding的HexMap系列教程,好長時間沒有新開坑寫點小工程了,這次又有了些新點子,與大家分享一下。
現在輪到本期主角出場了:《勇者斗惡龍:建造者2》(以下簡稱DQB2)


游戲類型是大家都不陌生的開放世界方塊建造類。這類游戲之前也玩過不少,比如《七日殺》、《傳送門騎士》,當然還有大名鼎鼎的《Minecraft》,但就個人感覺而言,DQB2在可玩性上要高很多,可以說是此類游戲的集大成之作。并且還融合了一些經營模擬養成,RPG戰斗的元素到其中,僅主線任務就讓我玩得不亦樂乎。
單就建造而論,DQB2里的工具就設計的非常實用,比如一次性更換多個同種類型方塊的刷子,一次獲取大量素材的大錘子等 ,此外還發現了一個十分貼心的功能。
大家都知道建造類游戲有一個問題,就是玩家上下限差距太大。例如《Minecraft》還有一個別稱叫"別人的世界"。好不容易自己搭建了一個火柴盒,突然看到視頻里大佬搭建的世界奇觀,突然就失去玩下去的動力了。即便是想仿照大佬的建筑復制一遍,所需要的工作量也是驚人的,大多數咸魚(比如我)就直接放棄了。而在DQB2中這個問題得到極大改善,你可以直接聯機到大佬的島上閑逛參觀,看見喜歡的建筑樣式可以直接把設計圖拷貝回來,甚至搭建都不用自己動手,在圖紙規劃地旁邊放上一個裝有材料的箱子,NPC就會自動幫忙建造。這簡直是建造游戲愛好者的福音,極大的提升了游戲體驗,同時也讓我對此功能的實現方式產生興趣。


那么關于安利部分就此打住,進入正題。
下面用Unity來對自動建造功能做一個探索,預計內容分為兩篇。第一篇是關于方塊建造游戲基礎功能在Unity內的實現,第二篇是NPC自動建造系統功能實現方式的猜想。
另外,由于難度直線升高,HexMap系列教程的翻譯進度會稍微放緩,但肯定會繼續更新下去直到完結,這一點可以放心。

1 搭建方塊場景
要實現方塊場景的搭建編輯功能,最簡單粗暴的方法是每一個方塊都視為一個單獨的GameObject,每次點擊時實例化一個方塊。簡單歸簡單,但這種方式在效率上肯定會有問題,特別是當在較大的地圖上計算物理碰撞而每個方塊都有自己的碰撞器時。我不知道好點的電腦運行起來如何,反正我的老爺機肯定就卡逑了。
剛好這個問題可以參考之前六邊形地圖教程里的思路,把每一次的編輯看做是對一整塊mesh里頂點的修改。
(1)獲取方塊放置坐標
首先要做的是在鼠標指向一個位置時,獲取這個位置的方塊坐標。即使是粗暴方法這一步也是省不掉的。
為方便起見就設定每個方塊的邊長是Unity里的標準單位1,那么無論怎么轉換,方塊坐標都處于方塊的中心位置,坐標的小數部分肯定都是是0.5。
[AppleScript] 純文本查看 復制代碼
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上要有碰撞器,不然射線檢測不會起作用。
   
[AppleScript] 純文本查看 復制代碼
 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);
        }
        
    }
[size=0.9em]紅線即鼠標射線擊中位置


(2)方塊構建
方塊的位置正確無誤之后,下一步就是添加方塊的操作。之前已經說了,要用修改頂點數據的方式來實現這個功能,所以第一步先定義當正方體中心為零點時八個頂點的相對坐標。
[AppleScript] 純文本查看 復制代碼
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新建一個類,用來處理方塊的形狀問題。
[AppleScript] 純文本查看 復制代碼
[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>();   
    }
}

由于是正方體,它的三角剖分非常簡單且有規律,所以可以寫一個較為通用的方法來三角化。這樣能使代碼更易讀,且更方便后續功能的添加。
[AppleScript] 純文本查看 復制代碼
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。
[AppleScript] 純文本查看 復制代碼
  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里的創造師手套搬運功能。
不知道有沒有同學注意到,之前在寫射線坐標轉換成方塊坐標時,代碼里給了一個射線反方向的微小偏移,這是為了防止方塊坐標在某些角度計算到了錯誤的位置。由于現在所有方塊共用的一個碰撞器,所以沒辦法通過碰撞信息來識別點擊的是哪個方塊。那么反過來考慮這個問題,直接通過給射線正方向的偏移,就能讓換算坐標變為當前鼠標指著的方塊坐標。
[AppleScript] 純文本查看 復制代碼
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。
[AppleScript] 純文本查看 復制代碼
public class CubeInfo : MonoBehaviour
{
    public string cubeName;   

    public Vector3 Position
    {
        get
        {
            return transform.localPosition;
        }
    }
}

新建上面的腳本并掛在一個空的GameObject上并拖成預制體,然后在添加方塊的時候實例化這個預制體并加到列表中。
[AppleScript] 純文本查看 復制代碼
 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();
    }

這樣一來刪除方塊的方法也容易寫了。
[AppleScript] 純文本查看 復制代碼
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,同時把方向用枚舉保存。
[AppleScript] 純文本查看 復制代碼
public enum CubeNeighborDirection
{
    front,
    frontUp,
    frontDown,

    back,
    backUp,
    backDown,

    left,
    leftUp,
    leftDown,

    right,
    rightUp,
    rightDown,

    up,
    dowm,
}

下一步是寫一個方法,根據當前方塊的坐標和指定方向來推算出這個方向上的方塊坐標。
[AppleScript] 純文本查看 復制代碼
 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的容器中找到與之對應的方塊。
   
[AppleScript] 純文本查看 復制代碼
 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不好找到規律,所以就用笨辦法。
[AppleScript] 純文本查看 復制代碼
  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;
        }
    }

當然也別忘了在刪除方塊時把相鄰關系也更新一下。
[AppleScript] 純文本查看 復制代碼
 public void RemoveNeighbor(CubeNeighborDirection direction)
    {
        neighbors[(int)direction] = null;
    }


現在就能在添加和刪除時設置正確的相鄰關系了,下一步就是優化頂點了。
(2)頂點優化
現在能知道方塊之間的相鄰關系,那在相鄰方塊的方向上隱藏當前面就是一句話的事情了。根據之前表示相鄰方向的枚舉可知,下標為0,3,6,9,12 , 13的相鄰方塊分別對應前,后,左,右,上,下,接下來就是根據當前三角化的面的朝向來檢測相鄰方塊是否為空。
[AppleScript] 純文本查看 復制代碼
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里定義方塊類型的枚舉字段。
[AppleScript] 純文本查看 復制代碼
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的值,很方便。
[AppleScript] 純文本查看 復制代碼
   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作為方塊類型選擇,并關聯至腳本里修改方塊類型的枚舉。
[AppleScript] 純文本查看 復制代碼
 public void TypeSelect(int type)
    {
        cubeType = (M_CubeType)type;
    }


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


4 方塊旋轉
我們的項目里現在都是方塊,而且由于我畫的UV圖除了最上面都是一個樣子,方塊能不能旋轉無所謂。但原版游戲中不是所有的建造素材都是方塊形狀,其中可能有階梯或者別的不對稱幾何形狀,我們后續也可以往這方面擴展,所以我們還是有必要去實現這個方塊旋轉功能。還是用枚舉來定義方塊的朝向,為方便起見,我們把旋轉的范圍限制在水平面上。
[AppleScript] 純文本查看 復制代碼
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();    }
使用屬性,在修改方塊朝向枚舉的同時也直接修改實際朝向。
[AppleScript] 純文本查看 復制代碼
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做了"減肥",那么現在旋轉了方塊之后對于需要隱藏面的判定就會出問題,所以這個地方需要修正。干脆直接把這個部分抽成一個函數。
[AppleScript] 純文本查看 復制代碼
 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);
            }
        }
    }

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


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


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

回復

使用道具 舉報

4四處流浪
419/500
排名
10343
昨日變化

0

主題

204

帖子

419

積分

Rank: 4

UID
248391
好友
0
蠻牛幣
355
威望
0
注冊時間
2017-10-12
在線時間
105 小時
最后登錄
2019-8-15
沙發
2019-6-21 17:10:02 只看該作者
可真好啊
回復

使用道具 舉報

3偶爾光臨
202/300
排名
12676
昨日變化

2

主題

77

帖子

202

積分

Rank: 3Rank: 3Rank: 3

UID
210943
好友
0
蠻牛幣
97
威望
0
注冊時間
2017-3-9
在線時間
51 小時
最后登錄
2019-8-13
板凳
2019-6-21 22:18:04 只看該作者
好多Switch,看著心里不爽 總想改
回復 支持 反對

使用道具 舉報

0

主題

56

帖子

77

積分

Rank: 2Rank: 2

UID
321303
好友
0
蠻牛幣
97
威望
0
注冊時間
2019-5-6
在線時間
21 小時
最后登錄
2019-7-11
地板
2019-6-24 08:58:58 只看該作者
回復

使用道具 舉報

3偶爾光臨
202/300
排名
11988
昨日變化

2

主題

53

帖子

202

積分

Rank: 3Rank: 3Rank: 3

UID
295357
好友
0
蠻牛幣
895
威望
0
注冊時間
2018-8-31
在線時間
81 小時
最后登錄
2019-8-10
5#
2019-6-24 10:56:51 只看該作者
666666666666666666
回復 支持 反對

使用道具 舉報

3偶爾光臨
202/300
排名
11988
昨日變化

2

主題

53

帖子

202

積分

Rank: 3Rank: 3Rank: 3

UID
295357
好友
0
蠻牛幣
895
威望
0
注冊時間
2018-8-31
在線時間
81 小時
最后登錄
2019-8-10
6#
2019-6-24 11:05:56 只看該作者
thank you share!
回復

使用道具 舉報

7日久生情
2092/5000
排名
4092
昨日變化

0

主題

1371

帖子

2092

積分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
254705
好友
1
蠻牛幣
1906
威望
0
注冊時間
2017-11-16
在線時間
359 小時
最后登錄
2019-8-16
7#
2019-6-25 08:23:39 只看該作者
66666666666666666666666666
回復 支持 反對

使用道具 舉報

0

主題

12

帖子

15

積分

Rank: 1

UID
317890
好友
0
蠻牛幣
4
威望
0
注冊時間
2019-3-28
在線時間
3 小時
最后登錄
2019-6-27
8#
2019-6-27 19:38:33 只看該作者
樓主頭像漂亮啊,本人嘛
回復 支持 反對

使用道具 舉報

排名
64933
昨日變化
1

0

主題

30

帖子

78

積分

Rank: 2Rank: 2

UID
259926
好友
0
蠻牛幣
162
威望
0
注冊時間
2017-12-16
在線時間
46 小時
最后登錄
2019-8-16
9#
2019-6-28 19:52:35 只看該作者
厲害了6666666
回復

使用道具 舉報

5熟悉之中
603/1000
排名
13274
昨日變化

2

主題

208

帖子

603

積分

Rank: 5Rank: 5

UID
16650
好友
0
蠻牛幣
1109
威望
0
注冊時間
2014-3-6
在線時間
299 小時
最后登錄
2019-8-16
10#
2019-7-1 17:32:42 只看該作者
可以.大佬.看看建造類游戲
回復 支持 反對

使用道具 舉報

2初來乍到
112/150
排名
18792
昨日變化

0

主題

22

帖子

112

積分

Rank: 2Rank: 2

UID
268303
好友
0
蠻牛幣
84
威望
0
注冊時間
2018-2-9
在線時間
58 小時
最后登錄
2019-8-11
11#
2019-7-6 13:59:09 只看該作者
謝謝分享^-^
回復

使用道具 舉報

5熟悉之中
739/1000
排名
10706
昨日變化

0

主題

485

帖子

739

積分

Rank: 5Rank: 5

UID
301976
好友
1
蠻牛幣
1102
威望
0
注冊時間
2018-10-31
在線時間
156 小時
最后登錄
2019-8-16
12#
2019-7-11 13:09:12 只看該作者
感謝大佬分享...
回復

使用道具 舉報

0

主題

5

帖子

24

積分

Rank: 1

UID
327439
好友
0
蠻牛幣
60
威望
0
注冊時間
2019-7-17
在線時間
19 小時
最后登錄
2019-8-16
13#
2019-7-19 11:44:44 只看該作者
感謝分享
回復

使用道具 舉報

排名
64933
昨日變化
1

0

主題

9

帖子

20

積分

Rank: 1

UID
313235
好友
0
蠻牛幣
110
威望
0
注冊時間
2019-1-31
在線時間
9 小時
最后登錄
2019-8-6
14#
2019-7-25 10:49:45 只看該作者
多謝樓主分享。。。。。。。
回復

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 注冊帳號

本版積分規則

捕鱼王怎么进不去 网上百家刷流水方法 蜂鸟团队彩票7728 pk10app计划软 北京pk10赛车计划软件 重时时彩现场开奖号码 pk10赛车计划免费版 快三豹子最佳规律 今天晚上福彩3d的丹东全图 最新欢乐生肖娱乐 斗地主的游戏规则 老重庆时时开彩结果 3d组选包胆组六计算 快三大小单双技巧规律分享 金牛国际官网永久网址 全天北京pk10赛车计划 棋牌中心