Sunday 26 May 2013

Street Race Swipe LibGDX Scene2D Tutorial



I have been meaning to write a LibGDX Scene2D tutorial for some time now (ever since my Quack Attack FREE Duck Hunt Game got ridiculously popular). The problem has been finding the time, instead of writing blog entries I've been making games!

I had a great idea for a multitasking game, play four independent games at one time to train your brain and test your ability to multitask. The result was my 4 Games 1 Screen FREE Brain Training Game. I built this game as four separate games from the outset, with the intention of possibly building further on each minigame and releasing as standalone apps. The first of these standalone apps is Street Race Swipe Racing Game, which brings us neatly to the point of this article...


Street Race Swipe Racing Game is such a simple game at its heart that it seems like an excellent candidate for a tutorial article :) I remember being massively impressed by the example SimpleApp on the LibGDX wiki when first starting out, there was so little to it but it touched on all the basics of what is needed to make a game. I'm hoping this mini project will be almost as simple and just as helpful to others!


  • You can get the example projects finished apk here (free!)
  • You can get a more polished version of what is essentially the same game here (free!)
  • You can get the free multigame app that includes this minigame here (free!)

If you're interested in tinkering with the open source tutorial project, grab it from github.

Provided you have your dev environment correctly setup, you can go ahead and import the existing project into eclipse and start tinkering :) If you need help on setting up your dev environment, check out this article.

Right, so by now you've seen the live games on Google Play, installed the example project apk, and are interested in figuring out how it all works, let's dig in...

First thing to note is that project is arranged in the standard core/android/desktop structure the LibGDX project creation wizard gives us.

