2d screen scroll/ 2d camera movement?

Hey guys, I’m currently working on a project with sdl and nearly complete, and the only thing missing, is the camera. There is a tutorial from lazyfoo, on scrolling but got stuck. I was wondering, if someone on here knows exactly knows how to achieve a simple 2d camera movement without the help of opengll.

There are other ways to think about it, but this is what makes sense to me.

Top-Down Scenes or Three Quarters view: Make the world big. Set the player in the middle of the screen and move the world instead of moving the player.
In other words, worldSpeed = -1 * playerSpeed.

  • You can do a little boarder-checking to allow the player to move to the edge of the screen if the world is about to meet the edge of the screen.
  • If the player is fast then I let the world’s speed lag a bit when the player first starts moving, but the world generally catches up with them. (It gives the player a kind of rubber-band effect so they don’t feel so tied to the center of the screen, but may affect player accuracy).

Side-Scrollers: Almost the same idea as the previous option, but you might also implement parallax to add the illusion of depth. (You can parallax both foreground and background elements).

1 Like

Alright. But do i need to put in a texture for the camera to scroll around the texture?

To be fair, I’m not totally sure I understand that question.
The short answer is no, you don’t need a camera texture. It is perfectly normal to SDL_RenderCopy the textures to the screen at their respective positions. If the positions are offscreen then they do not get drawn.
For scrollers you generally draw things in layers, you have a large gradient image as your sky background, then some long image of hills that parallax (or multiple images of hills, like super mario bros), and then the path that the player follows, and enemies/platforms and things to interact with.

But there is another option, one I don’t recommend unless you have a need for it.
This option is to render the entire world/scene onto a target texture, then present that final texture to the screen. This lets you take advantage of both srcRect and destRect in SDL_RenderCopy to scale/zoom into the world scene and move it like you were in control of a camera. But it is more difficult to pull off correctly, and you’ll get really weird behavior if you allow the srcRect to go past the boarders. There is also a moderate hit to the processing time. I generally recommend avoiding this option unless you have some time to experiment. It works much smoother in SDL3 because you have integer rounding in SDL2.

1 Like

Alright i have rect called camera and i got the screen width and screen height which is:
SDL_Rect camera ={0,0,640, 480}.
My question is, based off what you said, i dont want to use a texture for my camera scrolling because the border will pass and the image will start stretching. Do i use the viewport for my camera created from that sdl rect i gave?

When I did a scrolling camera for a 3 day game jam, I had a bit of slack in the middle of the screen so the user can turn back just a little bit without moving the camera. It made things less shaky

I have no idea how you’re rendering your world but iirc there isn’t a problem with starting with a negative x,y. I usually render game than UI. I’m not sure what you mean by texture and camera. You can draw past the right side and it shouldn’t stretch anything. Although I remember I was bit by one of the drawing functions modifying the rect I gave it. Maybe your width or height changed because you touched the border and will be fine if you reset the value?

Did you use the viewport to do your camera?

I don’t usually deal with a camera at all, that data is already contained in the position of objects as the game progresses. If the player moves forward by x, then other objects are moved backward by x. If they are within the screen’s drawing range then they get drawn. If you want to give the player a peek-ahead option, then move everything left including the player character.

However, I don’t think it’s too harmful to track the camera if it helps the programmer to get the job done.
I went ahead and wrote a simple pick-up-stick game where the user can move using the arrow keys, but can also peek ahead in search of the green stick using the keys A and D:

#include <SDL2/SDL.h>
#include <stdlib.h>
#include <time.h>
#include <vector>

SDL_Rect camera = {0, 0, 640, 480};

