wtorek, 25 kwietnia 2017

Sterowanie na Androidzie

Dzisiejszy wpis miał być kolejnym planowaniem. Jednak odwleczemy to jeszcze chwilę. Przed snem chciałem trochę pomyśleć co można fajnego dalej zrobić. Akurat wcześniej gdzieś tam przypadkiem wrzucił się fragment tego co zostało zrobione. Same patrzenie w ekran który nie można poruszyć pod dłuższym czasie dosyć szybko się nudzi. Więc czas zmienić ten stan rzeczy i stworzyć sterowanie na telefon.

Poszukiwania kontrolera

Od razu do głowy przyszło gdzieś tam stare wspomnienie że w grze rayman-ie na androidzie było to zrobione w dość fajny sposób. Tu co prawda akurat zdjęcie z innej platformy ale wygląda identycznie.


Zdjęcie stąd: http://www.imore.com/rayman-2-the-great-escape-now-in-app-store

Mamy tu sterowanie za pomocą analoga, coś na zasadzie określania pozycji dotyku palca (pole siwe) względem środka koła czerwonego. Trzeba więc tu przeliczać trochę współrzędnych, wychylenie. Zbyt czasochłonne aby to zrobić, więc rezygnujemy.


Szukając dalej w Internecie, w serwisie youtube w oko wpadła jedna gra. Filmik prezentuje po rosyjsku jakiś ranking gier mobilnych w 2015 roku, albo coś podobnego. Gra znajdziemy na google play pod link. Co szczególnego w tej grze to prostota, która wygląda dość fajnie. A i sterowanie idealne do naszych warunków polowych ;d



Link do tego rosyjskiego rankingu: https://www.youtube.com/watch?v=eoAG9lOPqTk

Klasa kontrolera

Więc zrobimy to w taki sam sposób jak w grze powyżej, przy czym później po prawej stronie nad przyciskiem skoku obsadzimy dodatkowe przyciski/tabliczki z słówkami. Plan więc jest, to do dzieła.

Na początku umieśmy grafiki odpowiedzialne z sterowanie w katalogu assets projektu androidowego.





Teraz stwórzmy klasę która będzie odpowiedzialna za rysowanie strzałek i wychwytywanie zdarzeń po ich kliknięciu. Tworzymy więc w pod pakiecie scenes nową klasę Controller. Klasa ta będzie miała dużo cech wspólnych z klasą Hud, gdzie wyświetlamy pola tekstowe u góry ekranu. Dodajemy elementy które będą potrzebne


Viewport viewport;
Stage stage;
boolean upPressed, leftPressed, rightPressed;
OrthographicCamera cam;

Viewport będzie obszarem zainteresowania który widzimy, stage jest sceną na której występują aktorzy, a w naszym przypadku to figury geometryczne które nasłuchujemy i które są odpowiedzialne za sterowanie. W zmiennych boolean będziemy przechowywać informacje czy akurat trzymamy palce nad danym przyciskiem, zmienna ta będzie mówiła o wysłaniu impulsu kierunku w odpowiednia stronę. Oraz ostatnią rzeczą będzie kamera mówiąca jak mamy patrzeć na ekran. Więcej informacji o tym w znajdziecie w wpisie.

Następnie tworzymy konstruktor wewnątrz którego umieszczamy


