Wiele gier na Androida nie ma nawet wystarczająco dużego zasięgu, aby uzasadnić zapisanie / wczytanie lub opcje / preferencje, nie wspominając o niestandardowych postaciach itp. Po prostu dlatego, że gra się je od ręki przez 10 minut w pociągu do domu.
Nie popełnij błędu, gdy popełniłem coś o dużym zasięgu w pierwszej grze na Androida. Stwórz kilka mniejszych gier, a następnie wybierz coś większego
Specyfika Androida mądra:
Struktura:
Poleciłbym mieć prawie całą grę w jednej klasie aktywności. Android działa na zasadzie, że każda aktywność jest jak mini-aplikacja, częściowo niezależna od innych działań w aplikacji. Aplikacja jest zasadniczo zbiorem działań, z których użytkownik widzi, co kiedykolwiek jest na górze.
Kiedy zdejmiesz jeden z góry, na ogół ulega zniszczeniu i zostanie odtworzony następnym razem, gdy użytkownik rozpocznie nową aktywność (intencja startActivity). Utrudnia to utrzymanie stanów między działaniami i prowadzi do architektury pojedynczego działania
Możesz chcieć mieć aktywność typu Ekran główny z menu „Nowa gra / Załaduj grę / Opcje” i spraw, aby była to aktywność uruchamiania. Będziesz jednak musiał mieć menu w grze, które będzie zawierało większość tych samych funkcji
Własną aplikację stworzyłem „MenuActivity”, która ma funkcje, które uruchamiają nową grę, zapisują, ładują, zmieniają opcje. Następnie moja HomeActivity rozszerza to, podobnie jak moja GameActivity.
Ponieważ wszystko znajduje się w jednej klasie Activity, zaleciłbym zrobienie hierarchii klas aktywności i użycie prywatnych, chronionych i domyślnych partii zakresu. Robiąc to w ten sposób, możesz przynajmniej podzielić rzeczy na inny plik, aby przestać mieć jeden niemożliwy do zarządzania plik działania. Np. Dla mojej własnej aplikacji:
GraphicsEngine extends MenuActivity
.
PhysicsEngine extends GraphicsEngine
.
GameLogicActivity extends PhysicsEngine
.
UIActivity extends GameLogicActivity
.
Ponieważ moją aplikacją są trójwymiarowe pliki OpenGL, wiele rzeczy, które robię, nie działają między różnymi aktywnościami, więc wszystko jest w tej samej aktywności, więc może jestem skłonny do korzystania z jednej architektury działań
-
Gwintowanie
W przypadku aktywności w grze masz dwa wątki (lub 3, jeśli robisz 3D w OpenGL). Jednym wątkiem jest interfejs użytkownika / główny wątek. To jest wątek, w którym aktywność zaczyna się i działa i jest dostarczana przez Androida.
Ten wątek interfejsu użytkownika jest jedynym, w którym można aktualizować elementy interfejsu użytkownika (widoki, układy itp.). Jest to również ten, w którym będą uruchamiane wszelkie nasłuchiwania danych wejściowych użytkownika. Nie widzisz żadnej mechaniki wątku interfejsu użytkownika, po prostu działa on gdzieś w pętli w tle.
Drugi wątek, który sam tworzysz (polecam korzystanie z AsyncTask , pomimo wszystkich jego wad). Robi to wszystko, co nie robisz w interfejsie użytkownika, np. Aktualizowanie ruchu, obliczanie kolizji, obliczenia walki i tak dalej.
Ten wątek / klasę AsyncTask tworzysz jako wewnętrzną klasę swojej aktywności. W ten sposób możesz mieć pewne obiekty o szerokim zakresie aktywności ( Vector<Spaceship>
), do których można uzyskać dostęp zarówno przez wątek interfejsu użytkownika, jak i wątek pętli gry.
Ponieważ logika gry toczy się w wątku pętli gry, jest to jedyny wątek, który będzie musiał faktycznie zmienić wartości zmiennych (aktualizacja prędkości czołgu, zmniejszenie HP gracza). Wątek interfejsu użytkownika po prostu odczytuje wartości, więc powinny wystąpić minimalne problemy z współbieżnością.
Trudnym zadaniem jest nakłonienie wątku interfejsu użytkownika do wykonania aktualizacji na żądanie wątku pętli gry. Można to zrobić na kilka sposobów. Jeśli czytasz dokumentację AsyncTask, ma ona metodę opublikujProgress () / onProgressUpdate (). To właściwie dodaje rzeczy do kolejki wątków interfejsu użytkownika, aby wykonać następną pętlę.
Program obsługi robi dokładnie to samo, jeśli implementujesz metodę handleMessage () i ta metoda jest faktycznie wykonywana przez następną pętlę wątku interfejsu użytkownika.
Wreszcie, wszelkie dane wprowadzone przez użytkownika, znajdujące się w wątku interfejsu użytkownika, można natychmiast zaktualizować z niego elementy interfejsu użytkownika (czyli wewnątrz implementacji dowolnych detektorów onClick / onTouch). Jeśli musisz zaktualizować obiekty gry, możesz albo użyć synchronizacji, albo zaimplementować własną kolejkę aktualizacji w AsyncTask, która podobnie jak wątek interfejsu użytkownika, przechodzi przez następne uruchomienie pętli gry
Android Threading Guide .
-
Interfejs użytkownika
Jeśli chodzi o rzeczywistą strukturę interfejsu użytkownika w ramach pojedynczego działania, zalecam układ ramki jako układ podstawowy. Elementy potomne w układzie ramki działają jak kolejka. Pierwszy element jest rysowany pierwszy, drugi rysowany na pierwszym, trzeci na drugim.
W ten sposób możesz mieć wiele plików układu XML, a zarządzając dziećmi podrzędnymi układu ramki, możesz łatwo zamieniać zestawy widoków na zewnątrz i na zewnątrz.
Jeśli zawsze masz SurfaceView na dole (pierwsze dziecko w układzie ramki), możesz użyć wszystkich zwykłych widoków / widżetów Androida (przyciski, widoki tekstu, widoki przewijania) itp. U góry widoku powierzchni, co po prostu powoduje część graficzna gry.
Na przykład, jeśli coś się ładuje, możesz zatrzymać pętlę gry / po prostu sprawić, aby pominąć wszystko, dodać nieprzejrzysty ekran „ładowanie” jako ostatnie dziecko do układu ramki, a użytkownik zobaczy na ekranie komunikat „ładowanie” , zupełnie nieświadomy, że stoją za tym inne poglądy. W ten sposób nie musisz usuwać widoków, których konfiguracja zajmuje dużo czasu ani nie powoduje komplikacji za każdym razem, gdy są dodawane / usuwane.
Poleciłbym również korzystanie z partii View.setVisibility. Możesz na przykład dodać cały układ „ekwipunku” do podstawowego układu ramki, a następnie po prostu ustawić na nim widoczność (View.Visible), gdy użytkownik kliknie, aby wyświetlić swój ekwipunek, i ustawić widoczność (View.Gone), gdy zamknie go ponownie. W ten sposób nie zarządzasz nawet elementami potomnymi układu ramki, a jedynie dodajesz wszystko i sprawiasz, że rzeczy są widoczne / niewidoczne, gdy użytkownik robi różne rzeczy
Pomaga to w ponownym wątkowaniu; gdy użytkownik klika, aby otworzyć swój ekwipunek, onCLickListener jest obsługiwany w wątku interfejsu użytkownika, inwentarz jest widoczny w nim, a metoda updateInventory jest wywoływana ponownie z wątku interfejsu użytkownika, a wywoływane są tylko obiekty pobierające wszystkie obiekty gry
Oto schemat, który stworzyłem dla wcześniejszego pytania dotyczącego idei pojedynczego działania / układu ramki: