2012-04-01

#10 - libgdx Tutorial: Accelerator and Keyboard

Libgdx version used on this post: 0.9.2 (download)
First of all, a big thanks to my first supporter! A german guy bought me a beer by donating some money. Vielen Dank! Mit diesem Geld kaufe ich ein deutsches Weizenbier!

Important changes

Updated the ADT Plugin on Eclipse to version 17. It seems that every JAR that's inside the "libs" folder of an Android project gets added to its classpath automatically. When testing I also noticed that tyrian-game was not being sent to the Android device, so the game crashed because of a ClassNotFoundException. This is what I did to fix the problems:
  1. In tyrian-android, I removed all the JARs from the classpath and let Android reference them through the new Android Dependencies classpath item.
  2. Exported the "gdx.jar" item in Properties > Java Build Path > Order and Export of tyrian-game.
  3. Exported the tyrian-game item in Properties > Java Build Path > Order and Export of tyrian-android.
More details about libgdx on ADT v17 can be found here.

Moving the ship

When rendering a screen (AbstractScreen#render method) we have to first process the game logic and then draw the result. In the first part we can check for user input and modify our actors accordingly. As we want to move the ship, a common approach is to use the device's accelerometer as an input, but we should also support a keyboard because we're testing the game on the Desktop.

We'll start by adding a moveShip(delta) method to Ship2D. This method will take care of all the movement details, like making sure the ship is inside the screen, calculating its variable movement speed and deciding whether to use a keyboard or the accelerometer. Notice the method's delta parameter. With this information in hand we can define a maximum movement speed for the ship whose unit is pixels per second. We could start off setting some speed constant and adjusting it as needed. For instance:
    private static final float MAX_MOVEMENT_SPEED = 250;
This means the ship can move 250 pixels per second. Our fixed game viewport is sized 400x240, so in practice the ship will take 400/250 (=1.6) seconds to fly from the left to the right edges of the screen, and 240/250 (=0.96) seconds from the bottom to the upper edges. We could go on with this approach, but let's say we change the fixed viewport's size in the future in order to deliver higher resolution graphics. The movement speed would need to be reviewed because of this. So instead, let's just use the time we want the ship to travel between the edges of the screen.
    private static final float MAX_HORIZONTAL_SPEED = ( AbstractScreen.GAME_VIEWPORT_WIDTH / 1.6f );
    private static final float MAX_VERTICAL_SPEED = ( AbstractScreen.GAME_VIEWPORT_HEIGHT / 0.96f );
Later we can adjust these constants as we test the game on a real device.

Using the Keyboard

No secrets here. I'll just check if the arrow keys are pressed and move the ship accordingly.
    if( Gdx.input.isKeyPressed( Input.Keys.UP ) ) y += ( MAX_VERTICAL_SPEED * delta );
    else if( Gdx.input.isKeyPressed( Input.Keys.DOWN ) ) y -= ( MAX_VERTICAL_SPEED * delta );
    if( Gdx.input.isKeyPressed( Input.Keys.LEFT ) ) x -= ( MAX_HORIZONTAL_SPEED * delta );
    else if( Gdx.input.isKeyPressed( Input.Keys.RIGHT ) ) x += ( MAX_HORIZONTAL_SPEED * delta );

Using the accelerometer

We can check if an accelerometer is present with the following call:
    Gdx.input.isPeripheralAvailable( Peripheral.Accelerometer )
With that we can decide which input mechanism to use. On Android we should not forget to declare the accelerometer requirement on the AndroidManifest.xml. It is just informational, but the app stores will use it to filter the supported applications for a given device.
    <uses-feature
        android:name="android.hardware.sensor.accelerometer"
        android:required="true" />
And the following calls retrieve the accelerometer's current data:
    Gdx.input.getAccelerometerX(); // points to the right (when in portrait orientation)
    Gdx.input.getAccelerometerY(); // points upwards (when in portrait orientation)
    Gdx.input.getAccelerometerZ(); // points to the front of the display (coming out of the screen)
Each retrieved value ranges from -10 to 10, but we should come up with specific ranges that suit our game. In order to do that, I added the following code to the moveShip method:
    if( Tyrian.DEV_MODE ) {
        Gdx.app.debug( Tyrian.LOG,
            Gdx.input.getAccelerometerX() + "," +
            Gdx.input.getAccelerometerY() + "," +
            Gdx.input.getAccelerometerZ() );
    }
Then I executed the game on my device, decided the best ways to tilt the phone to move the ship and noted down the values of the accelerometer. Notice that as the game is in landscape mode, our game's x-axis corresponds to the accelerator's y-axis. Here they are:
Horizontal movement (accelerator's y-axis):
  • [-10,-2]: moving left at maximum speed
  • (-2,0): moving left at calculated speed
  • 0: still
  • (0,2): moving right at calculated speed
  • [2,10]: moving right at maximum speed
Vertical movement (accelerator's x-axis):
  • [-10,0]: moving forward at maximum speed
  • (0,2): moving forward at calculated speed
  • 2: still
  • (2,4): moving back at calculated speed
  • [4,10]: moving back at maximum speed
Given that, I can use the accelerometer's values to modify the ship's position on the stage:
    // x: 4 (back), 2 (still), 0 (forward)
    // I'll translate the above values to (-2,0,2) so that my next calculations are simpler
    float adjustedX = ( Gdx.input.getAccelerometerX() - 2f );
    if( adjustedX < - 2f ) adjustedX = - 2f; else if( adjustedX > 2f ) adjustedX = 2f;

    // y: -2 (left), 0 (still), 2 (right)
    float adjustedY = Gdx.input.getAccelerometerY();
    if( adjustedY < - 2f ) adjustedY = - 2f; else if( adjustedY > 2f ) adjustedY = 2f;

    // since 2 is 100% of movement speed, let's calculate the final speed percentage
    adjustedX /= 2;
    adjustedY /= 2;

    // notice the inverted axis because the game is displayed in landscape mode
    x += ( adjustedY * MAX_HORIZONTAL_SPEED * delta );
    y += ( - adjustedX * MAX_VERTICAL_SPEED * delta );

Making sure the ship is inside the screen

We don't want the ship to go off the screen, right? So all we have to do is check the ship's position against the stage's dimensions. The following code does the job:
    if( x < 0 ) x = 0; else if( x > stage.width() - width ) x = stage.width() - width;
    if( y < 0 ) y = 0; else if( y > stage.height() - height ) y = stage.height() - height;
Notice that I used the ship's width and height because an actor's origin stays at the bottom left corner.

You can view the complete source code for the Ship2D class here.

Conclusion

The ship is now able to fly, but there are still two problems to solve:
  1. The ship has no acceleration (it responds to commands immediately).
  2. The ship is not tilting to the side it's moving.
On the next post we'll handle that. Here is the Subversion repository tag for this post. Thanks!

21 comments:

  1. Hi Gustavo,

    Nice tutorials! I really like your writing style, and I could read through them like reading a book!

    For the movement, I think you can keep a variable for velocity, and add or subtract a value from velocity from the accelerometer input.

    Going to start coding on my first libgdx game, and your tutorials would help a lot.

    Thank you! and keep them coming.

    ReplyDelete
    Replies
    1. Hi Yasith!
      I appreciate your feedback!
      In the next post we'll start using vectors. It will replace some of the code I wrote for this post, but I think it's a nice way for understanding the need of it.
      Thanks for reading!

      Delete
  2. hey Gustavo ..you rock dude ... all of 10 helps me a lot .. THANKS

    ReplyDelete
  3. Good job! I like your approach on solving common game development problems. I will apply some of these ideas into my games. Thanks!

    ReplyDelete
  4. Great tutorial!
    Except for the libgdx, you go over many important tools / features, and it really helps to see the 'big picture'.

    thanks, and waiting for #11 :)

    ReplyDelete
    Replies
    1. Hi Neo!
      These tools can really save us many valuable hours! :)
      I hope to finish the post #11 by sunday.
      Thanks for reading!

      Delete
  5. Great work Gustavo!! Very well done!
    I'm waiting for #11 :)

    samuele

    ReplyDelete
    Replies
    1. Thanks Samuele! I've been very busy, but I'll write it eventualy. :)

      Delete
    2. I am a newcomer of libgdx and I want to say to you that I learned more and in few hours/days with your tutorials that whole weeks that I spent to try to understand how to do something with libgdx and its sparse,old/new,fragmented,unfinished docs..
      Thank you! For me you are the bigger contributor to documentations and clarification on how to use libgdx.
      I am very disappointed for all the time that I waste before in the hell of switching from official/unofficial docs, tests source,library sources, unanswered questions on the forum, blog, google.. I think that libgdx itself is a really good piece but lack in documentations, your work is in the right direction!
      So, I hope that you can continue to write your articles in the spare time :)

      Delete
  6. Excellent blog and tutorials! I haven't seen any so useful. Kepp writing :)
    But I want to ask you will you write something about localization applications(translation, I mean, like in Android apps via xml files)?

    P.S. sorry my awful English :)

    ReplyDelete
    Replies
    1. Thanks Andrey!
      You're talking about internationalization (i18n). I don't have any plans for that, but I made a quick research and found this: http://siondream.com/blog/games/internationalization-for-libgdx-projects/

      Thanks for reading!

      Delete
    2. It's exactly what I need. Thank you!
      It seems I've been looking for in wrong places.

      Delete
  7. LOVE YA! if I finish my projects and ill make some bucks from admob I'll donate some of em to you, cos you helped me a lot. Cheers ;)

    ReplyDelete
  8. Hi,

    I'm pretty new to game development, so this may be a dumb question. Why isn't the way the ship moves/accelerate/etc. part of the domain objects?

    I would have tough that Ship2d would have simply displayed the ship where the domains tells it to, and but it seems it also responsible for its position and speed.

    Thanks,

    mp

    ReplyDelete
    Replies
    1. Hi Mathieu, this is a good question! The domain object could in fact have some intelligence regarding positioning. But in a 2D world we deal with X and Y axis. In a 3D world, we would also have a Z axis to deal with. So I chose the simplest way in this regard. It's a good thing to separate concerns, but sometimes we have to be realistic and find a balance.

      Delete
  9. Hey, when is the next post?

    ReplyDelete
    Replies
    1. Hi Ksam, to be honest, I don't think there will be a next one. When I started writing this blog libgdx was going through many refactorings, so I got a bit disapointed because the examples I was giving were soon outdated. Maybe I'll start all over again when libgdx gets mature.

      Delete
    2. I hope you restart with IntelliJ IDEA soon. Your writing style with tagged snapshot of the code repo is the best! Even after two years, this blog is still the best guide for libgdx.

      Delete