In this post, we’re going to learn how to access just one of the multitude of sensors present on an Android device by building a very small game that uses the accelerometer on the device. This game will simply show a red ball on the screen whose motion is tied to the device’s accelerometer. For example, tilting the device will cause the ball to accelerate in the direction of the tilt.
BUILD GAMES
FINAL DAYS: Unlock 250+ coding courses, guided learning paths, help from expert mentors, and more.
Download the full source code here.
Let’s get started! Create an Android Studio project called SensorsDemo and we’ll leave the defaults as is (but create an Empty Activity). We can go ahead and delete the activity_main.xml file in the layout folder since we’ll be using a custom view that will fill our entire screen and setContentView(…) has an overload where we can pass in a View object. Also, copy-and-paste the ball from the provided source code’s res/drawable directory to our res/drawable directory. If there isn’t a directory, create one. We’re going to create a custom view that will handle updating our ball.
However, we still have much to do before we can get to the custom View. As a placeholder, let’s just create our BallView private inner class inside MainActivity towards the end of the file like the following.
private class BallView extends View { public BallView(Context context) { super(context); } }
We’ll populate it with the logic to move the ball, but we first need to declare some top-level members representing our ball’s position, acceleration, and velocity in the x and y directions. We’ll also need members to represent the maximum x and y so we define boundaries restricting the ball’s motion so it doesn’t roll off the screen! Of course, we’ll need a member to be the ball itself as a Bitmap object. Finally, we need a SensorManager object so that we can register and unregister the listener for the accelerometer sensor.
private float xPos, xAccel, xVel = 0.0f; private float yPos, yAccel, yVel = 0.0f; private float xMax, yMax; private Bitmap ball; private SensorManager sensorManager;
Now that we’ve done this, let’s complete our onCreate(…) method. Since we’re using the accelerometer, it’s a good idea to keep our app in portrait mode so that the system doesn’t think we want to change orientations if we tilt a bit too much in one direction. We can do this to a call in setRequestedOrientation(…) method. Then we can create our custom BallView and set the view of our Activity to be the new BallView object. Then we need to grab the our device’s size so that the ball doesn’t roll off of the screen. Finally, we need to instantiate our SensorManager object by grabbing a reference to the system’s sensor manager.
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); BallView ballView = new BallView(this); setContentView(ballView); Point size = new Point(); Display display = getWindowManager().getDefaultDisplay(); display.getSize(size); xMax = (float) size.x - 100; yMax = (float) size.y - 100; sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); }
In onStart() and onStop() , we need to register and unregister our SensorManager, respectively. This is a great place to do this because these Activity lifecycle methods are called before the screen is visible to the user and after the screen has disappeared off the screen, respectively. We need to specify that we need the accelerometer sensor and we’ll need to specify a refresh rate, SENSOR_DELAY_GAME, in our case. Note that we perform our method calls after the call to the superclass in onStart() , but, in onStop() , we perform method calls before the call to the superclass. This is because we’d like to unregister our listener before the system does it’s tasks to free up resources, for example.
@Override protected void onStart() { super.onStart(); sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_GAME); } @Override protected void onStop() { sensorManager.unregisterListener(this); super.onStop(); }
This will force us to implement the SensorEventListener2 interface, which will make us implement onSensorChanged(…) , onFlushCompleted(…) , and onAccuracyChanged(…) . We won’t have to do anything in the latter two methods, but the former is the most important since this is where we actually retrieve data from the accelerometer. To gain a better understanding of why we have positive/negative values in our code, look at the following diagram of the orientation of the sensors with respect to the device.
(Source)
Also, according to the documentation, sensorEvent.values[i] stores data on our accelerometer, where i is a value from 0 to 2 representing the acceleration in the x, y, and z directions. From the diagram, note that we have to negate our y value, else the ball will just move in one dimension! After we get their values, we need to update our ball’s location. This is a common practice in game development to update the location of all of the entities of the game per frame. In our case, our only entity is the ball, and, since that’s tied to the accelerometer, our “frame” depends on the refresh rate of the sensors, which is still very fast.
@Override public void onSensorChanged(SensorEvent sensorEvent) { if (sensorEvent.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { xAccel = sensorEvent.values[0]; yAccel = -sensorEvent.values[1]; updateBall(); } }
Now in our updateBall() method, we need to change our velocities and calculate how far our ball has moved in that time. Then we need to combine our ball’s current location with the displacement and we now have the ball’s new position! Notice that because of the coordinate axes on the device, we have to actually subtract the delta position from the current position! Feel free to play around with these calculation to make your ball game more or less difficult! We also need to handle the cases where it goes off the screen. We can set the position to be the min or the max depending on how it goes off the screen.
private void updateBall() { float frameTime = 0.666f; xVel += (xAccel * frameTime); yVel += (yAccel * frameTime); float xS = (xVel / 2) * frameTime; float yS = (yVel / 2) * frameTime; xPos -= xS; yPos -= yS; if (xPos > xMax) { xPos = xMax; } else if (xPos < 0) { xPos = 0; } if (yPos > yMax) { yPos = yMax; } else if (yPos < 0) { yPos = 0; } }
Now we need to code our custom view to actually display the ball on the screen. Since our entire Activity is comprised of the BallView, we can just position the ball on the view and we’re sure that the ball will be on the screen. Note that we are forced to implement the constructor that calls the superclass’s constructor. In our constructor, we need to grab our Bitmap and resize it so it’s the appropriate size for our screen.
When we’re creating a custom view, we have to override a method called onDraw() since that’s called to draw the view once. However, we don’t just want to draw our ball once, we want to keep refreshing it. We can call an instance method inside View called invalidate() . This will tell the view to call onDraw(…) again sometime in the future.
private class BallView extends View { public BallView(Context context) { super(context); Bitmap ballSrc = BitmapFactory.decodeResource(getResources(), R.drawable.ball); final int dstWidth = 100; final int dstHeight = 100; ball = Bitmap.createScaledBitmap(ballSrc, dstWidth, dstHeight, true); } @Override protected void onDraw(Canvas canvas) { canvas.drawBitmap(ball, xPos, yPos, null); invalidate(); } }
This should be all we need to get our app running and using the accelerometer! Here’s what our app looks like running on a physical device.
As we move the device, the ball accelerates in the appropriate direction! For those of you that might not have an Android device, we can still use the accelerometer. However, instead of having a physical device, we need to manually connect with the emulator’s console through a client called telnet. If you’re on a Mac or Linux computer, you already have this installed. However, if you’re on Windows, you’ll need to enable it by following this procedure. To simulate the accelerometer, we’ll need to start up our emulator and open up a terminal window. Note the 4-digit number at the top of the emulator’s window. That’s called the port number and we need it to log into the emulator. In the console, type telnet localhost #### with #### as your emulator’s port number. Now we should see something like Android Console: type ‘help’ for a list of commands in the console. Now we can type sensor status to show the status of all of the emulator sensors. We know everything is good if we can see acceleration: enabled. in the list of sensors. Now if we wanted to change the acceleration, we can run the following command to set the acceleration: sensor set acceleration 0:0:0 or sensor set acceleration 50:50:50 . We can use this to change the acceleration of the emulator and play our game!
Conclusion
In this post, we learned how to access the accelerometer on our device/emulator to build a game using it as well. We built a game that has a ball on the screen whose motion is tied to the accelerometer. When we move the accelerometer, we also move the ball. Even though we only covered the accelerometer, this same approach can be done with different types of sensors as well. Make sure to check out the documentation on how to use the other kinds of sensors!