Unity-2D

張滿月。發表於2022-03-27

Unity-2D

1.Unity中的2D模式:

1)遊戲在二維上展示

啟用 2D 模式時將會設定正交(即無透視)檢視:攝像機沿 Z 軸觀察,而 Y 軸向上增加。因此可以輕鬆視覺化場景並放置 2D 物件。

2)設定專案預設模式:Edit > Project Settings > Default Behavior Mode

在 2D 專案模式下:

  • 所有影像(images)都會被當做 2D 圖片,並設定成 sprite mode 精靈模式

  • Sprite Packer 會被啟動

  • Scene 檢視預設為 2D

  • 預設遊戲物件沒有實時方向光。

  • 攝像機的預設位置為 0,0,–10。(在 3D 模式下為 0,1,–10。)

  • The camera projection is set to be Orthographic. (In 3D Mode it is Perspective.)攝像機投射模式被設定為正交(沒有遠小近大,沒有距離之分),而在 3D 模式下,是透視(遠小近大,有距離之分)

  • 在 Lighting 視窗中:

    • Skybox is disabled for new scenes:天空盒預設關閉

    • Ambient Source is set to Color. (With the color set as a dark grey: RGB: 54, 58, 66.) 保圍光源設定為 color ,預設為灰色

    • Realtime Global Illumination (Enlighten) is set to off.關閉實時光照

    • Baked Global Illumination is set to off.關閉全域性光照烘焙

    • Auto-Building set to off.自動建立關閉

2.在Unity中建立2D遊戲

1)Player的建立與控制:
  • 使用靜態精靈建立Player:
    • 精靈 Sprite 是 Unity 中 2D 素材的預設存在形式,是 Unity 中的 2D 圖形物件。

    • 在 2D 遊戲中,使用 2D 素材的過程: PNG(JPG 等)----> Sprite ----> GameObject

