Game apps for smartphones and tablets |
|
Research project PHBern |
Smartphone and tablet users often perform a 'sweep' gesture by moving rapidly with one finger over the screen. The system's reaction depends on the direction and the speed of the sweep. With Android this action is called a 'fling gesture' and is well supported by the Android API and JDroidLib. To make use of it, a GGFlingListener has to be implemented, which is registered with the method addFlingListener(). The callback method flingEvent is used to handle the fling event, containing the point, on which the fling started, the point where it ended and a velocity vector of the movement in pixels per second. |
| ![]() |
For computing the tracjectory, physical formulas are used. For this to go smoothly, the coordinates are converted to physical units (positions in metres, velocities in m/s and acceleration in m/s^2). These values are called user coordinates in JDroidLib. This makes it easier, to distinguish and convert between pixel coordinates and real physical coordinates. Be aware that when positioning actors pixel coordinates are used. But all drawing operations used by GGPanel can be made in user coordinates.
In touch events, pixel coordinates have to be converted into user coordinates using the methods toUserX() and toUserY(). These can be found in the class GGPanel. This is especially useful when using dynamic window size (with windowZoom(), to adapt sprite size to screen size) and still have consistent behavior.
// 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 | Adjusts ball velocity |
toPixelX(), to PixelY() | Converts the user coordinates (physical unit, metres) into pixel coordinates of the device. |
toUserX(), toUserY() | Converts the pixel coordinates into user coordinates |
int collide() | Callback method of the GGActorCollisionListeners |
basket.setCollisionRectangle (new Point(-10, -20), 40, 10); |
Only the upper boarder of the basket is activated as collision area. The coordinates are respective to the sprite and are automatically adjusted to screen size. |
if((Ball)actor1).getVelocityY() < -0.1 | Ensures that the ball is coming from above. |
((Ball)actor1).setVelocityX(0) ((Ball)actor1).setX(0.5) |
After collision with the upper boarder, the ball drops vertically downwards with fixed distance to the wall. |
(String.format("#shots: %d #hits: %d %%: %4.1f", shots, hits, 100.0 * hits / shots) |
Formats the score in the status bar nicely. %d stands for integers, %4.1f for decimal numbers exact to the first decimal place. |