代碼源自游戲《A Place for the Unwilling》
開發《A Place for the Unwilling》游戲第一部要解決的問題就是讓精靈可以圍繞其它精靈前后移動,呈現出真實的深度感覺。SpriteRenderer組件有兩個屬性,可以改變場景中Sprite的渲染順序。
- Sorting Layer 用於設置不同層的Sprite渲染順序
- Order in Layer 用於設置在同一層中的Sprite渲染順序
如果想實時改變多個Sprite的渲染順序,就需要修改一些屬性以便無論精靈在場景中如何移動,均以正確的順序渲染。由於“Oder in Layer”屬性僅接受整型參數,所以利用Z軸似乎是個更好的選擇。
Unity中Sprite的渲染優先級如下圖,從高到低:
如果兩個Sprite的“Sorting Layer”和“Order in Layer”均相同,那么在3D世界坐標中離相機更近的Sprite會被先渲染。
在明白了Sprite的渲染順序后,接下來之要寫個簡單的腳本更改Sprite坐標的Z值為與其Y值成固定比例即可。但在此之前,先來解釋一個重要的小概念,即如何設置精靈位於地面的底部。這里“底部”就是指3D世界中對象與地面接觸的部分,示例如下:
我們要做的是在改變Sprite坐標Y值的同時改變其Z值,上圖在3D環境的效果如下圖:
理解了以上內容,就可以寫腳本了,代碼如下:
1 using UnityEngine; 2 3 [ExecuteInEditMode] 4 public class IsometricStaticObject : MonoBehaviour { 5 6 [SerializeField] 7 private float m_floorHeight; 8 private float m_spriteLowerBound; 9 private float m_spriteHalfWidth; 10 private readonly float m_tan30 = Mathf.Tan(Mathf.PI / 5); 11 12 void Start() 13 { 14 SpriteRenderer spriteRenderer = GetComponent<SpriteRenderer>(); 15 m_spriteLowerBound = spriteRenderer.bounds.size.y * 0.5f; 16 m_spriteHalfWidth = spriteRenderer.bounds.size.x * 0.5f; 17 } 18 19 // Use this condition for objects that don’t move in the scene. 20 #if UNITY_EDITOR 21 void LateUpdate() 22 { 23 // Use this condition for objects that don’t move in the scene. 24 if (!Application.isPlaying) 25 { 26 // Update the position in the Z axis: 27 transform.position = new Vector3 28 ( 29 transform.position.x, 30 transform.position.y, 31 (transform.position.y - m_spriteLowerBound + m_floorHeight) * m_tan30 32 ); 33 } 34 } 35 #endif 36 37 void OnDrawGizmos() 38 { 39 Vector3 floorHeightPos = new Vector3 40 ( 41 transform.position.x, 42 transform.position.y - m_spriteLowerBound + m_floorHeight, 43 transform.position.z 44 ); 45 46 Gizmos.color = Color.magenta; 47 Gizmos.DrawLine(floorHeightPos + Vector3.left * m_spriteHalfWidth, floorHeightPos + Vector3.right * m_spriteHalfWidth); 48 } 49 }
首先需要設置的是“Floor Height”,該屬性決定Sprite的下邊界在Y方向的偏移。 在3D世界坐標中,它用於設置Sprite在場景中的Z深度。 如果一個Sprite的底部比其它Sprite更高,它將被渲染在其它Sprite后面。

然后存儲Sprite高度與寬度的半值,以便對Z坐標進行一些簡單的數學運算。在《A Place for the Unwilling》游戲中使用了30度的等距切角,但您也可以將Z坐標設為與Y坐標一致,不影響游戲效果。
這里使用OnDrawGizmos方法在當前的地面高度繪制一條線,以便可以在編輯器中設置為最終的精確位置。另外,對於有些游戲運行后永遠不會移動的對象,可以使用“if(!Application.isPlaying)”和“#if UNITY_EDITOR”條件在運行時保存計算結果,因為可能會有上百個Sprite同時綁定該腳本。
以上設置完成后,就可以在場景中移動Sprite並保證渲染順序正常了,但還有兩種情況需要更多的設置。
在處理中心不在中間位置的Sprite時,需要將其分為幾部分。以下面的建築為例,由於它的底部是矩形,如果整個建築僅設置一個Floor Height值,那角色將只能沿着它前方的那面行走,並且會遮擋角色!為了解決這個問題,就需要將建築Sprite分為兩個部分,並為每一邊設置不同的地面,如下圖:

另一種情況是將某個Sprite作為另一個的子對象時。仍然以建築為例,如果想為建築增加窗戶或招牌,這些附加物就不能使用與建築相同的腳本,因為有些窗戶可能位於建築后面或頂部。這個問題很容易解決,只需創建建築的子對象重置其坐標,並將Z坐標值設為-0.001,然后將所有需要附着在建築上物體放置於該子對象下,將這些物體的Z坐標設為0,這樣就可以與實際建築保持0.001的距離,並且它們離相機更近。
最終3D環境下的完整場景如下:
Unity引擎本身就已經提供了非常靈活的工具來實現這樣的功能,下面來看看這種實現方式存在的一些限制,以及一些有助於改進工作流程的擴展方法。
這種實現方式最大的限制就是制作很薄的牆壁時,因為使用該方法必須將Sprite切割為多個與牆壁厚度一致的部分,以便場景中的物體可以在牆壁前后移動。示例如下:
對於飛行物來說可能也比較麻煩,但如果注意其擺放的位置就可以避免出現問題。還可以通過修改Sorting Layer的值讓它們永遠位於場景主要對象的前方或后方。
最后分享一下如何擴展這種方式以適用更多的場景。
Isometric Colliders: 根據角色在游戲中的移動方式,實現一個小腳本為角色創建一個與游戲場景的圖片擺放角度一致的碰撞體。

IsoVector類:該類包含一些常用的方向向量(N,W,E,S,NE,NW,SE,SW),以及從自定義方向獲取向量(反之亦然)的方法,或者獲取給定方向的反向向量(例如輸入南獲取北)等。
本文介紹的內容不一定是最佳的解決方案,但也展現出了很好的學習思路,從最開始想到編寫腳本調整Sprite的Z值來正確渲染一切對象,解決了一開始構建游戲場景的問題。隨着繼續擴展代碼庫,也豐富了一些自定義類來加入新功能,同時維護好項目結構。希望這篇文章對正在使用Unity開發這種等距游戲的開發者有幫助!
原文連接:https://madewith.unity.com/en/stories/what-i-learned-from-trying-to-make-an-isometric-game-in-unity
原文作者:Martín Pane