Opanowanie kamer 2D w Unity: samouczek dla twórców gier

Opublikowany: 2022-03-11

Dla dewelopera kamera jest jednym z fundamentów procesu tworzenia gry. Od pokazywania widoku gry w aplikacji szachowej po mistrzowskie kierowanie ruchem kamery w grze 3D AAA w celu uzyskania efektów kinowych, kamery są zasadniczo używane w każdej grze wideo, jaką kiedykolwiek stworzono, nawet zanim nazwano ją „kamerą”.

W tym artykule wyjaśnię, jak zaprojektować system kamer do gier 2D, a także wyjaśnię kilka punktów dotyczących implementacji go w jednym z najpopularniejszych silników gier, Unity.

Od 2D do 2,5D: rozszerzalny system kamer

System kamer, który wspólnie zaprojektujemy, jest modułowy i rozszerzalny. Posiada podstawowy rdzeń składający się z kilku komponentów, które zapewnią podstawową funkcjonalność, a następnie różne komponenty/efekty, które można opcjonalnie wykorzystać, w zależności od sytuacji.

System kamer, który tutaj budujemy, jest skierowany do platformówek 2D, ale można go łatwo rozszerzyć na inne rodzaje gier 2D, gier 2.5D, a nawet gier 3D.

Opanowanie kamery 2D w Unity: samouczek dla twórców gier

Opanowanie kamery 2D w Unity: samouczek dla twórców gier
Ćwierkać

Mam zamiar podzielić funkcjonalność kamery na dwie główne grupy: śledzenie kamery i efekty kamery.

Śledzenie

Większość ruchu kamery, który tutaj wykonamy, będzie oparta na śledzeniu. Jest to zdolność obiektu, w tym przypadku kamery, do śledzenia innych obiektów poruszających się w scenie gry. Rodzaje śledzenia, które będziemy wdrażać, rozwiążą niektóre typowe scenariusze spotykane w grach platformowych 2D, ale można je rozszerzyć o nowe typy śledzenia dla innych konkretnych scenariuszy, które możesz mieć.

Efekty

Wprowadzimy kilka fajnych efektów, takich jak drgania aparatu, zoom aparatu, blaknięcie aparatu i nakładka kolorów.

Pierwsze kroki

Utwórz nowy projekt 2D w Unity i zaimportuj standardowe zasoby, zwłaszcza postać RobotBoy. Następnie utwórz pole ziemi i dodaj instancję znaku. Powinieneś być w stanie chodzić i skakać swoją postacią w aktualnej scenie. Upewnij się, że aparat jest ustawiony w trybie Orthographic (domyślnie jest ustawiony na Perspective).

Śledzenie celu

Poniższy skrypt doda podstawowe zachowanie śledzenia do naszego głównego aparatu. Skrypt musi być dołączony jako komponent do głównej kamery w twojej scenie i udostępnia pole do przypisania obiektu docelowego do śledzenia. Następnie skrypt zapewnia, że ​​współrzędne x i y kamery są takie same jak śledzony obiekt. Całe to przetwarzanie odbywa się na etapie aktualizacji.

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

Przeciągnij postać RobotBoya z hierarchii scen na pole „Cel śledzenia” ujawnione przez nasze następujące zachowanie, aby umożliwić śledzenie głównego bohatera.

Dodawanie przesunięcia

Wszystko dobrze, ale od razu widzimy ograniczenie: postać zawsze znajduje się w centrum naszej sceny. Dużo widzimy za postacią, co zwykle jest czymś, czym nie jesteśmy zainteresowani, i zbyt mało widzimy tego, co jest przed naszą postacią, co może mieć negatywny wpływ na rozgrywkę.

Aby rozwiązać ten problem, dodajemy do skryptu kilka nowych pól, które pozwolą na ustawienie kamery w przesunięciu od jej celu.

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

Poniżej możesz zobaczyć możliwą konfigurację dla dwóch nowych pól:

Wygładzanie rzeczy

Ruch kamery jest dość sztywny i powoduje zawroty głowy u niektórych graczy z powodu ciągłego postrzegania ruchu otoczenia. Aby temu zaradzić, dodamy opóźnienie w śledzeniu kamery za pomocą interpolacji liniowej oraz nowe pole do kontrolowania, jak szybko kamera znajdzie się na swoim miejscu po tym, jak postać zacznie zmieniać swoją pozycję.

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

Powstrzymaj zawroty głowy: blokowanie osi

