wtorek, 27 grudnia 2011

AndroidPlay - część 6: Obsługa TouchEventów

Wstęp.

Dziś już ostatnia lekcja przygotowawcza do tworzenia gry. Umiemy wystarczająco dużo, żeby wyświetlać użytkownikowi scenę naszej gry. Teraz każdy z was zapewne powie: ej chłopie, ale przecież gry to rozrywka interakcyjna, komunikacja musi zachodzić w obie strony - my umiemy powiedzieć coś użytkownikowi, ale co z nim?! Tym zajmiemy się teraz, przed nami obsługa ekranu dotykowego.


Gra w pełnym ekranie.

Na początek uruchomimy naszą aktywność w fullscreenie, przecież ten pasek z jej tytułem oraz pasek stanu u góry tylko nam przeszkadzają. W klasie naszej aktywności w metodzie onCreate() przed wywołaniem setContentView() wrzucamy poniższy kod:
// Wyłącz pasek tytułu
getWindow().requestFeature(Window.FEATURE_NO_TITLE);
// Wyłącz pasek stanu (ten ze stanem baterii i godziną) gdy to okno jest aktywne
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 
                     WindowManager.LayoutParams.FLAG_FULLSCREEN);
Krótkie przygotowania

Zrobimy teraz taki mały programik. Założenie jest takie, mamy jakąś okrągłą buźkę na ekranie, będziemy mogli nią obracać przy użyciu palca, dodatkowo jeśli walniemy nim na buźce, to będzie zmieniać wyraz twarzy. W dużej mierze korzystamy z kodu z lekcji piątej. Musimy tylko załadować dwie tekstury zamiast jednej i ustawić teksturę, która ma być używana jako pierwsza, bo jak pamiętamy, gdy rysujemy jakieś obiekty, to gdy teksturowanie jest włączone, to używana jest aktualna jednostka tekstury.
gl.glGenTextures(2, texIDs, 0);
        
Bitmap bmp1 = BitmapFactory.decodeResource(appContext.getResources(), R.drawable.face1);
gl.glBindTexture(GL10.GL_TEXTURE_2D, texIDs[0]);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_NEAREST);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bmp1, 0);
        
Bitmap bmp2 = BitmapFactory.decodeResource(appContext.getResources(), R.drawable.face2);
gl.glBindTexture(GL10.GL_TEXTURE_2D, texIDs[1]);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_NEAREST);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bmp2, 0);
        
// Ustaw jako aktualną teksturę uśmiechniętą minkę
gl.glBindTexture(GL10.GL_TEXTURE_2D, texIDs[0]);
Musimy także zmienić organizację kodu - sposób w jaki robiliśmy to dotychczas nie sprawdza się jeśli musimy obsługiwać wejście. Mianowicie obsługa TouchEventów jest zapewniana poprzez nadpisanie metody onTouchEvent(MotionEvent e) z GLSurfaceView. Tak samo postępujemy gdy chcemy obsłużyć inne wejścia np. onKeyDown, onKeyUp czy onTrackballEvent. Do tej pory tworzyliśmy po prostu obiekt tej klasy w zmiennej view, teraz tworzymy własną klasę, która dziedziczy po w/w klasie i to jej instancję umieszczamy w zmiennej view. Klasa ta także przejmie ustawianie Renderera i będzie przechowywać jego referencję - zapewni nam to dostęp do jego zmiennych w tej klasie. Musimy leciutko przerobić nasz kod.
// W metodzie onCreate() w klasie rozszerzającej Activity pozostaje tylko
// (...) <- ustawienie fullscreena
view = new AppView(this);
setContentView(view);
// Tworzymy klasę rozszerzającą GLSurfaceView
class AppView extends GLSurfaceView { 
    public AppView(Context context) {
 super(context);
 renderer = new AppRenderer(context);
        setRenderer(renderer);
    }
 
    AppRenderer renderer;
    float xOld;
    float yOld;

