回覆列表
  • 1 # 就是不說你

    在本篇教程中,我們將使用簡單的物理機制模擬一個動態的2D水體。我們將使用一個線性渲染器、網格渲染器,觸發器以及粒子的混合體來創造這一水體效果,最終得到可運用於你下款遊戲的水紋和水花。這裡包含了Unity樣本源,但你應該能夠使用任何遊戲引擎以相同的原理執行類似的操作。  設定水體管理器  我們將使用Unity的一個線性渲染器來渲染我們的水體表面,並使用這些節點來展現持續的波紋。  unity-water-linerenderer(from gamedevelopment)  我們將追蹤每個節點的位置、速度和加速情況。為此,我們將會使用到陣列。所以在我們的類頂端將新增如下變數:  float[] xpositions;  float[] ypositions;  float[] velocities;  float[] accelerations;  LineRenderer Body;  LineRenderer將儲存我們所有的節點,並概述我們的水體。我們仍需要水體本身,將使用Meshes來創造。我們將需要物件來託管這些網格。  GameObject[] meshobjects;  Mesh[] meshes;  我們還需要碰撞器以便事物可同水體互動:  GameObject[] colliders;  我們也儲存了所有的常量:  const float springconstant = 0.02f;  const float damping = 0.04f;  const float spread = 0.05f;  const float z = -1f;  這些常量中的z是我們為水體設定的Z位移。我們將使用-1標註它,這樣它就會呈現於我們的物件之前(遊戲邦注:你可能想根據自己的需求將其調整為在物件之前或之後,那你就必須使用Z座標來確定與之相關的精靈所在的位置)。  下一步,我們將保持一些值:  float baseheight;  float left;  float bottom;  這些就是水的維度。  我們將需要一些可以在編輯器中設定的公開變數。首先,我們將為水花使用粒子系統:  public GameObject splash:  接下來就是我們將用於線性渲染器的材料:  public Material mat:  此外,我們將為主要水體使用的網格型別如下:  public GameObject watermesh:  我們想要能夠託管所有這些資料的遊戲物件,令其作為管理器,產出我們遊戲中的水體。為此,我們將編寫SpawnWater()函式。  這個函式將採用水體左邊、跑馬度、頂點以及底部的輸入:  public void SpawnWater(float Left, float Width, float Top, float Bottom)  {  (雖然這看似有所矛盾,但卻有利於從左往右快速進行關卡設計)  創造節點  現在我們將找出自己需要多少節點:  int edgecount = Mathf.RoundToInt(Width) * 5;  int nodecount = edgecount + 1;  我們將針對每個單位寬度使用5個節點,以便呈現流暢的移動(你可以改變這一點以便平衡效率與流暢性)。我們由此可得到所有線段,然後需要在末端的節點 + 1。  我們要做的首件事就是以LineRenderer元件渲染水體:  Body = gameObject.AddComponent<LineRenderer>();  Body.material = mat;  Body.material.renderQueue = 1000;  Body.SetVertexCount(nodecount);  Body.SetWidth(0.1f, 0.1f);  我們在此還要做的是選擇材料,並透過選擇渲染佇列中的位置而令其在水面之上渲染。我們設定正確的節點資料,將線段寬度設為0.1。  你可以根據自己所需的線段粗細來改變這一寬度。你可能注意到了SetWidth()需要兩個引數,這是線段開始及末尾的寬度。我們希望該寬度恆定不變。  現在我們製作了節點,將初始化我們所有的頂級變數:  xpositions = new float[nodecount];  ypositions = new float[nodecount];  velocities = new float[nodecount];  accelerations = new float[nodecount];  meshobjects = new GameObject[edgecount];  meshes = new Mesh[edgecount];  colliders = new GameObject[edgecount];  baseheight = Top;  bottom = Bottom;  left = Left;  我們已經有了所有陣列,將控制我們的資料。  現在要設定我們陣列的值。我們將從節點開始:  for (int i = 0; i < nodecount; i++)  {  ypositions[i] = Top;  xpositions[i] = Left + Width * i / edgecount;  accelerations[i] = 0;  velocities[i] = 0;  Body.SetPosition(i, new Vector3(xpositions[i], ypositions[i], z));  }  在此,我們將所有Y位置設於水體之上,之後一起漸進增加所有節點。因為水面平靜,我們的速度和加速值最初為0。  我們將把LineRenderer (Body)中的每個節點設為其正確的位置,以此完成這個迴圈。  創造網格  這正是它棘手的地方。  我們有自己的線段,但我們並沒有水體本身。我們要使用網格來製作,如下所示:  for (int i = 0; i < edgecount; i++)  {  meshes[i] = new Mesh();  現在,網格儲存了一系列變數。首個變數相當簡單:它包含了所有頂點(或轉角)。  unity-water-Firstmesh(from gamedevelopment)  該圖表顯示了我們所需的網格片段的樣子。第一個片段中的頂點被標註出來了。我們總共需要4個頂點。  Vector3[] Vertices = new Vector3[4];  Vertices[0] = new Vector3(xpositions[i], ypositions[i], z);  Vertices[1] = new Vector3(xpositions[i + 1], ypositions[i + 1], z);  Vertices[2] = new Vector3(xpositions[i], bottom, z);  Vertices[3] = new Vector3(xpositions[i+1], bottom, z);  現在如你所見,頂點0處於左上角,1處於右上角,2是左下角,3是右下角。我們之後要記住。  網格所需的第二個效能就是UV。網格擁有紋理,UV會選擇我們想擷取的那部分紋理。在這種情況下,我們只想要左上角,右上角,右下角和右下角的紋理。  Vector2[] UVs = new Vector2[4];  UVs[0] = new Vector2(0, 1);  UVs[1] = new Vector2(1, 1);  UVs[2] = new Vector2(0, 0);  UVs[3] = new Vector2(1, 0);  現在我們又需要這些資料了。網格是由三角形組成的,我們知道任何四邊形都是由兩個三角形組成的,所以現在我們需要告訴網格它如何繪製這些三角形。  unity-water-Tris(from gamedevelopment)  看看含有節點順序標註的轉角。三角形A連線節點0,1,以及3,三角形B連線節點3,2,1。因此我們想製作一個包含6個整數的陣列:  int[] tris = new int[6] { 0, 1, 3, 3, 2, 0 };  這就創造了我們的四邊形。現在我們要設定網格的值。  meshes[i].vertices = Vertices;  meshes[i].uv = UVs;  meshes[i].triangles = tris;  現在我們已經有了自己的網格,但我們沒有在場景是渲染它們的遊戲物件。所以我們將從包括一個網格渲染器和篩網過濾器的watermesh預製件來創造它們。  meshobjects[i] = Instantiate(watermesh,Vector3.zero,Quaternion.identity) as GameObject;  meshobjects[i].GetComponent<MeshFilter>().mesh = meshes[i];  meshobjects[i].transform.parent = transform;  我們設定了網格,令其成為水體管理器的子項。  創造碰撞效果  現在我們還需要自己的碰撞器:  colliders[i] = new GameObject();  colliders[i].name = “Trigger”;  colliders[i].AddComponent<BoxCollider2D>();  colliders[i].transform.parent = transform;  colliders[i].transform.position = new Vector3(Left + Width * (i + 0.5f) / edgecount, Top – 0.5f, 0);  colliders[i].transform.localScale = new Vector3(Width / edgecount, 1, 1);  colliders[i].GetComponent<BoxCollider2D>().isTrigger = true;  colliders[i].AddComponent<WaterDetector>();  至此,我們製作了方形碰撞器,給它們一個名稱,以便它們會在場景中顯得更整潔一點,並且再次製作水體管理器的每個子項。我們將它們的位置設置於兩個節點之點,設定好大小,併為其添加了WaterDetector類。  現在我們擁有自己的網格,我們需要一個函式隨著水體移動進行更新:  void UpdateMeshes()  {  for (int i = 0; i < meshes.Length; i++)  {  Vector3[] Vertices = new Vector3[4];  Vertices[0] = new Vector3(xpositions[i], ypositions[i], z);  Vertices[1] = new Vector3(xpositions[i+1], ypositions[i+1], z);  Vertices[2] = new Vector3(xpositions[i], bottom, z);  Vertices[3] = new Vector3(xpositions[i+1], bottom, z);  meshes[i].vertices = Vertices;  }  }  你可能注意到了這個函式只使用了我們之前編寫的程式碼。唯一的區別在於這次我們並不需要設定三角形的UV,因為這些仍然保持不變。  我們的下一步任務是讓水體本身執行。我們將使用FixedUpdate()遞增地來調整它們。  void FixedUpdate()  {  執行物理機制  首先,我們將把Hooke定律寫Euler方法結合在一起找到新座標、加速和速度。  Hooke定律是F=kx,這裡的F是指由水流產生的力(記住,我們將把水體表面模擬為水流),k是指水流的常量,x則是位移。我們的位移將成為每個節點的y座標減去節點的基本高度。  下一步,我們將新增一個與力的速度成比例的阻尼因素來削弱力。  for (int i = 0; i < xpositions.Length ; i++)  {  float force = springconstant * (ypositions[i] – baseheight) + velocities[i]*damping ;  accelerations[i] = -force;  ypositions[i] += velocities[i];  velocities[i] += accelerations[i];  Body.SetPosition(i, new Vector3(xpositions[i], ypositions[i], z));  }  Euler方法很簡單,我們只要向速度新增加速,向每幀座標增加速度。  注:我只是假設每個節點的質量為1,但你可能會想用:  accelerations[i] = -force/mass;  現在我們將創造波傳播。以下節點是根據Michael Hoffman的教程調整而來的:  float[] leftDeltas = new float[xpositions.Length];  float[] rightDeltas = new float[xpositions.Length];  在此,我們要創造兩個陣列。針對每個節點,我們將檢查之前節點的高度,以及當前節點的高度,並將二者差別放入leftDeltas。  之後,我們將檢查後續節點的高度與當前檢查節點的高度,並將二者的差別放入rightDeltas(我們將乘以一個傳播常量來增加所有值)。  for (int j = 0; j < 8; j++)  {  for (int i = 0; i < xpositions.Length; i++)  {  if (i > 0)  {  leftDeltas[i] = spread * (ypositions[i] – ypositions[i-1]);  velocities[i - 1] += leftDeltas[i];  }  if (i < xpositions.Length – 1)  {  rightDeltas[i] = spread * (ypositions[i] – ypositions[i + 1]);  velocities[i + 1] += rightDeltas[i];  }  }  }  當我們集齊所有的高度資料時,我們最後就可以派上用場了。我們無法檢視到最右端的節點右側,或者最大左端的節點左側,因此基條件就是i > 0以及i < xpositions.Length – 1。  因此,要注意我們在一個迴圈中包含整片程式碼,並執行它8次。這是因為我們想以少量而多次的時間執行這一過程,而不是進行一次大型運算,因為這會削弱流動性。  新增水花  現在我們已經有了流動的水體,下一步就需要讓它濺起水花!  為此,我們要增加一個稱為Splash()的函式,它會檢查水花的X座標,以及它所擊中的任何物體的速度。將其設定為公開狀態,這樣我們可以在之後的碰撞器中呼叫它。  public void Splash(float xpos, float velocity)  {  首先,我們應該確保特定的座標位於我們水體的範圍之內:  if (xpos >= xpositions[0] && xpos <= xpositions[xpositions.Length-1])  {  然後我們將調整xpos,讓它出現在相對於水體起點的位置上:  xpos -= xpositions[0];  下一步,我們將找到它所接觸的節點。我們可以這樣計算:  int index = Mathf.RoundToInt((xpositions.Length-1)*(xpos / (xpositions[xpositions.Length-1] – xpositions[0])));  這就是它的執行方式:  1.我們選取相對於水體左側邊緣位置的水花位置(xpos)。  2.我們將相對於水體左側邊緣的的右側位置進行劃分。  3.這讓我們知道了水花所在的位置。例如,位於水體四分之三處的水花的值就是0.75。  4.我們將把這一數字乘以邊緣的數量,這就可以得到我們水花最接近的節點。  velocities[index] = velocity;  現在我們要設定擊中水面的物體的速度,令其與節點速度一致,以樣節點就會被該物體拖入深處。  Particle-System(from gamedevelopment)  注:你可以根據自己的需求改變這條線段。例如,你可以將其速度新增到當前速度,或者使用動量而非速度,併除以你節點的質量。  現在,我們想製作一個將產生水花的粒子系統。我們早點定義,將其稱為“splash”。要確保不要讓它與Splash()相混淆。  首先,我們要設定水花的參,以便調整物體的速度:  float lifetime = 0.93f + Mathf.Abs(velocity)*0.07f;  splash.GetComponent<ParticleSystem>().startSpeed = 8+2*Mathf.Pow(Mathf.Abs(velocity),0.5f);  splash.GetComponent<ParticleSystem>().startSpeed = 9 + 2 * Mathf.Pow(Mathf.Abs(velocity), 0.5f);  splash.GetComponent<ParticleSystem>().startLifetime = lifetime;  在此,我們要選取粒子,設定它們的生命週期,以免他們擊中水面就快速消失,並且根據它們速度的直角設定速度(為小小的水花增加一個常量)。  你可能會看著程式碼心想,“為什麼要兩次設定startSpeed?”你這樣想沒有錯,問題在於,我們使用一個起始速度設定為“兩個常量間的隨機數”這種粒子系統(Shuriken)。不幸的是,我們並沒有太多以指令碼訪問Shuriken的途徑 ,所以為了獲得這一行為,我們必須兩次設定這個值。

  • 2 # maimi32709

      在本篇教程中,我們將使用簡單的物理機制模擬一個動態的2D水體。我們將使用一個線性渲染器、網格渲染器,觸發器以及粒子的混合體來創造這一水體效果,最終得到可運用於你下款遊戲的水紋和水花。這裡包含了Unity樣本源,但你應該能夠使用任何遊戲引擎以相同的原理執行類似的操作。  設定水體管理器  我們將使用Unity的一個線性渲染器來渲染我們的水體表面,並使用這些節點來展現持續的波紋。  unity-water-linerenderer(from gamedevelopment)  我們將追蹤每個節點的位置、速度和加速情況。為此,我們將會使用到陣列。所以在我們的類頂端將新增如下變數:  float[] xpositions;  float[] ypositions;  float[] velocities;  float[] accelerations;  LineRenderer Body;  LineRenderer將儲存我們所有的節點,並概述我們的水體。我們仍需要水體本身,將使用Meshes來創造。我們將需要物件來託管這些網格。  GameObject[] meshobjects;  Mesh[] meshes;  我們還需要碰撞器以便事物可同水體互動:  GameObject[] colliders;  我們也儲存了所有的常量:  const float springconstant = 0.02f;  const float damping = 0.04f;  const float spread = 0.05f;  const float z = -1f;  這些常量中的z是我們為水體設定的Z位移。我們將使用-1標註它,這樣它就會呈現於我們的物件之前(遊戲邦注:你可能想根據自己的需求將其調整為在物件之前或之後,那你就必須使用Z座標來確定與之相關的精靈所在的位置)。  下一步,我們將保持一些值:  float baseheight;  float left;  float bottom;  這些就是水的維度。  我們將需要一些可以在編輯器中設定的公開變數。首先,我們將為水花使用粒子系統:  public GameObject splash:  接下來就是我們將用於線性渲染器的材料:  public Material mat:  此外,我們將為主要水體使用的網格型別如下:  public GameObject watermesh:  我們想要能夠託管所有這些資料的遊戲物件,令其作為管理器,產出我們遊戲中的水體。為此,我們將編寫SpawnWater()函式。  這個函式將採用水體左邊、跑馬度、頂點以及底部的輸入:  public void SpawnWater(float Left, float Width, float Top, float Bottom)  {  (雖然這看似有所矛盾,但卻有利於從左往右快速進行關卡設計)  創造節點  現在我們將找出自己需要多少節點:  int edgecount = Mathf.RoundToInt(Width) * 5;  int nodecount = edgecount + 1;  我們將針對每個單位寬度使用5個節點,以便呈現流暢的移動(你可以改變這一點以便平衡效率與流暢性)。我們由此可得到所有線段,然後需要在末端的節點 + 1。  我們要做的首件事就是以LineRenderer元件渲染水體:  Body = gameObject.AddComponent

  • 中秋節和大豐收的關聯?
  • 昨晚夢見在河裡抓了好多lagu?