I'm a beginner in Android game Development, and I developing a small Game.
I am facing some difficulties with the Motion Sensor: Accelerometer.
this game is in Landscape mode.
I want if my phone tilts in right, my character also goes right. ( same thing for left )
And when I stop tilting Character in the game should stop moving.
But i don't understand really good the operation of the accelerometer,
here is my code:
#Override
public void onSensorChanged(SensorEvent event) {
synchronized (this) {
long penchement = (long) (event.values[1]- 0.5);
if(penchement>0){
if(penchement>lastUpdate)
lastUpdate=penchement;
if(penchement>0.2){
booldroite=true; // boolean for going right
boolgauche=false; // boolean for going left
}
if(lastUpdate-penchement<0.2)
booldroite=false;
}
else{
if(penchement<lastUpdate)
lastUpdate=penchement;
if(penchement<-0.2){
boolgauche=true;
booldroite=false;
}
if(lastUpdate+penchement>-0.2)
boolgauche=false;
}
}
So my code works (a bit), but my character's moves aren't smooth at all. Sometimes my phone tilts but my Characters doesn't move...
Thank you very much in advance if you can help me.
Sensor readings are noisy by nature, that's the reason for the flickering movement of your character.
You will need to implement some kind of low pass filter after the readings. There's a basic low pass filter example in SensorEvent.
public void onSensorChanged(SensorEvent event) {
// alpha is calculated as t / (t + dT)
// with t, the low-pass filter's time-constant
// and dT, the event delivery rate
final float alpha = 0.8;
gravity[0] = alpha * gravity[0] + (1 - alpha) * event.values[0];
gravity[1] = alpha * gravity[1] + (1 - alpha) * event.values[1];
gravity[2] = alpha * gravity[2] + (1 - alpha) * event.values[2];
linear_acceleration[0] = event.values[0] - gravity[0];
linear_acceleration[1] = event.values[1] - gravity[1];
linear_acceleration[2] = event.values[2] - gravity[2];
}
You might want to use the values in gravity[] to find out the 'tilt' of the phone. Also, play around with the value of final float alpha: Values near 1.0 will improve the smoothness, while smaller values near 0.0 will obtain noisier readings, but have a faster response.
Bonne chance!
Related
I wonder how to caluclate the vertical movement of a mobile device regardless of its orientation in space. That is, up and down movement in the real world. Below are three examples to demonstrate my purpose:
Example 1: A mobile phone lies flat on a table and is moved towards the ceiling for 100ms.
Example 2: A mobile phone is in landscape mode and is then moved towards the ceiling for 100ms.
Example 3: A mobile phone is in landscape mode but is tilted 45° on the horizontal axis (earth's surface) and is moved towards the floor for 100ms.
The algorithm I am looking for should output for all examples above and in general for every possible phone orientation that at least the device was moved upwards respectively downwards during the time period in the real world coordinate system.
I am not asking how this works on a particular system or OS. I only want to be given instructions how something like this would be accomplished in general.
What I do have right now is the code example below (which is from this topic on stackoverflow) but a) I don't really understand how it works and b) it doesn't work so well at all. Especially upwards movement detection is quite bad. The alpha value seems to be chosen quite arbitrarily (as many threshholds in answers to related questions do) which leaves me a little confused what it actually means.
As an alternative, I though about reading values out of the gyroscope and the accelerometer. With gyroscope data I could calculate a rotation matrix. By multiplying the matrix with the accelerometer data (a 1D vector of the device's x, y and z acceleration in its own coordinate system) I should be able to obtain real world movement, shouldn't I?
I am developing for Android so I am able to make use of accelerometer, gyroskop, magnetometer etc.
Thanks!
class AccelerometerReader(context: Context): SensorEventListener {
private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
private val gravity = FloatArray(3)
private val linearAcc = FloatArray(3)
fun registerListener(){
sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL)
}
fun unregisterListener(){
sensorManager.unregisterListener(this)
}
override fun onSensorChanged(event: SensorEvent?) {
// alpha is calculated as t / (t + dT)
// with t, the low-pass filter's time-constant
// and dT, the event delivery rate
val alpha = 0.8f
gravity[0] = alpha * gravity[0] + (1 - alpha) * event!!.values[0]
gravity[1] = alpha * gravity[1] + (1 - alpha) * event.values[1]
gravity[2] = alpha * gravity[2] + (1 - alpha) * event.values[2]
linearAcc[0] = event.values[0] - gravity[0]
linearAcc[1] = event.values[1] - gravity[1]
linearAcc[2] = event.values[2] - gravity[2]
val scalarProduct: Float = gravity[0] * linearAcc[0] + gravity[1] * linearAcc[1] + gravity[2] * linearAcc[2]
val gravityVectorLength = sqrt(gravity[0] * gravity[0] + gravity[1] * gravity[1] + gravity[2] * gravity[2])
val linearAccVectorLength = sqrt(linearAcc[0] * linearAcc[0] + linearAcc[1] * linearAcc[1] + linearAcc[2] * linearAcc[2])
val cosVectorAngle = scalarProduct / (gravityVectorLength * linearAccVectorLength)
if (linearAccVectorLength > 2) { //increase to detect only bigger accelerations, decrease to make detection more sensitive but noisy
if (cosVectorAngle > 0.5) {
println("Down")
} else if (cosVectorAngle < -0.5) {
println("Up")
}
}
}
}
I want to be able to detect a situation where the phone has an acceleration towards the ground (probably means that the Gravity sensor has to be used here also).
I have read a lot about this topic in the Android docs, about High and Low pass filters and other posts, and right now what I have is a code sample that gets the acceleration in the X, Y and Z axis after stripping the gravity:
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
final float alpha = (float) 0.8;
gravity[0] = alpha * gravity[0] + (1 - alpha) * event.values[0];
gravity[1] = alpha * gravity[1] + (1 - alpha) * event.values[1];
gravity[2] = alpha * gravity[2] + (1 - alpha) * event.values[2];
linear_acceleration[0] = event.values[0] - gravity[0];
linear_acceleration[1] = event.values[1] - gravity[1];
linear_acceleration[2] = event.values[2] - gravity[2];
}
So, the linear_acceleration is supposedly the acceleration in the X, Y, Z axis without the gravity.
This is all nice, but the problem obviously is that is depends on how the user holds the phone, for example, in the elevator - if he holds it flat, parallel to the ground - the Z axis will change, if he holds it up straight - the Y axis will change , etc.
So, for example, if the user holds the phone diagonally, the acceleration will be "divided" between the different axes, and some sort of math work, considering where the gravity direction is, will be needed to calculate the actual acceleration in that direction.
Correct me if I am wrong?
Is there a reliable way to detect the downward (towards the earth) acceleration? maybe using other sensors like Gyroscope?
BTW - about the TYPE_LINEAR_ACCELERATION type, I read this answer, saying that its actually not very accurate.
Use some basic physics. Acceleration is a vector. The magnitude of a vector v is always equal to (v.v)^.5, or the square root of the dot product. Or in simpler terms (x^2+y^2+z^2)^.5. That will tell you the amount of acceleration, but not if its towards or away from the earth.
If you need to know if its going towards or away from earth- you can combine that with data from SensorManager.getOrientation. You may need to do that before they enter the elevator though- the orientation code uses gravity as one of its inputs, so it may be screwed up if you try to use it in an elevator. You'd need to test it out.
If you need to break it down to acceleration in terms of earth x, y, and z axes- simple geometry. Take the angle from the orientation result, and use trig properties to convert axes. If you don't know the formulas you need to read up on trig a bit or you'll get them wrong even if I tell them to you.
I also wanted to just be able to measure vertical movement. This is how I did it and it worked for me. First time posting on this site and I have no idea how to format correctly.
Use two different android Sensors: Type_linear_Acceleration and Type_Gravity
Linear acceleration will give you acceleration in the X, Y and Z axis of the phone, and Gravity will do the same, but just for gravity. You know that the sum of the gravity values should = 9.8, but this will be split between X, Y and Z coordinates depending on the phone orientation.
I wont go into the math of it too much, but the following will give you vertical acceleration without gravity. If you want to understand it a bit more run through some values as if the phone was held vertically, then horizontally, it works even if the phone is oblique.
vertical acceleration = (LinearAccelX * GravityX / 9.8)+ (LinearAccelY * GravityY / 9.8)+ (LinearAccelZ * GravityZ / 9.8).
See code below (irrelevant parts removed):
{public class MainActivity extends AppCompatActivity implements SensorEventListener {
SensorManager sm;
Sensor linearaccelerometer;
Sensor gravity;
double Yaccel;
double Xaccel;
double Zaccel;
double gravityY;
double gravityX;
double gravityZ;
double verticalAccel;
sm = (SensorManager) getSystemService(SENSOR_SERVICE);
linearaccelerometer = sm.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION);
gravity = sm.getDefaultSensor(Sensor.TYPE_GRAVITY);
sm.registerListener(this, linearaccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
sm.registerListener(this, gravity, SensorManager.SENSOR_DELAY_NORMAL);
}
public void onSensorChanged(SensorEvent event) {
Sensor sensor = event.sensor;
if (sensor.getType() == Sensor.TYPE_LINEAR_ACCELERATION) {
Xaccel = (double) event.values[0];
Yaccel = (double) event.values[1];
Zaccel = (double) event.values[2];
}
if (sensor.getType() == Sensor.TYPE_GRAVITY) {
gravityX = (double) event.values[0];
gravityY = (double) event.values[1];
gravityZ = (double) event.values[2];
}
verticalAccel = (Xaccel * gravityX / 9.8) + (Yaccel * gravityY / 9.8) + (Zaccel *gravityZ /9.8);
}
I am trying to track the movement of the device only on the vertical direction, i.e. upward and downward movement. This should be irrespective of the orientation of the device. Things that i already know or have tried are these
Linear acceleration is given by sensor TYPE_LINEAR_ACCELERATION and the axes is the phone axes and hence tracking any particular axes does not make a difference.
I tried applying transpose or inverse of rotation vector( inverse or transpose for the rotation vector are same) and then tried tracking the z direction of the linear acceleration vector. Does not seem to help.
I am trying to do a dot product with gravity values (TYPE_GRAVITY) to get the direction of the acceleration but it seems to be error prone. Even when i move my device swiftly up, it says going down.
I will outline this method here
dotProduct = vectorA[0]*vectorB[0]+vectorA[1]*vectorB[1] + vectorA[2]*vectorB[2];
cosineVal = dotProduct/(|vectorA|*|vectorB|)
if(cosineVal > 0 ) down else Up.
What is the flaw with the method ? Please help, I have been stuck on this for some time now.
As I see it, in the 3rd method you trying to find the cos of angle between two vectors (gravity vector and acceleration vector). And the idea is if the angle is close to 180 degrees you have up movement, if angle is close to 0 degrees you have down movement. Cosine is function that has positive value when angle is from -90 to 90 degrees. So when your cosineVal value is positive it means phone is going down and even if cosineVal closer to 1 movement is straight down. So it is true vice versa. When cosine is negative ( from 90 degrees to 270) you have up movement.
Probably you can get vectors from Sensor.TYPE_ACCELEROMETER from https://developer.android.com/reference/android/hardware/SensorEvent.html#values there you have gravity vector and acceleration vector.
I made a code snippet below you can try.
public class MainActivity extends AppCompatActivity implements SensorEventListener {
private float[] gravity = new float[3];
private float[] linear_acceleration = new float[3];
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SensorManager mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
Sensor mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
}
#Override
public void onSensorChanged(SensorEvent event) {
// alpha is calculated as t / (t + dT)
// with t, the low-pass filter's time-constant
// and dT, the event delivery rate
final float alpha = 0.8f;
gravity[0] = alpha * gravity[0] + (1 - alpha) * event.values[0];
gravity[1] = alpha * gravity[1] + (1 - alpha) * event.values[1];
gravity[2] = alpha * gravity[2] + (1 - alpha) * event.values[2];
linear_acceleration[0] = event.values[0] - gravity[0];
linear_acceleration[1] = event.values[1] - gravity[1];
linear_acceleration[2] = event.values[2] - gravity[2];
float scalarProduct = gravity[0] * linear_acceleration[0] +
gravity[1] * linear_acceleration[1] +
gravity[2] * linear_acceleration[2];
float gravityVectorLength = (float) Math.sqrt(gravity[0] * gravity[0] +
gravity[1] * gravity[1] + gravity[2] * gravity[2]);
float lianearAccVectorLength = (float) Math.sqrt(linear_acceleration[0] * linear_acceleration[0] +
linear_acceleration[1] * linear_acceleration[1] + linear_acceleration[2] * linear_acceleration[2]);
float cosVectorAngle = scalarProduct / (gravityVectorLength * lianearAccVectorLength);
TextView tv = (TextView) findViewById(R.id.tv);
if (lianearAccVectorLength > 2) {//increase to detect only bigger accelerations, decrease to make detection more sensitive but noisy
if (cosVectorAngle > 0.5) {
tv.setText("Down");
} else if (cosVectorAngle < -0.5) {
tv.setText("Up");
}
}
}
#Override
public void onAccuracyChanged(Sensor sensor, int i) {
}
}
When I run my code, the values returned by the accelerometer seem to be random and even if I pick up the device and rotate it around, there doesn't seem to be any dramatic change in values or so obvious indication that the device has changed from a rest position to a moving position. I copied the code from Google's site. Basically I am trying to see if the device has moved at all from a rest position. Solution should work on Android 2.2 and above if possible:
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
float[] gravity = new float[3];
#Override
public void onSensorChanged(SensorEvent event)
{
try
{
final float alpha = 0.8f;
float[] linear_acceleration = new float[3];
// Isolate the force of gravity with the low-pass filter.
gravity[0] = alpha * gravity[0] + (1 - alpha) * event.values[0];
gravity[1] = alpha * gravity[1] + (1 - alpha) * event.values[1];
gravity[2] = alpha * gravity[2] + (1 - alpha) * event.values[2];
// Remove the gravity contribution with the high-pass filter.
linear_acceleration[0] = event.values[0] - gravity[0];
linear_acceleration[1] = event.values[1] - gravity[1];
linear_acceleration[2] = event.values[2] - gravity[2];
}
catch (Exception ex)
{
}
}
When you are coping solution from documentation do it with understanding.
gravity have to be a field of the class since you need store state of low-pass filter.
gravity holds average values of accelerometer, fast changes are your linear acceleration.
Have you tried Sensor.TYPE_LINEAR_ACCELERATION? This sensor
returns acceleration force excluding gravity and you don't need to
calculate it
Sensors are too sensitives and there is a lot of noise. It is almost impossible to get 0,0,0 values. You need to exclude small values.
I have to write a compass app in Android. The only thing the user sees on the screen is a cube with a red wall which has to point north. This is not important. What's important is that I need to rotate that cube accordingly to the rotation of the device itself so that the red wall continues to point north no matter how the phone is being held. My code is simple and straightforward:
#Override
public void onSensorChanged(SensorEvent event) {
synchronized (this) {
switch (event.sensor.getType()){
case Sensor.TYPE_ACCELEROMETER:
direction = event.values[2];
break;
case Sensor.TYPE_ORIENTATION:
if (direction < 0) {
angleX = event.values[1];
angleY = -event.values[2];
angleZ = event.values[0];
} else {
angleX = -event.values[1];
angleY = -event.values[2];
angleZ = event.values[0];
}
break;
}
}
}
I have added this extra direction variable that simply stores whether the phone's display is pointing downwards or upwards. I don't know if I need it but it seems to fix some bugs. I am using the SensorSimulator for android but whenever my pitch slider goes in the [-90, 90] interval the other variables get mixed up. It's like they get a 180 offset. But I can't detect when I am in this interval because the range of the pitch is from -90 to 90 so I can move that slider from left to write and I will always be in that interval.
This was all just to show you how far has my code advanced. I am not saying how this problem should be solved because I will only probably stir myself into a dead end. You see, I have been trying to write that app for 3 days now, and you can imagine how pissed my boss is. I have read all sorts of tutorials and tried every formula I could find or think of. So please help me. All I have to do is know how to rotate my cube, the rotation angles of which are EULER ANGLES in degrees.
Here's some code I wrote to do something pretty similar, really only caring about the rotation of the device in the roll direction. Hope it helps! It just uses the accelerometer values to determine the pitch, no need to get orientation of the view.
public void onSensorChanged(SensorEvent event) {
float x = -1 * event.values[0] / SensorManager.GRAVITY_EARTH;
float y = -1 * event.values[1] / SensorManager.GRAVITY_EARTH;
float z = -1 * event.values[2] / SensorManager.GRAVITY_EARTH;
float signedRawRoll = (float) (Math.atan2(x, y) * 180 / Math.PI);
float unsignedRawRoll = Math.abs(signedRawRoll);
float rollSign = signedRawRoll / unsignedRawRoll;
float rawPitch = Math.abs(z * 180);
// Use a basic low-pass filter to only keep the gravity in the accelerometer values for the X and Y axes
// adjust the filter weight based on pitch, as roll is harder to define as pitch approaches 180.
float filterWeight = rawPitch > 165 ? 0.85f : 0.7f;
float newUnsignedRoll = filterWeight * Math.abs(this.roll) + (1 - filterWeight) * unsignedRawRoll;
this.roll = rollSign * newUnsignedRoll;
if (Float.isInfinite(this.roll) || Float.isNaN(this.roll)) {
this.roll = 0;
}
this.pitch = filterWeight * this.pitch + (1 - filterWeight) * rawPitch;
for (IAngleListener listener : listeners) {
listener.deviceRollAndPitch(this.roll, this.pitch);
}
}