    @Override
    public boolean onTouchEvent(MotionEvent e) {
        return true;
    }
}
Teraz jeszcze, aby mieć dostęp do potrzebnych zmiennych z renderera dajemy im dostęp pakietowy:
boolean isHappyFace = true;// humor minki
float angle = 0.0f;        // kąt obrotu
int resolutionX = 480,
    resolutionY = 320;
Reakcja na TouchEvent. Obiekt MotionEvent niesie ze sobą informacje o ruchu. Wykorzystamy metodę getAction() aby rozpoznać rodzaj wykonywanej akcji i odpowiednio zareagować. Pozycję akcji możemy pobrać metodami getX(), getY(), pamiętajmy, że tutaj lewy dolny róg to pozycja (0, 0), prawy górny to (maxX, maxY). Oto podstawowe akcje:
  • ACTION_DOWN - rozpoczęto ruch poprzez naciśnięcie palca
  • ACTION_MOVE - ruch jest w trakcie wykonywania
  • ACTION_UP - zakończono ruch poprzez oderwanie palca

Mamy już do nich dostęp w metodzie onTouchEvent, więc sprawdzimy, czy walnęliśmy w minkę wykrywając akcję oderwania już palca od ekranu i zmienimy wyraz twarzy na przeciwny. Tutaj także zajmiemy się ustawianiem odchylenia (kąta odchylenia) w reakcji na przesunięcie palca, dlatego w onDrawFrame usuwamy te zmiany kąta zależne od czasu - w zasadzie mierzenie czasu też już nie jest potrzebne. Uzupełniamy metodę onTouchEvent:
float x = e.getX();
float y = e.getY();
  
    switch(e.getAction()) {
        case MotionEvent.ACTION_UP:
            // Wylicz środek minki
     float xCenter = renderer.resolutionX/2.0f;
     float yCenter = renderer.resolutionY/2.0f;
     // i sprawdź czy oderwałem palec na mince
     if(x > xCenter - 75.0f && x < xCenter + 75.0f &&
        y > yCenter - 75.0f && y < yCenter + 75.0f   )
                renderer.isHappyFace = !renderer.isHappyFace; 
    
        case MotionEvent.ACTION_MOVE:
     float dx = xOld - x;
     float dy = yOld - y;
   
     // Odwróć kierunek obrotu powyżej środka
     if(y > renderer.resolutionY/2)
         dx = dx * -1;
   
     // Odwróć kierunek obrotu na lewo od środka
     if(x < renderer.resolutionX/2)
         dy = dy * -1;
   
     renderer.angle += (dx + dy);
    }
  
xOld = x;
yOld = y;
Rysując ustawiamy aktywną jednostkę tekstury w zależności od humoru minki. W porównaniu do lekcji 5: usunąłem też oddalenie od środka osi obrotu (środka ukł. współrzędnych), dlatego minka obraca się wokół własnej osi. Przypominam, że usunąłem zmianę kąta obrotu w tym miejscu.
if(isHappyFace)
    gl.glBindTexture(GL10.GL_TEXTURE_2D, texIDs[1]);
else
    gl.glBindTexture(GL10.GL_TEXTURE_2D, texIDs[0]);
        
        
gl.glPushMatrix();
// Przesun układ z lewego-dolnego rogu na środek okna.
gl.glTranslatef((float)resolutionX/2.0f, (float)resolutionY/2.0f, 0.0f);
    gl.glRotatef(angle, 0.0f, 0.0f, 1.0f); // Obróć
        
    gl.glVertexPointer(3, GL10.GL_FLOAT, 0, quadsVB);
    gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, quadsTCB);
    gl.glDrawElements(GL10.GL_TRIANGLES, 6, GL10.GL_UNSIGNED_SHORT, quadsIB);
gl.glPopMatrix();

Jeżeli pogubiłeś się w tych zmianach, bo było ich troszkę to proponuję zerknąć do całego kodu - znajdziesz go poniżej. Myślę, że umiemy już wystarczająco, aby prostą gierkę sobie napisać. Standardowo załączam screen.






<< Poprzednia lekcja
Materiały do pobrania:

Brak komentarzy:

Prześlij komentarz