2)Player的移動指令碼:
  • 鋪墊

    • Vector2 二維向量
      • 在數學中,Vector 向量/向量指的是帶方向的線段

      • 在 Unity 中,Transform 值使用 x 表示水平位置,使用 y 表示垂直位置,使用 z 表示深度。這 3 個數值組成一個座標。由於此遊戲是 2D 遊戲,你無需儲存 z 軸位置,因此你可以在此處使用 Vector2 來僅儲存 x 和 y 位置。

      • Transform 中 position 的型別,也是 Vector2。C# 這種強型別語言,賦值時,左右必須是同一型別才能進行

    • Unity 預設 Input Manager 設定
      • 在 Unity 專案設定中,可以通過 Input Manager 進行預設的遊戲輸入控制設定 Edit > Project Settings > Input

      • 鍵盤按鍵,以 2 個鍵來定義軸:

        • 負值鍵 negative button,被按下時將軸設定為 -1

        • 正值鍵 positive button ,被按下時將軸設定為 1

      • Axis 軸 Axes 是它的負數形式

        • Horizontal Axis: 水平軸 對應 X 軸

        • Vertical Axis:縱軸 對應 Y 軸

    • Input類
      • 使用該類來讀取傳統遊戲輸入中設定的軸/滑鼠/按鍵,以及訪問移動裝置上的多點觸控/加速度計資料。若要使用輸入來進行任何型別的移動行為,請使用 Input.GetAxis。 它為您提供平滑且可配置的輸入 - 可以對映到鍵盤、遊戲杆或滑鼠。 請將 Input.GetButton 僅用於事件等操作。不要將它用於移動操作。Input.GetAxis 將使指令碼程式碼更簡潔。

    • 時間和幀率
      • 當前的程式碼中,幀數越高,同一時間內,執行 Update 的次數越多,角色移動速度越快。如果遊戲以每秒 60 幀的速度執行,那麼 Ruby 將移動 0.1 _ 60,因此每秒移動 6 個單位。但是,如果遊戲以每秒 10 幀的速度執行,就像剛剛讓遊戲執行的那樣,那麼 Ruby 僅移動 0.1 _ 10,因此每秒移動 1 個單位!

      • 如果一個玩家的計算機非常陳舊,只能以每秒 30 幀的速度執行遊戲,而另一個玩家的計算機能以每秒 120 幀的速度執行遊戲,那麼這兩個玩家的主角的移動速度會有很大差異。這樣就會使遊戲的難易程度提高或降低,具體取決於執行遊戲的計算機。

      • 而幀數是由硬體水平影響的(越好越高),不同電腦中,會導致遊戲效果完全不同

  • 新建Player後,選中Player,在Inspector視窗中Add Component,自定義一個指令碼,移動指令碼的程式碼如下:

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

    public class RubyMover : MonoBehaviour
    {
       // Start is called before the first frame update
       void Start()
      {
           
      }
       public float speed = 0.1f;
       // speed訪問許可權設定為public,在Unity中可修改屬性。
       // Update is called once per frame
       void Update()
      {
           float x = Input.GetAxis("Horizontal");
           float y = Input.GetAxis("Vertical");

           Vector2 position = transform.position;
           position.x += 0.1f * x * speed * Time.deltaTime;
           position.y += 0.1f * y * speed * Time.deltaTime;
           transform.position = position;

      }
    }
    3)2D遊戲中瓦片地圖的建立和使用:
    • 瓦片地圖工作流程
      1. 預處理 sprite 資源:將圖片資源拖拽到 project 中,生成 sprite;然後一般需要進行切割 slice ,將其配置成需要的各個 tile;

      2. 建立要在其上繪製瓦片的瓦片地圖。此過程中還會自動建立 Grid 遊戲物件作為瓦片地圖的父級。

      3. 直接建立瓦片資源,或者通過將用作瓦片素材的精靈帶入 Tile Palette 視窗自動生成瓦片。

      4. 建立一個包含瓦片資源的 Tile Palette,並使用各種筆刷來繪製到瓦片地圖上。

    • 瓦片地圖的高階使用
      • 使用普通的瓦片地圖,構建整個世界,一個一個格子用筆刷來填充,非常費時,Unity 在不斷地升級中,新增了很多種快速構建瓦片地圖的方式,掌握了這些方法,能夠極大減少繪製地圖所用的時間。

        • 程式設計瓦片 Scriptable Tile

          • Unity 支援用程式碼建立自己的 Tile 類,自己書寫瓦片的繪製規則。還可以為瓦片建立自定義編輯器。這與指令碼化物件的自定義編輯器的工作方式相同。建立一個繼承自 TileBase(或 TileBase 的任何有用子類,如 Tile)的新類。重寫新的 Tile 類所需的所有方法。

        • 程式設計畫筆 Scriptable Brush

          • Unity 也支援建立自己的 Brush 類,設計適合自己遊戲的網格畫筆。

            建立一個繼承自 GridBrushBase(或 GridBrushBase 的任何有用子類,如 GridBrush)的新類。重寫新的 Brush 類所需的所有方法。建立可程式設計畫筆後,畫筆將列在 Palette 視窗的 Brushes 下拉選單 中。預設情況下,可程式設計畫筆指令碼的例項將經過例項化並儲存在專案的 Library 資料夾中。對畫筆屬性的任何修改都會儲存在該例項中。如果希望該畫筆有多個具備不同屬性的副本,可在專案中將畫筆例項化為資源。這些畫筆資源將在 Brush 下拉選單中單獨列出。

    • 2D Tilemap Extras (2D 瓦片地圖擴充套件)
      • Animated Tile 動畫瓦片

        • 動畫瓦片在遊戲執行時,按順序顯示 Sprite 列表以建立逐幀動畫

      • Rule Tile 規則瓦片

        • 可以為每個瓦片建立規則,在繪製時,unity 會自動響應這些規則,繪製地圖時更加智慧

        • RuleTile 使用步驟:

          • 準備 Tile 素材,配置素材屬性,分割素材;

          • 新建 RuleTile,為其新增規則,設定每個 Tile 的相鄰規則;

          • 將設定好的 RuleTile 拖拽到 Tile Palette 中,就可以使用了。

      • Rule Override Tile / Advanced Rule Override Tile 規則覆蓋瓦片

        • 可以用已經生成好的 Rule Tile,作為 Rule Override Tile 的規則來源,只替換對應的瓦片素材,而沿用原先的規則,可以快速的建立規則瓦片的方法。

