#include #include #include #include /** * 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 (1920) #define WINDOW_HEIGHT (1080) #define SECTION_WIDTH (WINDOW_WIDTH) #define NUM_SECTIONS (54) #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 = 10.0f; float Height = 10.0f; float XPosition = 0;// (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 = 50.0f;//70 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("crop.tif"); BackgroundTexture[1].pTexture = CreateTexture("crop1920.tif"); BackgroundTexture[2].pTexture = CreateTexture("crop3000.tif"); 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)) { if (Camera.State == CAMERA_STILL) { Player.State = PLAYER_MOVING_RIGHT; break; } if (Player.State == PLAYER_MOVING_RIGHT) { Player.State = PLAYER_STILL; break; } // 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; }