2D-Kameras in Unity beherrschen: Ein Tutorial für Spieleentwickler

Veröffentlicht: 2022-03-11

Für einen Entwickler ist die Kamera einer der Eckpfeiler des Spieleentwicklungsprozesses. Von der einfachen Anzeige Ihrer Spielansicht in einer Schach-App bis hin zur meisterhaften Steuerung der Kamerabewegung in einem 3D-AAA-Spiel, um filmische Effekte zu erzielen, werden Kameras grundsätzlich in jedem Videospiel verwendet, das jemals entwickelt wurde, noch bevor sie tatsächlich als „Kameras“ bezeichnet werden.

In diesem Artikel werde ich erklären, wie man ein Kamerasystem für 2D-Spiele entwirft, und ich werde auch einige Punkte erläutern, wie man es in einer der beliebtesten Spiele-Engines da draußen, Unity, implementiert.

Von 2D zu 2,5D: Ein erweiterbares Kamerasystem

Das Kamerasystem, das wir gemeinsam entwerfen werden, ist modular und erweiterbar. Es hat einen Grundkern, der aus mehreren Komponenten besteht, die die Grundfunktionalität sicherstellen, und dann verschiedene Komponenten/Effekte, die je nach Situation optional verwendet werden können.

Das Kamerasystem, das wir hier bauen, ist auf 2D-Plattformspiele ausgerichtet, kann aber leicht auf andere Arten von 2D-Spielen, 2,5D-Spiele oder sogar 3D-Spiele erweitert werden.

2D-Kamera in Unity beherrschen: Ein Tutorial für Spieleentwickler

2D-Kamera in Unity beherrschen: Ein Tutorial für Spieleentwickler
Twittern

Ich werde die Kamerafunktionalität in zwei Hauptgruppen aufteilen: Kameraverfolgung und Kameraeffekte.

Verfolgung

Die meisten Kamerabewegungen, die wir hier ausführen, basieren auf Tracking. Das ist die Fähigkeit eines Objekts, in diesem Fall der Kamera, andere Objekte zu verfolgen, während sie sich in der Spielszene bewegen. Die Tracking-Typen, die wir implementieren werden, lösen einige gängige Szenarien, die in 2D-Plattformspielen auftreten, aber sie können mit neuen Tracking-Typen für andere bestimmte Szenarien erweitert werden, die Sie möglicherweise haben.

Auswirkungen

Wir werden einige coole Effekte wie Kameraverwacklung, Kamerazoom, Kameraüberblendung und Farbüberlagerung implementieren.

Einstieg

Erstellen Sie ein neues 2D-Projekt in Unity und importieren Sie Standard-Assets, insbesondere den RobotBoy-Charakter. Erstellen Sie als Nächstes eine Bodenbox und fügen Sie eine Zeicheninstanz hinzu. Sie sollten in der Lage sein, mit Ihrem Charakter in Ihrer aktuellen Szene zu gehen und zu springen. Stellen Sie sicher, dass die Kamera auf den orthografischen Modus eingestellt ist (standardmäßig ist sie auf Perspektive eingestellt).

Ein Ziel verfolgen

Das folgende Skript fügt unserer Hauptkamera grundlegendes Tracking-Verhalten hinzu. Das Skript muss als Komponente an die Hauptkamera in Ihrer Szene angehängt werden und stellt ein Feld für die Zuweisung eines zu verfolgenden Zielobjekts bereit. Dann stellt das Skript sicher, dass die x- und y-Koordinaten der Kamera mit dem verfolgten Objekt übereinstimmen. Diese gesamte Verarbeitung erfolgt während des Aktualisierungsschritts.

 [SerializeField] protected Transform trackingTarget; // ... void Update() { transform.position = new Vector3(trackingTarget.position.x, trackingTarget.position.y, transform.position.z); }

