Categories

Wednesday, December 5, 2012

Create games with LIBGDX library in Android (1)

   This is a continuation of this tutorial:
Getting Started in Android Game Development with libgdx – Create a Working Prototype in a Day – Tutorial Part 1

That 1st part of the tutorial belongs entirely to obviam.net and I am in no way associated with them.
Since the second part hasn't appeared for almost a year and I've seen many comments asking for it, I decided to post my own version of finishing the game for anyone interested.


Quick reminder of where the game was left of

   If you've carefully read the tutorial on obviam.net then right now you probably already have the project in Eclipse with both versions: Desktop and Android. So far, it draws the world (blocks and the main character, Bob) and allows you basic movements. Since I am mostly interested in Android (and I'm sure you too) I am going to concentrate on this version mostly, although as you could see in the final part of the 1st tutorial (the one dealing with the controller) the only important differences between the two versions relies in the input (on the Desktop we have keys and mouse, on the phone we have a touchscreen).

I noticed a lot of unanswered questions in the first tutorial. If you still have problems, post here and I will help you to get the game running so that you can continue along!

We will continue to add functionality, piece by piece, also taking into account people's feedback on what they would like to see.


First objective: add terrain interaction

   First thing we'll do is stop Bob from getting off the screen or running into blocks.
   This is (as you may guess) very simple and shall be done inside the Bob class, more specifically inside the update() method. This is done in 3 steps:

  1. Update character's position just like we did before.
  2. Check if character is outside the screen or it has run into any block.
  3. If either of the above conditions are met, simply undo what had been done at step 1.

   Here is the new code for Bob's update method:


 public void update(float delta) {
                //step 1
  position.add(velocity.tmp().mul(delta));
  updateBounds();
                //step 2
  float newX = position.x;
  boolean ranIntoBlocks = false;
  for (Block b : world.getBlocks()) {
   if (Intersector.overlapRectangles(bounds,b.getBounds())) {
    ranIntoBlocks = true;
    break;  //we found a collision, no need to check the rest of the blocks
   }
  }
                //step 3
  if ( newX < 0 || newX > 10 - bounds.getWidth() || ranIntoBlocks) {
   position.add(velocity.tmp().mul(-delta)); //undo the changes
   updateBounds();
  }
 }


   First step needs no explanation and is already implemented: Bob's position (which is a 2D vector containing his X and Y coordinates) is updated using the velocity vector, which is non-zero only if a left/right action has been detected, otherwise the position is not affected.
   The second step actually has 2 parts. 
 Checking if the character is still in the screen is easy. For now we'll only do it by checking the X coordinate (since right now we've only implemented  movements on X axis). If the new X coordinate is smaller than 0 or it is greater than the maximum allowed (the width of the screen minus Bob's width, remember that Bob is a rectangle and his X coordinate is the rectangle's bottom-left corner's X coordinate). In our code, in the step 2 section we only memorize the X coordinate in the variable newX (to make it clearer).
 To check collisions is a little more complicated than that and we'll make use of gdx's class Intersector which has a beautiful method:

   boolean Intersector.overlapRectangles(Rectangle a, Rectangle b)

 As you might have guessed, it's a static method (we'll use it directly without instantiating the Intersector class) that returnes true if the two Rectangle arguments overlap. What we do in the code is check this condition for Bob's bounds and each block's bounds.

   Finally, step 3 checks if either the collision or the off-screen condition is true, in which case it updates Bob's position again, with the initial modification reversed (the minus sign for delta).

   You have noticed a new function in our code: updateBounds() is a private void method inside Bob class which does nothing more than to update Bob's bounds according to its position. When we move Bob to a new position, we must also move the rectangle that surrounds him (his bounds), otherwise his bounds will always remain in his initial position and we'll never detect any collision.

  The complete new Bob.java class is here:


package net.obviam.starassault.model;

import com.badlogic.gdx.math.Intersector;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;

public class Bob {

 public enum State {
  IDLE, WALKING, JUMPING, DYING
 }

 private static final float SPEED = 4f; // unit per second
 static final float JUMP_VELOCITY = 1f;
 private static final float SIZE = 0.5f; // half a unit
 private World world;

 Vector2  position = new Vector2();
 Vector2  acceleration = new Vector2();
 Vector2  velocity = new Vector2();
 Rectangle  bounds = new Rectangle();
 State  state = State.IDLE;
 boolean  facingLeft = true;

