最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

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

少年玩泥巴嗎?——用Unity復(fù)刻《一起玩陶藝》

2018-09-17 18:41 作者:皮皮關(guān)做游戲  | 我要投稿

作者:沈琰

本篇難度:★★★☆☆


前言

職業(yè)標(biāo)題黨又來(lái)寫(xiě)文章了。

先簡(jiǎn)單的介紹一下這期的主題:《一起玩陶藝(Let's Create! Pottery)》。

這是一個(gè)有點(diǎn)古老的休閑手游,從最低支持的安卓版本來(lái)看大約是10年前的游戲了,不過(guò)最近IOS版本還有更新。

游戲的玩法也很簡(jiǎn)單,就如同名字一樣,通過(guò)觸屏操作模擬塑造一個(gè)陶藝品的過(guò)程:

這個(gè)游戲有著手游剛剛興起那個(gè)年代游戲的特點(diǎn),即操作方式簡(jiǎn)單且極為貼合手機(jī)觸屏操作這個(gè)模式。憶往昔,那個(gè)年代不少這樣的優(yōu)秀游戲,如《水果忍者》、《無(wú)盡之劍》、《憤怒的小鳥(niǎo)》等等。

而現(xiàn)在手機(jī)游戲操作越來(lái)越趨于復(fù)雜,內(nèi)容變多了,但是反而有點(diǎn)漸漸失去了手游一開(kāi)始的簡(jiǎn)單樂(lè)趣。

扯遠(yuǎn)了,這期我們就來(lái)嘗試用Unity簡(jiǎn)單復(fù)刻一下這個(gè)游戲。

實(shí)現(xiàn)方法的猜想

看到這個(gè)能隨意變化的陶罐,第一反應(yīng)就是與Mesh脫不了關(guān)系。

我們?cè)谥暗奈恼吕飮L試過(guò)計(jì)算頂點(diǎn)構(gòu)建一個(gè)自定義的Mesh模型。而這一次我們需要更進(jìn)一步,要在游戲運(yùn)行時(shí)動(dòng)態(tài)改變Mesh的頂點(diǎn)的坐標(biāo),實(shí)現(xiàn)模型形狀的變化。

根據(jù)游戲里的表現(xiàn)形式來(lái)看,首先我們需要用代碼構(gòu)建陶罐的原型:一個(gè)中間鏤空的圓柱體,然后要在運(yùn)行中改變這個(gè)Mesh的形狀。

由游戲截圖可以看出這個(gè)變化的規(guī)律:所有頂點(diǎn)的移動(dòng)相對(duì)于物體自身的Y軸的變化都是對(duì)稱(chēng)的。換句話(huà)說(shuō)Mesh的基本結(jié)構(gòu)應(yīng)該是一層層的環(huán)狀結(jié)構(gòu),每個(gè)頂點(diǎn)移動(dòng)時(shí)相對(duì)于自己所在的高度的物體原點(diǎn)的距離都是一樣的。

從XZ的截面看也是一樣的


實(shí)現(xiàn)過(guò)程

1.Mesh構(gòu)建

到這里我們就可以開(kāi)始動(dòng)手了。這一步對(duì)于之前有過(guò)Mesh編程經(jīng)驗(yàn)的同學(xué)來(lái)說(shuō)并不難。

對(duì)這個(gè)地方有疑問(wèn)的同學(xué)可以先看一看上一期:zhuanlan.zhihu.com/p/38

不過(guò)在構(gòu)建之前還有一點(diǎn)要稍微注意一下:一開(kāi)始就需要想好每相鄰的兩層之間的頂點(diǎn)是否是公用的,因?yàn)檫@關(guān)系到后面法線(xiàn)的計(jì)算。

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer), typeof(MeshCollider))]

public class Potteryprototype : MonoBehaviour

{

    MeshFilter meshFilter;

    MeshRenderer meshRenderer;

    MeshCollider meshCollider;

    Mesh mesh;

 

    public int details = 40;

    public int layer = 20;

    public float Height = 0.1f;

 

    public float OuterRadius = 1.0f;

    public float InnerRadius = 0.9f;

 

    List<Vector3> vertices;

    List<Vector2> UV;

    List<int> triangles;

 

    float EachAngle ;

    int SideCount;

 

    public MouseControl mouse;

 

    void Start()

    {

        meshFilter = GetComponent<MeshFilter>();

        meshCollider = GetComponent<MeshCollider>();

        meshRenderer = GetComponent<MeshRenderer>();

    }

 

    [ContextMenu("GeneratePottery")]

    void GeneratePrototype()

    {

        vertices = new List<Vector3>();

        triangles = new List<int>();

        UV = new List<Vector2>();

 

        EachAngle = Mathf.PI * 2 / details;

        for (int i = 0; i < layer; i++)

        {

            GenerateCircle(i);

        }

        Capping();

       

        mesh = new Mesh();

        mesh.vertices = vertices.ToArray();

        mesh.triangles = triangles.ToArray();

        mesh.uv = UV.ToArray();

 

        mesh.RecalculateBounds();

        mesh.RecalculateTangents();

 

        meshFilter.mesh = mesh;

        mesh.RecalculateNormals();

        meshCollider.sharedMesh = mesh;

    }

 

    void GenerateCircle(int _layer)

    {

        //外頂點(diǎn)與內(nèi)頂點(diǎn)分開(kāi)存儲(chǔ),方便變化操作時(shí)的計(jì)算

        List<Vector3> vertices_outside = new List<Vector3>();

        List<Vector3> vertices_inside = new List<Vector3>();

 

      

        List<Vector2> UV_outside = new List<Vector2>();

        List<Vector2> UV_inside = new List<Vector2>();

 

 

        //外側(cè)和內(nèi)側(cè)頂點(diǎn)計(jì)算

        //注意這里讓每一圈的首尾重合了,也就是開(kāi)始和結(jié)尾的頂點(diǎn)坐標(biāo)一致

        //目的是計(jì)算UV坐標(biāo)時(shí)不會(huì)出現(xiàn)空缺

        for (float i = 0; i <= Mathf.PI * 2+EachAngle; i += EachAngle)

        {

            Vector3 v1 = new Vector3(OuterRadius * Mathf.Sin(i),  _layer * Height, OuterRadius * Mathf.Cos(i));

            Vector3 v2 = new Vector3(OuterRadius * Mathf.Sin(i),  (_layer +1)* Height, OuterRadius * Mathf.Cos(i));

            Vector3 v3 = new Vector3(InnerRadius * Mathf.Sin(i),  _layer * Height, InnerRadius * Mathf.Cos(i));

            Vector3 v4 = new Vector3(InnerRadius * Mathf.Sin(i),  (_layer+1) * Height, InnerRadius * Mathf.Cos(i));

            vertices_outside.Add(v1); vertices_outside.Add(v2);

            vertices_inside.Add(v3); vertices_inside.Add(v4);

 

            Vector2 uv1 = new Vector2(i / Mathf.PI*2, _layer*1.0f / layer * 1.0f);

            Vector2 uv2 = new Vector2(i / Mathf.PI*2, (_layer + 1)*1.0f / layer * 1.0f);

            Vector2 uv3 = new Vector2(i / Mathf.PI*2, _layer*1.0f / layer * 1.0f);

            Vector2 uv4 = new Vector2(i / Mathf.PI*2, (_layer + 1) *1.0f/ layer * 1.0f);

            UV_outside.Add(uv1); UV_outside.Add(uv2);

            UV_inside.Add(uv3); UV_inside.Add(uv4);

        }

        vertices.AddRange(vertices_outside);

        vertices.AddRange(vertices_inside);

 

        UV.AddRange(UV_outside);

        UV.AddRange(UV_inside);

 

        SideCount = vertices_outside.Count;

        int j = vertices_outside.Count * _layer * 2;

        int n = vertices_outside.Count;

        for (int i = j; i < j + vertices_outside.Count - 2; i += 2)

        {

 

            triangles.Add(i); triangles.Add(i + 2); triangles.Add(i + 1);

            triangles.Add(i + 2); triangles.Add(i + 3); triangles.Add(i + 1);

 

            triangles.Add(i + n); triangles.Add(i + n + 1); triangles.Add(i + n + 2);

            triangles.Add(i + n + 2); triangles.Add(i + n + 1); triangles.Add(i + n + 3);

        }     

    }