In the desktop projects src directory we have a utility class that makes use of gdx tools TexturePacker2 (you'll only need to run this if you alter the textures - simply right click the desktop project in eclipse, run as java application, Pack Textures, then right click the android project and refresh), and a Main class used to start the game on desktop (generated by the LibGDX project setup tool and only tweaked very slightly).

In the android projects assets directory we have an image atlas and its counterpart image file, these were generated automatically by the packing utility (it packs anything in the dev-images directory). In the android projects src directory we have a standard MainActivity class generated by the LibGDX project setup tool - I've added an exit dialog to this class, but apart from that it remains unchanged.

What remains is the core project...

The entry point is MyGame.java:

 
package com.theinvader360.scene2dtutorial.swiperace;

import com.badlogic.gdx.Game;

public class MyGame extends Game {
 public final static int WIDTH = 800;
 public final static int HEIGHT = 480;
 private GameScreen gameScreen;

 @Override
 public void create() {
  Assets.load();
  gameScreen = new GameScreen();
  setScreen(gameScreen);
 }

 @Override
 public void dispose() {
  Assets.dispose();
  gameScreen.dispose();
 }
}


First we define static final width and height constants that will be used to specify the target resolution of the game. We'll later use these constants to set the size of our game stage. Note that these values aren't physical pixel dimensions, they are an abstract idea of the size of our play area. Our LibGDX Scene2D Stage will be stretched to fit the players device as best possible, which means our game will display nicely on devices with 320x240px screens, 1920x1080px screens, and anything in between!

We also load our Assets, create a GameScreen and set it to active, and do some housekeeping on dispose. Not a lot to it really...

Assets.java:

 
package com.theinvader360.scene2dtutorial.swiperace;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.TextureRegion;

public class Assets {
 public static TextureAtlas atlas;
 public static TextureRegion car;
 public static TextureRegion road;

 public static void load() {
  atlas = new TextureAtlas(Gdx.files.internal("images.atlas"));
  car = atlas.findRegion("car");
  road = atlas.findRegion("road");
 }

 public static void dispose() {
  atlas.dispose();
 }
}


Not a lot of assets in this game - just an atlas that contains a whopping great two texture regions. Get the atlas from android internal assets directory, find the car and road regions, and make them all publicly available. Oh, and be a good citizen and dispose of the filehandle on closing the app.

Right, on to GameScreen.java then:
 
package com.theinvader360.scene2dtutorial.swiperace;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.input.GestureDetector;
import com.badlogic.gdx.input.GestureDetector.GestureListener;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.Stage;

public class GameScreen implements Screen, GestureListener {
 private Stage stage;
 private TrafficGame trafficGame;

 public GameScreen() {
  stage = new Stage();
  trafficGame = new TrafficGame();
  stage.addActor(trafficGame);
 }

 public void resize(int width, int height) {
  stage.setViewport(MyGame.WIDTH, MyGame.HEIGHT, true);
  stage.getCamera().translate(-stage.getGutterWidth(),
    -stage.getGutterHeight(), 0);
 }

 @Override
 public void render(float delta) {
  Gdx.gl.glClearColor(0, 0, 0, 1);
  Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
  stage.act(delta);
  stage.draw();
 }

 @Override
 public void show() {
  Gdx.input.setInputProcessor(new GestureDetector(this));
 }

 @Override
 public void hide() {
  Gdx.input.setInputProcessor(null);
 }

 @Override
 public boolean fling(float velocityX, float velocityY, int button) {
  if (velocityY < -100)
   trafficGame.playerCar.tryMoveUp();
  if (velocityY > 100)
   trafficGame.playerCar.tryMoveDown();
  return false;
 }

 @Override
 public void resume() {
 }

 @Override
 public void pause() {
 }

 @Override
 public void dispose() {
 }

 @Override
 public boolean tap(float x, float y, int count, int button) {
  return false;
 }

 @Override
 public boolean touchDown(float x, float y, int pointer, int button) {
  return false;
 }

 @Override
 public boolean longPress(float x, float y) {
  return false;
 }

 @Override
 public boolean pan(float x, float y, float deltaX, float deltaY) {
  return false;
 }

 @Override
 public boolean zoom(float initialDistance, float distance) {
  return false;
 }

 @Override
 public boolean pinch(Vector2 initialPointer1, Vector2 initialPointer2,
   Vector2 pointer1, Vector2 pointer2) {
  return false;
 }

}



So this is the active (and only) screen. GameScreen implements Screen, we know the render method will be called regularly, so this is where our main gameloop lives (not much to it - wipe the screen, tell the stage to act/update based on time passed since last update, draw the stage, repeat...). It has a Scene2D stage, and the resize method sets the stage viewport to our games virtual resolution (800x480) ensuring that if the device is of a different aspect ratio we do not stretch the stage to fit but instead display black bars in the screen gutters (letterboxing). It implements GestureListener too, and in the overridden fling() method we detect flings/swipes and trigger an action. Finally, there's a rather mysterious TrafficGame object being instantiated and added to the stage. This TrafficGame actor being on stage means that its act method will be called each time the render method asks the stage to update itself, and its draw method will be called each time the render method asks the stage to draw itself. Sounds exciting, let's find out more...

TrafficGame.java:
 
package com.theinvader360.scene2dtutorial.swiperace;

import java.util.Iterator;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.TimeUtils;

public class TrafficGame extends Table {
 private InfiniteScrollBg backgroundRoad;
 private Array<EnemyCar> enemyCars;
 private long lastCarTime = 0;
 public final float lane2 = 390;
 public final float lane1 = 240;
 public final float lane0 = 90;
 public PlayerCar playerCar;

 public TrafficGame() {
  setBounds(0, 0, 800, 480);
  setClip(true);
  backgroundRoad = new InfiniteScrollBg(getWidth(), getHeight());
  addActor(backgroundRoad);
  playerCar = new PlayerCar(this);
  addActor(playerCar);
  enemyCars = new Array<EnemyCar>();
 }

 @Override
 public void act(float delta) {
  super.act(delta);

  if (TimeUtils.nanoTime() - lastCarTime > 3000000000f)
   spawnCar();

  Iterator<EnemyCar> iter = enemyCars.iterator();
  while (iter.hasNext()) {
   EnemyCar enemyCar = iter.next();
   if (enemyCar.getBounds().x + enemyCar.getWidth() <= 0) {
    iter.remove();
    removeActor(enemyCar);
   }
   if (enemyCar.getBounds().overlaps(playerCar.getBounds())) {
    iter.remove();
    if (enemyCar.getX() > playerCar.getX()) {
     if (enemyCar.getY() > playerCar.getY())
      enemyCar.crash(true, true);
     else
      enemyCar.crash(true, false);
    } else {
     if (enemyCar.getY() > playerCar.getY())
      enemyCar.crash(false, true);
     else
      enemyCar.crash(false, false);
    }
   }
  }
 }

 private void spawnCar() {
  int lane = MathUtils.random(0, 2);
  float yPos = 0;
  if (lane == 0)
   yPos = lane0;
  if (lane == 1)
   yPos = lane1;
  if (lane == 2)
   yPos = lane2;
  EnemyCar enemyCar = new EnemyCar(getWidth(), yPos);
  enemyCars.add(enemyCar);
  addActor(enemyCar);
  lastCarTime = TimeUtils.nanoTime();
 }

 @Override
 public void draw(SpriteBatch batch, float parentAlpha) {
  batch.setColor(Color.WHITE);
  super.draw(batch, parentAlpha);
 }
}



Ok, so that's why the GameScreen render method was so deceptively empty, the real game logic lives in TrafficGames act method...

Let's take it from the top. TrafficGame extends Table, this is a special type of Actor that has a funky setClip(true) function, it means that anything outside the bounds of the TrafficGame won't be drawn. Remember that we don't allow the aspect ratio to be stretched, clipping means we won't have actors spilling out over our nice black letterboxing bars. I think this is probably abusing tables, it's no doubt not what they were really meant for, but I think it's a great little hack and am more than happy with the results!

In the constructor we create an InfiniteScrollBg and add it to the stage. We don't quite know what the deal is with this right now, but it's an actor added to the stage and will draw itself when asked to, that's all we care about for now.

We also create a PlayerCar and add it to the stage. Same story as InfiniteScrollBg, bit of a mystery for now, but I'd like to hope that the class names are reasonably descriptive :) Let's just add it to the stage and be happy in the knowledge that when the stage is updated and drawn it will take care of updating and drawing itself.

Finally, we create an empty array to hold references to EnemyCar objects (more mystery objects!)...

Right, back to the act method... It checks if an enemy car has been spawned recently, and if not spawns one (randomly assign to a lane, add to the enemyCars array, add to the stage, set the last spawn time to *now* so in three seconds time we can do this all over again). It also checks for enemy cars that are outside the play area and removes from the enemyCars array and removes the actor from the stage (well from TrafficGame, which is on the stage, so same diff). And it does a nice and easy overlap collision check (if an enemy cars bounds overlaps the player cars bounds, do something!). If this stuff looks familiar, it's because at it's heart this is a direct ripoff of the excellent SimpleApp example in the LibGDX wiki :)