 public Bob(Vector2 position) {
  this.position = position;
  this.bounds.height = getSize();
  this.bounds.width = getSize();
  updateBounds();
 }

 public Vector2 getPosition() {
  return position;
 }

 public Rectangle getBounds() {
  return bounds;
 }

 public static float getSize() {
  return SIZE;
 }
 
 public void setState(State newState) {
  this.state = newState;
 }
 
 private void updateBounds() {
  bounds.x = position.x;
  bounds.y = position.y;
 }
 
 public void update(float delta) {
  position.add(velocity.tmp().mul(delta));
  updateBounds();
  float newX = position.x;
  boolean ranIntoBlocks = false;
  for (Block b : world.getBlocks()) {
   if (Intersector.overlapRectangles(bounds,b.getBounds())) {
    ranIntoBlocks = true;
    System.out.println(b.getBounds().x);
    System.out.println(bounds.getX());
    break;
   }
  }
  if ( newX < 0 || newX > 10 - bounds.getWidth() || ranIntoBlocks) {
   position.add(velocity.tmp().mul(-delta)); //undo the changes
   updateBounds();
  }
 }

 public Vector2 getAcceleration() {
  return acceleration;
 }

 public void setAcceleration(Vector2 acceleration) {
  this.acceleration = acceleration;
 }

 public Vector2 getVelocity() {
  return velocity;
 }

 public void setVelocity(Vector2 velocity) {
  this.velocity = velocity;
 }

 public boolean isFacingLeft() {
  return facingLeft;
 }

 public void setFacingLeft(boolean facingLeft) {
  this.facingLeft = facingLeft;
 }

 public State getState() {
  return state;
 }

 public void setPosition(Vector2 position) {
  this.position = position;
 }

 public void setBounds(Rectangle bounds) {
  this.bounds = bounds;
 }

 public static float getSpeed() {
  return SPEED;
 }

 public World getWorld() {
  return world;
 }

 public void setWorld(World world) {
  this.world = world;
 }

}



  Please note that updateBounds() has to be called in the constructor too (I guess by now you understand why). So it becomes clear that we must also update all blocks' bounds! But since blocks are not moving (for now at least) we won't need a method to do that as we'll only do it once in the constructor. So go to Block.java class and modify the constructor as bellow:

 public Block(Vector2 pos) {
  this.position = pos;
  this.bounds.width = getSize();
  this.bounds.height = getSize();
  this.bounds.x = position.x;
  this.bounds.y = position.y;
 }

  BUT WAIT! Don't run your code yet, we've got one thing left to do. As you can see in step 2, we need to access all the blocks in the world to get their bounds. You might have noticed that I introduced a new private World member, called world (how original), that must contain a reference to the world containing Bob.

 That's why we need to go in World.java's constructor and modify it like this:

 private void createDemoWorld() {
  bob = new Bob(new Vector2(5, 2));
  bob.setWorld(this);
                ...
 }

  You also need to create a setter method for the private member world in Bob.java class (it's done in the code above).

  To test the new code, try placing Bob at different initial positions (by modifying his initial coordinates in World.java) and then moving left/right. Don't place him inside a block though :)

  

  That's it for now, we'll continue with this very soon. Next time we'll add gravity in the game (so that Bob won't float in the air), we'll enable jumping and we'll fix the "bugs" related to the touch/release problem (if you touch the screen in the "move left" area, drag your finger to the "move right" area then lift it, the leftReleased() action never triggers, so the character keeps moving to the left until you do a release in that zone).

  I'll also post the complete source code for you to download if that helps.
  Also I'll try to find a more friendly way of posting source code so that you can copy it in no time. Any suggestion helps!

  Feel free to post any problems you have so we can solve it quickly!

 The complete source code can be found here: star-assault source code

   

