How to scroll smoothly from background1.png to background2.png by resetting player position to x0,y0 when in background2 and so on?


#1

Hi guys,

I’m quite new to SDL2 and I’d like to scroll from a background to another

like in this old pc game to be exact (that I love) :slight_smile:

When the player reaches the end of the screen with background 1, there is the scrolling to background 2 and the player therefore starts at the beginning(0,0) of screen with background 2 and so on for the next screens…
I do not care for the player to go back to the previous screens, they might be destroyed I guess to save resources. After seeing that video I thought that a good idea to achieve the same is to have multiple backgrounds and scroll through them, so I tried to implement something similar by modifying what I studied in http://lazyfoo.net/tutorials/SDL/31_scrolling_backgrounds/index.php but it does not work the way I would like to (like in the youtube example).
The scrolling from background1 to background2 is not precise and also if I try to reset the x position of the player (in this case I’m using the dot) to the beginning(x0,y0) of the background2 it keeps rendering image 1 instead of image 2 because of course the condition is not met anymore.

Any idea how to implement the above like in the youtube video for scrolling through multiple backgrounds and getting rid of the previous ones?
Many thanks!
This is the part of C++ code I have modified:

[details=Summary] //The background scrolling offsets
int scrollingOffset1 = 0;
int scrollingOffset2= 0;

   //While application is running 
                while( !quit )
                {
                    //Handle events on queue
                    while( SDL_PollEvent( &e ) != 0 )
                    {
                        //User requests quit
                        if( e.type == SDL_QUIT )
                        {
                            quit = true;
                        }
                        
                        //Handle input for the dot
                        dot.handleEvent( e );
                    }
                    
                    //Move the dot
                    dot.move();
                    
                    //Scroll background
                    --scrollingOffset1; 
                    --scrollingOffset2;
                    if( scrollingOffset1 < -gBGTexture.getWidth() )
                    {
                        scrollingOffset1 = 0; 
                        
                    }
                    
                    //Clear screen
                    SDL_SetRenderDrawColor( gRenderer, 0xFF, 0xFF, 0xFF, 0xFF );
                    SDL_RenderClear( gRenderer );
                    
                    gBGTexture.render( 0 , 0 );
                  
                    if (dot.getmPosX() > 600)
                        
                    {
                        gBGTexture.render( scrollingOffset1, 0 );
                    
                        gBGTexture2.render( scrollingOffset1 + gBGTexture2.getWidth(), 0) ;
                
                    }
                        if ( scrollingOffset2 < -640)
                            
                        {
                            
                            gBGTexture2.render(0, 0);
                        
                    }
                    
                    //Render objects
                    dot.render(); //gDotTexture.render( mPosX, mPosY )
                    
                    //Update screen
                    SDL_RenderPresent( gRenderer );          
                }
        }
    }
   
    //Free resources and close SDL
    close();
    
    return 0;
}[/details]

#2

I made a small program to test out the scrolling part and you can download the program from here to test it:

The box is moved with the arrow keys and whenever the arrows is touched the camera scrolls left/right.
Is this what you had in mind? Let me know if this is the case and I’ll add the code here, after I’ve written some comments in the code explaining some of the things.


#3

Hi Naith,

Many thanks for your reply.

Well done, that’s similar to what I tried to achieve and it works in the way I like , the only difference in my case sections 1,2,3 etc… are different backgrounds.png , background 2.png, backround3.png, and I have no idea how to implement different .png and the player in a smooth succession like that.

Could you please share this code as well, it’s very well done! Many thanks!

I have no idea if in the original game they used one big background like you did (moving through it with camera position) or if they just split the game in several backgrounds (with same dimensions). What’s the best approach in your opinion when it comes to memory management?

Thanks again!


#4

Well, when it comes to memory consumption, I think it’s better to load 3 different background textures and place them beside each other on the X-axis and only render the background(s) that are visible, instead of rendering the whole level background like I do in my test program.
Even though SDL clips pixels that are outside of the viewport it’s always good to make some manual culling.

When it comes to memory management (which in my head means data management and loading/storing data), it’s obviously better, and faster, to load 1 texture instead of 3 since you only need to store 1 texture in memory.

If I would be making this kind of game myself, I would probably split up the background texture into section parts though, since, like I said, only the textures visible needs to be rendered.

How familiar are you with linear algebra, vectors and such? My code consist of some algebra and I need to know how much knowledge you’ve got so I know how much explaining needs to be made in the code. :slight_smile:


#5

And when it comes to the Armorik game. I assume they’ve used a technique called tiling, which means they use a big texture atlas with all the different ground pieces, which are then laid out in the level and only those tiles that are visible (i.e, those that are inside the camera) is rendered.
The star background is probably a 1 screen width image, which are looped endlessly.

Using tiles are much more memory friendly than using a big level background with all the ground in it.


#6

Hi there,

Thanks for your answer:

/Well, when it comes to memory consumption, I think it’s better to load 3 different background textures and place -them beside each other on the X-axis and only render the background(s) that are visible, instead of rendering the whole level background like I do in my test program./

Exactly that would be great.

Do you know how to do that scrolling by loading 3 different textures instead of a single image?

What i tried to do was (when player reaches the left of the screen) decreasing the x of actual background to move it to the left and perform that scrolling effect and at the same time decreasing the x of the next background until it was positioned at x0,y0, but it did not work properly/smoothly and I had problems to reset the player position at x0,x0 at the beginning of the next screen after it was scrolled into position. Being stuck in the Game Loop with the while ( ! quit ) condition running in every frame made things a bit complicated.

Tiling I think is great but being a beginner I think it would be better to keep things easier. If you know a good guide on that please let me know.

For me the important bit is to go to the next level with the scrolling effect like in armorik and in case i do no need the previous one I can destroy that object from memory.

I know the basics when it comes to vectors and arrays and when it comes to algebra, I have studied C++ programming in easy steps and Accelerated c++ but I need to practise so if you can comment the code in an easy way that would be great :slight_smile:

Many thanks again for your help!


#7

Hi again,

Now that I’m back at the computer, I’ve rewritten my code to work with multiple textures. Below is the complete code for making my test program. Note that I’ve stripped away unnecessary code, like how the SDL window is created etc.

One thing to note is that the origin of the player, the camera and the background textures are at the upper left corner, which is good to know when checking the code and when it comes to understanding the calculations being made.

Click here to show/hide the code
#include <SDL.h>
#include <SDL_image.h>

#define WINDOW_WIDTH	(800)
#define WINDOW_HEIGHT	(600)
#define SECTION_WIDTH	(WINDOW_WIDTH)
#define NUM_SECTIONS	(3)
#define LEVEL_WIDTH	(SECTION_WIDTH * NUM_SECTIONS)

double DeltaTime = 0.0;
double OldTime	 = 0.0;
double NewTime	 = 0.0;

enum EPlayerState
{
	PLAYER_STILL = 0,
	PLAYER_MOVING_LEFT,
	PLAYER_MOVING_RIGHT,
	NUM_PLAYER_STATES
};

enum ECameraState
{
	CAMERA_STILL = 0,
	CAMERA_SCROLLING_LEFT,
	CAMERA_SCROLLING_RIGHT,
	NUM_CAMERA_STATES
};

struct SPlayer
{
	float		Width	  = 32.0f;
	float		Height	  = 64.0f;
	float		XPosition = (WINDOW_WIDTH * 0.5f) - (Width * 0.5f);
	float		Velocity  = 256.0f;
	EPlayerState	State	  = PLAYER_STILL;
};