    //封頂,底面由于看不見(jiàn)就不用管了

    void Capping()

    {

       

        for (float i = 0; i <= Mathf.PI * 2+EachAngle; i += EachAngle)

        {

            Vector3 outer = new Vector3(OuterRadius * Mathf.Sin(i),layer * Height, OuterRadius * Mathf.Cos(i));

            Vector3 inner= new Vector3(InnerRadius * Mathf.Sin(i), layer * Height, InnerRadius * Mathf.Cos(i));

 

            vertices.Add(outer);vertices.Add(inner);

 

            Vector2 uv1 = new Vector2(i / Mathf.PI * 2,0); Vector2 uv2 = new Vector2(i / Mathf.PI * 2, 1);

           

            UV.Add(uv1); UV.Add(uv2);

        }

        int j = SideCount * layer * 2;

        for (int i=j;i<vertices.Count-2;i+=2)

        {

            triangles.Add(i);triangles.Add(i + 3);triangles.Add(i + 1);

            triangles.Add(i);triangles.Add(i + 2);triangles.Add(i + 3);

        }

        triangles.Add(vertices.Count - 2);triangles.Add(j + 1);triangles.Add(vertices.Count - 1);

        triangles.Add(vertices.Count - 2);triangles.Add(j);triangles.Add(j + 1);

       

    }

}

 

生成模型的網(wǎng)格長(zhǎng)這樣:

這里的選擇是不讓頂點(diǎn)公用,也就是每一層的上下頂點(diǎn)與相鄰層的并不是同一個(gè)頂點(diǎn),但是坐標(biāo)相同。這種情況下調(diào)用mesh.RecalculateNormals()自動(dòng)生成法線(xiàn)時(shí),每?jī)蓚€(gè)相同坐標(biāo)的頂點(diǎn)會(huì)有少許偏差,所以模型會(huì)有明顯的分層的感覺(jué)。

mesh.RecalculateNormals()的原理是根據(jù)這個(gè)頂點(diǎn)所連接的三角形順序里另外兩個(gè)頂點(diǎn)構(gòu)成的三角形的兩條邊叉乘計(jì)算出該點(diǎn)的法線(xiàn),當(dāng)有多個(gè)三角形共用一個(gè)頂點(diǎn)時(shí),則會(huì)將這幾個(gè)三角形的法線(xiàn)平均計(jì)算。

這樣的好處是每一層獨(dú)立計(jì)算三角形順序,在代碼構(gòu)成上比較方便,相應(yīng)的到后面計(jì)算法線(xiàn)平均化時(shí)會(huì)稍微麻煩一點(diǎn)。


2.動(dòng)態(tài)改變形狀

現(xiàn)在有了模型,我們進(jìn)行下一步。

先來(lái)整理一下思路,根據(jù)之前猜想的實(shí)現(xiàn)方式可知:

1.模型中相同高度的頂點(diǎn)移動(dòng)時(shí)到自身Y軸的距離是相同的。

2.不同高度的頂點(diǎn)移動(dòng)時(shí)其移動(dòng)的相對(duì)方向是相同的,不同的是移動(dòng)的距離。

我們把這個(gè)問(wèn)題轉(zhuǎn)化成需求:

1.獲得觸碰點(diǎn)(在這里就是鼠標(biāo)在屏幕上的坐標(biāo))投影到模型上的坐標(biāo)。

