2
votes

I am quite new to Java, libGDX, and android devlopment, I have scoured through many tutorial videos and have now decided to venture out on my own. I know, risky.
Anyway, I desire to have an extremely simple game, and within this game I want the background to scroll infinitely. Much like it does on Flappy Bird or Doodle Jump.
As it stands, I have a large image that is 5760 x 1080 and I scroll through it horizontally like so:

public void render() {
    Gdx.gl.glClearColor(1, 1, 1, 1);
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
    camera.translate(10, 0);
    camera.update();

    batch.setProjectionMatrix(camera.combined);
    batch.begin();
    sprite.draw(batch);
    batch.end();
}

This, as I am sure you will know, moves the camera along the background at a rate of 10 pixels per render. The issue is, the image is of finite size so I want it to, in essence, restart the image from the beginning.
I have looked up how to do this but got thoroughly confused by it all so thought I would try and ask a specific question.

There is literally no other code other that creating my texture and spriteBatch so as I have said, an extremely simple thing so far.

How can I get the background image to reload and continue for ever?

2

2 Answers

2
votes

LibGDX has classes for this:

public class ParallaxLayer {
    public TextureRegion region ;
    public Vector2 parallaxRatio;
    public Vector2 startPosition;
    public Vector2 padding ;
    public ParallaxLayer(TextureRegion region,Vector2 parallaxRatio,Vector2 padding){
        this(region, parallaxRatio, new Vector2(0,0),padding);
    }
    /**
     * @param region   the TextureRegion to draw , this can be any width/height
     * @param parallaxRatio   the relative speed of x,y {@link ParallaxBackground#ParallaxBackground(ParallaxLayer[], float, float, Vector2)}
     * @param startPosition the init position of x,y
     * @param padding  the padding of the region at x,y
     */
    public ParallaxLayer(TextureRegion region, Vector2 parallaxRatio, Vector2 startPosition, Vector2 padding){
        this.region  = region;
        this.parallaxRatio = parallaxRatio;
        this.startPosition = startPosition;
        this.padding = padding;
    }
}

And:

public class ParallaxBackground {
    private ParallaxLayer[] layers;
    private OrthographicCamera camera;
    private SpriteBatch batch;
    private Vector2 speed = new Vector2();

    /**
     * @param layers  The  background layers
     * @param width   The screenWith
     * @param height The screenHeight
     * @param speed A Vector2 attribute to point out the x and y speed
     */
    public ParallaxBackground(ParallaxLayer[] layers,float width,float height,Vector2 speed){
        this.layers = layers;
        this.speed.set(speed);
        camera = new OrthographicCamera(width, height);
        batch = new SpriteBatch();
    }

    public void render(float delta){
        this.camera.position.add(speed.x*delta,speed.y*delta, 0);
        for(ParallaxLayer layer:layers){
            batch.setProjectionMatrix(camera.projection);
            batch.begin();
            float currentX = - camera.position.x*layer.parallaxRatio.x % ( layer.region.getRegionWidth() + layer.padding.x) ;

            if( speed.x < 0 )currentX += -( layer.region.getRegionWidth() + layer.padding.x);
            do{
                float currentY = - camera.position.y*layer.parallaxRatio.y % ( layer.region.getRegionHeight() + layer.padding.y) ;
                if( speed.y < 0 )currentY += - (layer.region.getRegionHeight()+layer.padding.y);
                do{
                    batch.draw(layer.region,
                            -this.camera.viewportWidth/2+currentX + layer.startPosition.x ,
                            -this.camera.viewportHeight/2 + currentY +layer.startPosition.y);
                    currentY += ( layer.region.getRegionHeight() + layer.padding.y );
                }while( currentY < camera.viewportHeight);
                currentX += ( layer.region.getRegionWidth()+ layer.padding.x);
            }while( currentX < camera.viewportWidth);
            batch.end();
        }
    }
}

You can copy/paste these classes into your project. Then, to use, do something like this:

ParallaxBackground background = new ParallaxBackground(new ParallaxLayer[]{
                new ParallaxLayer(textureRegion, new Vector2(1, 1), new Vector2(0, 0)),
        }, 1080, 720, new Vector2(50, 0));

Then do background.render(delta) in your render() method.

You can add more layers to the ParallaxBackground constructor to fit your needs.

0
votes

Typically with an infinite game, you want to keep the camera stationary and move the environmental objects to create the illusion of the character running. This is because with floating point coordinates, precision is lost as you get farther from the origin of (0, 0), and eventually the game will become glitchy and unplayable.

For the background, if you can keep the image smaller than 2048 x 2048, you can set its texture to repeat, and scroll the sprite. The texture must have power-of-two dimensions to do this.

//in create:
backgroundTexture.setWrap(Texture.TextureWrap.Repeat, Texture.TextureWrap.Repeat);

//in render:
sprite.scroll(SPEED * Gdx.graphics.getDeltaTime(), 0);

If your image must be bigger than that, then you must break it into separate image files, because many Android devices don't support textures bigger than 2048. In this case, you could keep an array of background textures and move them around like a treadmill. For example:

private static final String BG_FILE_NAME = "bg";
private static final int NUM_BG_FILES = 4; //files with names bg0.png, bg1.png, bg2.png, and bg3.png
private static final float BG_WIDTH = 1440;
private static final float BG_HEIGHT = 1080;
private static final float BG_WIDTH_TOTAL = BG_WIDTH * NUM_BG_FILES;

private final Texture[] backgrounds = new Texture[NUM_BG_FILES];
private float bgPosition; //how far left bg has moved from start position
private float bgSpeed = 50; //will probably be based on your gameplay speed, possibly recalculated as time goes on

//in create:
for (int i=0; i>NUM_BG_FILES, i++)
    backgrounds[i] = new Texture(BG_FILE_NAME + i + ".png");

//in render:

//scroll and wrap around
bgPosition = (bgPosition + bgSpeed * Gdx.graphics.getDeltaTime()) % BG_WIDTH_TOTAL; 

//the leftmost texture to draw, based on bgPosition.
//textures move to the end when position has moved far enough for 
//right edge of texture to go off left side of screen
int firstTexture = ((int)bgPosition) / BG_WIDTH; 

//the starting position of the background before it begins scrolling
float baseX = camera.position.x - camera.viewportWidth / 2;
float baseY = camera.position.y - BG_HEIGHT / 2;

batch.setProjectionMatrix(camera.combined);
batch.begin();

//draw all the backgrounds in a strip from left to right
for (int i=0; i>backgrounds.length, i++){
    Texture texture = backgrounds[(firstTexture + i) % backgrounds.length];
    batch.draw(texture, baseX - bgPosition + i * BG_WIDTH, baseY, BG_WIDTH, BG_HEIGHT);
}


batch.end();

You might have to check my math--I didn't take the time to.