在 Unity 中掌握 2D 相機:遊戲開發者教程
已發表: 2022-03-11對於開發者來說,相機是遊戲開發過程的基石之一。 從僅在國際象棋應用程序中顯示您的遊戲視圖到在 3D AAA 遊戲中巧妙地引導攝像機移動以獲得電影效果,攝像機基本上用於任何製作過的視頻遊戲,甚至在真正被稱為“攝像機”之前。
在這篇文章中,我將解釋如何為 2D 遊戲設計一個攝像頭系統,我還將解釋如何在最流行的遊戲引擎之一 Unity 中實現它的一些要點。
從 2D 到 2.5D:可擴展的相機系統
我們將共同設計的相機系統是模塊化和可擴展的。 它有一個基本核心,由幾個組件組成,這些組件將確保基本功能,然後根據手頭的情況可以選擇使用的各種組件/效果。
我們這裡構建的相機系統是針對2D平台遊戲的,但可以很容易地擴展到其他類型的2D遊戲、2.5D遊戲甚至3D遊戲。
我將把相機功能分為兩大類:相機跟踪和相機效果。
追踪
我們將在這裡進行的大部分攝像機移動將基於跟踪。 這是一個對象(在本例中為相機)在遊戲場景中移動時跟踪其他對象的能力。 我們將實施的跟踪類型將解決 2D 平台遊戲中遇到的一些常見場景,但可以通過新類型的跟踪來擴展您可能擁有的其他特定場景。
效果
我們將實現一些很酷的效果,如相機抖動、相機變焦、相機淡入淡出和顏色疊加。
入門
在 Unity 中創建一個新的 2D 項目並導入標準資源,尤其是 RobotBoy 角色。 接下來,創建一個地面盒並添加一個角色實例。 您應該能夠在當前場景中與您的角色一起行走和跳躍。 確保相機設置為正交模式(默認設置為透視)。
跟踪目標
以下腳本將為我們的主攝像頭添加基本跟踪行為。 該腳本必須作為組件附加到場景中的主攝像機,並且它公開了一個用於指定要跟踪的目標對象的字段。 然後腳本確保相機的 x 和 y 坐標與其跟踪的對象相同。 所有這些處理都是在更新步驟中完成的。
[SerializeField] protected Transform trackingTarget; // ... void Update() { transform.position = new Vector3(trackingTarget.position.x, trackingTarget.position.y, transform.position.z); }
將 RobotBoy 角色從場景層次結構中拖到我們以下行為所顯示的“跟踪目標”字段上,以啟用對主角的跟踪。
添加偏移量
一切都很好,但我們可以直接看到一個限制:角色總是在我們場景的中心。 我們可以看到角色背後的很多東西,這通常是我們不感興趣的東西,而我們看到的角色前面的東西太少了,這可能對遊戲玩法不利。
為了解決這個問題,我們在腳本中添加了一些新字段,允許將相機定位在距目標偏移的位置。
[SerializeField] float xOffset; [SerializeField] float yOffset; // ... void Update() { transform.position = new Vector3(trackingTarget.position.x + xOffset, trackingTarget.position.y + yOffset, transform.position.z); }
您可以在下面看到兩個新字段的可能配置:
平滑事物
攝像機的移動非常僵硬,並且還會因不斷感知環境的移動而使某些玩家感到頭暈。 為了解決這個問題,我們將使用線性插值在攝像機跟踪中添加一些延遲,並使用一個新字段來控制角色開始改變其位置後攝像機進入到位的速度。
[SerializeField] protected float followSpeed; // ... protected override void Update() { float xTarget = trackingTarget.position.x + xOffset; float yTarget = trackingTarget.position.y + yOffset; float xNew = Mathf.Lerp(transform.position.x, xTarget, Time.deltaTime * followSpeed); float yNew = Mathf.Lerp(transform.position.y, yTarget, Time.deltaTime * followSpeed); transform.position = new Vector3(xNew, yNew, transform.position.z); }
停止頭暈:軸鎖定
由於您的大腦不喜歡一直看著鏡頭隨著角色上下移動,因此我們引入了軸鎖定。 這意味著我們可以將跟踪限制在一個軸上。 然後我們將我們的跟踪代碼分離為獨立於軸的跟踪,我們將考慮新的鎖定標誌。
[SerializeField] protected bool isXLocked = false; [SerializeField] protected bool isYLocked = false; // ... float xNew = transform.position.x; if (!isXLocked) { xNew = Mathf.Lerp(transform.position.x, xTarget, Time.deltaTime * followSpeed); } float yNew = transform.position.y; if (!isYLocked) { yNew = Mathf.Lerp(transform.position.y, yTarget, Time.deltaTime * followSpeed); }
車道系統
現在相機只水平跟踪玩家,我們被限制在一個屏幕的高度。 如果角色爬了一些梯子或跳得比這更高,我們必須跟隨。 我們這樣做的方式是使用車道系統。
想像以下場景:
角色最初位於下車道。 當角色保持在該車道的邊界內時,相機將僅在我們可以設置的車道特定高度偏移上水平移動。
一旦角色進入另一條車道,攝像機將過渡到該車道並繼續從那里水平移動,直到發生下一個車道變換。
必須注意車道設計,以防止在跳躍等動作期間快速切換車道,這會給玩家造成混亂。 僅當玩家的角色要在其上停留一段時間時才應更改車道。
車道的關卡可以根據設計師的特定需求在整個遊戲關卡中發生變化,或者可以完全中斷,而另一個攝像頭跟踪系統可以代替它們。 因此,我們需要一些限制器來指定車道區域。
執行
一種可能的實現是將車道添加為場景中的簡單對象。 我們將在上面的跟踪腳本中使用它們的 Y 位置坐標和 Y 偏移來實現系統。 因此,它們在 X 和 Z 坐標上的定位無關緊要。
將 LaneSystem 類與跟踪類一起添加到相機,並將車道對象分配給提供的數組。 還將玩家角色分配給參考字段。 由於參考位於一條車道和另一條車道之間,因此兩者中較低的一個將用於定位相機。
LaneSystem 類負責根據參考位置在車道之間移動相機。 此處再次使用followSpeed進行位置插值,防止車道切換過於突然:
[SerializeField] Transform reference; [SerializeField] List<Transform> lanes; [SerializeField] float followSpeed = 5f; // ... void Update() { float targetYCoord = transform.position.y; if (lanes.Count > 1) { int i = 0; for (i = 0; i < lanes.Count - 1; ++i) { if ((reference.position.y > lanes[i].position.y) && (reference.position.y <= lanes[i + 1].position.y)) { targetYCoord = lanes[i].position.y; break; } } if (i == lanes.Count - 1) targetYCoord = lanes[lanes.Count - 1].position.y; } else { targetYCoord = lanes[0].position.y; } float yCoord = Mathf.Lerp(transform.position.y, targetYCoord, Time.deltaTime * followSpeed); transform.position = new Vector3(transform.position.x, yCoord, transform.position.z); }
這個實現不是所見即所得的,而是留給讀者作為練習。