struct SCamera
{
	float		XPosition	= 0.0f;
	float		PrevXPosition	= 0.0f;
	float		Velocity	= 512.0f;
	ECameraState	State		= CAMERA_STILL;
	SDL_Rect	Quad;
};

struct STexture
{
	SDL_Texture*	pTexture = nullptr;
	SDL_Rect	Quad;
};

SPlayer	 Player;
SCamera	 Camera;
STexture BackgroundTexture[NUM_SECTIONS];

bool QuadVsQuad(const SDL_Rect& rQuad1, const SDL_Rect& rQuad2)
{
	// Quad vs quad intersection test
	if(rQuad1.x	       < rQuad2.x + rQuad2.w &&
	   rQuad1.x + rQuad1.w > rQuad2.x	     &&
	   rQuad1.y	       < rQuad2.y + rQuad2.h &&
	   rQuad1.y + rQuad1.h > rQuad2.y)
	   return true;

	else
	   return false;
}

bool Create(void)
{
	// All this can be done in a for loop, but I don't wanna cause confusion so I'll just do it the manual way

	BackgroundTexture[0].pTexture = CreateTexture("Data/Textures/Section1.png");
	BackgroundTexture[1].pTexture = CreateTexture("Data/Textures/Section2.png");
	BackgroundTexture[2].pTexture = CreateTexture("Data/Textures/Section3.png");

	if(!BackgroundTexture[0].pTexture) return false;
	if(!BackgroundTexture[1].pTexture) return false;
	if(!BackgroundTexture[2].pTexture) return false;

	int TextureWidth	= 0;
	int TextureHeight	= 0;

	// Position each texture quad next to each other, will be used for culling later

	SDL_QueryTexture(BackgroundTexture[0].pTexture, nullptr, nullptr, &TextureWidth, &TextureHeight);
	
	BackgroundTexture[0].Quad = {TextureWidth * 0, 0, TextureWidth, TextureHeight};
	BackgroundTexture[1].Quad = {TextureWidth * 1, 0, TextureWidth, TextureHeight};
	BackgroundTexture[2].Quad = {TextureWidth * 2, 0, TextureWidth, TextureHeight};

	// Also used for culling later
	Camera.Quad = {0, 0, WINDOW_WIDTH, WINDOW_HEIGHT};

	return true;
}

void Update(void)
{
	// Handle events from the keyboard
	// When the camera scrolls, the player input is disabled
	// Also sets the player's state (see EPlayerState enumeration)
	HandleEvents();

	// Calculate the deltatime
	NewTime	  = SDL_GetTicks();
	DeltaTime = (NewTime - OldTime) / 1000.0;
	OldTime	  = NewTime;

	//////////////////////////////////////////////////////////////////////////

	// Whenever the player is moving
	if(Player.State != PLAYER_STILL)
	{
		// Just a fancy way of saying: "If the player state is set to PLAYER_MOVING_LEFT, move the player to the left. Else, move the player to the right"
		Player.XPosition += (Player.State == PLAYER_MOVING_LEFT ? -Player.Velocity : Player.Velocity) * (float)DeltaTime;

		// How near the player has to be the left- or right side of the window for the camera scrolling to start
		const float DistanceToWindowEdge = 70.0f;

		// If the player has reached the left side of the window
		if(Player.XPosition <= (Camera.XPosition + DistanceToWindowEdge))
		{
			// The camera is only allowed to scroll to the left if it's not at the start of the level
			if(Camera.XPosition > 0.0f)
			{
				Camera.State = CAMERA_SCROLLING_LEFT;

				Player.State = PLAYER_STILL;
			}
		}

		// If the player has reached the right side of the window
		else if(Player.XPosition + Player.Width >= (Camera.XPosition + WINDOW_WIDTH) - DistanceToWindowEdge)
		{
			// The camera is only allowed to scroll to the right if it's not at the end of the level
			if(Camera.XPosition < (LEVEL_WIDTH - WINDOW_WIDTH))
			{
				Camera.State = CAMERA_SCROLLING_RIGHT;

				Player.State = PLAYER_STILL;
			}
		}

		// Make sure that the player can't go outside the left- and the right side of the level
		     if(Player.XPosition < 0.0f)			Player.XPosition = 0.0f;
		else if(Player.XPosition > LEVEL_WIDTH - Player.Width)	Player.XPosition = LEVEL_WIDTH - Player.Width;
	}

	// Whenever the camera is scrolling
	if(Camera.State != CAMERA_STILL)
	{
		const bool ScrollingLeft = (Camera.State == CAMERA_SCROLLING_LEFT);

		// See the player movement code above for an explanation on how this works
		Camera.XPosition += ((ScrollingLeft ? -Camera.Velocity : Camera.Velocity) * (float)DeltaTime);

		/*
		By making the player walk a bit by itself during the scrolling, the player won't end up outside the window edges when the scrolling is finished
		The walking direction depends on the direction the camera is scrolling
		The walking speed is based on the camera's velocity - a bigger division value (in this case it's 0.25f) will make the player walk faster,
		which means the player will end up further away from the window edge when the scrolling is finished
		A smaller division value does the opposite
		Note: a value smaller than 0.22f will, with the current player width, cause an endless scrolling loop bug
		*/
		const float Velocity = Camera.Velocity * 0.25f;

		Player.XPosition += (ScrollingLeft ? -Velocity : Velocity) * (float)DeltaTime;

		// Check the distance between the camera's current- and previous position
		// The calculation result depends on the direction the camera is scrolling in
		const float Distance = (ScrollingLeft ? (Camera.PrevXPosition - Camera.XPosition) : (Camera.XPosition - Camera.PrevXPosition));

		// If the camera has scrolled a full section width
		if(Distance >= SECTION_WIDTH)
		{
			// Calculate the camera's final position
			// Again, this calculation result depends on the direction the camera is scrolling in
			const float FinalCameraPosition = (ScrollingLeft ? (Camera.PrevXPosition - SECTION_WIDTH) : (Camera.PrevXPosition + SECTION_WIDTH));

			Camera.XPosition	= FinalCameraPosition;
			Camera.PrevXPosition	= FinalCameraPosition;
			Camera.State		= CAMERA_STILL;
		}
	}
}

