国产精品天干天干,亚洲毛片在线,日韩gay小鲜肉啪啪18禁,女同Gay自慰喷水

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

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

2019-05-10 15:53 作者:皮皮關(guān)做游戲  | 我要投稿

作者:沈琰


前言

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

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

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

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

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

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


那么關(guān)于安利部分就此打住,進(jìn)入正題。

下面用Unity來對自動建造功能做一個探索,預(yù)計內(nèi)容分為兩篇。第一篇是關(guān)于方塊建造游戲基礎(chǔ)功能在Unity內(nèi)的實(shí)現(xiàn),第二篇是NPC自動建造系統(tǒng)功能實(shí)現(xiàn)方式的猜想。

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

1 搭建方塊場景

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

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

(1)獲取方塊放置坐標(biāo)

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

為方便起見就設(shè)定每個方塊的邊長是Unity里的標(biāo)準(zhǔn)單位1,那么無論怎么轉(zhuǎn)換,方塊坐標(biāo)都處于方塊的中心位置,坐標(biāo)的小數(shù)部分肯定都是是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;

??? }

?

然后通過屏幕發(fā)射射線,換算擊中坐標(biāo)為方塊坐標(biāo),并用Gizmo驗(yàn)證一下計算是否正確。當(dāng)然,別忘了新建的測試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)方塊構(gòu)建

方塊的位置正確無誤之后,下一步就是添加方塊的操作。之前已經(jīng)說了,要用修改頂點(diǎn)數(shù)據(jù)的方式來實(shí)現(xiàn)這個功能,所以第一步先定義當(dāng)正方體中心為零點(diǎn)時八個頂點(diǎn)的相對坐標(biāo)。

public static Vector3[] cubeVertex =

?? {

??????? //上面四個頂點(diǎn)

??????? //左上

??????? 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),

??????? //下面四個頂點(diǎn)

??????? //左上

??????? 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>();??

??? }

}

?

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


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

}

?

頂點(diǎn)和三角形數(shù)據(jù)填充進(jìn)去后再刷新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();

??? }

?

可以看到方塊雖然是一個一個添加的,但數(shù)據(jù)是表現(xiàn)在一個mesh上的。

(3)刪除方塊

能添加自然就應(yīng)該能刪除,所以下一步是實(shí)現(xiàn)刪除的功能,后續(xù)還能擴(kuò)展成DQB2里的創(chuàng)造師手套搬運(yùn)功能。

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

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;

??? }

?

坐標(biāo)計算是沒問題,但是該如何告訴mesh刪除這些指定的頂點(diǎn)和三角形呢?

辦法當(dāng)然是有,射線的RaycastHit結(jié)構(gòu)體里是可以獲取擊中位置的三角形下標(biāo)和uv坐標(biāo)的,憑借這些信息已經(jīng)足夠計算出要刪除的頂點(diǎn)和三角形下標(biāo)了。

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

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

public class CubeInfo : MonoBehaviour

{

??? public string cubeName;??

?

??? public Vector3 Position

??? {

??????? get

??????? {

??????????? return transform.localPosition;

??????? }

??? }

}

?

新建上面的腳本并掛在一個空的GameObject上并拖成預(yù)制體,然后在添加方塊的時候?qū)嵗@個預(yù)制體并加到列表中。


public void AddCube(Vector3 position)

??? {

??????? CubeInfo cube = Instantiate(CubePrefab, position, Quaternion.identity, transform);

??????? Debug.Log("傳入坐標(biāo)" + position + "||cube本地坐標(biāo)" + 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 設(shè)置相鄰方塊與頂點(diǎn)優(yōu)化

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

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

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

public enum CubeNeighborDirection

{

??? front,

??? frontUp,

??? frontDown,

?

??? back,

??? backUp,

??? backDown,

?

??? left,

??? leftUp,

??? leftDown,

?

??? right,

??? rightUp,

??? rightDown,

?

??? up,

??? dowm,

}

?

下一步是寫一個方法,根據(jù)當(dāng)前方塊的坐標(biāo)和指定方向來推算出這個方向上的方塊坐標(biāo)。

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;

??? }

?

下一步就是根據(jù)這個坐標(biāo),在之前保存的所有CubeInfo的容器中找到與之對應(yīng)的方塊。

??? 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;

??? }