2.把這個(gè)坐標(biāo)點(diǎn)轉(zhuǎn)化到模型自身的坐標(biāo)系后,取其Y值。

3.遍歷模型中的每一個(gè)頂點(diǎn),根據(jù)頂點(diǎn)的Y值與之前求得的Y值之間的相對(duì)距離計(jì)算出頂點(diǎn)位移長(zhǎng)度。計(jì)算出來(lái)的長(zhǎng)度應(yīng)該與鼠標(biāo)投影到模型上的坐標(biāo)的Y值和當(dāng)前頂點(diǎn)的Y值成曲線(xiàn)關(guān)系。

在所知的函數(shù)圖像中,我們發(fā)現(xiàn)余弦函數(shù)比較符合游戲原型的曲線(xiàn)變化形式:

//這個(gè)函數(shù)放在Update()里調(diào)用

    void GetMouseControlTransform()

    {

        //從屏幕鼠標(biāo)位置發(fā)射一條射線(xiàn)到模型上,獲取這個(gè)坐標(biāo)

        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

        RaycastHit info;

 

        if (Physics.Raycast(ray.origin, ray.direction, out info))

        {

 

            //在Unity中無(wú)法直接修改MeshFilter中Mesh的信息,需要新建一個(gè)Mesh修改其引用關(guān)系

            Mesh mesh = meshFilter.mesh;

            Vector3[] _vertices = mesh.vertices;

 

            for (int i = 0; i < _vertices.Length; i++)

            {

 

                //x,z平面變換

                //頂點(diǎn)移動(dòng)與Y值的關(guān)系限制在5倍單層高度

                //這里可以自行修改,限制高度越大,曲線(xiàn)越平滑

                if (Mathf.Abs(info.point.y - transform.TransformPoint(_vertices[i]).y) < (5 * Height))

                {

                    //計(jì)算頂點(diǎn)移動(dòng)方向的向量

                    Vector3 v_xz = (transform.TransformPoint(_vertices[i]) - new Vector3(transform.position.x, transform.TransformPoint(_vertices[i]).y, transform.position.z));

 

                    //外頂點(diǎn)與內(nèi)頂點(diǎn)移動(dòng)時(shí)相對(duì)距離應(yīng)該保持不變

                    //因?yàn)槲覀冎理旤c(diǎn)數(shù)組內(nèi)的順序關(guān)系,所以可以通過(guò)計(jì)算總頂點(diǎn)數(shù)除以每層單側(cè)頂點(diǎn)數(shù)的商的奇偶關(guān)系來(lái)判斷是外頂點(diǎn)還是內(nèi)頂點(diǎn)

                    int n = i / SideCount;

                    bool side = n % 2 == 0;

                    //判斷頂面頂點(diǎn)內(nèi)外關(guān)系

                    bool caps = (i - (SideCount * layer * 2)) % 2 == 0;

 

                    //限制每個(gè)頂點(diǎn)最大和最小的移動(dòng)距離

                    float max;

                    float min;

                    if (i < SideCount * layer * 2)

                    {

                        max = side ? 2f * OuterRadius : 2f * OuterRadius - (OuterRadius - InnerRadius);

 

                        min = side ? 0.5f * OuterRadius : 0.5f * OuterRadius - (OuterRadius - InnerRadius);

                    }

                    else

                    {

                        max = caps ? 2f * OuterRadius : 2f * OuterRadius - (OuterRadius - InnerRadius); ;

                        min = caps ? 0.5f * OuterRadius : 0.5f * OuterRadius - (OuterRadius - InnerRadius);

                    }

                    //計(jì)算當(dāng)前頂點(diǎn)到鼠標(biāo)Y值之間的距離,再用余弦函數(shù)算出實(shí)際位移距離

                    float dif = Mathf.Abs(info.point.y - transform.TransformPoint(_vertices[i]).y);

                    if (Input.GetKey(KeyCode.RightArrow))

                    {

                        float outer = max - v_xz.magnitude;

                        _vertices[i] += v_xz.normalized * Mathf.Min(0.01f * Mathf.Cos(((dif / 5 * Height) * Mathf.PI) / 2), outer);

                    }

                    else if (Input.GetKey(KeyCode.LeftArrow))

                    {

                        float inner = v_xz.magnitude - min;

                        _vertices[i] -= v_xz.normalized * Mathf.Min(0.01f * Mathf.Cos(((dif / 5 * Height) * Mathf.PI) / 2), inner);

                    }

 

                    //Y軸變換

                    float scale_y = transform.localScale.y;

                    if (Input.GetKey(KeyCode.UpArrow))

                    {

                        scale_y = Mathf.Min(transform.localScale.y + 0.000001f, 2.0f);

                    }

                    else if (Input.GetKey(KeyCode.DownArrow))

                    {

 

                        scale_y = Mathf.Max(transform.localScale.y - 0.000001f, 0.3f);

                    }

                    transform.localScale = new Vector3(transform.localScale.x, scale_y, transform.localScale.z);

 

                }

 

                mesh.vertices = _vertices;

                mesh.RecalculateBounds();

                mesh.RecalculateNormals();

                meshFilter.mesh = mesh;

                meshCollider.sharedMesh = mesh;

            }

        }

    }

}

 