Ponieważ nie jest przyjemnie patrzeć, jak kamera porusza się w górę iw dół wraz z postacią, wprowadzamy blokowanie osi. Oznacza to, że możemy ograniczyć śledzenie tylko do jednej osi. Następnie podzielimy nasz kod śledzenia na śledzenie niezależne od osi i uwzględnimy nowe flagi blokujące.

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

Układ pasów

Teraz, gdy kamera śledzi gracza tylko w poziomie, jesteśmy ograniczeni do wysokości jednego ekranu. Jeśli postać wspina się po jakiejś drabinie lub skacze wyżej, musimy podążać za nią. Robimy to za pomocą systemu pasów.

Wyobraź sobie następujący scenariusz:

Postać jest początkowo na dolnym pasie. Podczas gdy postać pozostaje w granicach tego pasa, kamera będzie poruszać się tylko poziomo na określonym przesunięciu wysokości pasa, które możemy ustawić.

Gdy tylko postać wejdzie na inny pas, kamera przejdzie na ten pas i będzie dalej poruszała się poziomo, aż do następnej zmiany pasa.

Należy zwrócić uwagę na projekt pasa, aby zapobiec szybkiej zmianie pasa podczas akcji, takich jak skoki, które mogą powodować zamieszanie dla gracza. Pas powinien być zmieniany tylko wtedy, gdy postać gracza ma na nim pozostać przez jakiś czas.

Poziomy pasów mogą się zmieniać w trakcie gry w zależności od konkretnych potrzeb projektanta lub mogą zostać całkowicie przerwane, a ich miejsce może zająć inny system śledzenia kamery. Dlatego potrzebujemy pewnych ograniczników do określania stref pasów.

Realizacja

Możliwą implementacją jest dodanie pasów jako prostych obiektów w scenie. Użyjemy ich współrzędnej pozycji Y w połączeniu z przesunięciem Y w powyższym skrypcie śledzenia, aby wdrożyć system. Dlatego ich położenie na współrzędnych X i Z nie ma znaczenia.

Dodaj klasę LaneSystem do kamery wraz z klasą śledzącą i przypisz obiekty torów do podanej tablicy. Przypisz także postać gracza do pola referencyjnego. Ponieważ odniesienie znajduje się między jednym pasem a innym pasem, do pozycjonowania kamery zostanie użyty dolny z nich.

A klasa LaneSystem zajmuje się przenoszeniem kamery między pasami w oparciu o pozycję odniesienia. FollowSpeed ​​jest tutaj ponownie używany do interpolacji pozycji, aby zapobiec zbyt gwałtownej zmianie pasów:

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

Ta implementacja nie jest implementacją WYSIWYG i jest pozostawiona jako ćwiczenie dla czytelnika.

Zablokuj system węzłów

Posiadanie kamery poruszającej się po pasach jest świetne, ale czasami potrzebujemy, aby kamera była nastawiona na coś, na interesujący punkt (POI) w scenie gry.

Można to osiągnąć, konfigurując takie POI w scenie i dołączając do nich zderzacz wyzwalający. Za każdym razem, gdy postać wchodzi do zderzacza wyzwalającego, poruszamy kamerą i pozostajemy w punkcie POI. Gdy postać się porusza, a następnie opuszcza zderzacz wyzwalający POI, wracamy do innego rodzaju śledzenia, zwykle standardowego zachowania podążania.

Przełączanie śledzenia kamery do węzła blokującego iz powrotem można wykonać za pomocą prostego przełącznika lub systemu stosu, w którym tryby śledzenia są wciskane i wyskakiwane.

Realizacja

Aby skonfigurować węzeł blokady, po prostu stwórz obiekt (może być pusty lub jak na poniższym zrzucie ekranu, sprite) i dołącz do niego duży komponent Circle Collider 2D, aby zaznaczył obszar, w którym będzie się znajdował gracz, gdy kamera będzie skoncentruj się na węźle. Możesz wybrać dowolny typ zderzacza, tutaj jako przykład wybieram Circle. Utwórz również tag, który możesz łatwo sprawdzić, np. „CameraNode” i przypisz go do tego obiektu.

Dodaj następującą właściwość do skryptu śledzenia w aparacie:

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