鎖節點系統
讓攝像頭在車道上移動很棒,但有時我們需要將攝像頭鎖定在遊戲場景中的某個興趣點 (POI) 上。
這可以通過在場景中配置此類 POI 並將觸發對撞機附加到它們來實現。 每當角色進入觸發對撞機時,我們都會移動相機並停留在 POI 上。 隨著角色移動然後離開 POI 的觸發對撞機,我們回到另一種類型的跟踪,通常是標準的跟隨行為。
攝像機跟踪到鎖定節點和返回的切換可以通過簡單的開關或堆棧系統完成,在該堆棧系統上推送和彈出跟踪模式。
執行
為了配置一個鎖定節點,只需創建一個對象(可以是空的或像下面的屏幕截圖中那樣,一個精靈)並將一個大的 Circle Collider 2D 組件附加到它上,這樣它就可以標記玩家在相機將要進入的區域聚焦節點。 您可以選擇任何類型的對撞機,我在這裡選擇 Circle 作為示例。 還要創建一個您可以輕鬆檢查的標籤,例如“CameraNode”並將其分配給該對象。
將以下屬性添加到相機上的跟踪腳本中:
public Transform TrackingTarget { get { return trackingTarget; } set { trackingTarget = value; } }
然後將以下腳本附加到播放器,這將允許它臨時將相機的目標切換到您設置的鎖定節點。 該腳本還將記住其先前的目標,以便當玩家離開觸發區域時它可以返回到它。 如果需要,您可以繼續將其轉換為一個完整的堆棧,但出於我們的目的,因為我們不重疊多個鎖節點,這將是可行的。 另外請注意,您可以調整 Circle Collider 2D 的位置,或者再次添加任何其他類型的對撞機來觸發相機鎖定,這只是一個示例。
public class LockBehavior : MonoBehaviour { #region Public Fields [SerializeField] Camera camera; [SerializeField] string tag; #endregion #region Private private Transform previousTarget; private TrackingBehavior trackingBehavior; private bool isLocked = false; #endregion // Use this for initialization void Start() { trackingBehavior = camera.GetComponent<TrackingBehavior>(); } void OnTriggerEnter2D(Collider2D other) { if (other.tag == tag && !isLocked) { isLocked = true; PushTarget(other.transform); } } void OnTriggerExit2D(Collider2D other) { if (other.tag == tag && isLocked) { isLocked = false; PopTarget(); } } private void PushTarget(Transform newTarget) { previousTarget = trackingBehavior.TrackingTarget; trackingBehavior.TrackingTarget = newTarget; } private void PopTarget() { trackingBehavior.TrackingTarget = previousTarget; } }
相機變焦
攝像機縮放可以在用戶輸入時執行,也可以在我們想要關注諸如 POI 或關卡中更緊湊的區域之類的東西時作為動畫執行。
Unity 3D 中的 2D 相機縮放可以通過操縱相機的 orthographicSize 來實現。 將下一個腳本作為組件附加到相機並使用 SetZoom 方法更改縮放因子將產生所需的效果。 1.0 表示不縮放,0.5 表示放大兩次,2 表示縮小兩次,以此類推。
[SerializeField] float zoomFactor = 1.0f; [SerializeField] float zoomSpeed = 5.0f; private float originalSize = 0f; private Camera thisCamera; // Use this for initialization void Start() { thisCamera = GetComponent<Camera>(); originalSize = thisCamera.orthographicSize; } // Update is called once per frame void Update() { float targetSize = originalSize * zoomFactor; if (targetSize != thisCamera.orthographicSize) { thisCamera.orthographicSize = Mathf.Lerp(thisCamera.orthographicSize, targetSize, Time.deltaTime * zoomSpeed); } } void SetZoom(float zoomFactor) { this.zoomFactor = zoomFactor; }
屏幕抖動
每當我們需要在遊戲中顯示地震、爆炸或任何其他效果時,相機抖動效果都會派上用場。
GitHub 上提供瞭如何執行此操作的示例實現:gist.github.com/ftvs/5822103。 實現相當簡單。 與我們迄今為止介紹的其他效果不同,它依賴於一點隨機性。
淡入淡出和疊加
當我們的關卡開始或結束時,淡入或淡出效果很好。 我們可以通過在整個屏幕上延伸的面板中添加不可交互的 UI 紋理來實現這一點。 最初是透明的,我們可以用任何顏色和不透明度填充它,或者對其進行動畫處理以達到我們想要的效果。
這是該配置的示例,請注意 UI 面闆對像被分配給主相機對象的“相機疊加”子對象。 Camera Overlay 公開了一個名為 Overlay 的腳本,該腳本具有以下功能:
[SerializeField] Image overlay; // ... public void SetOverlayColor(Color color) { overlay.color = color; }
為了獲得淡入效果,通過在下一個腳本中使用 SetOverlayColor 設置的目標顏色添加插值來更改覆蓋腳本,並將面板的初始顏色設置為黑色(或白色)和目標顏色到疊加層的最終顏色。 您可以將 fadeSpeed 更改為適合您需要的任何值,我認為 0.8 對於初學者來說是一個不錯的選擇。 fadeSpeed 的值用作時間修飾符。 1.0 意味著它將發生在多個幀上,但在 1 秒的時間範圍內。 0.8 意味著它實際上需要 1/0.8 = 1.25 秒才能完成。
public class Overlay : MonoBehaviour { #region Fields [SerializeField] Image overlay; [SerializeField] float fadeSpeed = 5f; [SerializeField] Color targetColor; #endregion void Update() { if (overlay.color != targetColor) { overlay.color = Color.Lerp(overlay.color, targetColor, Time.deltaTime * fadeSpeed); } } #region Public public void SetOverlayColor(Color color) { targetColor = color; } #endregion }
包起來
在本文中,我試圖展示為您的遊戲配備模塊化 2D 相機系統所需的基本組件,以及設計它所需的思維定勢。 當然,所有遊戲都有其特定的需求,但是通過這裡描述的基本跟踪和簡單效果,您可以獲得很長的路要走,並且還可以擁有實現自己效果的藍圖。 然後,您可以更進一步,將所有內容打包到一個可重複使用的 Unity 3D 包中,您也可以將其轉移到其他項目中。
攝像頭系統對於為您的玩家傳達正確的氛圍非常重要。 當您想到古典戲劇和電影之間的區別時,我喜歡使用一個很好的比較。 相機和膠卷本身為場景帶來瞭如此多的可能性,以至於它最終獨立發展成為一種藝術,所以如果你不打算實現另一個“乒乓”遊戲,那麼在任何遊戲項目中,高級相機都應該是你的首選工具。 '從現在開始承擔。