class Object
{
	public:
	Object(SDL_Renderer * screen)
	{
		renderer = screen;
		color = {200, 90, 90, 255};
		pos = {-200 +rand() % 4000, -50 + rand() % 600, 40, 90};
	}
	void move(float x, float y)
	{
		pos.x += x;
		pos.y += y;
	}
	void draw()
	{
		SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
		SDL_Rect offset = {pos.x - camera.x, pos.y - camera.y, pos.w, pos.h};
		SDL_RenderDrawRect(renderer, &offset);
	}
	public:
	SDL_Renderer * renderer;
	SDL_Rect pos;
	SDL_Color color;
};
void moveCamera(float x, float y)
{
	camera.x += x;
	camera.y += y;
}

int main()
{
	SDL_Init(SDL_INIT_EVERYTHING);
	SDL_Window * window = SDL_CreateWindow("Title", 10, 10, 640, 480, SDL_WINDOW_SHOWN);
	SDL_Renderer * screen = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC);
	srand(time(NULL));

	Object player(screen);
	player.pos = {320, 200, 50, 50};
	player.color = {10,10,  255, 255};

	Object goal(screen);
	goal.color = {10, 255, 10, 255};

	std::vector <Object *> world;
	for(int i = 0; i < 300; i ++)
	{
		world.push_back(new Object(screen));
	}

	// flags
	bool left = false;
	bool right = false;
	bool up = false;
	bool down = false;
	bool key_a = false;
	bool key_d = false;
	bool run = true;
	while(run)
	{
		SDL_Event ev;
		while(SDL_PollEvent(&ev))
		{
			switch(ev.type)
			{
				case SDL_KEYDOWN:
					switch(ev.key.keysym.sym)
					{
						case SDLK_LEFT:
							left = true;
							break;
						case SDLK_RIGHT:
							right = true;
							break;
						case SDLK_UP:
							up = true;
							break;
						case SDLK_DOWN:
							down = true;
							break;
						case SDLK_a:
							key_a = true;
							break;
						case SDLK_d:
							key_d = true;
							break;
						case SDLK_ESCAPE:
							run = false;
							break;
					}
					break;
				case SDL_KEYUP:
					switch(ev.key.keysym.sym)
					{
						case SDLK_LEFT:
							left = false;
							break;
						case SDLK_RIGHT:
							right = false;
							break;
						case SDLK_UP:
							up = false;
							break;
						case SDLK_DOWN:
							down = false;
							break;
						case SDLK_a:
							key_a = false;
							break;
						case SDLK_d:
							key_d = false;
							break;
					}
					break;
				case SDL_QUIT:
					run = false;
					break;
			}
		}
		// update camera and positions
		if(left)
		{
			player.move(-2, 0);
			moveCamera(-2, 0);
		}
		if(right)
		{
			player.move(2, 0);
			moveCamera(2, 0);
		}
		if(up)
		{
			player.move(0, -1);
			moveCamera(0, -1);
		}
		if(down)
		{
			player.move(0, 1);
			moveCamera(0, 1);
		}
		if(key_a)
		{
			moveCamera(-8, 0);
		}
		if(key_d)
		{
			moveCamera(8, 0);
		}
		SDL_SetRenderDrawColor(screen, 10, 10, 10, 255);
		SDL_RenderClear(screen);
		size_t len = world.size();
		for(size_t i = 0; i < len; i ++)
		{
			world[i]->draw();
		}
		player.draw();
		goal.draw();
		SDL_RenderPresent(screen);
		if(SDL_HasIntersection(&(player.pos), &goal.pos))
		{
			SDL_Log("You Win!");
			run = false;
		}
	}
	SDL_Quit();
}
1 Like

You might also check out SDL_RenderSetLogicalSize() if you want to implement zooming in and out. It could be set using the width and height of the camera rectangle.
Edit:
Sorry to post such similar code, but I hadn’t tested that function out yet, it was fun to implement, so here’s the code with the keys W and S controlling zoom level:

#include <SDL2/SDL.h>
#include <stdlib.h>
#include <time.h>
#include <vector>

SDL_Rect camera = {0, 0, 640, 480};;

