1
\$\begingroup\$

In my previous game, I was moving my sprite like so:

float time = 10f;  //Number of seconds to pass a pre-defined area (using the screen width in this example)
float velocity = 1f/time; //work out velocity based on above time
float realX-=velocity*dt; //Move the sprite to the left (where dt is previously defined delta)
x = realX*screenWidth //convert to screen coordinates so it can be rendered

sprite.draw(x, y); //Working out the y position in the same way

This all works great when using touch controls. In the above example, the sprite will cross the screen in precisely 10 seconds regardless of the device on which it is running.

However, the game I'm currently developing uses the accelerometer to move the sprite. (I'm only moving the sprite left/right). Currently, what I'm doing is this:

@Override
public void onSensorChanged(SensorEvent event){ 
    tilt = event.values(axis);       
}

Once I have the value, I apply it to my sprite movement like this:

float time = 10f;  //Number of seconds to pass a pre-defined area (using the screen width in this example)
float velocity = 1f/time; //work out velocity based on above time
float realX-=(tilt * velocity)*dt; //Move the sprite to the left **Now using the tilt value*
x = realX*screenWidth //convert to screen coordinates so it can be rendered

Although this does work - it gives nice fluid movement of the sprite, when I run it on an old Galaxy Ace, the sprite accelerates way too fast. This changes the feel of the game - note I'm talking only about acceleration here, so if I tilt the phone from left to right repeatedly, the sprite is extremely (too) responsive on the Ace, but 'just right' on my 10" tablet).

I've tried a couple of other available games and they seem to run pretty consistently across devices.

I'm wondering if my code is correct and if so, what can I do to get all devices running more consistently with regards to the accelerometer?

Edit

Just to clarify what it is I'm asking. I have a sprite in the middle of the screen sitting on a horizontal platform. When the device is tilted to the left, the sprite moves to the left. The rate of movement should be linked to the angle the device is tilted. So, if the device is vertical there should be no movement. If the device is tilted slowly to the left, further and further the sprite should increase in speed also (up to a limit) ie, accelerate.

The other (real world) way to think of it is sitting a ball bearing inside a vertical box (the box representing the device) and then simply tilting the box to the left and right. The ball accelerates towards to lowest corner. The sprite should act like a real ball. If it's accelerating towards to left corner, and the box is tilted in the opposite direction, then the sprite should start to decelerate to a stop, and then srart accelerating in the opposite direction. Just as the real-world ball bearing would.

My Original code achieves this, however, on some devices it's simply too responsive, it will decelerate and accelerate too quickly. And therefore change the 'feel' of the game - making it too easy for one thing.

So basically when I say the sprite movement feels 'natural' I'm referring to how the ball bearing would act/feel in the real-world.

This is exactly what I'm looking for - hope this helps!

\$\endgroup\$
1
\$\begingroup\$

If I understand properly, you are approximating your physics calculations perhaps too much. When making non-representative physics calculations, it makes it very challenging to keep things straight, and causes problems down the road if you build off them.

When you compute position in a game, you do so by multiplying velocity by a small increment of time, then add to the current position:

float positionX = positionX + velocityX * elapsedTimeMillis;

When you begin to introduce acceleration, you now need to compute velocity before computing your position. Do this by multiplying your current acceleration by a small increment of time, and then add that to the current velocity.

float velocityX = velocityX + accelerationX * elapsedTimeMillis;

In the code you provided, you have kept your velocity at a constant, and are not adjusting it based of the accelerometer's acceleration reading.

It is also important to keep your axes consistent. When using these formulas, you still need to adjust your acceleration so that it points in the correct direction (adjust by changing sign).


Now, it looks like you are computing the actual screen coordinate by using normalized position and scaling to the screen. This should work very nice for cross-device compatibility. However, if you normalize the position, you also need to normalize the velocity & acceleration.

Make sure that all of your units are consistent.


Also, I see that you've said:

when I run it on an old Galaxy Ace, the sprite accelerates way too fast

This screams out to me it is because of the improper physics computations. It's difficult to tell what the exact cause is from what you've described, but I'd take a look at first fixing your physics computations and see if it changes.