?

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

??? 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;

??????? }

??? }

?

當(dāng)然也別忘了在刪除方塊時把相鄰關(guān)系也更新一下。

? public void RemoveNeighbor(CubeNeighborDirection direction)

??? {

??????? neighbors[(int)direction] = null;

??? }

?

現(xiàn)在就能在添加和刪除時設(shè)置正確的相鄰關(guān)系了,下一步就是優(yōu)化頂點(diǎn)了。

(2)頂點(diǎn)優(yōu)化

現(xiàn)在能知道方塊之間的相鄰關(guān)系,那在相鄰方塊的方向上隱藏當(dāng)前面就是一句話的事情了。根據(jù)之前表示相鄰方向的枚舉可知,下標(biāo)為0,3,6,9,12 , 13的相鄰方塊分別對應(yīng)前,后,左,右,上,下,接下來就是根據(jù)當(dāng)前三角化的面的朝向來檢測相鄰方塊是否為空。

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);??????

??????? }

??? }

?

減肥前
減肥成功后

雖然看不出來變化,但表現(xiàn)在數(shù)據(jù)上還是蠻明顯的。

3 方塊的類型UV設(shè)置

由于所有方塊都用一個Mesh表示,所以直接改其材質(zhì)球的顏色是無法區(qū)分方塊類型的。那么辦法就是把所有的方塊紋理集合在一張紋理圖上,而根據(jù)方塊的類型不同傳入不同的UV坐標(biāo)。所以首先在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個類型,因?yàn)檎叫斡辛鶄€面,設(shè)置為六種類型剛好紋理圖就是正方形。

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

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

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

接下來在添加頂點(diǎn)信息時同時把UV信息也添加進(jìn)去。這里用了一個易于擴(kuò)展的寫法,之后擴(kuò)展方塊類型也可以直接修改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圖為正方形,則暫設(shè)正方體的類型為n種

??????? //v坐標(biāo)基點(diǎn):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作為方塊類型選擇,并關(guān)聯(lián)至腳本里修改方塊類型的枚舉。


??? public void TypeSelect(int type)

??? {

??????? cubeType = (M_CubeType)type;

??? }

?

現(xiàn)在就可以根據(jù)選中的類型來方便切換方塊類型了。

4 方塊旋轉(zhuǎn)

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

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里定義方塊的朝向字段,在添加方塊時將當(dāng)前朝向一并傳入。

? 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("傳入坐標(biāo)" + position + "||cube本地坐標(biāo)" + cube.transform.localPosition+"type:"+(int)type);

?? ????

?

??????? SetNeighbors(cube);

?

??????? TriangulateAllCubes();

??? }

?

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

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,剛好用來檢測旋轉(zhuǎn)功能是否正確。(怎么可能是巧合,我肯定是故意的)

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

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);

??????????? }

??????? }

??? }

?

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

結(jié)束

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

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

本期工程地址:https://link.zhihu.com/?target=https%3A//github.com/tank1018702/CubeBuilder


想系統(tǒng)學(xué)習(xí)游戲開發(fā)的童鞋,歡迎訪問:http://levelpp.com/???

另有專業(yè)開發(fā)交(gao)流(ji)群等待大家強(qiáng)勢插入:869551769


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

分享到微博請遵守國家法律
沛县| 莱阳市| 腾冲县| 芒康县| 辽中县| 株洲县| 寿光市| 达州市| 奎屯市| 大城县| 临邑县| 清水河县| 大埔区| 延长县| 平谷区| 天柱县| 乌鲁木齐市| 荥经县| 色达县| 宣汉县| 福海县| 阳东县| 德清县| 尚志市| 漠河县| 锦州市| 旺苍县| 汝南县| 买车| 临沂市| 诸城市| 清丰县| 凤山市| 都昌县| 喀喇沁旗| 浦东新区| 邯郸县| 高淳县| 定襄县| 全椒县| 吉木萨尔县|