45 comments:

  1. Thank you so much for continuing this tutorial!! I was looking for something like this for a long time! Great tutorial, maybe you could change this ugly background color to something less painful for my eyes (like white) xD

    ReplyDelete
    Replies
    1. Hi! I'm glad you like it! I'll be posting once or twice per week so I'm also open to suggestions on the direction to continue with the game.

      Thanks for the color advice, it's only temporary until I find a good formula :)

      Stay tuned as the next part is coming up soon.

      Delete
  2. oh, and could you put a download of the source code?

    ReplyDelete
  3. Here's what i managed to do after I completed the first part of obviam.net's tutorial:
    https://docs.google.com/open?id=0B7w5FuHl8_jkZktuQjZMRngzOHc
    As you can see, my terrain interaction is a little bit glitchy, maybe because I didn't do it like you...

    ReplyDelete
    Replies
    1. Ok I'll post the source code now and then I'm looking over yours.

      Delete
    2. Try the link at the end of the post. I'll take a look at your code now.

      Delete
    3. I ran your jar and I can see you've made it pretty far. However we'll continue here from where it was left of. Check and run the code I posted and tell me if you have any problems.

      Delete
  4. My rectangle bob's position starts off as an interger say 3,5 than I press a direction button which I have set to have a constant velocity until it meets a block making up the world which I have set for collision detection which stops the block from moving. However my new position will be 4.983,5.093 instead of integers which I want. Anyone know why? the new positions/bounds are inconsistant too I get a different number double every time.

    ReplyDelete
    Replies
    1. First of all, Bob will not always be perfectly aligned with the blocks, so his coordinates obviously will not always be integers.

      Why the new bounds are inconsistent and you get different values every time? That's because in order to obtain new coordinates when moving the speed value is multiplied with the elapsed time, which will not always have the same value (that time is very small, usually miliseconds, and at different times the system (being busy with drawing and computing) will not always give the same time value for computing the new position. That's why when Bob's path is blocked by something in his left, and he stops, he most likely hasn't reached EXACTLY that obstacle's coordinate value. For example, the obstacle may be at x=4, and Bob's initial position it at x=5. He starts moving left, and when x becomes 4.05 collision is not detected yet, so x decreases and only when it reaches say 3.98 Bob stops and goes back to the previous valid value, say 4.01. Othertimes he may reach 4.02 and then 3.99 so he goes back and stops at 4.02.

      Why your final y position is y=5.093? This can only happen if you jumped. If you only moved on the X axis, y does not change. If you do jump, then the previous paragraph applies to y too.

      If I didn't answer your question please post the code that updates Bob's position and I'll look into it.

      Delete
  5. This comment has been removed by the author.

    ReplyDelete
  6. Bogdan,
    Thanks so much for continuing this! Trying to get into game programming and I have modified this tutorial a million times to try to get animations working/tied to movement, but just no luck, really hoping that you can help me out on that. Also, can you maybe use the Libgdx Touchpad in order to move Bob?

    Thanks again!

    ReplyDelete
    Replies
    1. Hi Brent,

      I hope I'll be posting the next tutorial very soon, I'm thinking on how to continue with the game.
      When we refine the game code in future tutorials I'll definitely introduce the Touchpad class if necessary, right now we're focusing on understanding the basic of game programming, mainly organizing the code.
      It's a very good idea though and I'll keep it in mind. Thanks!

      Delete
    2. That's cool man, just whatever you want to do with it. Right now I'm just struggling with getting the animations to work, can get the sprite to show up and move, update position etc. but can't get an animation to work. i was trying to use a case, switch statement.
      If you need any collaboration / help let me know, I can do art assets, and started learning libgdx a few months ago.

      Delete
    3. Ok, I'll keep that in mind, working together is always a good thing! Thanks

      Delete
    4. I created a class for the animations: public class Sprite {
      Animation animation;
      float stateTime;
      TextureRegion[] frames;

      public Sprite(Texture texture, int x, int y, int width, int height, int columns, int rows, float timeBetween) {
      frames = new TextureRegion[columns * rows];
      int index = 0;
      for (int v = 0; v < rows; v++) {
      for (int u = 0; u < columns; u++) {
      int var1 = width / columns; //to make code clearer
      int var2 = height / rows;
      int u1 = x + var1 * u;
      int v1 = height - var2 * (v+1);
      frames[index] = new TextureRegion(texture, u1, v1, var1, var2);
      index++;
      }
      }

      animation = new Animation(timeBetween, frames);
      stateTime = 0f;
      }

      public TextureRegion getKeyFrame() {
      stateTime += Gdx.graphics.getDeltaTime();
      return animation.getKeyFrame(stateTime, true);
      }
      }
      x, y, width and height is to position the sprite on the texture. than it divides that region by the specified amount of columns and rows. Each textureRegion than created is put in a table. the function getKeyFrame() returns the textureRegion to render based on the elapsed time. Hope it helps

      Delete
    5. aVo!d,
      Thanks for that. How would I go about using this class? Do I extend it within the Bob class? Have you used it successfully? If you could give me an example of how you're using it that would be awesome.

      Libgdx does have it's own animation class within the graphics.g2d package. And I have tried using that, but I always get force closes for some reason when trying, again, I'm trying to use case switch statements, much akin to what Mario (Libgdx creator) used in SuperJumper.

      Thanks again!

      Delete
    6. I have used it successfully many times. To use it, simply instantiate a "Sprite" class for every animation you have. For example, in this Star Assault project, I used this picture to move to the left: https://docs.google.com/open?id=0B7w5FuHl8_jkOTJiaWlmMHg1LUk
      So to use it, in WorldRenderer you simply have to add this:
      "private Texture bobSpriteLeft;
      private Sprite spriteLeft;
      bobSpriteLeft = new Texture(Gdx.files.internal("images/bobSprite.png"));
      spriteLeft = new Sprite(bobSpriteLeft, 0, 0, 160, 32, 5, 1, 0.12f);"

      NOTE: "0, 0" is the lower left corner of the TextureRegion to use, and "160, 32" the upper right one. 5 is the number of columns that the animation has, 1 the number of rows and 0.12f the time (in seconds) that every single frame lasts.


      then to render it, you add this in the render() method: "spriteBatch.draw(spriteLeft.getKeyFrame(), bob.getPosition().x * ppuX, bob.getPosition().y * ppuY, Bob.SIZE * ppuX, Bob.SIZE * ppuY); "
      As you can see you basically use getKeyFrame() to get the texture to render.

      Hope this helped and that it worked for you, if not please tell me what error you got and I'll do my best to help you! (I also hope I didn't forget anything ;)

      Delete
    7. Awesome, thanks!

      So in the original star assault tutorial, we had a drawBob() method. Which just did a static sprite, using the above, how do I differentiate from left and right, I guess I need to do something with the isFacingRight() boolean?

      I haven't had a chance to test this yet, didn't find your reply to me last night, and now I'm at work. But I definitely will try this later today!

      Delete
    8. aVo!d,
      Thanks a million!!! it worked! I'm so psyched, I think that part of my problem was using a case switch statement, rather than just if, else if. But man, you've helped me out big time. You too Bogdan!

      Delete
    9. Simply test the boolean to decide which side to use. I'm glad you solved it, I've finally got a few days off to work on the project.

      Cheers!

      Delete
    10. Thanks, Good to hear.
      As always I've got another question, I want to use either aVo!d's sprite class or the libgdx animation class, but I'd like to use a textureAtlas to cut down on all the power of two stuff for my art assets.
      Looking at aVo!d's class, I can follow what he's doing, but I'm not sure how to go about modifying it to use an atlas rather than a standard sprite.

      Delete
    11. I haven't had the time to follow your conversation with aVo!d here but tonight I'm looking into it, along with my next post. You'll hear from me on that.

      Delete
    12. Never heard of TextureAtlas :O
      so if you find your answer, put it here because I am curious!
      I'm glad this helped you!

      Delete
  7. I got everything to work but for me my debugging lines are no longer in the correct spot. Before bob had his outline and each box had their own in the correct placement but as soon as I use the UpdateBounds on both objects the debug lines seem to shift. The collision detection works perfectly but I'm curious as to why these lines are no longer in the correct spot.

    Any ideas?

    ReplyDelete
    Replies
    1. Hi Reggie,

      I'm at work right now, but tonight I'll have a look at the code on my laptop.

      The thing is I did not use the debug lines at all, so I've probably ommited to update their position too during movement. I'll check it out and let you know.

      Delete
    2. Reggie,

      I just looked over the code, it seems there are some mistakes in calculating the coordinates for the blocks before drawing them. I never used the "debug" mode so I didn't notice the mistakes, although when I first written this code from obviam.net I remember I was intrigued by the way those rectangles were calculated. Unfortunately I initialized the world renderer directly with false argument for debug and forgot about it.

      I'll fix it in the next release for you, just for fun :)

      Delete
    3. Hey Bogdan,

      Yeah. Everything works as expected but only the lines were off, so it must be a initialization error. Nothing major, though for future development it may be a good idea to fix.

      Great tutorials though! I've now subscribed for your future works though I'm definitely taken some detours for my own fun.

      Delete
    4. ah ha! I actually fixed it.

      In the WorldRenderer class we need to fix the drawDebug() function. Since you added the UpdateBounds() function we don't need to compensate for them within the drawDebug code.

      So instead of:

      Rectangle rect = block.getBounds(); // This will have the correct bounds because of UpdateBounds()
      float x1 = block.getPosition().x + rect.x // But this will update the x bound location again
      float y1 = block.getPosition().y + rect.y // and then the y location

      So we only need:

      final Rectangle rect = block.getBounds();
      float x1 = block.getPosition().x;
      float y1 = block.getPosition().y;

      Apply the same to y for both block and bob.

      Delete
    5. Haha it's so funny I was about to post the same thing today, there's no need to add the rectangle's dimensions indeed.

      Delete
  8. I like to know how you do the gravity part..

    ReplyDelete
    Replies
    1. It's in the next part, it's already implemented, right now I'm just refactoring some code and rewriting some functions I don't like. Stay tuned

      Delete
  9. Thank you Bogdan for this 2nd part of your great tutorial. I have followed your firts part published on obviam.net and I was so sad not seeing the following.
    Waiting for Part 3 :-)

    ReplyDelete
    Replies
    1. Hi Frederic! I'm glad you wish to stay in touch with us with this tutorial.

      I need to point out first that what is published at obviam.net does not belong to me. I am simply continuing this from where it's left off.

      Delete
  10. I would like also to understand how to code the jump action. Indeed, we cannot manage it as we did for left and right movements as a jump is temporary. Bob goes up for a limited duration and then goes down to land again.

    ReplyDelete
    Replies
    1. That's exactly what I am coding right now and I will try to explain it in great detail. Tonight I'm working on finishing the next post here.

      Delete
  11. Hello Bogdan... thanks in advance for these tutorials.

    Just one question... shouldn't the collision test be made in the WorldController to keep the Bob class separated from the World class?

    ReplyDelete
    Replies
    1. Hi,
      These are really two different solutions and there are arguments for each side.

      Depending on how your project expands you might treat the collision test either as a characteristic of the class (Bob has the "ability" to tests himself whether he's ok) or as a general mechanism of the "world" which makes sure all of its components are ok.

      The solution we have here is easier because the test is done just after the position update and if something's wrong we can immediately undo the changes.

      I agree with you that theoretically the test should be made in the WorldController, out of principle of separation rather than commodity.

      However, from a different point of view, one might argue that the same principle of separation would require that the test be done inside the Bob class (as I said previously, it's an "ability") so that in whatever world he would be, he'd do the test himself, INDEPENDENTLY. It would seem more natural this way - Bob is placed inside a world that he can "see" (that's why I passed him a reference of the world when he's created) and so he can react by himself to his surroundings and detect collisions while moving.

      These are really decisions that are up to the programmer who must keep a wide vision over the future of the developing process and anticipate whether or not separation is necessary over commodity.

      Hope this helps!

      Delete
  12. I didn't think about "viewing differents worlds"... taking this into account, it seems a good strategy being part of Bob.

    Thank you!

    ReplyDelete
  13. Hi! I'm doing some part of my application the same like this. But I wonder if there is a solution for the InputProcessor class, because it calls the method touchDown() before the ApplicationListener.render(). Now we changed the ApplicationListener in Screen and it doesn't work no more.
    There is a solution for this? How can I call touchDown of InputProcessor befor the render of the screen?
    Thanks for the help and sorry for my bad english.

    ReplyDelete
  14. Anyone has the problem that resizing doesn't work? When resizing my window the debug lines are still drawn correctly but the textures either get too small or too big and everything shifts position. Looking at the coordinates and sizes passed to the draw function everything is correct and perfectly corresponds to where the debug lines are and the textures should be, but they aren't there or the right size. At fullscreen the textures get so big, only three complete boxes fit vertically on the screen and Bob can't be seen because he's far outside of the window. Making the window smaller leads to the textures becoming ever smaller so they aren't filling the whole screen.

    ReplyDelete
    Replies
    1. I found a fix, though still not sure I've just missed something or if it's the correct thing to do. According to the documentation the SpriteBatch uses the current application size to set itself up, so I just re-create the SpriteBatch in the setSize method and now everything works fine.

      batch.dispose();
      batch = new SpriteBatch();

      Delete
    2. I was wondering about this as well -- thank you.

      Delete