class Object
{
	public:
	Object(SDL_Renderer * screen)
	{
		renderer = screen;
		color = {200, 90, 90, 255};
		pos = {-200 +rand() % 4000, -50 + rand() % 600, 40, 90};
	}
	void move(float x, float y)
	{
		pos.x += x;
		pos.y += y;
	}
	void draw()
	{
		SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
		SDL_Rect offset = {pos.x - camera.x, pos.y - camera.y, pos.w, pos.h};
		SDL_RenderFillRect(renderer, &offset);
	}
	public:
	SDL_Renderer * renderer;
	SDL_Rect pos;
	SDL_Color color;
};
void moveCamera(float x, float y)
{
	camera.x += x;
	camera.y += y;
}

int main()
{
	SDL_Init(SDL_INIT_EVERYTHING);
	SDL_Window * window = SDL_CreateWindow("Title", 10, 10, 640, 480, SDL_WINDOW_SHOWN);
	SDL_Renderer * screen = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC);
	srand(time(NULL));

	Object player(screen);
	player.pos = {320, 200, 50, 50};
	player.color = {10,10,  255, 255};

	Object goal(screen);
	goal.color = {10, 255, 10, 255};

	std::vector <Object *> world;
	for(int i = 0; i < 300; i ++)
	{
		world.push_back(new Object(screen));
	}

	// flags
	bool left = false;
	bool right = false;
	bool up = false;
	bool down = false;
	bool key_a = false;
	bool key_d = false;
	bool key_w = false;
	bool key_s = false;
	bool run = true;
	while(run)
	{
		SDL_Event ev;
		while(SDL_PollEvent(&ev))
		{
			switch(ev.type)
			{
				case SDL_KEYDOWN:
					switch(ev.key.keysym.sym)
					{
						case SDLK_LEFT:
							left = true;
							break;
						case SDLK_RIGHT:
							right = true;
							break;
						case SDLK_UP:
							up = true;
							break;
						case SDLK_DOWN:
							down = true;
							break;
						case SDLK_a:
							key_a = true;
							break;
						case SDLK_d:
							key_d = true;
							break;
						case SDLK_w:
							key_w = true;
							break;
						case SDLK_s:
							key_s = true;
							break;
						case SDLK_ESCAPE:
							run = false;
							break;
					}
					break;
				case SDL_KEYUP:
					switch(ev.key.keysym.sym)
					{
						case SDLK_LEFT:
							left = false;
							break;
						case SDLK_RIGHT:
							right = false;
							break;
						case SDLK_UP:
							up = false;
							break;
						case SDLK_DOWN:
							down = false;
							break;
						case SDLK_a:
							key_a = false;
							break;
						case SDLK_d:
							key_d = false;
							break;
						case SDLK_w:
							key_w = false;
							break;
						case SDLK_s:
							key_s = false;
							break;
					}
					break;
				case SDL_QUIT:
					run = false;
					break;
			}
		}
		// update camera
		if(left)
		{
			player.move(-2, 0);
			moveCamera(-2, 0);
		}
		if(right)
		{
			player.move(2, 0);
			moveCamera(2, 0);
		}
		if(up)
		{
			player.move(0, -1);
			moveCamera(0, -1);
		}
		if(down)
		{
			player.move(0, 1);
			moveCamera(0, 1);
		}
		if(key_a)
		{
			moveCamera(-8, 0);
		}
		if(key_d)
		{
			moveCamera(8, 0);
		}
		if(key_w)
		{
			if(camera.w < 4000)
			{
				camera.w += 16;
				camera.h += 12;
			}
			else
			{
				camera.w = 4000;
				camera.h = 3000;
			}
			SDL_RenderSetLogicalSize(screen, camera.w, camera.h);
			camera.x = player.pos.x - camera.w/2;
			camera.y = player.pos.y - camera.h/2;
		}
		if(key_s)
		{
			// camera.w *= 0.99222223;
			// camera.h *= 0.9922223;
			camera.w -= 16;
			camera.h -= 12;
			if(camera.w < 320)
			{
				camera.w = 320;
				camera.h = 240;
			}
			SDL_RenderSetLogicalSize(screen, camera.w, camera.h);
			camera.x = player.pos.x - camera.w/2;
			camera.y = player.pos.y - camera.h/2;

		}
		SDL_SetRenderDrawColor(screen, 10, 100, 100, 255);
		SDL_RenderClear(screen);
		size_t len = world.size();
		for(size_t i = 0; i < len; i ++)
		{
			world[i]->draw();
		}
		player.draw();
		goal.draw();
		SDL_RenderPresent(screen);
		if(SDL_HasIntersection(&(player.pos), &goal.pos))
		{
			SDL_Log("You Win!");
			run = false;
		}
	}

	SDL_Quit();
}