Ziehen Sie die RobotBoy-Figur aus Ihrer Szenenhierarchie über das Feld „Verfolgungsziel“, das durch unser folgendes Verhalten freigelegt wird, um die Verfolgung der Hauptfigur zu ermöglichen.

Offset hinzufügen

Alles gut, aber eine Einschränkung sehen wir auf Anhieb: Die Figur steht immer im Mittelpunkt unserer Szene. Wir können viel hinter den Charakter sehen, was uns normalerweise nicht interessiert, und wir sehen zu wenig von dem, was vor unserem Charakter liegt, was dem Gameplay abträglich sein könnte.

Um dies zu lösen, fügen wir dem Skript einige neue Felder hinzu, die es ermöglichen, die Kamera versetzt zu ihrem Ziel zu positionieren.

 [SerializeField] float xOffset; [SerializeField] float yOffset; // ... void Update() { transform.position = new Vector3(trackingTarget.position.x + xOffset, trackingTarget.position.y + yOffset, transform.position.z); }

Unten sehen Sie eine mögliche Konfiguration für die beiden neuen Felder:

Dinge glätten

Die Kamerabewegung ist ziemlich steif und wird bei einigen Spielern auch Schwindel durch die ständig wahrgenommene Bewegung der Umgebung hervorrufen. Um dies zu beheben, werden wir eine Verzögerung bei der Kameraverfolgung mit linearer Interpolation und ein neues Feld hinzufügen, um zu steuern, wie schnell die Kamera an Ort und Stelle kommt, nachdem der Charakter beginnt, seine Position zu ändern.

 [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); } 

Stoppen Sie den Schwindel: Achsensperre

Da es für Ihr Gehirn nicht angenehm ist, zuzusehen, wie die Kamera die ganze Zeit zusammen mit der Figur auf und ab geht, führen wir die Achsenverriegelung ein. Dadurch können wir das Tracking auf nur eine Achse beschränken. Dann trennen wir unseren Tracking-Code in achsenunabhängiges Tracking und berücksichtigen die neuen Locking-Flags.

 [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); } 

Lane-System

Da die Kamera den Spieler nun nur noch horizontal verfolgt, sind wir auf die Höhe eines Bildschirms beschränkt. Wenn der Charakter eine Leiter erklimmt oder höher springt, müssen wir ihm folgen. Wir tun dies, indem wir ein Spursystem verwenden.

Stellen Sie sich folgendes Szenario vor:

Der Charakter befindet sich zunächst auf der unteren Spur. Während die Figur innerhalb der Grenzen dieser Spur bleibt, bewegt sich die Kamera nur horizontal um einen spurspezifischen Höhenversatz, den wir einstellen können.

Sobald die Figur eine andere Spur betritt, wechselt die Kamera zu dieser Spur und bewegt sich von dort weiter horizontal, bis der nächste Spurwechsel erfolgt.

Beim Bahndesign muss darauf geachtet werden, dass bei Aktionen wie Sprüngen ein schneller Bahnwechsel verhindert wird, der den Spieler verwirren kann. Eine Bahn sollte nur gewechselt werden, wenn der Charakter des Spielers eine Weile darauf bleiben wird.

Die Ebenen der Bahnen können sich während des gesamten Spiellevels ändern, je nach den spezifischen Bedürfnissen des Designers, oder sie können ganz unterbrochen werden und ein anderes Kamera-Tracking-System kann ihren Platz einnehmen. Daher benötigen wir einige Begrenzer zum Spezifizieren von Spurzonen.

Implementierung

Eine mögliche Implementierung besteht darin, Bahnen als einfache Objekte in die Szene einzufügen. Wir werden ihre Y-Positionskoordinate gepaart mit dem Y-Offset im obigen Tracking-Skript verwenden, um das System zu implementieren. Daher spielt ihre Positionierung auf den X- und Z-Koordinaten keine Rolle.