4)場景中的圖形順序:
  • 偽透檢視
    • 透檢視指的是有深度、距離感的圖,一般要三維中的深度軸來表現場景的深度,而二維遊戲中沒有這個深度,只能通過前後來仿造深度效果,稱為“偽透檢視”

    • 先前通過調整瓦片的 Order in Layer 屬性來解決了瓦片地圖的排序問題,但並非總是希望一個遊戲物件在另一個遊戲物件之上,比如,在同一個瓦片地圖中,玩家角色在一個物體之前(比如一棵樹)時,應該是玩家遮擋樹,而玩家移動到樹後時,應該是樹遮擋玩家,這就需要“偽造”透檢視。

    • 在 2D 遊戲中,場景裡的 “前後” 是由 Y 軸決定的,需要讓 Unity 根據遊戲物件的 y 座標來繪製遊戲物件Y 軸 y 座標值越小,越靠前,應該遮擋 y 座標值較大的遊戲物件,也就是 y 座標較小的遊戲物件後繪製,就會位於上層

    • 在遊戲中,如果要設定 2D 偽透視試圖,需要在專案設定中進行更改:

      • Edit > Project Settings > Graphics > Camera Settings > Transparency Sort Mode = Custom Axis > Transparency Sort Axis x = 0 / y = 1 / z = 0

      • 此設定告訴 Unity 在 y 軸上基於精靈的位置來繪製精靈。

    img

    • 按 Play 以進入執行模式並測試你的更改。現在,你的角色比箱子高時,角色應該會繪製在箱子的後面;而角色比箱子低時,繪製在箱子的前面。

    • Sprite 軸心 pivot

      • 每個 Sprite 都有一個軸心(中心點),Unity 根據 pivot 對 sprite 進行定位,這個 pivot 可以在 sprite editor 中調整,可以將其設定到 sprite 上任意位置

      • 在 2D Rpg 遊戲場景中的遊戲物件,如果想要實現較為真實的 “偽透視” 效果,最好將遊戲物件的 sprite 中 pivot 都設定到素材的最下方正中。

      • 然後將遊戲物件的 Sprite Sort Point 由 Center 改為 Pivot 即可.

5 )物理系統:
  • 鋪墊:
    • Unity中內建的物理系統可以模仿地球上的物理系統,使Unity中建立的一切精靈都具有類似地球上的物理屬性。

  • 物理系統中比較重要的指令碼元件:
    • Rigidbody :專案中的剛體元件

      • 定義了物件收到外力後,如何模擬其行為,如翻滾,掉落。當為一個物件新增了 Rigidbody 元件後,就會模擬受重力而掉落。如果再加上collider元件,則會響應外部的力,而運動。新增 Rigidbody後,我們就不能通過 transform 元件來移動物體了,只能由物理系統來模擬驅動。當然這不是絕對的,某些特定情境,需要關掉物理模擬,將物體擺放到指定位置,再重新開啟物理模擬。有時,我們希望物件參與物理模擬,但是其行為還是由邏輯控制,比如,對於遊戲內的NPC,我們需要由程式碼控制其運動,同時又需要新增rigidbody,這樣才能被Trigger檢測到,對於這種情況,可以將 Rigidbody 設定為動力學物體(Is Kinemiac)。

    • ···collider : 專案中碰撞體元件:

      • 定義了物體的形狀來進行碰撞模擬。物理碰撞體的形狀不需要嚴格和物體一致,只要能表示其物理形狀即可。比如一個複雜的人,我們可以用一個膠囊體來定義其碰撞形狀。

      • Unity內建了很多碰撞體,以下時常用的碰撞體:

        • BoxCollider 立方體碰撞體 SphereCollider 球形碰撞體 CapsuleCollider 膠囊碰撞體 MeshCollider 從物件的網格建立碰撞體。MeshCollider之間不支援碰撞檢測,效率太低。可以將MeshCollider的Convex選項開啟,則能支援MeshCollider之間的碰撞檢測,同時提高效能。 WheelCollider 建立載具的輪子的碰撞體 TerrainCollider 處理Unity地形系統的碰撞

        • 2D中相應的BoxCollider2D,CircleCollider2D,CapsuleCollider2D,以及其它轉為2D建立的碰撞體型別,如PolygonCollider2D。

        • Box,Sphere,Circle,Capsule這些簡單碰撞體的效率相對都是較高的,建議使用這些簡碰撞體。

      • 當一個精靈比較複雜時(如建立的一個人物角色),可以採用組合碰撞體的方式,對角色不同的身體部位分別採用不同的碰撞體元件。

    • 觸發器:
      • 為碰撞體元件選中is Trigger核取方塊,會發現碰撞體不再組織移動了。觸發器用於檢測碰撞但不產生碰撞。在2D專案中要使用OnTriggerEnter2D方法。

      • code:

      • private void OnTriggerEnter2D(Collider2D collision)
          {
               Debug.Log($"與{collision}發生碰撞了!");
          }
    • OnColliderEnter(2D) 和OnTriggerEnter(2D):

      • OnCollisionEnter方法必須是在兩個碰撞物體都不勾選isTrigger的前提下才能進入,反之只要勾選一個isTrigger那麼就能進入OnTriggerEnter方法。

      • OnCollisionEnter和OnTriggerEnter是衝突的不能同時存在的。

      • OnTriggerEnter和OnCollisionEnter的選擇。

        • 如果想實現兩個剛體物理的實際碰撞效果時候用OnCollisionEnter,Unity引擎會自動處理剛體碰撞的效果。

        • 如果想在兩個物體碰撞後自己處理碰撞事件用OnTriggerEnter。

