Advertisement

click and drag sprite

Started by January 11, 2025 12:23 AM
18 comments, last by NubDevice 4 hours, 46 minutes ago

All I want to do is click and drag a sprite

else if (event.type == SDL_MOUSEBUTTONDOWN||SDL_MOUSEMOTION) {
						if (event.button.button == SDL_BUTTON_LEFT) {
							int mouseX, mouseY;
							SDL_GetMouseState(&mouseX, &mouseY);
							cout << mouseX << " " << mouseY << endl;
							if (mouseX > 522 && mouseX < 567 && mouseY>394 && mouseY < 439)
							{
								SDL_Rect rect_two;
								rect_two.x = 567;
								rect_two.y = 394;
								SDL_BlitSurface(gHelloWorld_one, NULL, gScreenSurface, &rect_two);
								SDL_UpdateWindowSurface((gWindow));
							}
						}

pbivens67 said:
All I want to do is click and drag a sprite

A good exercise regarding realtime applications.

It's clear that you need some persistent data that lives not just for a single frame, or within a single function call.
Examples are the sprites position and the state of the mouse button.
SDL already does the latter for you, but you need to cara about the sprite. The easiest way would be to use a global variable. (Global variables are bad practice in general. It works for the simple test case, but if the program gets more complex global variables usually become a nightmare.)

Your code looks like you can click within some area, and if you do so it draws a rectangle over that area. But you can not yet move it, i guess.

I can give some example, but i don't know SDL, so you'd need to do some things differently, at the right callbacks SDL provides.

struct Sprite
{
	SDL_Rect rect;
	SDL_Color color; // idk if sdl has such type for color
};

Sprite globalSprite; // bad practice, but i know you love global variables and magic numbers, so...
Sprite *dragSprite = NULL; // if the user is dragging some sprite, this pointer will point to it. Otherwise pointer is zero.

void main()
{
	// set some initial state at application startup...
	
	globalSprite.rect.left = 522;
	//globalSprite.rect.right = ....
	globalSprite.color = white;
	
	// setup SDL, create Window, and all that...
	
	while (true) // some loop, not using callbacks for input, which SDL probably does
	{
		if (SDL_MouseButtonPressed()) // eventually grab the sprite and make it red
		{
			if (dragSprite == NULL && MouseCoordsInsideRectangle(globalSprite))
			{
				dragSprite = &globalSprite;
				dragSprite->color = RED;
			}
		}
		if (SDL_MouseButtonReleased()) // releasing the sprite - make it white again and set pointer to zero
		{
			if (dragSprite) dragSprite->color = WHITE;
			dragSprite = NULL;
		}
		if (dragSprite && SDL_MouseMoved()) // move the dragged Sprite
		{
			dragSprite->rect.left += SDL_MouseDeltaX();
			dragSprite->rect.up += SDL_MouseDeltaY();
			// assuming rect also has width and height which stays the same. 
			// If it has right and bottom we need to change that too ofc.
		}
		
		SDL_RenderRect (globalSprite.rect, globalSprite.color);
		
		SDL_Wait(60 fps);
	}
}

You need to figure out how to do this with SDL, but the code should help to see what global state is needed, and where in the loop this state is affected by what.

By using a pointer (not just a bool for example), the example can be easily extended to handle… multiple sprites! \:D/

Advertisement

I have worked on my code but it still does not click and drag a sprite

			bool isDragging = false;
			int startX, startY;
			int objectX = 0, objectY = 0;

			bool quit = false;
			while (quit == false) {
				SDL_Event event;
				while (SDL_PollEvent(&event)) {
					if (event.type == SDL_KEYDOWN)
					{
						Uint8 const* keys = SDL_GetKeyboardState(nullptr);
						if (keys[SDL_SCANCODE_ESCAPE] == 1)
							quit = true;

					}
					else if (event.type == SDL_MOUSEBUTTONDOWN)
						if (event.button.button == SDL_BUTTON_LEFT) {
							isDragging = true;
							startX = event.button.x;
							startY = event.button.y;
						}
						else if (event.type == SDL_MOUSEBUTTONUP)
							if (event.button.button == SDL_BUTTON_LEFT) {
								isDragging = false;
							}
							else if (event.type == SDL_MOUSEMOTION)
								if (isDragging) {
									int offsetX = event.motion.x - startX;
									int offsetY = event.motion.y - startY;
									objectX += offsetX;
									objectY += offsetY;
									startX = event.motion.x;
									startY = event.motion.y;
								}
				}
			}
							int mouseX, mouseY;
							SDL_GetMouseState(&mouseX, &mouseY);
							cout << mouseX << " " << mouseY << endl;
							if (mouseX > 522 && mouseX < 567 && mouseY>394 && mouseY < 439)
							{
								SDL_Rect rect_two;
								rect_two.x = 567;
								rect_two.y = 394;
								SDL_BlitSurface(gHelloWorld_one, NULL, gScreenSurface, &rect_two);
								SDL_UpdateWindowSurface((gWindow));
							}
					}
				}

I have to fix tabs so formatting becomes better readable.