And last but not least (or maybe literally least, come to think of it), we have the draw method. It simply resets the spritebatch color to white (just incase any other cheeky actors have been messing with the spritebatch) and then calls its own super method which does all the heavy lifting. Awesome.

Let's get back to those mystery classes, starting with the simplest - InfiniteScrollBg.java:
 
package com.theinvader360.scene2dtutorial.swiperace;

import static com.badlogic.gdx.scenes.scene2d.actions.Actions.*;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.scenes.scene2d.Actor;

public class InfiniteScrollBg extends Actor {
 public InfiniteScrollBg(float width, float height) {
  setWidth(width);
  setHeight(height);
  setPosition(width, 0);
  addAction(forever(sequence(moveTo(0, 0, 1f), moveTo(width, 0))));
 }

 @Override
 public void draw(SpriteBatch batch, float parentAlpha) {
  super.draw(batch, parentAlpha);
  batch.draw(Assets.road, getX() - getWidth(), getY(), getWidth() * 2,
    getHeight());
 }
}



What do we have here then? We extend actor, so provided this thing is on stage when we call stage.act() it'll update itself, and provided it's on stage when we call stage.draw() it'll draw itself. InfiniteScrollBg has dimensions that match our stage size, but when it draws itself it is actually drawing something twice the size of the stage, the road texture is drawn at InfiniteScrollBg's x position minus its own width, and the texture is stretched over a width twice that of the objects own width. Since we initialise the objects position as being on the right edge of the screen, what we are in effect doing is drawing an image that covers the whole of the viewport and the same size area again directly to the right of the visible area. What's the point of that I hear you say...

Actions is the point of that. Look closely at this line - "addAction(forever(sequence(moveTo(0, 0, 1f), moveTo(width, 0))));". When we construct this object, we tell it that we want it to constantly repeat the following actions forever: First, we want it to move left, the distance to move is its own width (or the width of the visible play area), and we want this action to happen over a timespan of one second. Next, we want it to move back to its starting point, and we want this movement to be instantaneous. These two sequential actions looping forever, combined with an image twice the width of the visible play area, gives us the illusion of a never ending infinitely scrolling background. Neat! :) To see this principle being used to achieve parallax scrolling and multiple different games at once, check out my 4 Games 1 Screen FREE Brain Training Game!

Two more classes to go, the PlayerCar and EnemyCar objects...

PlayerCar.java:
 
package com.theinvader360.scene2dtutorial.swiperace;

import static com.badlogic.gdx.scenes.scene2d.actions.Actions.*;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.scenes.scene2d.Actor;

public class PlayerCar extends Actor {
 private TrafficGame trafficGame;
 private Rectangle bounds = new Rectangle();
 private int lane;

 public PlayerCar(TrafficGame trafficGame) {
  this.trafficGame = trafficGame;
  setWidth(160);
  setHeight(85);
  lane = 1;
  setPosition(100, trafficGame.lane1 - getHeight() / 2);
  setColor(Color.YELLOW);
 }

 @Override
 public void act(float delta) {
  super.act(delta);
  updateBounds();
 }

 @Override
 public void draw(SpriteBatch batch, float parentAlpha) {
  batch.setColor(getColor().r, getColor().g, getColor().b, getColor().a);
  batch.draw(Assets.car, getX(), getY(), getWidth() / 2, getHeight() / 2,
    getWidth(), getHeight(), 1, 1, getRotation());
 }

 private void updateBounds() {
  bounds.set(getX(), getY(), getWidth(), getHeight());
 }

 public void tryMoveUp() {
  if ((getActions().size == 0) && (lane != 2))
   moveToLane(lane + 1);
 }

 public void tryMoveDown() {
  if ((getActions().size == 0) && (lane != 0))
   moveToLane(lane - 1);
 }

 private void moveToLane(int lane) {
  this.lane = lane;

  switch (lane) {
  case 0:
   addAction(moveTo(getX(), trafficGame.lane0 - getHeight() / 2, 0.5f));
   break;
  case 1:
   addAction(moveTo(getX(), trafficGame.lane1 - getHeight() / 2, 0.5f));
   break;
  case 2:
   addAction(moveTo(getX(), trafficGame.lane2 - getHeight() / 2, 0.5f));
   break;
  }
 }

 public Rectangle getBounds() {
  return bounds;
 }
}



Another actor that takes care of its own act and draw methods, great stuff. It has a shape and size, roughly rectangular, so we can get away with simple rectangle bounds for overlap collision checks. Every time act is called, update the bounds in case the car has moved. When we draw, we use the actors own color, which we've set to a rather fetching shade of yellow. This is why we had to set the batch color to white in TrafficGame, because our actors can (and do) screw with the spritebatch tint :)

We've a couple of public methods that you may recall are triggered when swiping the GameScreen, tryMoveUp() and tryMoveDown(). These methods simply check the car's current position (is it in lane 0 or 2?), and based on whether or not the requested movement is valid or not (if you're in the top lane you can't move up, if you're in the bottom lane you can't move down, if you're currently moving between lanes i.e. have actions in your queue you can't accept another move request) either does nothing or calls moveToLane(int lane).

moveToLane accepts an integer parameter, and based on the requested lane adds the appropriate action to the PlayerCar, these actions should be familiar from the InfiniteScrollBg class - move to a new position, and spread the movement over a timespan of half a second.

And that brings us to our last class, EnemyCar.java:
 
package com.theinvader360.scene2dtutorial.swiperace;

import static com.badlogic.gdx.scenes.scene2d.actions.Actions.*;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.scenes.scene2d.Actor;

public class EnemyCar extends Actor {
 private Rectangle bounds = new Rectangle();

 public EnemyCar(float x, float y) {
  setWidth(160);
  setHeight(85);
  setPosition(x, y - getHeight() / 2);

  int rnd = MathUtils.random(0, 3);
  if (rnd == 0)
   setColor(Color.RED);
  if (rnd == 1)
   setColor(Color.GREEN);
  if (rnd == 2)
   setColor(Color.WHITE);
  if (rnd == 3)
   setColor(Color.BLUE);

  addAction(moveTo(-getWidth(), getY(), MathUtils.random(4.0f, 6.0f)));
 }

 @Override
 public void act(float delta) {
  super.act(delta);
  updateBounds();
 }

