Unity3d QuickTip – czyli szybkie porady, rozwiązania częstych problemów i sztuczki w Unity3d!
Dzisiejszy odcinek: Jak zrobić kamerę do gry RTS?
Uwaga! Jest to poradnik typu QuickTip. Zatem skupia się on na osiągnięciu założonego celu. Zatem zakładamy że użytkownik zna na tyle program Unity3d, aby samodzielnie wykonać najprostsze czynności, jak np. dodanie modelu kostki do sceny czy dodanie modelowi jakiegoś komponentu. Jeżeli brakuje Ci tej podstawowej wiedzy, zapraszam do tutoriala:
Unity Tutorial – Podstawy
Teoria
W większości gier do dyspozycji dostajemy bohatera, którego poczynania obserwujemy od boku, zza pleców, albo z pierwszej osoby. Jednak gatunek gier RTS (Real Time Strategy) kompletnie zmieniają perspektywę. Losy wielu postaci obserwujemy z góry (pod lekkim kątem). Dawniej, kamerę dało się jedynie przesuwać, bo świat gry wypełniony był dwuwymiarowymi spritami.
Z biegiem czasu i rozwojem technologii, w grach RTS zaczęły pojawiać się modele 3D. Przez to, że środowisko było w trzech wymiarach, kamera mogła zacząć się nieco ruszać, tym samym pojawiał się opcja przybliżania obrazu czy jego rotowania. Właśnie taką kamerę sobie dziś zrobimy.
Przygotowanie
Jeżeli chodzi o obiekty, to po utworzeniu domyślnej sceny w Unity nie mamy wiele do dodatnia. Potrzebujemy punktów odniesienia. Ja utworzyłem sobie teren z jakimiś pagórkami w losowych miejscach. Nie musi to mieć sensu, chodzi o to, żebyśmy widzieli jak zachowuje się kamera. Od razu tworzymy sobie skrypt (CameraController.cs), który przypisujemy do głównej kamery.
Jest tylko jedno małe ale. Nie możemy pracować naszym skryptem bezpośrednio na kamerze. Tzn. możemy, ale gdy zaczniemy tworzyć rotację kamery, wszystko się posypie – wyjaśnię to przy punkcie tworzenia rotacji. Dlatego od razu dodajemy sobie pusty GameObject [CTRL + SHIFT + N]. Kamerę umieszczamy jako child tego obiektu (ja go nazwałem Camera Holder). Teraz kluczowe jest tutaj ustawienie obiektów względem siebie.
Camera Holder umieszczamy w sumie dowolnie, ale przy powierzchni planszy. Za to sama kamera, musi mieć identyczny układ osi względem Holdera i znajdować się nieco wyżej i za nim. Brzmi skomplikowanie, dlatego dodaję screen:
Dla pewności podaję też położenie obiektów:
- Camera Holder: Position: (20, 0, 20) ; Rotation: (0, 0, 0)
- Camera: Position: (0, 30, -40) ; Rotation (30, 0, 0)
Dzięki temu, otrzymujemy potrzebną relację, oraz charakterystyczną lekko pochyloną kamerę. Wchodzimy teraz do edycji przygotowanego skryptu. Napoczynamy go takim kodem:
[Header("Speed")] public float scrollSpeed = 15; public float zoomSpeed = 25; public float rotationSpeed = 15; public float scrollKeyboardSpeed = 15; [Header("Limits")] public float minZoom = 40; public float maxZoom = 20; public Vector2 minPosition = new Vector2 (0, 0); public Vector2 maxPosition = new Vector2 (400, 400); private GameObject contener; private float scrollSpeedUp = 0; void Start() { contener = gameObject.transform.parent.gameObject; }
Mamy tutaj zestaw pomocniczych zmiennych określających szybkość różnych ruchów kamery (przesuwania, obrotu, przybliżania) oraz ograniczniki dla tych ruchów (żeby nie dało się przybliżać i oddalać w nieskończoność, czy wyjechać za obszar mapy). Zmienne są publiczne, żeby dało się je dostosować z poziomu Unity. Zmienna contener to odnośnik do naszego Camera Holdera, który ustawiamy w funkcji start. Jako, że jest rodzicem obiektu kamery, można to zrobić bardzo prosto. Druga zmienna prywatna to zmienna tymczasowa, która pozwala przyspieszyć przewijanie – najczęściej w grach RTS, przewijanie za pomocą strzałek na klawiaturze jest szybsze, niż przewijanie przez przybliżenie kursora myszy do krawędzi ekranu.
Jeżeli kogoś zastanawia skąd takie, a nie inne limity czy prędkości. Parametry dobierałem metodą prób i błędów. Czyli ustawiałem jakąś wartość i zmniejszałem lub zwiększałem, jeżeli efekt nie był zadowalający. Dzięki temu, że zmienne są publiczne, można to bardzo łatwo zrobić z poziomu Unity.
Ciekawe jeszcze mogą być linijki z [Header(“”)]. Cóż to takiego? Prosta dyrektywa, która doda nam elegancki nagłówek dla publicznych zmiennych w inspektorze.
Od teraz reszta kodu znajdzie się w funkcji Update.
Przesuwanie kamery
Zaczniemy od podstawowej czynności jaką powinna robić kamera, czyli od opcji przesuwania jej.
// Przesuwanie kamery myszka i klawiatura if ((Input.mousePosition.y >= Screen.height * 0.95 || Input.GetKey (KeyCode.UpArrow)) && contener.transform.position.z < maxPosition.y) { contener.transform.Translate (Vector3.forward * Time.deltaTime * (scrollSpeed + scrollSpeedUp)); } if ((Input.mousePosition.y <= Screen.height * 0.05 || Input.GetKey (KeyCode.DownArrow)) && contener.transform.position.z > minPosition.y) { contener.transform.Translate (Vector3.back * Time.deltaTime * (scrollSpeed + scrollSpeedUp)); } if ((Input.mousePosition.x <= Screen.width * 0.05 || Input.GetKey (KeyCode.LeftArrow)) && contener.transform.position.x > minPosition.x) { contener.transform.Translate (Vector3.left * Time.deltaTime * (scrollSpeed + scrollSpeedUp)); } if ((Input.mousePosition.x >= Screen.width * 0.95 || Input.GetKey (KeyCode.RightArrow)) && contener.transform.position.x < maxPosition.x) { contener.transform.Translate (Vector3.right * Time.deltaTime * (scrollSpeed + scrollSpeedUp)); }
Wszystkie ify są analogiczne, więc opisze tylko jednego.
W Unity nie ma funkcji, która bezpośrednio wykryję dotknięcie krawędzi ekranu przez kursor. Dlatego robimy to poniekąd ręcznie. Dysponujemy jedynie pozycją myszki na ekranie oraz kompletną rozdzielczością ekranu. Więc sprawdzamy, czy pozycja myszki jest mniejsza od 5% szerokości/wysokości ekranu, albo czy jest większa od 95% wysokości/szerokości ekranu. Zaraz za tym warunkiem, mamy spójnik “lub” (||), a za nim odpowiadającą ruchowi myszki strzałkę. Dzięki temu, wykona się ruch bez względu na to, czy operujemy strzałkami czy myszką.
Jeżeli następuje ruch, sprawdzamy jeszcze jeden warunek (za spójnikiem “i” (&&)). Mianowicie, czy nie przekroczyliśmy dopuszczalnego zasięgu, opisanego w limitach. Jeżeli wszystko się zgadza, dokonujemy ruchu kamery (a właściwie kontenera) w odpowiednią stronę. Dlaczego kontenera? Bo nasza kamera jest pochylona, przez to ruch do przodu, wcale nie byłby ruchem w górę mapy.
[stextbox id=”info” defcaption=”true”]Jest tutaj alternatywa. Można zastosować trzeci parametr funkcji Translate, czyli określić układ współrzędnych dla ruchu. Wtedy kod wyglądałby tak:
gameObject.transform.Translate (Vector3.right * Time.deltaTime * (scrollSpeed + scrollSpeedUp), Space.World);
Jednak ta metoda ma jedną wadę. Jeżeli wykonamy rotację dla kamery, taki zapis sprawiłby, że ciągle przesuwalibyśmy się po tym samym układzie współrzędnych. Co za tym idzie, mogłoby dojść do sytuacji, że gdy chcemy przesuwać kamerę w lewo, faktycznie przesuwamy ją w przód.[/stextbox]
Zostaje kwestia przyspieszania za pomocą strzałek. Robi to następujący kod, umieszczony nad poprzednimi ifami:
if (Input.GetButton ("Horizontal") || Input.GetButton ("Vertical")) { scrollSpeedUp = scrollKeyboardSpeed; } else { scrollSpeedUp = 0; }
Sprawa jest prosta. Jeśli naciskamy dowolną strzałkę, podciągamy prędkość. Zmienna scrollSpeedUp jest dodawana do standardowego mnożnika, więc ustawienie jej na zero niczego nie psuje.
Zoomowanie
Tutaj sprawa w sumie jest najprostsza:
if ((Input.mouseScrollDelta.y / 10) > 0 && gameObject.transform.position.y > maxZoom) { gameObject.transform.Translate(Vector3.forward * Time.deltaTime * zoomSpeed); } if ((Input.mouseScrollDelta.y / 10) < 0 && gameObject.transform.position.y < minZoom) { gameObject.transform.Translate(Vector3.back * Time.deltaTime * zoomSpeed); }
Najpierw wyłapujemy zmianę scrolla myszki. (Moglibyśmy skorzystać z funkcji GetAxis, ale jeśli wynik mouseScrollDelta podzielimy przez 10, dostaniemy ten sam wynik, a zaproponowana przeze mnie funkcja jest nico szybsza w działaniu). Ponownie po “and” sprawdzamy czy nie przekroczyliśmy limitu. Jeśli nie, przybliżamy.
Jak widać, ciekawe tutaj jest, że przybliżając, przesuwamy obiekt kamery do przodu. Dzięki temu, że kamera jest pochylona, otrzymujemy żądany efekt.
Obracanie kamery
Tech ruch najczęściej następuje, gdy naciśniemy kółko myszki i przybliżymy kursor do jednej z krawędzi ekranu. Zrobimy dokładnie tak samo.
if (Input.GetMouseButton(2)) { // Tu będziemy wstawiać nowy kod } else { // Tutaj należy przenieść kod odpowiedzialny za przesuwanie kamery }
Z racji, że będziemy operować na kodzie z przesuwania, skrypt musi wiedzieć kiedy ma obracać, a kiedy przesuwać. Kod, który napisaliśmy do przesuwania kamery, należy wkleić do funkcji else (tam gdzie występuje komentarz w kodzie). Dodatkowe linijki dopisujemy wewnątrz ifa:
// Obrót po naciśnięciu kółka if (Input.mousePosition.x <= Screen.width * 0.05 || Input.GetKey(KeyCode.LeftArrow)) { contener.transform.Rotate(Vector3.down * Time.deltaTime * rotationSpeed); } if (Input.mousePosition.x >= Screen.width * 0.95 || Input.GetKey(KeyCode.RightArrow)) { contener.transform.Rotate(Vector3.up * Time.deltaTime * rotationSpeed); }
Kod wygląda tak samo jak ten do przesuwania, tylko zamiast funkcji Translate mamy Rotate. Za to, tutaj widać jak potrzebny jest nam nasz Camera Holder. Gdybyśmy obracali sam obiekt kamery, obracalibyśmy się wokół własnej osi, a nie wokół punktu na który patrzymy, co było by mylące i bez sensu.
Koniec!