It seems the section to render the sprite is outside the scope of the loop,
so the object is not rendered until we quit.

Another problem is, i expect the delta keeps growing even if movement would be zero.
So it will propell the sprite offscreen within a few frames, which is a typical mistake.

Might work now:

			bool isDragging = false;
			int prevX, prevY; // instead using a start, i use the previous coordinates to calculate movement relative to that each frame
			int objectX = 0, objectY = 0;

			bool quit = false;
			while (quit == false) 
			{
				SDL_Event event;
				while (SDL_PollEvent(&event)) 
				{
					if (event.type == SDL_KEYDOWN)
					{
						Uint8 const* keys = SDL_GetKeyboardState(nullptr);
						if (keys[SDL_SCANCODE_ESCAPE] == 1)
							quit = true;

					}
					else if (event.type == SDL_MOUSEBUTTONDOWN &&
							event.button.button == SDL_BUTTON_LEFT) 
					{
							isDragging = true;
							startX = event.button.x;
							startY = event.button.y;
					}
					else if (event.type == SDL_MOUSEBUTTONUP &&
							event.button.button == SDL_BUTTON_LEFT) 
					{
							isDragging = false;
					}
					else if (event.type == SDL_MOUSEMOTION/* && isDragging*/) // calculate mouse delta every frame, no matter if dragging or not
					{
						int deltaX = event.motion.x - prevX;
						int deltaY = event.motion.y - prevY;
						prevX = event.motion.x;
						prevY = event.motion.y;
						
						if (isDragging) // eventually move object
						{
							objectX += deltaX;
							objectY += deltaY;
							
						}

									//int offsetX = event.motion.x - startX;
									//int offsetY = event.motion.y - startY;
									//objectX += offsetX;
									//objectY += offsetY;
									//startX = event.motion.x;
									//startY = event.motion.y;
					}
				}
			//} // this must be after the rendering code, so it is executed in the loop, not after it
			
							//int mouseX, mouseY;
							//SDL_GetMouseState(&mouseX, &mouseY);
							//cout << mouseX << " " << mouseY << endl;
							//if (mouseX > 522 && mouseX < 567 && mouseY>394 && mouseY < 439)
							
							cout << objectX << " " << objectX << endl; // should change only if mouse moves

				//{
								SDL_Rect rect_two;
								
								rect_two.x = objectX; // give the sprite the changing coordinates
								rect_two.y = objectY;
								rect_two.width = rect_two.height = 10; // you need to give it some size too
								
								SDL_BlitSurface(gHelloWorld_one, NULL, gScreenSurface, &rect_two);
								SDL_UpdateWindowSurface((gWindow));
				//}
			}
		

is this code correct?

			bool isDragging = false;
			int startX, startY;
			int objectX = 567, objectY = 394;
			int prevX=0, prevY=0;
			bool quit = false;
			while (quit == false) {
				SDL_Event event;
				while (SDL_PollEvent(&event)) {
					if (event.type == SDL_KEYDOWN)
					{
						Uint8 const* keys = SDL_GetKeyboardState(nullptr);
						if (keys[SDL_SCANCODE_ESCAPE] == 1)
							quit = true;

					}
					else if (event.type == SDL_MOUSEBUTTONDOWN)
						if (event.button.button == SDL_BUTTON_LEFT)
						{
							isDragging = true;
							prevX = event.button.x;
							prevY = event.button.y;
						}
						else if (event.type == SDL_MOUSEBUTTONUP)
							if (event.button.button == SDL_BUTTON_LEFT)
							{
								isDragging = false;
							}
							else if (event.type == SDL_MOUSEMOTION)
							{
								int deltaX = event.motion.x - prevX;
								int deltaY = event.motion.y - prevY;
								prevX = event.motion.x;
								prevY = event.motion.y;
								if (isDragging)
								{
									objectX += deltaX;
									objectY += deltaY;
								}
							}

				}

				SDL_Rect rect_two;
				rect_two.x = objectX;
				rect_two.y = objectY;
				SDL_BlitSurface(gHelloWorld_one, NULL, gScreenSurface, &rect_two);
				SDL_UpdateWindowSurface((gWindow));

			}
		}
	}

well I got the code to work but I want it to erase the sprite after it draws it. yes your code works and mine is incorrect

Advertisement

pbivens67 said:
I want it to erase the sprite after it draws it.

What do you mean? Maybe you want to preserve the background, which the sprite overwrites while moving?
Or maybe drawing the moving sprite, but making it disappear after the mouse button is released?

I want it to preserve the background which the sprite overwrites while moving.

pbivens67 said:
I want it to preserve the background which the sprite overwrites while moving. Like Quote Reply

Just draw everything each frame. E.g. first the background, then sprites, then HUD.
Then it's never a problem if some background gets overdrawn with moving sprites.

It's not necessarily the most efficient solution ofc. You could also have each sprite keeping a copy of the background below it. So when you move it, you first revert the background to ersae it, then you draw it at the new place.
But even for simple 2D games such effort probably isn't worth it anymore with modern HW.

here is an image of my problem

Advertisement