Edit: I just made a recent change to avoid the horizontal bar issue when zooming. The original code had issues with integer rounding, and then I had the problem that I thought 1080p and 480p had the same aspect ratio. Anyhow, this version should demonstrate smooth scaling without the horizontal bars.

1 Like

Thnx for this! Ill try this to see if it works

The default. I picked a window size (like 1366x768 which is laptop resolution) and draw. I don’t change anything. I could use fill rect {-5, -5, 10, 10} to draw a 5x5 box on the top left of the screen

Thnx for sharing your code with me, I couldn’t get that working however. I like to share my code with you based on what I got, and hopefully I can try to get a camera working:

#include
#include “SDL2/SDL.h”

#pragma comment (lib, “sdl2.lib”)
#pragma comment (lib, “sdl2main.lib”)

int main(int argc, char * argv)
{
SDL_Init(SDL_INIT_VIDEO);
SDL_Window* window = SDL_CreateWindow(“Camera Test”, SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED, 640, 480, SDL_WINDOW_RESIZABLE);
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0);
SDL_Surface* bmpSurface = SDL_LoadBMP(“box.bmp”);
SDL_Surface* backGroundSurface = SDL_LoadBMP(“1280-720-sample.bmp”);
SDL_SetColorKey(bmpSurface, SDL_TRUE, SDL_MapRGB(bmpSurface->format, 0, 0, 0xFF));
SDL_Rect destRect = { 0,0,bmpSurface->w,bmpSurface->h };
SDL_Rect destRectBack{ 0,0,backGroundSurface->w,backGroundSurface->h };
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, bmpSurface);
SDL_Texture* backGroundTexture = SDL_CreateTextureFromSurface(renderer, backGroundSurface);
SDL_Rect camera = { 0,0,640,480 };

camera.x = destRect.x / 2 - 640 / 2;
camera.y = destRect.y / 2 - 480 / 2;

//destRect.x = 600;

if (camera.x < 0)
	camera.x = 0;
if (camera.y < 0)
	camera.y = 0;
if (camera.x > camera.w)
	camera.x = camera.w;
if (camera.y > camera.h)
	camera.y = camera.h;

SDL_Event event;
bool quit = false;

while (!quit)
{
	while (SDL_PollEvent(&event))
	{
		switch (event.type)
		{
		case SDL_QUIT:
			quit = true;
			break;
		case SDL_KEYDOWN:

			if (event.key.keysym.sym == SDLK_UP)
				destRect.x = 600;

			printf("Key: %s\n", SDL_GetKeyName(event.key.keysym.sym));
			if (event.key.keysym.sym == SDLK_ESCAPE)
				quit = true;
			break;
		default:
			break;
		}
	}
	
	SDL_RenderSetViewport(renderer, &camera);
	SDL_RenderCopy(renderer, backGroundTexture, NULL, &destRectBack);
	SDL_RenderCopy(renderer, texture, NULL, &destRect);
	SDL_RenderPresent(renderer);

}
SDL_FreeSurface(bmpSurface);
SDL_DestroyTexture(texture);
SDL_DestroyTexture(backGroundTexture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();

return 0;

}Preformatted text