6)Unity中的世界互動:
  • 可收集的物件:
    • 例如玩家通過觸發器實現回血或者扣血的操作:

    • //扣血的類

      using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;

      public class collectibleHealth : MonoBehaviour
      {
         public int healther = -1;

         private void OnTriggerEnter2D(Collider2D collision)
        {
             Debug.Log($"與{collision}發生碰撞了!");

             //獲取主角遊戲物件
             RubyMover RM = collision.GetComponent<RubyMover>();
             if(RM!=null)
            {
                 //呼叫改變血量的方法時,建議再加一層判斷,血滿時無法呼叫這個方法。
                 RM.ChangeHealth(healther);
            Destroy(gameObject);
            }
             
        }  
      }

      //玩家類
      //玩家類中的一些屬性(例如血量或者其他的一些私有屬性)建議設定為私有,並提供get/set方法。
      public class RubyMover : MonoBehaviour
      {
         public float speed = 0.1f;
         Rigidbody2D rigidbody;
         float x;
         float y;
         int CurHealth;
         int MaxHealth;

         // Start is called before the first frame update
         void Start()
        {
             rigidbody = GetComponent<Rigidbody2D>();
             MaxHealth = CurHealth = 5;
        }
         
         // Update is called once per frame
         void Update()
        {
              x = Input.GetAxis("Horizontal");
              y = Input.GetAxis("Vertical");
        }

         private void FixedUpdate()
        {
             Vector2 position = rigidbody.position;
             position.x += 0.1f * x * speed * Time.deltaTime;
             position.y += 0.1f * y * speed * Time.deltaTime;
             rigidbody.MovePosition(position);
        }

         //有關血量更改的程式碼
         public void ChangeHealth(int HCer)
        {
             CurHealth = Mathf.Clamp(CurHealth + HCer, 0, MaxHealth);
             Debug.Log("當前生命值:" + CurHealth + "/" + MaxHealth);
        }
      }
  • 設定攝像機跟隨Player移動(使用自帶指令碼的的方式):
7)U2D中的精靈動畫:
  • 參考網頁教程,百度資源。

8)一些問題及解決方式:
  • 建立的角色在於環境中的一些元件發生碰撞時角色發生旋轉、抖動的問題:
    • 旋轉:在2D專案中,選中建立的角色,在Inspector皮膚中找到新增的Rigidbody 2D指令碼,在Constrains勾選Freeze Rotation Z。

    • 抖動:引起抖動的原因可能是你在角色移動指令碼里寫程式碼是利用的transform來移動角色。此時指令碼移動角色的位置會和碰撞體矛盾從而引起抖動。(說白了就是你在指令碼中強行將角色移到一個碰撞體的碰撞範圍內,這個碰撞體又將角色彈了回來)。解決的方法是:利用剛體的移動代替transfoem的移動方式。程式碼指令碼如下:

    • //原transform移動方式見上


      using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;

      public class RubyMover : MonoBehaviour
      {
         public float speed = 0.1f;
         Rigidbody2D rigidbody;
         float x;
         float y;
         // Start is called before the first frame update
         void Start()
        {
             rigidbody = GetComponent<Rigidbody2D>();
        }
         
         // Update is called once per frame
         void Update()
        {
              x = Input.GetAxis("Horizontal");
              y = Input.GetAxis("Vertical");

             

        }
      //使物理計算保持穩定,定期更新。只要你想直接影響物體元件或剛體,就要使用這個函式。
         private void FixedUpdate()
        {
             Vector2 position = rigidbody.position;
             position.x += 0.1f * x * speed * Time.deltaTime;
             position.y += 0.1f * y * speed * Time.deltaTime;
             rigidbody.MovePosition(position);
        }
      }