Loading...
Logo
Processing Forum

I have just created a very simple Android version of my `Bouncing Ball' sketch: a little ball bounces around your phone's screen, move it with your finger or by moving your phone.

This is a trivial example of course, based on information from the Wiki and this forum. However, seeing the complete code of such an example might help the absolute beginner (i.e. someone like me).

The app is on the market (free and no ads of course) here:
https://play.google.com/store/apps/details?id=processing.test.bouncingball

The source code is available below or at
http://www.gwoptics.org/processing/android/BouncingBall/

Andreas

Copy code
  1. /*
  2.  * Bouncing Ball, a simple example for an Android app made with
  3.  * This app has been tested with Processing 2.05a, Android SDK 17
  4.  * on a Nexus 1 device. Andreas Freise 27.03.2012
  5.   */

  6. import ketai.sensors.*;
  7. KetaiSensor sensor;
  8. float accelerometerX, accelerometerY;
  9. // some global variables to simplify matters
  10. boolean rest;
  11. boolean showinfo;
  12. boolean screenpressed;
  13. float fps=60; // frames per second for frameRate() command
  14. Ball ball;
  15. void setup()
  16. {
  17.   size(screenWidth, screenHeight, P3D);
  18.   orientation(LANDSCAPE);
  19.   frameRate(fps);  // set framerate explicitly
  20.   ball = new Ball(screenWidth/2.0, screenHeight/2.0);
  21.   // init global variables
  22.   rest=false;
  23.   screenpressed=false;
  24.   showinfo=false;
  25.   // for text output we might need to set some text properties
  26.   textAlign(CENTER, CENTER);
  27.   textSize(36);
  28. }
  29. void draw()
  30. {
  31.   background(0);
  32.   ball.move();
  33.   ball.display();
  34.   if (showinfo) {
  35.     pushStyle();
  36.     fill(200);
  37.     text("Bouncing Ball: \n" +
  38.       "A simple example app made with Processing\n" +
  39.       "See http://www.gwoptics.org for more!", screenWidth/2.0, screenHeight/2.0);
  40.     popStyle();
  41.   }
  42.   // if the phone has been shaken,
  43.   // wake up the ball
  44.   if (accelerometerX*accelerometerX+accelerometerY* accelerometerY>12*12) {
  45.     rest=false;
  46.   }
  47. }
  48. /*
  49.  * Read the touch events to simply know when a finger is
  50.  * on the screen or not.
  51.  */
  52. public boolean surfaceTouchEvent(MotionEvent me) {
  53.   final int action = me.getAction() & MotionEvent.ACTION_MASK;
  54.   if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN)
  55.   {
  56.     rest=false;    // if screen has been pressed, wake up ball
  57.     screenpressed=true;
  58.     ball.reset(mouseX, mouseY, 0, 0); // set ball position to finger
  59.   }
  60.   else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP)
  61.   {
  62.     screenpressed=false;
  63.     ball.reset(mouseX, mouseY, 2*(mouseX-pmouseX), 2*(mouseY-pmouseY)); // throw ball!
  64.   }
  65.   return super.surfaceTouchEvent(me);
  66. }
  67. /* Read accelerometer data and set two gloabal variables */
  68. void onAccelerometerEvent(float x, float y, float z)
  69. {
  70.   accelerometerX = x;
  71.   accelerometerY = y;
  72. }
  73. /* Check for MENU button */
  74. void keyPressed() {
  75.   if (key == CODED) {
  76.     if (keyCode == MENU) {
  77.       if (showinfo) {
  78.         showinfo=false;
  79.       }
  80.       else {
  81.         showinfo=true;
  82.       }
  83.       keyCode=1;
  84.     }
  85.   }
  86. }
  87. /* Override the parent (super) Activity class:
  88.  * States onCreate(), onStart(), and onStop() aren't called by the sketch. 
  89.  * Processing is entered at onResume() and exits at onPause()
  90.  */
  91. public void onResume(){
  92.   super.onResume();
  93.   sensor = new KetaiSensor(this); // init sensors
  94.   sensor.start(); // start sensors
  95. public void onPause() {
  96.   sensor.stop(); // stop sensors when skecht is paussed/quit
  97.   super.onPause();
  98. }

And the Ball class:

Copy code
  1. public class Ball
  2. {   
  3.   // some local variables of the ball
  4.   float x;
  5.   float y;
  6.   float xold;
  7.   float yold;
  8.   float xspeed;
  9.   float yspeed;
  10.   float phi;
  11.   float phispeed;
  12.   float gravity;
  13.   float friction;
  14.   float phifriction;
  15.   float wallfriction;
  16.   float slowing;
  17.   float reverseSpeed;
  18.   float slowSpeed;
  19.   PImage ball;
  20.   float xmin;
  21.   float ymin;
  22.   float xmax;
  23.   float ymax;
  24.   float phiscale;
  25.   Ball(float tempX, float tempY)
  26.   {
  27.     rest = false;
  28.     x = tempX;
  29.     y = tempY;
  30.     xspeed = 0;
  31.     yspeed = 0;
  32.     phi = 0;
  33.     phispeed = 0;
  34.     slowing=0.0;
  35.     gravity = 0.6*30.0/fps;
  36.     friction = 0.98;
  37.     phifriction = 0.99;
  38.     wallfriction=0.5;
  39.     reverseSpeed = -0.98;
  40.     slowSpeed = 0.98;
  41.     ball=loadImage("ball.png");
  42.     xmin = 0;
  43.     ymin = 0;
  44.     xmax = width-ball.width;
  45.     ymax = height-ball.height;
  46.     phiscale =2.0/(ball.height);
  47.   }
  48.   void reset(float newX, float newY, float newXspeed, float newYspeed)
  49.   {
  50.     x=newX;
  51.     y=newY; 
  52.     xspeed=newXspeed;
  53.     yspeed=newYspeed;
  54.     xold=x-xspeed;
  55.     yold=y-yspeed;
  56.   }
  57.   void move()
  58.   {
  59.     /* if ball is not at rest, check for motion and collisions */
  60.     if (!rest && !screenpressed)
  61.     {     
  62.       xold=x;
  63.       yold=y;
  64.       y=y+yspeed;
  65.       x=x+xspeed;
  66.       phi=phi+phispeed;
  67.       if (y >= ymax)
  68.       {
  69.         yspeed = yspeed * reverseSpeed;
  70.         xspeed = xspeed * slowSpeed;
  71.         y = ymax;
  72.       }
  73.       if (y <= ymin)
  74.       {
  75.         yspeed = yspeed * reverseSpeed;
  76.         xspeed = xspeed * slowSpeed;
  77.         y = ymin;
  78.       }
  79.       if (x >= xmax)
  80.       {
  81.         xspeed = xspeed * reverseSpeed;
  82.         yspeed = yspeed * slowSpeed;
  83.         x = xmax;
  84.       }
  85.       if (x <= xmin)
  86.       {
  87.         xspeed = xspeed * reverseSpeed;
  88.         yspeed = yspeed * slowSpeed;
  89.         x = xmin;
  90.       }
  91.       /* change speed due to acceleration */
  92.       xspeed=xspeed+gravity * accelerometerY;
  93.       yspeed=yspeed+gravity * accelerometerX;
  94.       /* apply frcition */
  95.       xspeed=friction*xspeed;
  96.       yspeed=friction*yspeed;
  97.       /* if we are in contact with a wall, set rotational speed */
  98.       if (y >= ymax)
  99.       {
  100.         phispeed=wall_speed(phispeed, x-xold);
  101.       }
  102.       if (y <= ymin)
  103.       {
  104.         phispeed=wall_speed(phispeed, -1.0*(x-xold));
  105.       }
  106.       if (x >= xmax)
  107.       {
  108.         phispeed=wall_speed(phispeed, -1.0*(y-yold));
  109.       }
  110.       if (x <= xmin)
  111.       {
  112.         phispeed=wall_speed(phispeed, y-yold);
  113.       }
  114.       phispeed=phifriction*phispeed;
  115.       /* check if ball is still moving */
  116.       if ( y==yold && x==xold && phispeed<1)
  117.       {
  118.         slowing=slowing+1;
  119.       }
  120.       else
  121.       {
  122.         slowing=0.0;
  123.       }
  124.       /* wait roughly 2 seconds before setting ball to rest */
  125.       if (slowing>2*fps) {
  126.         rest=true; 
  127.         slowing=0.0;
  128.       }
  129.     }
  130.     else {
  131.       if (screenpressed) {
  132.         x=mouseX;
  133.         y=mouseY;
  134.       }
  135.     }
  136.   }
  137.   void display()
  138.   {   
  139.     pushStyle();
  140.     pushMatrix();
  141.     stroke(255);
  142.     float offsetX=x+ball.width/2;
  143.     float offsetY=y+ball.height/2;
  144.     translate(offsetX, offsetY);
  145.     rotate(phi);
  146.     translate(-offsetX, -offsetY);
  147.     image(ball, x, y);
  148.     popMatrix();
  149.     popStyle();
  150.   }
  151.   float wall_speed(float oldphispeed, float speed2) {
  152.     return wallfriction*oldphispeed+(1-wallfriction)*speed2*phiscale;
  153.   }
  154. }



Replies(3)

Small update: after testing this on a Sony Tablet S, I realised that the screen X/Y axes and accelerometer X/Y axes where not aligned properly on other devices. I think this needs to be handled by switching the orientation of the screen based on the screen layout. So, I have now added this:


Copy code
  1. import android.content.res.Configuration;
  2. ...
  3. void setup()
  4. {
  5.   ...
  6.   Configuration config = getResources().getConfiguration();
  7.   if ((config.screenLayout & Configuration.SCREENLAYOUT_LONG_MASK) == Configuration.SCREENLAYOUT_LONG_YES)
  8.   {
  9.     println("screen is long (portrait)");
  10.     orientation(PORTRAIT);
  11.   }
  12.   else {
  13.     println("screen is wide (landscape)");
  14.     orientation(LANDSCAPE);
  15.   }
(and added a minus sign to the accelerometer signal in Y direction in the Ball class)

and it seems to work on HTC Nexus 1 and Sony Tablet S. Don't know about others yet.

The updated full code is available at:
http://www.gwoptics.org/processing/android/BouncingBall/
as before.

Andreas
 
Of course I had not thought this trough to the end before posting the above. There are two problems with the above solution:
  • switching orientation during setup makes some things more complicated, for example one has to take care to rotate background images and so on
  • more importantly, there is the strange problem that on some devices the restart of the application after the sleep mode (power button) runs through the setup/resume functions more than once. This can make it difficult to write proper resume functions (I did not manage to make this work on the Nexus 1). The recommended solution is to fix the orientation and the virtual keyboard in the manifest file (see below). Then of course one cannot switch orientation during setup any longer.
In the latest version I changed the above to something like:

Copy code
  1.   orientation(LANDSCAPE);
  2.   Configuration config = getResources().getConfiguration();
  3.   if ((config.screenLayout & Configuration.SCREENLAYOUT_LONG_MASK) == Configuration.SCREENLAYOUT_LONG_YES)
  4.   {
  5.     println("screen is long (portrait)");
  6.     rotate_axes=true;
  7.   }
  8.   else {
  9.     println("screen is wide (landscape)");
  10.     rotate_axes=false;
  11.   }
And the use the rotate_axes variable to chnage the way the accelerometer output is applied.

I also added the following lines to the AndroidManifest.xml file:

Copy code
  1.     <activity android:name=""
  2.                             android:screenOrientation="landscape"
  3.                             android:configChanges="orientation|keyboardHidden">
Then, I tested loading and scaling of a background image and did some other minor tweaks. Finally I also added a Javasript version (using Processing.js) of the same code to the webpage:
http://www.gwoptics.org/processing/android/BouncingBall/

Andreas
As a final test in this round I created a Javascript version of this demo to run in fullscreen mode on an iPad. You can try this by clicking on the image below, then via the bookmark menu add this to the home screen. When loaded the next time it'll start fullscreen and will be cached for offline use.



The main code is still the same with the following changes:
  • some Javascript has been added to the setup function to handle orientation change and resize events
  • the index.php file includes apple-specific meta-tags
  • a manifest file specifies the files for offline caching
The good news is that this very simple example runs very fast on an iPad2 (while this Javascript version is terribly slow on Android devices). A better integration could probably be done with PhoneGap and similar frameworks, but that's not what I wanted to test now.

Andreas