Game-Apps für Smartphones und Tablets

Bern University oh Teacher Education  
HomeOnline-Editor startenDruckenAndroid-TurtlegrafikJava-Online

Fling-Events


Smartphones und Tables werden oft mit einer "Wischbewegung" bedient, bei der man schnell mit einem Finger über den Bildschirm streicht.
Die Reaktion des Systems hängt von der Richtung und der Geschwindigkeit der Bewegung ab. In JDroidLib können solche Aktionen mit Fling-Events programmiert werden. Dazu wird ein GGFlingListener implementiert, der mit der Methode addFlingListener() registriert wird. Bei einer Fling-Bewegung liefert die Callbackmethode flingEvent() den Punkt, an dem der Finger zuerst den Bildschirm berührt, den Punkt, wo der Finger den Bildschirm verlässt und den Geschwindigkeitsvektor der Bewegung zwischen den beiden Punkten (in Pixel pro Sekunde).


In unserem Beispiel wird ein Ball mit einer Wischbewegung über den Bildschirm geschleudert, wobei die Fingerbewegung rechts der grünen Linie enden muss. Der Ball bewegt sich auf einer Wurfparabel. Die Position, Geschwindigkeit und Beschleunigung werden physikalisch berechnet. Wird der Basketballkorb von oben getroffen, wird dies als ein Treffer gezählt. Ziel ist es, möglichst viele Treffer zu erreichen.

Beispiel im Online-Editor bearbeiten

App installieren auf Smartphone oder Tablet

QR-Code

Sources downloaden (AndroidEx24.zip)

 

Für die Berechnung der Wurfparabel werden die physikalischen Formeln verwendet. Dabei rechnet man mit realen Koordinaten (Positionen in Metern, Geschwindigkeiten in m/s und Beschleunigungen m/s^2). Um die Situation auf einem Smartphone- bzw. Tablet-Display darzustellen, werden in JDroidLib Userkoordinaten aus der Klasse GGPanel verwendet. Dabei handelt es sich um double Koordinaten (x-Achse nach rechts, y-Achse nach ober mit frei wählbarem Koordinatenursprung. Damit lassen sich oft umständliche Umrechnungen zwischen realen und Pixelkoordinaten vermeiden. Allerdings beziehen sich Locations immer auf Pixelkoordinaten, was bei der Positionierung der Actors wichtig ist. Alle Zeichenoperationen werden mit GGPanel-Methoden ausgeführt und beziehen sich auf Userkoordinaten.

Touchevents liefern Pixelkoordinaten und müssen mit den Methoden toUserX() und toUserY() aus der Klasse GGPanel auf Userkoordinaten umgerechnet werden, damit die Berechnungen mit physikalischen Formeln erfolgen kann. Mit windowZoom() werden alle Sprite-Bilder an die Grösse des Displays angepasst.



Programmcode:

// AndroidEx24.java

package app.ex24;

import ch.aplu.android.*;
import android.graphics.Point;

public class AndroidEx24 extends GameGrid implements GGFlingListenerGGActorCollisionListener
{
  private final double vFactor = 50;
  private double roomHeight = 5;
  private Basket basket;
  protected GGStatusBar status;
  protected GGPanel p;
  protected int hits = 0;
  protected int shots = 0;

  public AndroidEx24()
  {
    super(WHITE, falsetruewindowZoom(600));
    setScreenOrientation(LANDSCAPE);
    status = addStatusBar(30);
  }

  public void main()
  {
    addFlingListener(this);
    setSimulationPeriod(30);
    p = getPanel(0, roomHeight, 0);  // center lower-right
    p.setAutoRefreshEnabled(
false);
    p.setLineWidth(4);
    p.setPaintColor(GREEN);
    p.line(6, 0, 6, roomHeight);
    basket = new Basket();
    addActor(basket, new Location(p.toPixelX(0.5), p.toPixelY(3)));
    basket.setCollisionRectangle(new Point(-10-20), 40, 10);
    doRun();
    status.setText("Fling the ball!");
  }

  public boolean flingEvent(Point start, Point end, GGVector velocity)
  {
    double = p.toUserX(end.x);
    double = p.toUserY(end.y);
    double vx = vFactor * velocity.x;
    double vy = -vFactor * velocity.y;

    if (x > 6)
    {
      Ball ball = new Ball(thisx, y, vx, vy);
      addActorNoRefresh(ball, new Location(end.xend.y));
      ball.addCollisionActor(basket);
      ball.addActorCollisionListener(this);
      ball.setCollisionCircle(new Point(0, 0), 24);
      shots++;

    }
    else
      showToast("Stay behind the line!");
    return true;
  }

  public int collide(Actor actor1, Actor actor2)
  {
    if (((Ball)actor1).getVelocityY() < -0.1)
    {
      hits++;
      playTone(1200, 20);
      ((Ball)actor1).setVelocityX(0);
      ((Ball)actor1).setX(0.5);
    }
    return 10;
  }

  protected void displayResult()
  {
    status.setText(String.format("#shots: %d   #hits: %d   %%: %4.1f",
      shots, hits, 100.0 * hits / shots));
  }
}

// -----------class Ball-----------------
class Ball extends Actor
{
  private final double = 9.81; // in m/s^2
  private double x, y;  // in m 
  private double vx, vy; // in  m/s
  private double dt = 0.030;  // in s (simulation period)
  private AndroidEx24 app;

  public Ball(AndroidEx24 app, double x, double y, double vx, double vy)
  {
    super("ball");
    this.app = app;
    // Initial conditions:
    this.= x;
    this.= y;
    this.vx = vx;
    this.vy = vy;
  }

  public void act()
  {
    vy = vy - * dt;
    x = + vx * dt;
    y = + vy * dt;
    setLocation(new Location(app.p.toPixelX(x), app.p.toPixelY(y)));
    if (x < 0)
      vx = -vx;
    else
    {
      if (!isInGrid())
      {
        removeSelf();
        app.displayResult();
      }
    }
  }

  protected void setX(double x)
  {
    this.= x;
  }

  protected void setVelocityX(double vx)
  {
    this.vx = vx;
  }

  protected double getVelocityY()
  {
    return vy;
  }
}

//-----------class Basket --------------
class Basket extends Actor
{
  public Basket()
  {
    super("basket");
  }
}

Erklärungen zum Programmcode:
vFactor = 50 Passt die Ballgeschwindigkeit an
toPixelX(), to PixelY() Rechnet die Userkoordinaten (Meter) in die Pixelkoordinaten des aktuellen Android-Gerätes um
toUserX(), toUserY() Rechnet die Pixelkoordinaten in die $userkoordinaten um
int collide() Callbackmethode des GGActorCollisionListeners. Wird bei Kollisionen des Balls und des Basketballkorbes aufgerufen
basket.setCollisionRectangle
(new Point(-10, -20), 40, 10);
Als Collisionsarea wird nur der obere (rote) Rand des Korbes gewählt. Die Koordinaten beziehen sich auf das Sprite-Bild und werden automatisch auf die Bildschirmauflösung angepasst
if((Ball)actor1).getVelocityY() < -0.1 Der Ball muss von oben kommen
((Ball)actor1).setVelocityX(0)
((Ball)actor1).setX(0.5)
Nach der Kollision mit dem oberen Rand des Korbes bewegt sich der Ball senkrecht nach unten im Wandabstand des Korbes
(String.format("#shots: %d   #hits: %d   %%: %4.1f",
      shots, hits, 100.0 * hits / shots)
Formatierte Zahlenausgabe in der Statuszeile. %d steht für ganze Zahlen, %4.1f für Dezimalzahlen mit einer Dezimalstelle