Następnie dołącz poniższy skrypt do gracza, który pozwoli mu tymczasowo przełączyć cel kamery na ustawiony węzeł blokady. Skrypt zapamiętuje również swój poprzedni cel, dzięki czemu będzie mógł do niego wrócić, gdy gracz znajdzie się poza obszarem aktywacji. Możesz iść dalej i przekształcić to w pełny stos, jeśli tego potrzebujesz, ale dla naszego celu, ponieważ nie nakładamy się na wiele węzłów blokujących, to wystarczy. Należy również pamiętać, że można dostosować pozycję Circle Collider 2D lub ponownie dodać dowolny inny rodzaj zderzacza, aby uruchomić blokadę aparatu, to tylko zwykły przykład.

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

Zoom aparatu

Zoom kamery może być wykonywany na podstawie danych wejściowych użytkownika lub jako animacja, gdy chcemy skupić się na czymś takim jak POI lub węższy obszar na poziomie.

Zoom kamery 2D w Unity 3D można uzyskać, manipulując ortograficznym rozmiarem kamery. Dołączenie następnego skryptu jako komponentu do kamery i użycie metody SetZoom do zmiany współczynnika powiększenia da pożądany efekt. 1.0 oznacza brak powiększenia, 0.5 oznacza dwukrotne powiększenie, 2 oznacza dwukrotne oddalenie i tak dalej.

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

Wstrząsanie ekranu

Ilekroć musimy pokazać trzęsienie ziemi, jakąś eksplozję lub jakikolwiek inny efekt w naszej grze, przydaje się efekt potrząsania kamerą.

Przykładowa implementacja, jak to zrobić, jest dostępna na GitHub: gist.github.com/ftvs/5822103. Implementacja jest dość prosta. W przeciwieństwie do innych efektów, które omówiliśmy do tej pory, opiera się na odrobinie losowości.

Blaknięcie i nakładka

Kiedy nasz poziom zaczyna się lub kończy, fajny jest efekt zanikania lub zanikania. Możemy to zaimplementować, dodając nieinteraktywną teksturę interfejsu użytkownika w panelu rozciągającym się na cały ekran. Początkowo przeźroczysty, możemy go wypełnić dowolnym kolorem i kryciem lub animować, aby uzyskać pożądany efekt.

Oto przykład takiej konfiguracji, proszę zwrócić uwagę, że obiekt Panelu interfejsu użytkownika jest przypisany do elementu podrzędnego „Nakładka kamery” obiektu kamery głównej. Nakładka aparatu udostępnia skrypt o nazwie Nakładka, który zawiera następujące elementy:

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

Aby uzyskać efekt zanikania, zmień swój skrypt nakładki, dodając interpolację do docelowego koloru ustawionego za pomocą SetOverlayColor, jak w następnym skrypcie, i ustaw początkowy kolor naszego Panelu na Czarny (lub Biały) i kolor docelowy do ostatecznego koloru Twojej nakładki. Możesz zmienić fadeSpeed ​​na wszystko, co odpowiada Twoim potrzebom, myślę, że 0.8 to dobry na początek. Wartość fadeSpeed ​​działa jako modyfikator czasu. 1.0 oznacza, że ​​nastąpi to w wielu klatkach, ale w ciągu 1 sekundy. 0,8 oznacza, że ​​ukończenie zadania zajmie 1/0,8 = 1,25 sekundy.

 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 }

Zakończyć

W tym artykule próbowałem zademonstrować podstawowe komponenty potrzebne do posiadania modułowego systemu kamer 2D w twojej grze, a także jaki jest wymagany nastawienie do jej zaprojektowania. Oczywiście wszystkie gry mają swoje szczególne potrzeby, ale dzięki opisanemu tutaj podstawowym śledzeniu i prostym efektom możesz przejść długą drogę, a także mieć plan implementacji własnych efektów. Następnie możesz pójść jeszcze dalej i spakować wszystko do pakietu Unity 3D wielokrotnego użytku, który możesz przenieść również do innych projektów.

Systemy kamer są bardzo ważne w tworzeniu odpowiedniej atmosfery dla twoich graczy. Dobrym porównaniem, którego lubię używać, jest myślenie o różnicy między klasycznym teatrem a filmem. Same kamery i film wniosły tak wiele możliwości do sceny, że ostatecznie przekształciła się ona w sztukę samą w sobie, więc jeśli nie planujesz wdrażać kolejnej gry „Pong”, zaawansowane kamery powinny być Twoim narzędziem z wyboru w każdym projekcie gry podejmę się od teraz.

Powiązane: Unity z MVC: jak podnieść poziom rozwoju gry