這一段代碼可能看起來(lái)有點(diǎn)亂,究其根本是因?yàn)槲覀冃枰陧旤c(diǎn)數(shù)組中找到每個(gè)頂點(diǎn)的相對(duì)關(guān)系,即它到底是外側(cè)頂點(diǎn)還是內(nèi)側(cè)頂點(diǎn)。

所幸整個(gè)模型是我們自己計(jì)算出來(lái)的,我們知道頂點(diǎn)的下標(biāo)與其位置的對(duì)應(yīng)關(guān)系。

同學(xué)們自己動(dòng)手復(fù)刻時(shí)不必完全照搬,對(duì)應(yīng)關(guān)系心里有數(shù)或者用另外一個(gè)容器轉(zhuǎn)換一道也行。

至于頂點(diǎn)在Y軸方向移動(dòng)計(jì)算就很簡(jiǎn)單了,因?yàn)槊恳粚禹旤c(diǎn)相對(duì)高度不變,其實(shí)就是在計(jì)算整個(gè)模型在Y軸方向的縮放。因此直接修改transform.localScale.y的值即可。

運(yùn)行效果如下:

是不是有那么點(diǎn)像了?


不過(guò)還沒(méi)完,當(dāng)我們想仿照游戲原型,讓模型沿自身的Y軸旋轉(zhuǎn)時(shí)再來(lái)控制形狀變化時(shí)會(huì)出現(xiàn)問(wèn)題:

因?yàn)镚IF截圖的精度問(wèn)題可能旋轉(zhuǎn)看起來(lái)不太明顯,在場(chǎng)景中把顯示模式切換成ShadedWireFrame模式后明顯看到相鄰層的頂點(diǎn)相對(duì)坐標(biāo)的XZ值偏移了。

原因是當(dāng)模型旋轉(zhuǎn)時(shí)我們計(jì)算出的頂點(diǎn)XZ平面的移動(dòng)向量也發(fā)生了旋轉(zhuǎn),頂點(diǎn)的坐標(biāo)值是相對(duì)于物體本身的坐標(biāo),但是在計(jì)算時(shí)會(huì)默認(rèn)轉(zhuǎn)換成世界坐標(biāo)。

要修改也好辦:

//計(jì)算時(shí)就把頂點(diǎn)坐標(biāo)系轉(zhuǎn)換為自身坐標(biāo)系,求得向量后再轉(zhuǎn)換為世界坐標(biāo)系

    Vector3 v_xz = transform.TransformDirection(transform.InverseTransformPoint(_vertices[i]) - transform.InverseTransformPoint(new Vector3(0, _vertices[i].y, 0)));

 