 @Override
 public void draw(SpriteBatch batch, float parentAlpha) {
  batch.setColor(getColor().r, getColor().g, getColor().b, getColor().a);
  batch.draw(Assets.car, getX(), getY(), getWidth() / 2, getHeight() / 2,
    getWidth(), getHeight(), 1, 1, getRotation());
 }

 private void updateBounds() {
  bounds.set(getX(), getY(), getWidth(), getHeight());
 }

 public void crash(boolean front, boolean above) {
  clearActions();
  addAction(fadeOut(1f));
  if (front && above)
   addAction(sequence(
     parallel(rotateBy(-360, 1.5f), moveBy(200, 200, 1.5f)),
     removeActor()));
  if (front && !above)
   addAction(sequence(
     parallel(rotateBy(360, 1.5f), moveBy(200, -200, 1.5f)),
     removeActor()));
  if (!front && above)
   addAction(sequence(
     parallel(rotateBy(360, 1.5f), moveBy(-200, 200, 1.5f)),
     removeActor()));
  if (!front && !above)
   addAction(sequence(
     parallel(rotateBy(-360, 1.5f), moveBy(-200, -200, 1.5f)),
     removeActor()));
 }

 public Rectangle getBounds() {
  return bounds;
 }
}



It's another extension of Actor, quelle surprise! As with the PlayerCar it has a bounding rectangle that can be used for collision checks. The constructor randomly assigns a colour (which the draw method respects), and an action is assigned that boils down to move from offcreen right to offscreen left and take between 4 and 6 seconds to do so.
TrafficGame's collision detection code calls the public crash(bool,bool) method. The parameters tell the EnemyCar where the PlayerCar was in relation the EnemyCar at time of collision. If the EnemyCar was to the right and above the player car for example, spin the EnemyCar in a clockwise direction, move it to the right and upward, all the while fading out (draw method respects color which has an alpha element to it), and finally remove itself from the stage.

And that's all there is to it. I'm far from an expert on any of this stuff, programming is not my day job, just a hobby, so don't take anything I write here as being the way of doing things. You can safely assume that it is certainly a way of doing things though, and it does work - check out my games if you want proof :)

I really hope that someone out there finds this article helpful and informative. It's a lot more fun making games than writing about making games, but if this kind of thing is helpful then that gives me a warm fuzzy feeling inside too. If you would like to say thanks, please leave a comment here or on facebook/twitter/youtube/wherever, or just install my games and have fun with them!