Sorry I messed up the code here, i’m stll trying to figure out how to post code here properly. So what I’m trying to achieve here, is I created a 2d camera, and i’m trying to position that with my box.bmp within bmpSurface. The goal here is for my camera to follow the box.bmp regardless on where the box is going on the screen

When you post code here, select all the text of the code and hit the keys “control” and “e”.
Or hit the button at the top of the text box that looks like this </>

#include <iostream>
#include "SDL2/SDL.h"

#pragma comment (lib, "sdl2.lib")
#pragma comment (lib, "sdl2main.lib")

int main(int argc, char * argv[])
{
	SDL_Init(SDL_INIT_VIDEO);
	SDL_Window* window = SDL_CreateWindow("Camera Test", SDL_WINDOWPOS_UNDEFINED,
		SDL_WINDOWPOS_UNDEFINED, 640, 480, SDL_WINDOW_RESIZABLE);
	SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0);
	SDL_Surface* bmpSurface = SDL_LoadBMP("box.bmp");
	SDL_Surface* backGroundSurface = SDL_LoadBMP("1280-720-sample.bmp");
	SDL_SetColorKey(bmpSurface, SDL_TRUE, SDL_MapRGB(bmpSurface->format, 0, 0, 0xFF));
	SDL_Rect destRect = { 0,0,bmpSurface->w,bmpSurface->h };
	SDL_Rect destRectBack{ 0,0,backGroundSurface->w,backGroundSurface->h };
	SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, bmpSurface);
	SDL_Texture* backGroundTexture = SDL_CreateTextureFromSurface(renderer, backGroundSurface);
	SDL_Rect camera = { 0,0,640,480 };


	camera.x = destRect.x / 2 - 640 / 2;
	camera.y = destRect.y / 2 - 480 / 2;

	//destRect.x = 600;

	if (camera.x < 0)
		camera.x = 0;
	if (camera.y < 0)
		camera.y = 0;
	if (camera.x > camera.w)
		camera.x = camera.w;
	if (camera.y > camera.h)
		camera.y = camera.h;

	SDL_Event event;
	bool quit = false;

	while (!quit)
	{
		while (SDL_PollEvent(&event))
		{
			switch (event.type)
			{
			case SDL_QUIT:
				quit = true;
				break;
			case SDL_KEYDOWN:

				if (event.key.keysym.sym == SDLK_UP)
					destRect.x = 600;

				printf("Key: %s\n", SDL_GetKeyName(event.key.keysym.sym));
				if (event.key.keysym.sym == SDLK_ESCAPE)
					quit = true;
				break;
			default:
				break;
			}
		}
		
		SDL_RenderSetViewport(renderer, &camera);
		SDL_RenderCopy(renderer, backGroundTexture, NULL, &destRectBack);
		SDL_RenderCopy(renderer, texture, NULL, &destRect);
		SDL_RenderPresent(renderer);

	}
	SDL_FreeSurface(bmpSurface);
	SDL_DestroyTexture(texture);
	SDL_DestroyTexture(backGroundTexture);
	SDL_DestroyRenderer(renderer);
	SDL_DestroyWindow(window);
	SDL_Quit();

	return 0;
}

Alright I got it! Sorry if I took too long, but here’s my source code of what I got so far

Just so I understand what it is you want to achieve with your code. Is this your goal?

Camera

1 Like

Thats it!!! Im just having issues with the code I provided. Like i got the camera rect but i cant get the camera to scroll

You by any chance know how i can achieve just that?

I wrote the test application that’s shown in the above gif.
I will paste the code here later today, when I have access to the code.

The code doesn’t use SDL_RenderSetViewport but instead offsets all the rendered objects by the camera’s position. So no messing with changing the viewport etc.

This is one way of doing it, you have gotten some other suggestions by other users above. Which alternative you use in the end is up to you. I personally think the easiest (and most logic) way of doing it is to use the offset-alternative that my code example use.

1 Like