Fügen Sie der Kamera die LaneSystem-Klasse zusammen mit der Tracking-Klasse hinzu, und weisen Sie die Spurobjekte dem bereitgestellten Array zu. Weisen Sie auch den Spielercharakter dem Referenzfeld zu. Da die Referenz zwischen einer Fahrspur und einer anderen Fahrspur positioniert ist, wird die untere der beiden verwendet, um die Kamera zu positionieren.

Und die LaneSystem-Klasse sorgt dafür, dass die Kamera basierend auf der Referenzposition zwischen den Fahrspuren bewegt wird. Der followSpeed ​​wird hier wieder zur Positionsinterpolation verwendet, um einen zu abrupten Spurwechsel zu vermeiden:

 [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); }

Diese Implementierung ist keine WYSIWYG-Implementierung und wird als solche dem Leser als Übung überlassen.

Lock-Node-System

Es ist großartig, die Kamera auf Bahnen bewegen zu können, aber manchmal muss die Kamera auf etwas fixiert sein, z. B. auf einen Point of Interest (POI) in der Spielszene.

Dies kann erreicht werden, indem man solche POIs in der Szene konfiguriert und ihnen einen Trigger Collider anhängt. Immer wenn der Charakter diesen Trigger Collider betritt, bewegen wir die Kamera und bleiben auf dem POI. Wenn sich der Charakter bewegt und dann den Trigger-Collider des POI verlässt, kehren wir zu einer anderen Art der Verfolgung zurück, normalerweise dem Standard-Folgeverhalten.

Das Umschalten des Kamera-Trackings auf einen Lock Node und zurück kann entweder durch einen einfachen Schalter oder durch ein Stack-System erfolgen, auf dem Tracking-Modi gepusht und gepoppt werden.

Implementierung

Um einen Sperrknoten zu konfigurieren, erstellen Sie einfach ein Objekt (kann leer sein oder wie im Screenshot unten ein Sprite) und befestigen Sie eine große Circle Collider 2D-Komponente daran, damit es den Bereich markiert, in dem sich der Spieler befindet, wenn die Kamera es tut Fokussieren Sie den Knoten. Sie können jede Art von Collider auswählen, ich wähle hier Circle als Beispiel. Erstellen Sie auch ein Tag, nach dem Sie leicht suchen können, wie „CameraNode“, und weisen Sie es diesem Objekt zu.

Fügen Sie dem Tracking-Skript auf Ihrer Kamera die folgende Eigenschaft hinzu:

 public Transform TrackingTarget { get { return trackingTarget; } set { trackingTarget = value; } }

Fügen Sie dann das folgende Skript an den Player an, das es ihm ermöglicht, das Ziel der Kamera vorübergehend auf den von Ihnen festgelegten Sperrknoten umzuschalten. Das Skript merkt sich auch sein vorheriges Ziel, sodass es dorthin zurückkehren kann, wenn sich der Spieler außerhalb des Auslösebereichs befindet. Sie können dies bei Bedarf in einen vollständigen Stapel umwandeln, aber für unseren Zweck reicht dies aus, da wir nicht mehrere Sperrknoten überlappen. Bitte beachten Sie auch, dass Sie die Position des Circle Collider 2D anpassen oder jede andere Art von Collider hinzufügen können, um die Kamerasperre auszulösen. Dies ist nur ein Beispiel.

 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; } } 

Kamera-Zoom

Der Kamerazoom kann entweder auf Benutzereingaben oder als Animation ausgeführt werden, wenn wir uns auf etwas wie einen POI oder einen engeren Bereich innerhalb eines Levels konzentrieren möchten.

Der 2D-Kamerazoom in Unity 3D kann durch Manipulieren der orthographicSize der Kamera erreicht werden. Wenn Sie das nächste Skript als Komponente an eine Kamera anhängen und die SetZoom-Methode verwenden, um den Zoomfaktor zu ändern, wird der gewünschte Effekt erzielt. 1,0 bedeutet kein Zoom, 0,5 bedeutet zweimal hineinzoomen, 2 bedeutet zweimal herauszoomen und so weiter.

 [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; }