33 comments:

  1. Thanks for the tutorial, I'm going through it right now.

    One thing though, do you plan on uploading the image atlas and various image files?

    ReplyDelete
  2. Hey Dr. Gero! Click here to make all your dreams come true - https://github.com/TheInvader360/swipe-race-tutorial/tree/master/swipe-race-tutorial-android/assets

    ReplyDelete
  3. Thanks for the quick response!

    ReplyDelete
  4. Thanks for the quick response!

    ReplyDelete
  5. This is a small child of your tutorial =) the code is on github fork =)
    https://play.google.com/store/apps/details?id=com.cubo2d.morrocoy

    Thanks for the tutorial and congratulations for your million installations!

    ReplyDelete
  6. Wow very cool, thanks for sharing your game Daniel :) I'm installing it right now!

    ReplyDelete
  7. Thanks a lot for your pretty open source games. You filled me with enthusuasm. Hey, I am trying to make my tile based game. Previously I was using Sprite class (not extending Actor ..a simple POJO class with position and velocity vector and other common attributes)as a base class for Player, Enemy, Coin etc.But now I am thinking I should let them extend Actor ..but because they use vectors for position and velocity. In your AbstractActor..there is nothing like that. For a platform game, I will need these vector calculations for sure.Please comment on this. Will it be right for a tiled base game to rely on Scene2D

    ReplyDelete
  8. Hi Java Wolf, there's nothing to say that your game actors can't extend Scene2D Actor if any of the other Scene2D stuff would be useful to you, and just add the velocity vector etc to you objects as you are already doing. You'd want to avoid using the moveTo and moveBy methods during normal gameplay, but could still benefit from stuff like fadeOut and movement actions for death sequences etc. It's up to you to decide if Scene2D/Actor gives you anything useful or not. As a side note, I have started messing about with a tile based platform game of my own, and I do not use Scene2D at all in that project (yet, at least!).

    ReplyDelete
  9. Thanks ... I had expected a similar suggestion. Very helpful.

    ReplyDelete
  10. Thanks. I started looking into this tutorial and its GREAT.
    Question: I've seen games that you get points; well I guess in this game you loose points. Let's say that when you hit blue car, you loose 10 and red car 15. How would you show the points where you hit the car and sort-of have it fading in a second or so? I've seen games that it shows the score (at collide location) and then it animates (e.g. moves up) and gradually fades. Would be great if you could cover this - THANKS

    ReplyDelete
  11. Hi "Unknown",

    Thanks for the comment, glad you like it :)

    The way I did this kind of thing in Quack Attack FREE Duck Hunt (https://play.google.com/store/apps/details?id=com.theinvader360.duckhunt.quackattackfree) was create a new class that extends Actor (FloatingScore.java). Constructor sets position, a local 'score' string, and creates an action sequence (move up a bit over time, then removeActor()). Overridden draw() simply draws the score string at the relevant position - font.drawMultiLine(batch, score, getX(), getY(), getWidth(), HAlignment.CENTER)... All you have to do then is on collision create a FloatingScore actor (e.g. addActor(new FloatingScore(x, y, score)).

    As always, this isn't necessarily the best/right way of doing this, but it works, and that's what matters most :) Have fun making your games!

    ReplyDelete
    Replies
    1. Hi TheInvader360,

      Thanks for your quick reply. These are great info that you have here.
      Re your quackattack, is it also in github / open-sourced so that I could inspect the code for how the score / animation is handled?

      Thanks again

      Delete
    2. Hello again!

      Quack Attack is not open source, but really there's not a lot to it... Here's a pastebin of my FloatingScore class: http://pastebin.com/u6kV5BeP

      Just call "addActor(new FloatingScore(x, y, score));" as and when required (in this tutorial project I guess you might stick it after "if (enemyCar.getBounds().overlaps(playerCar.getBounds())) {" in TrafficGame.java)

      Delete
    3. Awesome! Works great.
      I'm learning by playing with the code.
      I'm adding a crash sound (.mp3, 8k, 1 second). I added the following in the TrafficGame:
      Sound sound = Gdx.audio.newSound(Gdx.files.internal("sounds/electric_clock_ticking.mp3"))

      and in the act method of same class, right after addActor(floatingScore), I added: sound.play();

      Is above the proper approach?

      BTW, do you have any tutorial (or references) that includes physics; e.g. bouncing image (e.g. ball))

      THANKS for quick replies.

      Delete
    4. Hello again M Hossein :)

      I wouldn't say that I know the best way of doing things (I'm of the opinion that if something does what it's supposed to do, then why worry). Since act() is called each render loop, what you describe would result in the sound being played many times, if this is not what you want you might be better off triggering it elsewhere e.g. in the FloatingScore constructor maybe? Other than that, seems fine.

      For discussions with people that can guide you better on how to do things the 'right' way rather than my 'it works so it's ok' way, check out the libgdx forum and irc channel :)

      As for ball physics, Mario himself has created a great LibGDX 101 slideshow that can be found on the forum, this cover the basic unrealistic physics of a pong/breakout ball. If you are interested in more realistic physics, you might want to research Box2D a little. I'm afraid I can't help further than that at the moment as I'm only just starting to learn Box2D myself!

      Delete
  12. Clearly and simply illustrated. Thank you.

    ReplyDelete
  13. Hi TheInvader360,

    In the classes that extend Actor I'm unable to override draw. When I place @Override on top of the draw method, an error comes indicating I should remove the override notation.

    Why does this occur?

    Thanks.

    ReplyDelete
    Replies
    1. In Eclipse, right click your project -> Properties -> Java Compiler and check your JDK Compliance. Latest JDK versions allow @Override annotations on implemented methods. If I'm not mistaken since 1.6? Correct me if I'm wrong.

      Delete
    2. In Eclipse, right click your project -> Properties -> Java Compiler and check your JDK Compliance. Latest JDK versions allow @Override annotations on implemented methods. If I'm not mistaken since 1.6? Correct me if I'm wrong.

      Delete
  14. Hi thank you for your hard and good work!
    I downloaded the game after reading this article because I was curious. And it seems to have the same problem I am facing also in my test. The scrolling is not smooth. Every 30 moves or so there is a little jump in the scrolling. I noticed in your app as well. And I have it also in my tests running on a mac book desktop. Is it a problem of libgdx or with the code we are using? Does it help drawing to a texture and swap it to screen at the end of drawing? Or is it the hardware not used properly? I am testing your app on a Samsung note 3 so I guess it is plenty of power.
    Did you notice this problem?
    Thank you!
    Camillo

    ReplyDelete
  15. theInvader360: thank you so much, i want to use your project to develop my graduation thesis :v. are you agree ?

    ReplyDelete
  16. I'm new to libGDX. I found this and it looked like a good learning project. I downloaded all this code. I have Eclipse and recent patches but I found some differences in the code. E.g. no getGutterWidth() or getGutterHeight(), GL20 instead of GL10. I made changes to get rid of errors but I'm get only a blank screen. What am I doing wrong?

    ReplyDelete
    Replies
    1. Hi lostvincy, there were some changes made in libgx 1.0 - OpenGL 1 support dropped and changes to simplify usage of the camera/viewport. To run with this example as written, either clone from https://github.com/TheInvader360/swipe-race-tutorial, or grab an older version of libgdx (0.9.9 or older should be ok). I've updated https://play.google.com/store/apps/details?id=com.theinvader360.fishy.fishing.fish.game.free to LibGDX 1.0 - I replaced GL10 with GL20, instantiated stage as stage = new Stage(new ScalingViewport(Scaling.fit, Constants.WIDTH, Constants.HEIGHT));, and in the resize method stage.getViewport().update(width, height, true); Hope this helps :)

      Delete
  17. Thanks TheInvader360. I did replace the GL10 with GL20. For whatever reason, the issue seems to be with the draw(SpriteBatch batch, float parentAlpha) function. If I make this draw(Batch batch, ....) it works fine but with type SpriteBatch, I get a blank screen. I tested this with your InfiniteScrollBg, PlayerCar and EnemyCar classes. Anyone where I make the batch a SpriteBatch is not visible.
    So I was able to solve this by using Batch. I've already reworked the size to make scalable. I also used some math instead of hardcoded sizes (e.g. lane0=getHeight()/5.5f, lane1=getHeight()/2f and lane2=2*lane1-lane0. Also the PlayerCar and EnemyCar classes I used Gdx.graphics.getWidth()/5f and Gdx.graphics.getHeight()/5.65f for width and height respectively. The result then scales fine on Android and iOS. Thanks a lot. This has been a great example to get started with LibGDX and I think I actually learned more than if it had worked perfectly. I still need to figure out why SpriteBatch isn't working but that just means more learning. Thanks again.

    ReplyDelete
    Replies
    1. Cool, glad you got it working :) I'm especially happy to hear this article is still quite helpful even though it uses a pre 1.0 version if libgdx. It's a popular article so I will try to get round to updating it soon, but right now real life is crazy and I dont have *any* time to do fun android stuff!!

      Delete
  18. Hi,

    i couldn't import the TexturePacker class! Any help/suggestion?

    ReplyDelete
    Replies
    1. Managed to get it working Need to add dependency after reading this https://github.com/libgdx/libgdx/wiki/Texture-packer#running-texturepacker and then this https://github.com/libgdx/libgdx/wiki/Updating-LibGDX

      Delete
  19. Hi, I see in this game one feature share score on facebook. Do you teach a demo example share score on facebook with libgdx? Thanks

    ReplyDelete
  20. Firstly, great tutorial! Thanks.
    I have one question: in TrafficGame.java you pass "this" as an argument in order to create a new PlayerCar:
    playerCar = new PlayerCar(this);
    I'm no expert, but I know that referencing "this" from within the constructor may lead to problems. Is there a reason why here it is OK do to it?

    ReplyDelete
  21. Hi! Awesome lesson!!!

    Thank You a lot!!!

    Your post really helped me!

    But, if You have a time, please update code due to new LibGDX API. But still You did great work!!!

    Regards!

    ReplyDelete
  22. Hi! Awesome lesson!!!

    Thank You a lot!!!

    Your post really helped me!

    But, if You have a time, please update code due to new LibGDX API. But still You did great work!!!

    Regards!

    ReplyDelete
  23. Okay I get the code 98%. Here is what I don't get? Where do you set the screen to be the main menu screen that has the play and options buttons? And, how do you handle the user touch when the user taps on the play button to start the game?

    ReplyDelete
  24. hi i'm trying to import to android studio but failed.also some of the libgdx methods have changed.

    ReplyDelete