So in sum, you must:

  • Keep your position, velocity, and acceleration units consistent and all must be normalized it one dimension is normalized. Or otherwise, the values must be scaled to one other (it doesn't have to be normalized between 0 to 1 or -1 to +1).
  • Make sure you compute velocity depending on acceleration before computing position. Your position variable should be completely unaware that you are accelerating (only velocity should care).
  • If you need to have further control, set the default values for velocity & position (and sometimes acceleration). If you need just a smooth glide like in your first game, the acceleration should be held at 0, but the velocity should have an initial speed. That way the acceleration doesn't change the velocity.

    public void initVariables() {
        accelerationX = 0.0f; //0 units / second / second.
        velocityX = 10.0f; //10 units / second.
        positionX = 50.0f; //50 units.
    }
    
    public void mainGameLoop() {
        //... compute delta time
    
        velocityX = velocityX + accelerationX * elapsedTimeMillis;
        positionX = positionX + velocityX * elapsedTimeMillis;
    
        //... compute screen position based on positionX
    }
    
    @Override
    public void onSensorChanged(SensorEvent event){ 
        //MAKE SURE THIS IS NORMALIZED AND HAS CORRECT DIRECTION.
        //MAKE ADJUSTMENTS AS NEEDED
        accelerationX = event.values(axis) / MAX_ACCELERATION_VALUE; 
    }
    

An Example for Choosing Values

Since I have no context for what is valid/invalid for you game, I'll mock up an example.

Assume:

  • Screen width maps to a 10 meter game-space.
  • Screen height remains proportional (game-space size depends on aspect ratio).
  • The objective is to create a starship that moves left & right according to the accelerometer's reading. We will scale/normalize the acceleration values so that they vary between -1 & +1. The units are measured in meters/second/second.
  • The starship should never move faster than 2m/s. It also cannot exceed the boundaries of a 10m-wide game-space.
  • Let's assume the left edge of the screen is at 0m in game-space, and the right edge is 10m.
  • Let's also assume the starship is located in the center (5m game-space).

Physics Walkthrough

Trial 1:

  1. At first the velocity is 0, and the acceleration is 0.
  2. Synchronized with the game-loop, the acceleration changes to +0.35 m/s^2
  3. It takes 35millis for the game loop to be re-executed.
  4. In game-loop, the velocity games:
    0m/s + 0.35m/s^2 × 35millis
  5. The new velocity is 0.01225m/s just after the first 35 milliseconds.
  6. Now, the position must be updated based on the velocity:
    5m + 0.01225m/s × 35millis
  7. The new position is 5.00042875m, just a small nudge.

Trial 2:

  1. At first the velocity is 0, and the acceleration is 0.
  2. Synchronized with the game-loop, the acceleration changes to +1 m/s^2
  3. It takes 1s for the game loop to be re-executed.
  4. In game-loop, the velocity games:
    0m/s + 1m/s^2 × 1s
  5. The new velocity is 1m/s just after the first second.
  6. Now, the position must be updated based on the velocity:
    5m + 1m/s × 1s
  7. The new position is 6m, a much more substantial change.

Trial 3:

  1. At first the velocity is 0, and the acceleration is 0.
  2. Synchronized with the game-loop, the acceleration changes to -1 m/s^2
  3. It takes 0.5s for the game loop to be re-executed.
  4. In game-loop, the velocity games:
    0m/s + -1m/s^2 × 0.5s
  5. The new velocity is -0.5m/s just after the first 0.5 seconds.
  6. Now, the position must be updated based on the velocity:
    5m + -0.5m/s × 0.5s
  7. The new position is 4.75m.

Trial 4:

  1. Now, assume position begins at 10m
  2. At first the velocity is 0, and the acceleration is 0.
  3. Synchronized with the game-loop, the acceleration changes to -1 m/s^2
  4. It takes 1s for the game loop to be re-executed.
  5. In game-loop, the velocity games:
    0m/s + -1m/s^2 × 1s
  6. The new velocity is -1m/s just after the first second.
  7. Now, the position must be updated based on the velocity:
    10m + -1m/s × 1s = 9m
  8. The next game loop happens 1 second later (acceleration is held to -1 m/s^2)
  9. Velocity changes to:
    -1m/s + -1m/s^2 × 1s = -2m/s
  10. Position changes to:
    9m + -2m/s * 1s = 7m

Now, I assumed that the width of the screen map to 10m in game-sace Lets assume that screen dimensions are 320 x 480 (in pixels), and that the width is the larger dimension (480).

So.. we need to map these numbers:

0m, 1m, 2m, ..., 10m

To these numbers:

0px, 1px, 2px, ..., 480px

Well, this should be straightforward to compute the screen location because we know that 480px corresponds to 10m, and every other value depends on a linear relationship between game-space & screen-space:

screenXPosition = gameXPosition / 10m * 480px

//Re-written looks like:
screenXPosition = gameXPosition * (480px / 10m)

//Where your constant factor for the linear relationship is (480px / 10).

If you need to change devices, then you simply change the screen width parameter:

screenXPosition = gameXPosition * (screenWidthInPixels / 10m)

You mentioned you need to place limits on your velocity, that should look like this:

float newVelocityX = velocityX + accelerationX * elapsedTimeMillis;
if (Math.abs(newVelocityX) > MAXIMUM_SPEED_X)
{
    //If the new speed exceeds the allowed limit, then we need to make
    //the speed go to the limit, while preserving the direction of the
    //velocity.

    //Direction should be either -1 or 1.
    float direction = Math.abs(newVelocityX) / newVelocityX;

    newVelocityX = MAXIMUM_SPEED_X * direction;
}
velocityX = newVelocityX;
\$\endgroup\$
  • \$\begingroup\$ .@NickMiller, what is the 'MAX_ACCELERATION_VALUE' value?, Also I'm not clear on what you mean by "MAKE SURE THIS IS NORMALIZED AND HAS CORRECT DIRECTION." - I've tried this (as far as I can understand it) and am getting some very bizarre results! Thanks. \$\endgroup\$ – BungleBonce Oct 27 '15 at 23:57
  • \$\begingroup\$ Ah, apologies for the confusion. MAX_ACCELERATION_VALUE varies, I'm not sure what is required for Android, but essentially is what it says: the maximum possible acceleration value. By diving the acceleration by the the max. possible value, you should get a normalized value between -1 & 1. Normalization simply refers to having all of your values vary been the range (-1 to 1, or 0 to 1). Make sure you know the range of values you are working with (and the units they have). If you post more code I should be able to update my answer to be more applicable. I had to make some guesses... \$\endgroup\$ – Nicholas Miller Oct 28 '15 at 1:33
  • \$\begingroup\$ In regards to the correct direction bit... I will assume that tilting left will yield a positive X acceleration and tilting right will yield a negative X acceleration. If you want the thing being moved to go right on a right tilt, then you must invert (multiply by -1) the acceleration because its backwards. \$\endgroup\$ – Nicholas Miller Oct 28 '15 at 1:44
  • \$\begingroup\$ OK @NickMiller, here are the issues I'm facing 1) The direction - when I tilt left, my sprite goes right and vice-versa. 2) The acceleration is really slow - ie the sprite isn't responsive enough, 3) the sprite keeps accelerating and it's velocity is never capped. Out of interest, I believe (correct me if I'm wrong), that in my example I am using Euler Integration which the whole game uses, however your method seems a lot different. I would really like to stick to Euler (or a method similar to that which I'm already using), which code do you want me to update the question with? Thanks! \$\endgroup\$ – BungleBonce Oct 28 '15 at 12:04
  • 1
    \$\begingroup\$ Acceleration is defined as the change (increase) in rate/speed of an object as far as I know. (and Deceleration being a decrease in rate/speed) I've update the question with some clarification details - hope it helps @NickMiller ! \$\endgroup\$ – BungleBonce Oct 29 '15 at 2:52
0
\$\begingroup\$

By looking at the code, I think all you need to do is to take the sign of the variable tilt, instead of taking its actual value, to achieve the equivalent of your touch based code. So change your onSensorChanged listener to this

@Override
public void onSensorChanged(SensorEvent event){ 
    tilt = event.values[axis];   
    tilt = tilt>=0?1:-1;
}

Let me know if this is not what you are looking for.

\$\endgroup\$
  • \$\begingroup\$ Unfortunately @Aj_, this doesn't work at all. The sprite just very slowly move in the direction of the tilt regardless of how much the device is tilted (i.e, no acceleration), thanks. \$\endgroup\$ – BungleBonce Oct 27 '15 at 23:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.