Bildschirmerschütterung

Wann immer wir ein Erdbeben, eine Explosion oder einen anderen Effekt in unserem Spiel zeigen müssen, ist ein Kameraverwacklungseffekt praktisch.

Eine Beispielimplementierung dafür ist auf GitHub verfügbar: gist.github.com/ftvs/5822103. Die Implementierung ist ziemlich einfach. Im Gegensatz zu den anderen Effekten, die wir bisher behandelt haben, ist er auf ein wenig Zufälligkeit angewiesen.

Überblenden & Überlagern

Wenn unser Level beginnt oder endet, ist ein Ein- oder Ausblendeffekt schön. Wir können dies implementieren, indem wir eine nicht interagierbare UI-Textur in einem Panel hinzufügen, das sich über unseren gesamten Bildschirm erstreckt. Anfangs transparent, können wir dies mit jeder Farbe und Deckkraft füllen oder animieren, um den gewünschten Effekt zu erzielen.

Hier ist ein Beispiel für diese Konfiguration. Bitte beachten Sie, dass das UI-Panel-Objekt dem untergeordneten „Kamera-Overlay“ des Hauptkamera-Objekts zugewiesen ist. Camera Overlay stellt ein Skript namens Overlay bereit, das Folgendes bietet:

 [SerializeField] Image overlay; // ... public void SetOverlayColor(Color color) { overlay.color = color; } 

Um einen Einblendeffekt zu erzielen, ändern Sie Ihr Overlay-Skript, indem Sie eine Interpolation zu einer Zielfarbe hinzufügen, die Sie wie im nächsten Skript mit SetOverlayColor festlegen, und die Anfangsfarbe unseres Panels auf Schwarz (oder Weiß) und die Zielfarbe setzen bis zur endgültigen Farbe Ihres Overlays. Sie können die FadeSpeed ​​so ändern, wie es Ihren Bedürfnissen entspricht. Ich denke, 0,8 ist für den Anfang gut. Der Wert von fadeSpeed ​​fungiert als Zeitmodifikator. 1.0 bedeutet, dass es über mehrere Frames hinweg geschieht, aber innerhalb eines Zeitrahmens von 1 Sekunde. 0,8 bedeutet, dass es tatsächlich 1/0,8 = 1,25 Sekunden dauert, bis es fertig ist.

 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 }

Einpacken

In diesem Artikel habe ich versucht, die grundlegenden Komponenten zu demonstrieren, die für ein modulares 2D-Kamerasystem für Ihr Spiel erforderlich sind, und auch, welche Denkweise für die Entwicklung erforderlich ist. Natürlich haben alle Spiele ihre besonderen Bedürfnisse, aber mit dem hier beschriebenen grundlegenden Tracking und einfachen Effekten kommt man weit und hat auch eine Blaupause für die Implementierung eigener Effekte. Dann können Sie noch weiter gehen und alles in ein wiederverwendbares Unity 3D-Paket packen, das Sie auch auf andere Projekte übertragen können.

Kamerasysteme sind sehr wichtig, um die richtige Atmosphäre für Ihre Spieler zu vermitteln. Ein guter Vergleich, den ich gerne verwende, ist der Unterschied zwischen klassischem Theater und Film. Die Kameras und der Film selbst brachten so viele Möglichkeiten in die Szene, dass sie sich schließlich zu einer eigenen Kunst entwickelten. Wenn Sie also nicht vorhaben, ein weiteres „Pong“-Spiel zu implementieren, sollten fortschrittliche Kameras das Werkzeug Ihrer Wahl für jedes Spielprojekt sein werde von nun an übernehmen.

Siehe auch: Unity mit MVC: Wie Sie Ihre Spieleentwicklung verbessern