Game-Apps für Smartphones und Tablets |
|
Bern University oh Teacher Education |
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). |
| ![]() |
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.
// AndroidEx24.java package app.ex24; import ch.aplu.android.*; import android.graphics.Point; public class AndroidEx24 extends GameGrid implements GGFlingListener, GGActorCollisionListener { 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, false, true, windowZoom(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 x = p.toUserX(end.x); double y = p.toUserY(end.y); double vx = vFactor * velocity.x; double vy = -vFactor * velocity.y; if (x > 6) { Ball ball = new Ball(this, x, y, vx, vy); addActorNoRefresh(ball, new Location(end.x, end.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 g = 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 = x; this.y = y; this.vx = vx; this.vy = vy; } public void act() { vy = vy - g * dt; x = x + vx * dt; y = 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 = x; } protected void setVelocityX(double vx) { this.vx = vx; } protected double getVelocityY() { return vy; } } //-----------class Basket -------------- class Basket extends Actor { public Basket() { super("basket"); } } |
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 |