Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I'm trying to implement Parallax scrolling in two dimensions using SDL, but I've run into an issue using SDL_BlitSurface() to blit my layer images to the screen buffer. The images I'm using for this are borrowed from http://en.wikipedia.org/wiki/Parallax_scrolling, and I was able to get it working fine just scrolling along the X axis, using the following code:

void Parallax::Draw(SDL_Surface* buffer) {

    //draw each layer starting with the bottom
    std::vector<ParallaxLayer*>::iterator it;

    for(it=layers.begin(); it != layers.end(); it++){

        ParallaxLayer* layer = (*it);
        layer->x_offset %= buffer->w;
        int draw_x = layer->x_offset - layer->image->w;

        SDL_Rect blitrect = buffer->clip_rect;

        while(draw_x <= buffer->w) {
            blitrect.x = draw_x;
            SDL_BlitSurface(layer->image, &(layer->image->clip_rect), buffer, &blitrect);
            draw_x += layer->image->w;
        }
    }
}

This works.

Now, I've tried to adapt this into two dimensions. Here's the draw method I'm using now:

void Parallax::Draw(SDL_Surface* buffer) {

    //draw each layer starting with the bottom
    std::vector<ParallaxLayer*>::iterator it;

    for(it=layers.begin(); it != layers.end(); it++){

        ParallaxLayer* layer = (*it);

        //resize offset so as not to overflow ints or whatever
        layer->x_offset %= buffer->w;
        layer->y_offset %= buffer->h;

        int draw_x = layer->x_offset - layer->image->w;
        int draw_y = layer->y_offset - layer->image->h;

        SDL_Rect blitrect = buffer->clip_rect;

        while(draw_x <= buffer->w && draw_y <= buffer->h) {

            blitrect.x = draw_x;
            blitrect.y = draw_y;

            SDL_BlitSurface(layer->image, &(layer->image->clip_rect), buffer, &blitrect);

            if(draw_x <= buffer->w)
                draw_x += layer->image->w;
            if(draw_y <= buffer->h)
                draw_y += layer->image->h;
        }
    }
}

I've added the logic for blitting a layer that's moving in the vertical axis, but when I tested it, this just doesn't play nice. Areas of the screen end up not being blitted, which causes a lovely un-drawn effect:

Link to image as I can't post them directly (new user): SDL blitting bug example

Just to be clear, the images themselves are pushed around in another method. The front two layers are moving along the horizontal axis with varying velocity, and the background is moving vertically with a fixed velocity and horizontal with a variable velocity, but that's largely irrelevant as it's my blit function which is bugged.

It looks like I'm not re-drawing the area of the screen that has the repeating image on it, but I'm lost as to how to go about this.

share|improve this question
    
I've worked out what's wrong with this and why I'm an idiot. I'll post my solution as an answer to this question when I finish it. –  Inks Jul 15 '12 at 22:42
add comment

1 Answer 1

up vote 1 down vote accepted

The code example I was working from was fine for scrolling in one dimension, but in two dimensions it does not tile the image to cover the whole screen. This was producing the artifacts seen in the linked image. So once I thought about it and really understood what that code actually does, I was 75% nearer to figuring out a solution.

To render properly, the image must be tiled as many times as nescessary to cover the screen size. Programming Linux Games, by John R. Hall, gives a similar example on pages 143-147, which I was able to adapt to solve the problem. The final method is as follows:

void Parallax::Draw(SDL_Surface* buffer) {

    int start_x, start_y = NULL;
    int draw_x, draw_y = NULL;

    std::vector<ParallaxLayer*>::iterator it;

    //draw each layer starting with the bottom
    for(it=layers.begin(); it != layers.end(); it++) {

        ParallaxLayer* layer = (*it);

        //resize offset so as not to overflow ints or whatever
        layer->x_offset %= layer->image->w;
        layer->y_offset %= layer->image->h;

        // start_x, start_y - initial position of rectangle to tile.
        start_x = layer->x_offset - layer->image->w;
        start_y = layer->y_offset - layer->image->h;

        SDL_Rect blitrect = buffer->clip_rect;

        // Use nested loops to scan down the screen, drawing rows of tiles:
        draw_y = start_y;
        while(draw_y <= buffer->h) {

            draw_x = start_x;
            while(draw_x <= buffer->w) {

                SDL_Rect destrect;

                blitrect.x = draw_x;
                blitrect.y = draw_y;

                SDL_BlitSurface(layer->image, &(layer->image->clip_rect), buffer, &blitrect);

                draw_x += layer->image->w;
            }

            draw_y += layer->image->h;
        }
    }
}

The inner while loops in this example iterate while the image is inside the screen, and cover the entire surface with blitted images of each layer.

The only real optimisation I made over the version in the book was to use ->clip_rect to retrieve the drawable portion of the image, as seen in the original 1d method used.

Again, just thinking about the problem was the solution... ;)

share|improve this answer
    
Modding by the buffer size only worked in the above example because the images were the same size as the SDL surface. We should actually mod by layer->image->w; and height, which should handle tiling to a buffer of any size. –  Inks Aug 9 '12 at 16:38
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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