public Controller() {
    cam = new OrthographicCamera();
    viewport = new FitViewport(800, 480, cam);
    stage = new Stage(viewport, WordCharger.batch);

Zwykła inicjalizacja. Tu dosyć ważne przy tworzeniu sceny ustalamy gdzie mamy patrzeć oraz w drugim parametrze powinno się umieszczać batch (pojemnik z elementami do wyświetlania), jednak nie nowy, a ten z głównej klasy gry. Musimy więc poczynić małą zmianę, aby móc się do niej odwołać. W klasie WordCharger dodajemy zmiennej batch modyfikator static, dzięki temu określamy że od tego momentu może być tylko jeden taki egzemplarz tej zmiennej oraz istniejący przez cały czas działania programu.


public static SpriteBatch batch;

InputProcessor z nasłuchem

Dalej w konstruktorze ustalamy elementowi stage Input Processor, który jest odpowiedzialny za wyłapywanie wszelkich sygnałów wejściowych (touch screen, myszki, czy ekranu).


Gdx.input.setInputProcessor(stage); 

Kolejnym krokiem jest dodanie grafik zgodnie z kierunkiem sterowania. Zrobimy to dla jednego kierunku. Reszta wygląda analogicznie. Tworzymy więc obiekt Image nadając jakąś nazwę związana z kierunkiem np. controlsRightImage. Dalej w parametrze obiektu tworzymy nowy Texture wraz z nazwą pliku graficznego umieszczonego w folderze assets controlRight.png.


Image controlsRightImage = new Image(new Texture("controlRight.png"));

Ustalamy rozmiar jaki ma przyjąć obrazek


controlsRightImage.setSize(96, 96);

I teraz ważne dodajemy Listener przez stworzenie nowego listenera, otwieramy klamry wewnątrz których chcemy zaimplementować użyteczne nam metody. Listener jest obiektem który nasłuchuje, tzn. sprawdza czy nie pojawia się jakiś sygnał wejściowy.
 

controlsRightImage.addListener(new InputListener() {

}


Metody listenera

Klikając wewnątrz otwartego listenera prawym przyciskiem myszy wybieramy Generate… a następnie Override Methods… Z pośród wszystkich dostępnych wybieramy touchDown i touchUp. Te są akurat odpowiedzialne za dotknięcia, co będziemy wykonywać na ekranie naszego telefonu. Poza tym można zobaczyć że są inne odpowiedzialne za scroolowanie (przewijanie), obsługa myszy, czy klawiatury.

@Override

@Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
     rightPressed = true;
     return true;
}

@Override
public void touchUp(InputEvent event, float x, float y, int pointer, int button) {
     rightPressed = false;
}

Metoda down jak nazwa mówi wykona się jak paluch będzie nad tym elementem strzałki. Ustawiamy więc wartość boolean rightPressed na true. Metoda jest typu boolean więc taką wartość trzeba zwrócić przez return true. Zresztą środowisko w którym pracujemy nas o tym poinformuje przez podkreślenie błędu. Metoda touchUp jest przeciwieństwem więc trzeba ustawić wartość boolean na false. Sama metoda jest typu void więc nic nie zwraca. Krótko podsumowując tworzymy obiekty obrazków które będą nasłuchiwane/sprawdzane czy nie następuje ich dotknięcie. W zależności od tego ustawiamy odpowiednią zmienną boolean. Ta w późniejszych etapach będzie przekazana do głównej pętli gry i w zależności od wartości tych zmiennych będzie wykonywany impuls w odpowiednia stronę.

Wyświetlanie w table

Teraz czas aby wyświetlić wszystkie strzałki kierunku. Aby zachować pewną organizacją i łatwość ustalania położenia skorzystamy z table (stołu). Już to wcześniej robiliśmy. Obszerna dokumentacja o wszystkich możliwych położeniach jest tutaj.

Więc tak generalnie umieszczamy przycisk lewo i prawo na dole ekranu po lewej stronie. Tworzymy stół ustalając jego pozycję i metodą setFillParent mówimy aby dopasował się wielkości stage.


Table tableLeftRight = new Table();
tableLeftRight.left().bottom();
tableLeftRight.setFillParent(true);

Table tableJump = new Table();
tableJump.right().bottom();
tableJump.setFillParent(true);

Tworzymy 2 stoły już tłumacze dlaczego. Chcemy stworzyć przyciski lewa/prawa po lewej stronie i góra (up) po prawej.
Metodą left() można od razu określić pozycję stołu po lewej stronie. I to by było ok na ten moment. Dodatkowo chcemy lekko odsunąć od lewej krawędzi ekranu, co przy różnych ekranach zawsze było by uwzględniane. Problem pojawia się przy dodaniu przycisku po prawej stronie. Ustalając raz pozycję stołu, zmieniając teraz pozycję na prawo metodą right() przeniosło by to wszystkie elementy na prawo. To nas nie urządza.

Drugą opcją byłoby dodaniu przestrzeni ileś pikseli zaczynając od lewej strony np. przy korzystaniu z metody pad. Tylko to zupełnie nie zdało by testu przy innych ekranach.

Co nam potrzeba to złapać prawą stronę i to od niej odliczyć odległość jaką chcemy od prawej krawędzi ekranu. Te rozwiązanie zda test przy każdych rozdzielczościach, dlatego więc tworzymy 2 niezależne stoły.

Teraz poniżej image które stworzyliśmy do istniejących stołów dodajemy właśnie te obiekty, a następnie całość dodajemy jako aktor do stage. Stage jest sceną z obiektami które będą mogły być wyrysowane.

Dla przycisku lewa i prawa


tableLeftRight.padBottom(10);
tableLeftRight.add().pad(10);
tableLeftRight.add(controlsLeftImage).size(controlsLeftImage.getWidth(), controlsLeftImage.getHeight());
tableLeftRight.add().pad(20);
tableLeftRight.add(controlsRightImage).size(controlsRightImage.getWidth(), controlsRightImage.getHeight());
stage.addActor(tableLeftRight);

dla przycisku skoku


tableJump.padBottom(10);
tableJump.add(controlsUpImage).size(controlsUpImage.getWidth(), controlsUpImage.getHeight());
tableJump.add().pad(10);
stage.addActor(tableJump); 

Warto zwrócić uwagę że przy dodawaniu do stołu dodaliśmy obiekt obrazku wraz z  jego rozmiarem który, określiliśmy przy tworzeniu obiektu.

odległości które poustawialiśmy wyżej


Obsługa z zewnątrz

Potrzebujemy teraz móc wywołać metodę rysowania na obiekcie tego stage z klasy głównej gry, tworzymy więc do tego odpowiednią metodę.


public void draw() {
    stage.draw();
}

Pora użyć zbudowanego kontrolera w grze. W klasie PlayScreen deklarujemy


Controller controller;

W konstruktorze PlayScreen zaś inicjujemy tworząc nowy obiekt Controller


controller = new Controller();

W metodzie render czas na wyświetlenie. Robimy to na końcu, dzięki czemu będzie ostatnie rysowane i będzie przykrywało wszystkie inne obiekty (będzie na wierzchu)


controller.draw();

Warto przy okazji przenieść tutaj na koniec kiedyś dokonane rysowanie hud, co dotychczas było niepoprawnie, ponieważ było niewidoczne zakryte gdzieś pod spodem


hud.stage.draw();

Efekt już widać


Touch, Touch, Touch

Jak można sprawdzić jest wszystko widoczne, ale brak reakcji na jakiekolwiek dotknięcie. Trzeba sobie przypomnieć jak to działało wcześniej dla klawiatury. W metodzie update która wykonywana jest cyklicznie co określony przyrost czasu była wywoływana metoda handleInput(). A w wewnątrz niej sprawdzaliśmy czy jakiś klawisz nie jest wciśnięty. Teraz aby dokonać tego samego dla stworzonego kontrolera trzeba dodać kolejne warunki w handleInput()


if (contoller.isRightPressed() &&  player.b2dBody.getLinearVelocity().x <= 2) {
    player.b2dBody.applyLinearImpulse(new Vector2(2f, 0), player.b2dBody.getWorldCenter(), true);
}
if (contoller.isLeftPressed() && player.b2dBody.getLinearVelocity().x >= -2) {
    player.b2dBody.applyLinearImpulse(new Vector2(-2f, 0), player.b2dBody.getWorldCenter(), true);
}
if (contoller.isUpPressed()) {
    player.b2dBody.applyLinearImpulse(new Vector2(0, 1f), player.b2dBody.getWorldCenter(), true);
}

Nie będzie to omawiane. Najważniejszym punktem jest zmiana pierwszej części warunku z input na sprawdzenie wartości boolean w klasie controller. Musimy więc wydobyć te wartości z klasy kontrolera. Przechodzimy i tworzymy wewnątrz Controllera gettery, czyli metody odpowiedzialne za zwracanie wartości. Getter działa dosyć prosto zwraca wartości zmiennej która jest widoczna w danej klasie. Ponieważ do zmiennych w klasie nie powinno się mieć dostęp w sposób bezpośredni, stosuje się metody które  służą tylko do wyciągania. W tym przypadku daje to pewne zabezpieczenie, ponieważ getter oferuje tylko zwracanie wartości która jest ustawiona, nie może jednak tej wartości z innej klasy zmienić. Chyba że istnieją jeszcze settery. Na ten moment te wyjaśnienie z pewnością wystarczy. Zarówno gettery jak i settery można wygenerować automatyczne, z racji że jest to często używany mechanizm, środowiska programistyczne nam to udogadniają.

Klikamy prawym przyciskiem gdzieś poniżej ciała konstruktora i wybieramy Generate… i następnie Getter. Wybieramy leftPressed, rightPressed i upPressed.


public boolean isLeftPressed() {
    return leftPressed;
}

public boolean isRightPressed() {
    return rightPressed;
}

public boolean isUpPressed() {
    return upPressed;
}

Mamy więc gettery z których możemy skorzystać jako metody klasy Controller. Przechodzimy z powrotem do warunku odpowiedzialnego w metodzie handleInput()
I wywołujemy na obiekcie kontroler właśnie te metody contoller.isRightPressed(). Metoda zwróci nam odpowiednią wartość boolean zmianianą przez listener obrazka strzałki sterowania  i jeżeli któraś z nich będzie będzie true to zostanie wykonany impuls.

Po przetestowaniu dodatkowo został zmieniony wektor siły przy wciśnięciu UpPressed na newVector (0, 1f), jest bardziej realistyczny i nie wywalający od razu w górę.

Tyle, Efekt jak wyżej na rysunku, tylko można dodatkowo sterować.

Pozdrawiam

https://github.com/KrzysztofPawlak/WordCharger/tree/wpis16

Brak komentarzy:

Prześlij komentarz