void Render(void)
{
	// Clear the current render target
	SDL_RenderClear(pRenderer);

	//////////////////////////////////////////////////////////////////////////

	for(int i = 0; i < NUM_SECTIONS; ++i)
	{
		if(BackgroundTexture[i].pTexture)
		{
			// Create a temporary texture quad that is relative to the camera
			const SDL_Rect TextureQuad = {	BackgroundTexture[i].Quad.x - (int)Camera.XPosition,
							BackgroundTexture[i].Quad.y,
							BackgroundTexture[i].Quad.w,
							BackgroundTexture[i].Quad.h};

			/*
			Easy and fast culling
			Check if the texture quad is inside the camera quad (i.e, if the texture is in view)
			If it's not in view, just continue without rendering the texture
			During camera scrolling, 2 textures will be visible at the same time
			When no scrolling occurs, only 1 texture will be visible and rendered
			*/
			if(!QuadVsQuad(Camera.Quad, TextureQuad))
				continue;

			SDL_RenderCopy(pRenderer, BackgroundTexture[i].pTexture, nullptr, &TextureQuad);
		}
	}

	// Render the player as a quad
	const SDL_Rect PlayerQuad = {(int)(Player.XPosition - Camera.XPosition), WINDOW_HEIGHT - (int)Player.Height, (int)Player.Width, (int)Player.Height};
	SDL_SetRenderDrawColor(pRenderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
	SDL_RenderFillRect(pRenderer, &PlayerQuad);
	SDL_SetRenderDrawColor(pRenderer, 255, 255, 255, SDL_ALPHA_OPAQUE);

	//////////////////////////////////////////////////////////////////////////

	// Update the screen
	SDL_RenderPresent(pRenderer);
}

int main(int argc, char *argv[])
{
	// Initialize SDL, create the SDL window, the SDL renderer and so on
	if(InitializeSDL())
	{
		// Create the textures and other data
		if(Create())
		{
			while(Running)
			{
				Update();
				Render();

				// Let the processor do something else for a short while to limit the processor usage
				SDL_Delay(1);
			}

			// Destroy all created data
			Destroy();
		}

		// Destroy SDL, the SDL window and the SDL renderer
		DeinitializeSDL();
	}

	return 0;
}

Hope it helps. :slight_smile:


#8

Hi there,

I’m not home atm, I’ll check it tonight or tomorrow and let you know :+1::wink: Could you kindly share the full source code as well? I’d like to test it under xcode to see how it performs.

Many thanks again for your help!


#9

That is actually the full source code, without, like I said, the creation of the SDL window etc. You should be able to write that code by yourself and test it out.

Let me know how it turns out.


#10

Hi Naith, having bit of problems with the code, especially with CreateTexture had linker problems in xCode for symbols not recognised by my architecture, at the end I had to modify it and few member functions, events managements and input and it compiled successfully and I can see my loaded background but nothing move. I think I messed up somewhere being a beginner with SDL.
If you can please upload yours I will try to run it and see how it goes, at least I know it works and I can see why mine does not work. Many thanks again for your understanding and help!

            [details=Summary]#include <SDL2/SDL.h>
            #include <SDL2_image/SDL_image.h>
            #include <iostream>
            #include <string>
            using namespace std;
               
            #define WINDOW_WIDTH	(800)
            #define WINDOW_HEIGHT	(600)
            #define SECTION_WIDTH	(WINDOW_WIDTH)
            #define NUM_SECTIONS	(3)
            #define LEVEL_WIDTH	(SECTION_WIDTH * NUM_SECTIONS)

            SDL_Event e;

            //The window we'll be rendering to
            SDL_Window* pWindow = NULL;

            //The window renderer
            SDL_Renderer* pRenderer = NULL;

            bool InitializeSDL();


            bool InitializeSDL()
            {
               
                
                //Initialize SDL
                if( SDL_Init( SDL_INIT_VIDEO ) < 0 )
                    

                        //Set texture filtering to linear
                    SDL_SetHint( SDL_HINT_RENDER_SCALE_QUALITY, "1" );
                        
                        
                        //Create window
                pWindow = SDL_CreateWindow( "SDL Tutorial", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_SHOWN );
                                        //Create vsynced renderer for window
                pRenderer = SDL_CreateRenderer( pWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC );
                                                                //Initialize renderer color
                SDL_SetRenderDrawColor( pRenderer, 0xFF, 0xFF, 0xFF, 0xFF );
                                
                //Initialize PNG loading
                int imgFlags = IMG_INIT_PNG;
                IMG_Init( imgFlags );
                return true ;

            }




            SDL_Texture* SDL_CreateTexture(std:: string path);

            void Update(void);
            void Destroy();
            void Destroy()
            {
                
            }


            void DeinitializeSDL();
            void DeinitializeSDL()

            {
                
                //Destroy window
                SDL_DestroyRenderer( pRenderer );
                SDL_DestroyWindow( pWindow );
                pWindow = NULL;
                pRenderer = NULL;
                
                //Quit SDL subsystems
                IMG_Quit();
                SDL_Quit();
            }

            double DeltaTime = 0.0;
            double OldTime	 = 0.0;
            double NewTime	 = 0.0;

            enum EPlayerState
            {
                PLAYER_STILL = 0,
                PLAYER_MOVING_LEFT,
                PLAYER_MOVING_RIGHT,
                NUM_PLAYER_STATES
            };

            enum ECameraState
            {
                CAMERA_STILL = 0,
                CAMERA_SCROLLING_LEFT,
                CAMERA_SCROLLING_RIGHT,
                NUM_CAMERA_STATES
            };

            struct SPlayer
            {
                float		Width	  = 32.0f;
                float		Height	  = 64.0f;
                float		XPosition = (WINDOW_WIDTH * 0.5f) - (Width * 0.5f);
                float		Velocity  = 256.0f;
                EPlayerState	State	  = PLAYER_STILL;
                void HandleEvents(SDL_Event& e);
            };

            struct SCamera
            {
                float		XPosition	= 0.0f;
                float		PrevXPosition	= 0.0f;
                float		Velocity	= 512.0f;
                ECameraState	State		= CAMERA_STILL;
                SDL_Rect	Quad;
            };

            struct STexture
            {
                SDL_Texture*	pTexture = nullptr;
                SDL_Rect	Quad;
                
                bool loadFromFile( std::string path );
                void free();
            };

            void STexture::free()
            {
                //Free texture if it exists
                if( pTexture != NULL )
                {
                    SDL_DestroyTexture( pTexture );
                    pTexture = NULL;
                   
                }
            }

            SPlayer	 Player;
            SCamera	 Camera;
            STexture BackgroundTexture[NUM_SECTIONS];

            bool QuadVsQuad(const SDL_Rect& rQuad1, const SDL_Rect& rQuad2)
            {
                // Quad vs quad intersection test
                if(rQuad1.x	       < rQuad2.x + rQuad2.w &&
                   rQuad1.x + rQuad1.w > rQuad2.x	     &&
                   rQuad1.y	       < rQuad2.y + rQuad2.h &&
                   rQuad1.y + rQuad1.h > rQuad2.y)
                    return true;
                
                else
                    return false;
            }

            bool STexture::loadFromFile( std::string path )
            {
                //Get rid of preexisting texture
                free();
                
                //The final texture
                SDL_Texture* newTexture = NULL;
                
                //Load image at specified path
                SDL_Surface* loadedSurface = IMG_Load( path.c_str() );
                if( loadedSurface == NULL )
                {
                    cout << "Unable to load image! SDL_image Error: \n" << path.c_str() << IMG_GetError();
                }
                else
                {
                    //Color key image
                    SDL_SetColorKey( loadedSurface, SDL_TRUE, SDL_MapRGB( loadedSurface->format, 0, 0xFF, 0xFF ) );
                    
                    //Create texture from surface pixels
                    newTexture = SDL_CreateTextureFromSurface( pRenderer, loadedSurface );
                    
                    //Get rid of old loaded surface
                    SDL_FreeSurface( loadedSurface );
                }
                
                //Return success
                pTexture = newTexture;
                return pTexture != NULL;
            }

            bool Create(void)
            {
                // All this can be done in a for loop, but I don't wanna cause confusion so I'll just do it the manual way
                
                BackgroundTexture[0].loadFromFile("armoriktest/bg.png");
                BackgroundTexture[1].loadFromFile("armoriktest/bg2.png");
                BackgroundTexture[2].loadFromFile("armoriktest/bg3.png");
                
                if(!BackgroundTexture[0].pTexture) return false;
                if(!BackgroundTexture[1].pTexture) return false;
                if(!BackgroundTexture[2].pTexture) return false;
                
                int TextureWidth	= 0;
                int TextureHeight	= 0;
                
                // Position each texture quad next to each other, will be used for culling later
                
                SDL_QueryTexture(BackgroundTexture[0].pTexture, nullptr, nullptr, &TextureWidth, &TextureHeight);
                
                BackgroundTexture[0].Quad = {TextureWidth * 0, 0, TextureWidth, TextureHeight};
                BackgroundTexture[1].Quad = {TextureWidth * 1, 0, TextureWidth, TextureHeight};
                BackgroundTexture[2].Quad = {TextureWidth * 2, 0, TextureWidth, TextureHeight};
                
                // Also used for culling later
                Camera.Quad = {0, 0, WINDOW_WIDTH, WINDOW_HEIGHT};
                
                return true;
            }

            void SPlayer::HandleEvents( SDL_Event& e )
            {
                //If a key was pressed
                if( e.type == SDL_KEYDOWN && e.key.repeat == 0 )
                {
                    //Adjust the velocity
                    switch( e.key.keysym.sym )
                    {
                    
                        case SDLK_LEFT:  Player.Velocity-= 1; break;
                        case SDLK_RIGHT: Player.Velocity += 1; break;
                    }
                }
                //If a key was released
                else if( e.type == SDL_KEYUP && e.key.repeat == 0 )
                {
                    //Adjust the velocity
                    switch( e.key.keysym.sym )
                    {
                       
                        case SDLK_LEFT: Player.Velocity += 1; break;
                        case SDLK_RIGHT: Player.Velocity -= 1; break;
                    }
                }
            }



            void Update(void)
            {
                // Handle events from the keyboard
                // When the camera scrolls, the player input is disabled
                // Also sets the player's state (see EPlayerState enumeration)
                Player.HandleEvents(e);
                
                // Calculate the deltatime
                NewTime	  = SDL_GetTicks();
                DeltaTime = (NewTime - OldTime) / 1000.0;
                OldTime	  = NewTime;
                
                //////////////////////////////////////////////////////////////////////////
                
                // Whenever the player is moving
                if(Player.State != PLAYER_STILL)
                {
                    // Just a fancy way of saying: "If the player state is set to PLAYER_MOVING_LEFT, move the player to the left. Else, move the player to the right"
                    Player.XPosition += (Player.State == PLAYER_MOVING_LEFT ? -Player.Velocity : Player.Velocity) * (float)DeltaTime;
                    
                    // How near the player has to be the left- or right side of the window for the camera scrolling to start
                    const float DistanceToWindowEdge = 70.0f;
                    
                    // If the player has reached the left side of the window
                    if(Player.XPosition <= (Camera.XPosition + DistanceToWindowEdge))
                    {
                        // The camera is only allowed to scroll to the left if it's not at the start of the level
                        if(Camera.XPosition > 0.0f)
                        {
                            Camera.State = CAMERA_SCROLLING_LEFT;
                            
                            Player.State = PLAYER_STILL;
                        }
                    }
                    
                    // If the player has reached the right side of the window
                    else if(Player.XPosition + Player.Width >= (Camera.XPosition + WINDOW_WIDTH) - DistanceToWindowEdge)
                    {
                        // The camera is only allowed to scroll to the right if it's not at the end of the level
                        if(Camera.XPosition < (LEVEL_WIDTH - WINDOW_WIDTH))
                        {
                            Camera.State = CAMERA_SCROLLING_RIGHT;
                            
                            Player.State = PLAYER_STILL;
                        }
                    }
                    
                    // Make sure that the player can't go outside the left- and the right side of the level
                    if(Player.XPosition < 0.0f)			Player.XPosition = 0.0f;
                    else if(Player.XPosition > LEVEL_WIDTH - Player.Width)	Player.XPosition = LEVEL_WIDTH - Player.Width;
                }
                
                // Whenever the camera is scrolling
                if(Camera.State != CAMERA_STILL)
                {
                    const bool ScrollingLeft = (Camera.State == CAMERA_SCROLLING_LEFT);
                    
                    // See the player movement code above for an explanation on how this works
                    Camera.XPosition += ((ScrollingLeft ? -Camera.Velocity : Camera.Velocity) * (float)DeltaTime);
                    
                    /*
                     By making the player walk a bit by itself during the scrolling, the player won't end up outside the window edges when the scrolling is finished
                     The walking direction depends on the direction the camera is scrolling
                     The walking speed is based on the camera's velocity - a bigger division value (in this case it's 0.25f) will make the player walk faster,
                     which means the player will end up further away from the window edge when the scrolling is finished
                     A smaller division value does the opposite
                     Note: a value smaller than 0.22f will, with the current player width, cause an endless scrolling loop bug
                     */
                    const float Velocity = Camera.Velocity * 0.25f;
                    
                    Player.XPosition += (ScrollingLeft ? -Velocity : Velocity) * (float)DeltaTime;
                    
                    // Check the distance between the camera's current- and previous position
                    // The calculation result depends on the direction the camera is scrolling in
                    const float Distance = (ScrollingLeft ? (Camera.PrevXPosition - Camera.XPosition) : (Camera.XPosition - Camera.PrevXPosition));
                    
                    // If the camera has scrolled a full section width
                    if(Distance >= SECTION_WIDTH)
                    {
                        // Calculate the camera's final position
                        // Again, this calculation result depends on the direction the camera is scrolling in
                        const float FinalCameraPosition = (ScrollingLeft ? (Camera.PrevXPosition - SECTION_WIDTH) : (Camera.PrevXPosition + SECTION_WIDTH));
                        
                        Camera.XPosition	= FinalCameraPosition;
                        Camera.PrevXPosition	= FinalCameraPosition;
                        Camera.State		= CAMERA_STILL;
                    }
                }
            }




            void Render(void)
            {
                // Clear the current render target
                SDL_RenderClear(pRenderer);
                
                //////////////////////////////////////////////////////////////////////////
                
                for(int i = 0; i < NUM_SECTIONS; ++i)
                {
                    if(BackgroundTexture[i].pTexture)
                    {
                        // Create a temporary texture quad that is relative to the camera
                        const SDL_Rect TextureQuad = {	BackgroundTexture[i].Quad.x - (int)Camera.XPosition,
                            BackgroundTexture[i].Quad.y,
                            BackgroundTexture[i].Quad.w,
                            BackgroundTexture[i].Quad.h};
                        
                        /*
                         Easy and fast culling
                         Check if the texture quad is inside the camera quad (i.e, if the texture is in view)
                         If it's not in view, just continue without rendering the texture
                         During camera scrolling, 2 textures will be visible at the same time
                         When no scrolling occurs, only 1 texture will be visible and rendered
                         */
                        if(!QuadVsQuad(Camera.Quad, TextureQuad))
                            continue;
                        
                        SDL_RenderCopy(pRenderer, BackgroundTexture[i].pTexture, nullptr, &TextureQuad);
                    }
                }
                
                // Render the player as a quad
                const SDL_Rect PlayerQuad = {(int)(Player.XPosition - Camera.XPosition), WINDOW_HEIGHT - (int)Player.Height, (int)Player.Width, (int)Player.Height};
                SDL_SetRenderDrawColor(pRenderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
                SDL_RenderFillRect(pRenderer, &PlayerQuad);
                SDL_SetRenderDrawColor(pRenderer, 255, 255, 255, SDL_ALPHA_OPAQUE);
                
                //////////////////////////////////////////////////////////////////////////
                
                // Update the screen
                SDL_RenderPresent(pRenderer);
                
            }






            int main(int argc, char *argv[])
            {
                
                bool Running = true;
                // Initialize SDL, create the SDL window, the SDL renderer and so on
                if(InitializeSDL())
                {
                    // Create the textures and other data
                    if(Create())
                    {
                        while(Running)
                        {
                            Update();
                            Render();
                            
                            // Let the processor do something else for a short while to limit the processor usage
                            SDL_Delay(1);
                        }
                        
                        // Destroy all created data
                        Destroy();
                    }
                    
                    // Destroy SDL, the SDL window and the SDL renderer
                    DeinitializeSDL();
                }
                
                return 0;
            }[/details]

#11

You’re not setting the player’s state correctly in your HandleEvents function.
See code below for the correct execution:

Click here to show/hide code
void SPlayer::HandleEvents(SDL_Event& rEvent)
{
	Player.State = PLAYER_STILL;

	switch(rEvent.type)
	{
	case SDL_KEYDOWN:
		{
			switch(rEvent.key.keysym.sym)
			{
			case SDLK_ESCAPE:
			{
				Running = false;

				break;
			}


			case SDLK_LEFT:
				{
					if(Camera.State == CAMERA_STILL)
						Player.State = PLAYER_MOVING_LEFT;

					break;
				}

			case SDLK_RIGHT:
				{
					if(Camera.State == CAMERA_STILL)
						Player.State = PLAYER_MOVING_RIGHT;

					break;
				}

			default:
				break;
			}

			break;
		}

	default:
		break;
	}
}

Copy-paste the above code into your project and it will work as it should.


#12

Hi Neith,

Thanks for your reply.

I’ve done that but unluckily it did not fix the problem.

The creation of the window does not look right to me, like I said something went wrong when I wrote the missing code :confused:


#13

Upload your full source code, the textures, exe file etc to a webspace and add the link here, and I’ll take a look at it.
Don’t add the full source code here since it’s very hard to read it when added on this page.


#14

Thank you Naith for your help!

You can find the full source code here:

Many thanks.


#15

Alright. Your code was quite a mess and instead of writing here regarding all the changes that needs to be made, code that need to be added etc, I’ve just fixed up all the code and re-arranged it to look more clean and structured.
I’ve also added a few more comments in the code.
Try to keep the good structure of the code when adding new function, variables etc and place them where they should be place. For example, if you’re planning on adding a new function, add it below where the other ones are declared so you have everything in it’s own structured place, which gives a more easily-read code.

Read through all the code and comments and make sure that you understand everything and also, don’t be afraid to tweak the values, like the player’s walking speed etc.

Also, please note that I have a different map structure for locating files in the code. You’ll have to tweak that aswell to locate your files properly.

Click here to view/hide the code
#include <iostream>
#include <string>

#include <SDL.h>
#include <SDL_image.h>

/**
* Some defines and settings
* Tweak NUM_SECTIONS to get more/less amount of sections in the level (which means a longer/shorter level)
*/
#define WINDOW_WIDTH	(800)
#define WINDOW_HEIGHT	(600)
#define SECTION_WIDTH	(WINDOW_WIDTH)
#define NUM_SECTIONS	(3)
#define LEVEL_WIDTH	(SECTION_WIDTH * NUM_SECTIONS)

//////////////////////////////////////////////////////////////////////////

/**
* Function declarations
* Earlier, some functions were declared inside one of the structures below, don't do that
*/
SDL_Texture*	CreateTexture	(const std::string& rPath);
bool		QuadVsQuad	(const SDL_Rect& rQuad1, const SDL_Rect& rQuad2);
float		Interpolate	(const float Start, const float End, const float Fraction);
bool		InitializeSDL	(void);
void		DeinitializeSDL	(void);
bool		Create		(void);
void		Destroy		(void);
void		HandleEvents	(void);
void		Update		(void);
void		Render		(void);

//////////////////////////////////////////////////////////////////////////

// The different states the player can be in
enum EPlayerState
{
	PLAYER_STILL = 0,
	PLAYER_MOVING_LEFT,
	PLAYER_MOVING_RIGHT,
	NUM_PLAYER_STATES
};

// The different states the camera can be in
enum ECameraState
{
	CAMERA_STILL = 0,
	CAMERA_SCROLLING_LEFT,
	CAMERA_SCROLLING_RIGHT,
	NUM_CAMERA_STATES
};

/**
* Just a simple way of collection the player's different variables
* Tweak Velocity to get a faster/slower walking speed
*/ 
struct SPlayer
{
	float		Width		= 30.0f;
	float		Height		= 60.0f;
	float		XPosition	= (WINDOW_WIDTH * 0.5f) - (Width * 0.5f);
	float		PrevXPosition	= XPosition;
	float		Velocity	= 200.0f;
	EPlayerState	State		= PLAYER_STILL;
};

/**
* Just a simple way of collection the camera's different variables
* Tweak Velocity to get a faster/slower camera scrolling speed
*/ 
struct SCamera
{
	float		XPosition	= 0.0f;
	float		PrevXPosition	= XPosition;
	float		Velocity	= 400.0f;
	ECameraState	State		= CAMERA_STILL;
	SDL_Rect	Quad;
};

// Just a simple way of collection a texture's different variables
struct STexture
{
	SDL_Texture*	pTexture = nullptr;
	SDL_Rect	Quad;
};

SDL_Window*	pWindow	  = nullptr;
SDL_Renderer*	pRenderer = nullptr;

SDL_Event Event;

double DeltaTime = 0.0;
double OldTime	 = 0.0;
double NewTime	 = 0.0;

// How near the player has to be the left- or right side of the window for the camera scrolling to start
float DistanceToWindowEdge = 70.0f;

bool Running = true;

// And here we actually use the structures declared above
SPlayer	 Player;
SCamera	 Camera;
STexture BackgroundTexture[NUM_SECTIONS];

//////////////////////////////////////////////////////////////////////////

SDL_Texture* CreateTexture(const std::string& rPath)
{
	SDL_Surface* pSurface = IMG_Load(rPath.c_str());

	if(!pSurface)
	{
		std::cout << "Failed to create SDL surface. " << IMG_GetError() << std::endl;

		return nullptr;
	}

	SDL_Texture* pTexture = SDL_CreateTextureFromSurface(pRenderer, pSurface);

	SDL_FreeSurface(pSurface);
	pSurface = nullptr;

	if(!pTexture)
	{
		std::cout << "Failed to create SDL texture. " << SDL_GetError() << std::endl;

		return nullptr;
	}

	return pTexture;
}

bool QuadVsQuad(const SDL_Rect& rQuad1, const SDL_Rect& rQuad2)
{
	// Quad vs quad intersection test
	if(rQuad1.x		< rQuad2.x + rQuad2.w	&&
	   rQuad1.x + rQuad1.w	> rQuad2.x		&&
	   rQuad1.y		< rQuad2.y + rQuad2.h	&&
	   rQuad1.y + rQuad1.h	> rQuad2.y)
	   return true;

	else
		return false;
}

bool InitializeSDL()
{
	// Initialize SDL
	if(SDL_Init(SDL_INIT_EVERYTHING) < 0)
	{
		std::cout << "Failed to initialize SDL. " << SDL_GetError() << std::endl;

		return false;
	}

	// Initialize SDL_Image
	if(IMG_Init(IMG_INIT_PNG) == 0)
	{
		std::cout << "Failed to initialize SDL_Image. " << IMG_GetError() << std::endl;

		return false;
	}

	// Set render scale quality
	if(SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1") == SDL_FALSE)
		std::cout << "Failed to set the render scale quality. " << SDL_GetError() << std::endl;

	// Create the SDL window
	pWindow = SDL_CreateWindow("SDL Tutorial", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_SHOWN);

	if(!pWindow)
	{
		std::cout << "Failed to create the SDL window. " << SDL_GetError() << std::endl;

		return false;
	}

	// Create the SDL renderer
	pRenderer = SDL_CreateRenderer(pWindow, -1, SDL_RENDERER_ACCELERATED);

	if(!pRenderer)
	{
		std::cout << "Failed to create the SDL renderer. " << SDL_GetError() << std::endl;
		
		return false;
	}

	// Initialize the render draw color
	if(SDL_SetRenderDrawColor(pRenderer, 0xFF, 0xFF, 0xFF, 0xFF) == -1)
		std::cout << "Failed to set the SDL render draw color. " << SDL_GetError() << std::endl;

	return true;
}

void DeinitializeSDL()
{
	if(pRenderer)
	{
		SDL_DestroyRenderer(pRenderer);
		pRenderer = nullptr;
	}

	if(pWindow)
	{
		SDL_DestroyWindow(pWindow);
		pWindow = nullptr;
	}
	
	IMG_Quit();
	SDL_Quit();
}

bool Create(void)
{
	// The creation below can be made in a for loop, but I don't wanna cause confusion so I'll just do it the manual way

	// Change the file path to work with your map structure
	BackgroundTexture[0].pTexture = CreateTexture("Data/Textures/bg.png");
	BackgroundTexture[1].pTexture = CreateTexture("Data/Textures/bg2.png");
	BackgroundTexture[2].pTexture = CreateTexture("Data/Textures/bg3.png");

	if(!BackgroundTexture[0].pTexture) return false;
	if(!BackgroundTexture[1].pTexture) return false;
	if(!BackgroundTexture[2].pTexture) return false;

	int TextureWidth  = 0;
	int TextureHeight = 0;

	SDL_QueryTexture(BackgroundTexture[0].pTexture, nullptr, nullptr, &TextureWidth, &TextureHeight);

	// Position each texture quad next to each other, will be used for culling later
	BackgroundTexture[0].Quad = {TextureWidth * 0, 0, TextureWidth, TextureHeight};
	BackgroundTexture[1].Quad = {TextureWidth * 1, 0, TextureWidth, TextureHeight};
	BackgroundTexture[2].Quad = {TextureWidth * 2, 0, TextureWidth, TextureHeight};

	// Also used for culling later
	Camera.Quad = {0, 0, WINDOW_WIDTH, WINDOW_HEIGHT};

	return true;
}

void Destroy(void)
{
	for(int i = 0; i < NUM_SECTIONS; ++i)
	{
		if(BackgroundTexture[i].pTexture)
		{
			SDL_DestroyTexture(BackgroundTexture[i].pTexture);
			BackgroundTexture[i].pTexture = nullptr;
		}
	}
}

void HandleEvents(void)
{
	while(SDL_PollEvent(&Event))
	{
		switch(Event.type)
		{
			// The user has pressed the X in the upper right corner of the window
			case SDL_QUIT:
			{
				Running = false;

				break;
			}

			// The user is holding down a key on the keyboard
			case SDL_KEYDOWN:
			{
				switch(Event.key.keysym.sym)
				{
					case SDLK_ESCAPE:
					{
						Running = false;

						break;
					}

					case SDLK_LEFT:
					{
						if(Camera.State == CAMERA_STILL)
							Player.State = PLAYER_MOVING_LEFT;

						break;
					}

					case SDLK_RIGHT:
					{
						if(Camera.State == CAMERA_STILL)
							Player.State = PLAYER_MOVING_RIGHT;

						break;
					}

					default:
						break;
				}

				break;
			}

			// The user is releasing a key on the keyboard
			case SDL_KEYUP:
			{
				switch(Event.key.keysym.sym)
				{
					case SDLK_LEFT:
					{
						if(Player.State == PLAYER_MOVING_LEFT)
							Player.State = PLAYER_STILL;

						break;
					}

					case SDLK_RIGHT:
					{
						if(Player.State == PLAYER_MOVING_RIGHT)
							Player.State = PLAYER_STILL;

						break;
					}

					default:
						break;
				}
			}

			default:
				break;
		}
	}
}

void Update(void)
{
	// Handle events from the keyboard
	HandleEvents();

	// Calculate the deltatime
	NewTime	  = SDL_GetTicks();
	DeltaTime = (NewTime - OldTime) / 1000.0;
	OldTime	  = NewTime;

	//////////////////////////////////////////////////////////////////////////

	// Whenever the player is moving
	if(Player.State != PLAYER_STILL)
	{
		// Just a fancy way of saying: "If the player state is set to PLAYER_MOVING_LEFT, move the player to the left. Else, move the player to the right"
		Player.XPosition += (Player.State == PLAYER_MOVING_LEFT ? -Player.Velocity : Player.Velocity) * (float)DeltaTime;

		// If the player has reached the left side of the window
		if(Player.XPosition <= (Camera.XPosition + DistanceToWindowEdge))
		{
			// The camera is only allowed to scroll to the left if it's not at the start of the level
			if(Camera.XPosition > 0.0f)
			{
				Camera.State = CAMERA_SCROLLING_LEFT;

				Player.State = PLAYER_STILL;

				Player.XPosition     = Camera.XPosition + DistanceToWindowEdge;
				Player.PrevXPosition = Player.XPosition;
			}
		}

		// If the player has reached the right side of the window
		else if(Player.XPosition + Player.Width > (Camera.XPosition + WINDOW_WIDTH) - DistanceToWindowEdge)
		{
			// The camera is only allowed to scroll to the right if it's not at the end of the level
			if(Camera.XPosition < (LEVEL_WIDTH - WINDOW_WIDTH))
			{
				Camera.State = CAMERA_SCROLLING_RIGHT;

				Player.State = PLAYER_STILL;

				Player.XPosition     = (Camera.XPosition + SECTION_WIDTH) - Player.Width - DistanceToWindowEdge;
				Player.PrevXPosition = Player.XPosition;
			}
		}

		// Make sure that the player can't go outside the left- and the right side of the level
		     if(Player.XPosition < 0.0f)			Player.XPosition = 0.0f;
		else if(Player.XPosition > LEVEL_WIDTH - Player.Width)	Player.XPosition = LEVEL_WIDTH - Player.Width;
	}

	// Whenever the camera is scrolling
	if(Camera.State != CAMERA_STILL)
	{
		const bool ScrollingLeft = (Camera.State == CAMERA_SCROLLING_LEFT);

		/*
		By making the player walk a bit by itself during the scrolling, the player won't end up outside the window edges when the scrolling is finished
		The walking direction depends on the direction the camera is scrolling
		The walking speed is based on the camera's velocity - a bigger division value (in this case it's 0.25f) will make the player walk faster,
		which means the player will end up further away from the window edge when the scrolling is finished
		A smaller division value does the opposite
		Note: a value smaller than 0.22f will, with the current player width, cause an endless scrolling loop bug
		*/
		Player.XPosition += (ScrollingLeft ? -(Camera.Velocity * 0.25f) : (Camera.Velocity * 0.25f)) * (float)DeltaTime;

		//////////////////////////////////////////////////////////////////////////

		// See the player movement code above for an explanation on how this works
		Camera.XPosition += ((ScrollingLeft ? -Camera.Velocity : Camera.Velocity) * (float)DeltaTime);

		// Check the distance between the camera's current- and previous position
		// The calculation result depends on the direction the camera is scrolling in
		const float Distance = (ScrollingLeft ? (Camera.PrevXPosition - Camera.XPosition) : (Camera.XPosition - Camera.PrevXPosition));

		// If the camera has scrolled a full section width
		if(Distance >= SECTION_WIDTH)
		{
			// Calculate the camera's final position
			// Again, this calculation result depends on the direction the camera is scrolling in
			const float FinalCameraPosition = (ScrollingLeft ? (Camera.PrevXPosition - SECTION_WIDTH) : (Camera.PrevXPosition + SECTION_WIDTH));

			Camera.XPosition	= FinalCameraPosition;
			Camera.PrevXPosition	= FinalCameraPosition;
			Camera.State		= CAMERA_STILL;
		}
	}
}

void Render(void)
{
	// Clear the current render target
	SDL_RenderClear(pRenderer);

	//////////////////////////////////////////////////////////////////////////

	for(int i = 0; i < NUM_SECTIONS; ++i)
	{
		if(BackgroundTexture[i].pTexture)
		{
			// Create a temporary texture quad that is relative to the camera
			const SDL_Rect TextureQuad = {	BackgroundTexture[i].Quad.x - (int)Camera.XPosition,
							BackgroundTexture[i].Quad.y,
							BackgroundTexture[i].Quad.w,
							BackgroundTexture[i].Quad.h};

			/*
			Easy and fast culling
			Check if the texture quad is inside the camera quad (i.e, if the texture is in view)
			If it's not in view, just continue without rendering the texture
			During camera scrolling, 2 textures will be visible at the same time
			When no scrolling occurs, only 1 texture will be visible and rendered
			*/
			if(!QuadVsQuad(Camera.Quad, TextureQuad))
				continue;

			SDL_RenderCopy(pRenderer, BackgroundTexture[i].pTexture, nullptr, &TextureQuad);
		}
	}

	// Render the player as a quad
	const SDL_Rect PlayerQuad = {(int)(Player.XPosition - Camera.XPosition), WINDOW_HEIGHT - (int)Player.Height, (int)Player.Width, (int)Player.Height};
	SDL_SetRenderDrawColor(pRenderer, 255, 255, 255, SDL_ALPHA_OPAQUE);
	SDL_RenderFillRect(pRenderer, &PlayerQuad);
	SDL_SetRenderDrawColor(pRenderer, 255, 255, 255, SDL_ALPHA_OPAQUE);

	//////////////////////////////////////////////////////////////////////////

	// Update the screen
	SDL_RenderPresent(pRenderer);
}

int main(int argc, char *argv[])
{
	// Initialize SDL, create the SDL window, the SDL renderer and so on
	if(InitializeSDL())
	{
		// Create the textures and other data
		if(Create())
		{
			while(Running)
			{
				Update();
				Render();

				// Let the OS do something else for a short while to limit the processor usage
				SDL_Delay(1);
			}

			// Destroy all created data
			Destroy();
		}

		// Destroy SDL, the SDL window and the SDL renderer
		DeinitializeSDL();
	}

	return 0;
}

#16

Many thanks Naith. It looks great and works perfectly!

I’m going through the code now (1.44am here), I let you know tomorrow my thoughts.

Thanks again!


#17

Hi Neith,

I really enjoyed your style of coding and thinking, it’s very well written and I hope one day to reach a similar level, I’m still a beginner atm. :confused:

Everything works, I tweaked the values, added more backgrounds, works flawlessly!

I got how it works but I missed few important bits.

I kindly need to understand how the following works:

I got that when we scroll left we apply -Camera.Velocity (and if we scroll right we apply Camera.Velocity) to Camera.XPosition
and I got that Camera.XPosition before the actual scrolling in the first level is 0, second level is 800, 3 level is 1600, and so on …
----------------------------------------------------------------------------------------------------------------------------
Camera.XPosition += ((ScrollingLeft ? -Camera.Velocity : Camera.Velocity) * (float)DeltaTime);

The thing is we are applying Camera.Velocity (that is 400.0f or -400.0f) to Camera.XPosition (positive or negative…) BUT I was checking with

cout << "Camera.XPosition is " << Camera.XPosition << endl;

And I realised that Camera.XPosition in the first level when we scroll right into the second level keeps increasing from 0 to 800
(example 1,2,3…etc… until 800)
when we scroll from second level into third it increases from 800 to 1600 (801, 802…etc until 1600)
Where does this happens in the code? Also where does the code mention the increasing value contained in Camera.XPosition has to stop to 800 or 1600 and so on ?

Also if I modify by increasing Camera.Velocity variable the above happens anyway (faster),
That leaves me a bit confused, I mean if Velocity is applied to Camera.XPosition, by increasing it of 100.0f for example does not mean that we should move Camera.XPosition of 100 pixel on the right from the current position (because we used +=) ???

I just kindly need to understand where all this increasing numbers (0,1,2,…800/ 801,802…1600/etc…) and their limits (example not increase more than 800 after first scrolling on the right, not increase more than 1600 after second scrolling on the right) happen to be in the code.

——————————————————————————————————————————
2)
I guess has something to do with the following or delta time because I did not get how it works:
I mean in the code below when player scrolls right THE FIST TIME 400.0.f is applied to Camera.XPosition so I guess we do not check the condition ( if(Distance >= SECTION_WIDTH) )the first time being section_width 800…but not sure how it works really, please I need a easy clarification here :slight_smile:

CODE:

[details=Summary] const float Distance = (ScrollingLeft ? (Camera.PrevXPosition - Camera.XPosition) : (Camera.XPosition - Camera.PrevXPosition));
// If the camera has scrolled a full section width that is 800
if(Distance >= SECTION_WIDTH)
{
// Calculate the camera’s final position
// Again, this calculation result depends on the direction the camera is scrolling in.

                    const float FinalCameraPosition = (ScrollingLeft ? (Camera.PrevXPosition - SECTION_WIDTH) : (Camera.PrevXPosition + SECTION_WIDTH));
                    
                   Camera.XPosition		= FinalCameraPosition;
                   Camera.PrevXPosition	= FinalCameraPosition;
                   Camera.State			= CAMERA_STILL;
                }[/details]

Please can you give me an example of how the following variables works with assigned values (just to understand if I got the order of their assigned values right ). When do we use them?

    NewTime		= SDL_GetTicks(); //time in msecs running since this call
    DeltaTime	= (NewTime - OldTime) / 1000.0; //convert msecs in seconds
    OldTime		= NewTime;

Also I noticed that for the in the player structure you wrote:

float XPosition = (WINDOW_WIDTH * 0.5f) - (Width * 0.5f);

instead of for example:
float XPosition = 400;

I guess you wrote like that because it’s relative to the word? Please let me know

Camera.Quad.x and Camera.XPosition:

still new in using these so please explain where we use the first and the latter in a simple example they look similar to me, unless has something to do with relative positions… probably I’m not thinking abstractly.

Sorry for the long question, I got all the other things but these above are the missing pieces of the puzzle :slight_smile:

Many thanks again!!!


#18

Thank you for the kind words.

There’s a few questions you’ve got there, and I’m gonna try answering them all.

[details=Click here to view/hide the answers]1 + 2:
The code which controls how far the camera scrolls each time is contained in the distance check code, inside the if(Camera.State != CAMERA_STILL) code block (in the Update function).
The code if(Distance >= SECTION_WIDTH) checks if the camera has scrolled a full section width (or more) and if that’s the case, sets the camera’s final position and ends the camera scrolling by changing the camera state from CAMERA_SCROLLING_LEFT or CAMERA_SCROLLING_RIGHT to CAMERA_STILL. By changing the camera state to CAMERA_STILL, the scrolling stops and the camera scrolling code block isn’t entered again, until the camera scrolling is supposed to start again, which is whenever the player reach the left or right edge of the window.
So when the program first start, Camera.XPosition and Camera.PrevXPosition is both set to 0.0f.
When the scrolling occur, Camera.XPosition scrolls forward (to the right). Whenever Camera.XPosition is >= 800.0f, the if(Distance >= SECTION_WIDTH) check is true, the block is entered and Camera.XPosition and > Camera.PrevXPosition is set to their final position. The reason why Camera.PrevXPosition even exist is that we need to know where the camera started from when the scrolling starts, so that we can make a distance check.
It’s a bit hard to explain but I hope you’ll understand.

3.
The three variables (all double’s) is used to calculate the deltatime, which is the time between the last frame and the current frame. By multiplying all the movement code (for example when applying velocity to Player.XPosition), we get a constant speed on different computers, since computers often operate in different speeds, different processor speeds and so on.
SDL_GetTicks() returns the time in milliseconds since the last time the function was called.
The DeltaTime is calculated based on that, and by dividing the result with 1000.0, I get the deltatime in seconds, instead of in milliseconds. That’s just a personal preference though. Some people like to work in milliseconds, but I’m just not one of them.
Here you can read up on how deltatime works and why it’s important: https://en.wikipedia.org/wiki/Delta_timing

4.
The reason why I’ve written float XPosition = (WINDOW_WIDTH * 0.5f) - (Width * 0.5f); is basicly so the player is positioned in the middle of the window, regardless of the window width. So even if I change the window width to 1280 and change the player’s width to a higher/lower value, the player will still start in the middle of the window at startup.
But like you said, I could have written 400.0f as the start position, but then if I decide to change the window width to a higher value, I would have to recalculate where the middle position of the window is, based on the window width and the player’s width. Now I don’t have to care about that. :slight_smile:

5.
Camera.Quad is a SDL_Rect structure, which contains int variables (whole numbers, so no decimals).
Camera.XPosition is a float value (i.e, a decimal number). By using a float variable to position and move an object, I get much more precision and can move in subpixel values. Then, when the rendering takes place, and I call SDL_RenderCopy(), I cast the float to a int value to get the final position on the screen, since SDL works in whole numbers when it positions objects on the screen, and not in decimal numbers. Check the player rendering code to see the casting taking place.
If I would have used the Player.Quad.x directly and translated/moved it with the Player.Velocity, I would have ended up with a very jerky player movement.[/details]


#19

Hi Naith many thanks for your answers,
Everything is more than clear but I still have a doubt about the first question:

The only doubt left is the following:

Let’s assume I have 5 levels, Camera.XPosition gets assigned values from FinalCameraPosition that will be set according to the level we are to 800, 1600, 2400, 3200 (values set to avoid further scrolling, because once we reach them my backgrounds will be centered and therefore camera will be set to still) - I got this and how the if(Distance >= SECTION_WIDTH) works.

What I do not get is how to get there from:

if the camera is scrolling and therefore not still…

    Camera.XPosition += ((ScrollingLeft ? -Camera.Velocity : Camera.Velocity) * (float)DeltaTime);

we apply a value to its current position (in this case let’s assume Camera.Velocity is 100.0f * DeltaTime)

That tells me that (assuming we are in level 1 where default Camera.XPosition is set to 0 by default) we move the camera.XPosition of only 100 pixels plus obviously something more 'cause there is the delta time variable…
NOW for a full camera scrolling (to center the backgrounds) we need to move 800 /-800 pixels.

Question 1: how do we make up for the missing 700 pixels (according my example above)?

First time we run the program PrevXPosition is 0, so in the case above I would have only a value of -100.0f or 100 .0f * DeltaTime assigned to Distance according the following code:

const float Distance = (ScrollingLeft ? (Camera.PrevXPosition - Camera.XPosition) : (Camera.XPosition - Camera.PrevXPosition));   

Also to get into the following block that will stop the scrolling according to the final camera position…

if(Distance >= SECTION_WIDTH)

…Distance should be bigger or equal to 800 (SECTION_WIDTH is set to 800), how do I get there if I have a Camera.Velocity of 100.0f?

I mean as soon as I run the program and I press Left or Right (when Player is on the end left/ end right side of the screen) it is evident that Camera.XPosition gets quickly assigned 800, 1600, 2400, 3200 and 0 by pressing Left in first screen and not other values, and from what I can see that happens only in if(Distance >= SECTION_WIDTH), does that means that…

Question 2 : (Distance >= SECTION_WIDTH) should always be true in every iteration of
while(Running) {Update( );… ???
If so how?

Please let me know and many thanks once again for your help!


#20

Not entirely sure what you mean by the “missing 700 pixels”.
When the camera scrolling occur, Camera.XPosition will be increased/decreased until the camera has reached it’s correct destination. If the Camera.Velocity is set very low, this process can take a very long time. The amount Camera.XPosition increases/decreases every second depends on the deltatime variable, because, if the previous frame took a bit longer, the program needs to compensate for the longer frame, deltatime will be a higher value and therefore the velocity will be a bit higher than the previous frame and the scrolling will go a bit faster. That’s the beauty of deltatime.
The distance check being made will always be correct, because when the camera scrolling starts, the program knows where the camera’s start position is (i.e, stored inside Camera.PrevXPosition). Then all it has to do is start the scrolling of the camera by increasing/decreasing Camera.XPosition, and each frame this occur, a distance check is made. Since Camera.PrevXPosition is static, and never moves during the scrolling, the distance check will always be (somewhat) correct and it’s easy to calculate the scrolling distance.
The if(Distance >= SECTION_WIDTH) needs to be checked every frame, but only when the scrolling actually occur, and regarding your second question - I don’t see a reason checking it when the camera is still and I also can’t see a reason why if(Distance >= SECTION_WIDTH) would ever be true if the camera is still, since the distance between Camera.XPosition and Camera.PrevXPosition will be 0.
As soon as the scrolling is finished (i.e, the if statement above is true), both Camera.XPosition and Camera.PrevXPosition is set to the same value, because when the next camera scrolling process should occur, the program knows the camera’s start position, which again is stored inside Camera.PrevXPosition.

Edit: and also - the scrolling is actually only occuring if the camera is allowed to scroll. If, for example, the camera is positioned at 0.0f (so at the beginning of the level) and the player reach the far left side of the window, the scrolling to the left-process won’t start, since the camera is not allowed to move lower than 0.0f.

Sorry I don’t have a better answer than this.
It’s sometimes hard to explain a certain code piece for someone else. It’s all clear, easy and logic inside my head, and I know the code in- and out, but as soon as things should be explained, it’s suddenly becomes hard.