把計(jì)算頂點(diǎn)位移向量的值修改為如上代碼, 改好以后控制模型的變化就如同我們預(yù)期的一樣:



3.法線(xiàn)平均化

先大致說(shuō)說(shuō)什么是法線(xiàn)。

簡(jiǎn)單點(diǎn)來(lái)說(shuō)就是光線(xiàn)到達(dá)物體表面反射的對(duì)稱(chēng)軸。Unity中的3D物體會(huì)有立體的感覺(jué)是因?yàn)槲矬w的每個(gè)面的法線(xiàn)不同,產(chǎn)生的光影效果勾勒出了物體的輪廓。

舉個(gè)例子,先在Unity中新建兩個(gè)一樣的Cube:

然后把右邊cube的法線(xiàn)全部賦值為相同的方向:

可以看見(jiàn)形狀并沒(méi)有變化,但是你很難再分辨出來(lái)具體是什么形狀了。

那么現(xiàn)在回到項(xiàng)目中來(lái),我們計(jì)算法線(xiàn)是為了什么目的呢?

前面說(shuō)過(guò)因?yàn)闃?gòu)建模型時(shí)相鄰頂點(diǎn)沒(méi)有共用,所以模型會(huì)呈現(xiàn)一個(gè)層層堆疊的感覺(jué)。但我們最終要的效果是看起來(lái)像一個(gè)花瓶一樣光滑的感覺(jué)。

IEnumerator Print_Normals()

    {

    

        for (int i = 0; i < meshFilter.mesh.vertices.Length; i++)

        {   

            if (i % 2 == 0)

            {

                Debug.DrawRay(transform.TransformPoint(meshFilter.mesh.vertices[i]), transform.TransformDirection(meshFilter.mesh.normals[i] * 0.3f), Color.green, 1000f);

            }

            else

            {

                Debug.DrawRay(transform.TransformPoint(meshFilter.mesh.vertices[i]), transform.TransformDirection(meshFilter.mesh.normals[i] * 0.3f), Color.blue, 1000f);

            }

 

            yield return new WaitForSeconds(Time.deltaTime);

 

        }

    }

 

我們用協(xié)程把每個(gè)頂點(diǎn)上的法線(xiàn)顯示出來(lái)后就能看到問(wèn)題所在了:

每一層的上下頂點(diǎn)之間特意分別用藍(lán)綠不同顏色表示,可以看到雖然頂點(diǎn)的相對(duì)坐標(biāo)相同,但是法線(xiàn)方向卻有偏差。那么現(xiàn)在需求就出來(lái)了:我們需要讓每?jī)蓚€(gè)相對(duì)位置相同的頂點(diǎn)的法線(xiàn)相同,并且這個(gè)法線(xiàn)的方向是這兩個(gè)頂點(diǎn)法線(xiàn)方向的平均值。

但具體該怎么計(jì)算呢?還是先整理下思路:

首先分別在水平和垂直兩個(gè)方向分別叉乘兩個(gè)法線(xiàn)得到轉(zhuǎn)軸,然后計(jì)算兩個(gè)方向分量的角度,接著計(jì)算出兩條法線(xiàn)轉(zhuǎn)向平均值的夾角,最后讓法線(xiàn)沿著這個(gè)軸轉(zhuǎn)到計(jì)算出的角度,對(duì)不對(duì)?


錯(cuò)了!

恭喜,你們被我?guī)侠锪恕S?jì)算法線(xiàn)平均值沒(méi)有這么麻煩,讓相同坐標(biāo)的頂點(diǎn)上的法線(xiàn)相加取模不就是了?壓根不用考慮什么軸和角度問(wèn)題。

好吧,其實(shí)我不是有意要皮這么一下的,只是想順帶說(shuō)個(gè)趣事:

前一段時(shí)間寫(xiě)別的程序時(shí)查到一個(gè)把四元數(shù)分解成軸和角度的API后,總想著哪里有機(jī)會(huì)試著用一下。當(dāng)后面遇到這個(gè)計(jì)算法線(xiàn)的問(wèn)題時(shí),不假思索的就用上面這個(gè)方法去寫(xiě)了。頗有一種拿著錘子看什么都像釘子的感覺(jué),結(jié)果就是代碼寫(xiě)的超級(jí)復(fù)雜還算不對(duì)。當(dāng)后面一個(gè)圍觀的小伙伴提醒了我一句后,頓時(shí)心中感覺(jué)如萬(wàn)馬奔騰...

回到項(xiàng)目上來(lái)。這段法線(xiàn)計(jì)算的代碼就不放上來(lái)了,大致就是根據(jù)頂點(diǎn)在數(shù)組中的下標(biāo)去判斷位置是否相同,然后把該頂點(diǎn)的法線(xiàn)相加即可。大家自己構(gòu)建Mesh時(shí)的頂點(diǎn)順序可能會(huì)不太一樣。

最后效果如下:

從GIF上看起來(lái)不太明顯,我們還是把頂點(diǎn)的每條法線(xiàn)顯示出來(lái)看看效果:

可以看到藍(lán)綠兩根法線(xiàn)重合在了一起,雖然模型的形狀和精度沒(méi)變,但整個(gè)模型看起來(lái)有一種光滑的感覺(jué)。

表現(xiàn)效果提升

到目前為止,基本功能差不多實(shí)現(xiàn)了。現(xiàn)在可以去找點(diǎn)材質(zhì)來(lái)裝點(diǎn)一下我們的游戲。

在尋找黏土的材質(zhì)時(shí)一直找不到合適的,遂用了個(gè)取巧的辦法。

首先新建一個(gè)材質(zhì)球,把顏色調(diào)整到黏土的顏色。

然后找了張有橫向花紋的貼圖拖到Unity里,把貼圖的類(lèi)型設(shè)置為法線(xiàn)貼圖。把它設(shè)為材質(zhì)球的法線(xiàn)貼圖。

這樣會(huì)根據(jù)貼圖原來(lái)的灰度生成一張紋理圖,雖然還是一個(gè)平面,但是看起來(lái)會(huì)有凸凹不平的感覺(jué)。最終的效果如下:

結(jié)束

通過(guò)這期文章我們對(duì)Mesh的理解又稍微深入了一些,可以看到代碼本身其實(shí)并沒(méi)有多復(fù)雜,本質(zhì)上其實(shí)是一個(gè)數(shù)學(xué)問(wèn)題。

動(dòng)態(tài)改變mesh的形狀在游戲中應(yīng)用的很廣泛,比如游戲中的汽車(chē)撞到了東西,車(chē)頭會(huì)凹進(jìn)去等等。如果大家的游戲里需要加入類(lèi)似的功能不知道如何去做,而本文能幫到大家稍微整理下思路,不再毫無(wú)頭緒,那目的也就達(dá)到了。

本期文章工程地址:https://github.com/tank1018702/unity_003



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

游戲開(kāi)發(fā)攪基QQ群:869551769      

微信公眾號(hào):皮皮關(guān)

少年玩泥巴嗎?——用Unity復(fù)刻《一起玩陶藝》的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
永昌县| 华蓥市| 平阴县| 敦化市| 左云县| 育儿| 西乡县| 静乐县| 米林县| 云龙县| 宣恩县| 洛南县| 平江县| 丹江口市| 襄城县| 财经| 沽源县| 新龙县| 改则县| 兴义市| 南川市| 简阳市| 文成县| 南通市| 黄陵县| 万安县| 新昌县| 栾川县| 上杭县| 禄丰县| 开阳县| 和平县| 墨竹工卡县| 宜宾市| 衡南县| 白城市| 比如县| 常山县| 广饶县| 怀安县| 犍为县|