Abozanona
Abozanona

Reputation: 2285

Calculate the displacement of device in unity

I'm making a 3D game in unity where the object should move forward and backward as the android device moves/accelerates in the Z axes. ie. When the player moves the devise in the direction of the +ve Z axis, the object should move forward, and when the player moves the devise in the direction of the -ve Z axis, the object should move backward.

This game is a multiplayer game, and the players will move in a large football field.

My idea to do this is using the accelerometer to calculate the acceleration of the device, then integrate the data of acceleration to get the device speed in the Z axis. and use the speed to move the device.

Using this equation

V2=V1 + ΔA . ΔT
Where
V2 : final velocity.
V1 : initial velocity.
ΔA : difference between the initial and final acceleration.
ΔT : difference between the initial and final time.

At first I tried to use kinematic equations to calculate the final speed, but I realized then that it can be only used when acceleration is constant. So a friend of me who studies physics differentiated this equation for me to use it when acceleration is variable.

I know that there will be some error in calculating the accurate displacement, and that the error will increase after the integration of acceleration, but this small percentage of error is okay for my application; I thought at first in using GPS instead of accelerometer but I found that GPS accuracy will be less than the sensors.

I know also that the error will be incredibly high after some time, so I reset the values of acceleration and velocity every 10 seconds. I'm also using a low-pass filter to reduce the noise of the sensor.

public class scriptMove : MonoBehaviour
{
    const float kFilteringFactor = 0.1f;

    public Vector3 A1;
    public Vector3 A2;
    public Vector3 A2ramping; // for the low-pass filter
    public Vector3 V1;
    public Vector3 V2;

    public int SpeedFactor=1000; //this factor is for increasing acceleration to move in unity world

    void resetAll()
    {
        Input.gyro.enabled = true;
        A2 = Vector3.zero;
        V1 = Vector3.zero;
        V2 = Vector3.zero;
        A2ramping = Vector3.zero;
    }
    // Use this for initialization
    void Start()
    {
        InvokeRepeating("resetAll", 0, 10);
    }

    //http://stackoverflow.com/a/1736623
    Vector3 ramping(Vector3 A)
    {
        A2ramping = A * kFilteringFactor + A2ramping * (1.0f - kFilteringFactor);
        return A - A2ramping;
    }

    void getAcceleration(float deltaTime)
    {
        Input.gyro.enabled = true;

        A1 = A2;
        A2 = ramping(Input.gyro.userAcceleration) * SpeedFactor;

        V2 = V1 + (A2 - A1) * deltaTime;

        V1 = V2;
    }

    //Update is called once per frame
    void Update()
    {

        getAcceleration(Time.deltaTime);

        float distance = -1f;
        Vector3 newPos = transform.position;

        transform.Translate(Vector3.forward * Time.deltaTime * V2.z * distance);
    }
}

The problem:

My code doesn't work always as expected when I move with the device;

My questions:

Upvotes: 3

Views: 4084

Answers (3)

Fernando Bonet
Fernando Bonet

Reputation: 596

I don't know if you still need it but if anyone in the future need I will post what I found:

When I first used the Unity accelerometer I was thinking that the output was simply the device's rotation, and in a way is, but more than that it give us the acceleration but in order to have this value your must filter the gravity then you have the value.

I created a plugin for Android and get the Android's Accelerometer and Linear Accelerometer, the standard accelerometer give us a similar value of Unity accelerometer, the main difference is that is raw, and unity give us some refined output, for example if your game is Landscape unity automatically inverts X and Y axis, while the Android raw information don't. And the Linear accelerometer that is a fusion of sensors including the standard accelerometer, the output is acceleration without the gravity but the speed is terrible, while both (Unity and Android) accelerometer are updated every frame, the Linear accelerometer was updated every 4 to 5 frames what is a terrible rate for user's experience.

But going for Android plugin was great because it gave the light how to solve my problem of removing gravity from Unity Accelerometer, as you can find here: https://developer.android.com/reference/android/hardware/SensorEvent.html Under Sensor.TYPE_ACCELEROMETER

If we tilt the device, Unity Accelerometer gives you a value, for example 6, and while you hold in that position this is the value, is not a wave, if you tilt back really fast or really slowly it will give the value from 6 to 0 (supposing you move back to zero), what I wanted and accomplished with the code I'm sharing below is, when you turn it does a wave, returns the acceleration and back to zero, so is a acceleration deceleration curve, if you turn it really slow the acceleration returned is almost zero, if you turn it fast the response reflects this speed. If this is the result you are looking for you just need to create this class:

using UnityEngine;

public class AccelerometerUtil
{
    public float alpha = 0.8f;
    public float[] gravity = new float[3];

    public AccelerometerUtil()
    {
        Debug.Log("AccelerometerUtil Init");
        Vector3 currentAcc = Input.acceleration;
        gravity[0] = currentAcc.x;
        gravity[1] = currentAcc.y;
        gravity[2] = currentAcc.z;
    }

    public Vector3 LowPassFiltered()
    {
        /*
         https://developer.android.com/reference/android/hardware/SensorEvent.html

          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];
         */


        Vector3 currentAcc = Input.acceleration;
        gravity[0] = alpha * gravity[0] + (1 - alpha) * currentAcc.x;
        gravity[1] = alpha * gravity[1] + (1 - alpha) * currentAcc.y;
        gravity[2] = alpha * gravity[2] + (1 - alpha) * currentAcc.z;

        Vector3 linearAcceleration =
            new Vector3(currentAcc.x - gravity[0],
                currentAcc.y - gravity[1],
                currentAcc.z - gravity[2]);

        return linearAcceleration;
    }
}

Once you have this class, just create it into your MonoBehaviour:

using UnityEngine;

public class PendulumAccelerometer : MonoBehaviour
{
    private AccelerometerUtil accelerometerUtil;

    // Use this for initialization
    void Start()
    {
        accelerometerUtil = new AccelerometerUtil();
    }

    // Update is called once per frame
    void Update()
    {
        Vector3 currentInput = accelerometerUtil.LowPassFiltered();
        //TODO: Create your logic with currentInput (Linear Acceleration)
    }
}

Notice that the TODO on MonoBehaviour is to be implemented, is up to you create an algorithm how to handle this values, in my case I found really useful to create a Graphic output and analise my acceleration before write it.

Really hope it helps

Upvotes: 1

Lukas Borges
Lukas Borges

Reputation: 1

I am trying to do the same thing as you. It is not trivial to get device's linear acceleration using just one sensor. You will need to implement a solution using both the accelerometer and the gyroscope (sensor fusion). Google has an android specific solution which behaves differently according to how sophisticated your device is. It uses multiple sensors as well as low/high pass filters (see Android TYPE_LINEAR_ACCELERATION sensor - what does it show?). Google's Tango tablet should have sensors to address such issues.

If you want to get accelerometer data in Unity, try:

public class scriptMove : MonoBehaviour{

  private float accelX;
  private float accelY;
  private float accelZ;

  void Update(){
    accelX = Input.acceleration.x;
    accelY = Input.acceleration.y;
    accelZ = Input.acceleration.z;

    //pass values to your UI
  }
}

What I am currently trying is to port Google's solution to Unity using IKVM.

This link might be helpful too: Unity3D - Get smooth speed and acceleration with GPS data

Upvotes: 0

DisturbedNeo
DisturbedNeo

Reputation: 743

The movement is based on acceleration, so it will be dependant on how quickly you rotate your device. This is also why the object does not stop when you do. Suddenly stopping your device is a lot of acceleration, which then gets added to the amount the object is translating, which causes it to move a much greater distance than you intend.

I think what may be easier for you is to use the attitude of the gyro rather than the userAcceleration. The attitude returns a quaternion of the rotation of the device. https://docs.unity3d.com/ScriptReference/Gyroscope-attitude.html

(You'll have to do a bit of experimenting, because I don't know what (0,0,0,0) on the attitude is. It could mean the device is flat on a table, or that it is sideways being held in front of you, or it could simply be the orientation of the device when the app first starts, I don't know how Unity initialises it.)

Once you have that Quaternion, you should be able to adjust velocity directly based off of how far in either direction the user is rotating the device. So if they rotate +ve Z-axis, you move forwards, if they move more, it moves faster, if they move -ve Z-axis, it slows down or moves backwards.

Regarding the GPS coordinates, you need to use LocationService for that. http://docs.unity3d.com/ScriptReference/LocationService.html You'll need to start LocationServices, wait for them to initialise (this bit is important), and then you can query the different parts using LocationService.lastData

Upvotes: 0

Related Questions