Dzuvan

SDL Mouse selection on overlapping shapes

Recommended Posts

Hi there fellas,  I'm the new guy in need of help.

I'm writing a simple tangram puzzle using c++ and sdl2. Goal of the game is to fit pieces on the grid. Piece movement is done via mouse drag and drop.

The problem I'm having is when the cursor is over overlapping objects and I click and drag every shape under it gets dragged. I need some kind of check for object on top or something like that (I'm thinking it has to do something with height). Any help would be  greatly appreciated.

I've made Vec2 class that holds two int values for anything that has x  and y values.

Here is GameObject code that I use for puzzle pieces :

void GameObject::render() {
    SDL_SetRenderDrawColor(Game::Instance()->getRenderer(), m_color.r, m_color.g, m_color.b, m_color.a);
    SDL_Rect rects[] = { 
     m_position.getX(), m_position.getY(), m_dimension.getX(), m_dimension.getY(),
     m_position2.getX(), m_position2.getY(), m_dimension2.getX(), m_dimension2.getY() };
    
    SDL_RenderFillRects(Game::Instance()->getRenderer(), rects, 2);
    SDL_RenderDrawRects(Game::Instance()->getRenderer(),rects, 2);
}

void GameObject::update() {
    if (InputHandler::Instance()->getMouseButtonState(0) && 
        intersects(m_position, m_dimension, *InputHandler::Instance()->getMouseButtonPosition()) 
        || intersects(m_position2, m_dimension2, *InputHandler::Instance()->getMouseButtonPosition())) {

            offset = (*InputHandler::Instance()->getMouseButtonPosition() - m_position);
            offset2 = (*InputHandler::Instance()->getMouseButtonPosition() - m_position2);

            dragging = true;
    }
    else if (!InputHandler::Instance()->getMouseButtonState(0) && dragging) {
        offset = Vec2(0,0);
        offset2 = Vec2(0,0);
        dragging = false;
   }
   if(dragging){
        m_position = *InputHandler::Instance()->getMousePosition() - offset;
        m_position2 = *InputHandler::Instance()->getMousePosition() - offset2;
   }
}

bool GameObject::intersects(Vec2 object, Vec2 dimensions, Vec2 mouse) {
    if(mouse.getX() < object.getX() || mouse.getY() < object.getY()) {
        return false;
    }

    if(mouse.getX() > object.getX() + dimensions.getX() || mouse.getY() > object.getY() + dimensions.getY()) {
         return false;
    }
    return true;
}

And here is it being called in the game singleton.

void Game::render() {
    SDL_SetRenderDrawColor(m_pRenderer, 0, 0, 0, 0);
    SDL_RenderClear(m_pRenderer);    
    for (int i = 0; i < m_gameObjects.size(); i++) {
        m_gameObjects[i]->render();

    }
    SDL_RenderPresent(m_pRenderer);
}

void Game::update() {
    for (int i = m_gameObjects.size()-1; i >= 0; i--) {
            m_gameObjects[i]->update();
    }
}

And here is input handler, also a singleton:

void InputHandler::update() {
    SDL_Event event;

    while (SDL_PollEvent(&event)) {
        m_keystates = SDL_GetKeyboardState(0);
        if (event.type == SDL_QUIT) {
            Game::Instance()->quit();
           
        }
        if (event.type == SDL_MOUSEBUTTONDOWN) {
                m_mouseButtonPosition->setX(event.button.x);
                 m_mouseButtonPosition->setY(event.button.y);
            if (event.button.button == SDL_BUTTON_LEFT) {
                 
                 m_mouseButtonStates[LEFT] = true;
            }

            if (event.button.button == SDL_BUTTON_MIDDLE) {
                m_mouseButtonStates[MIDDLE] = true;
            }

            if (event.button.button == SDL_BUTTON_RIGHT) {
                m_mouseButtonStates[RIGHT] = true;
            }
        }

        if (event.type == SDL_MOUSEBUTTONUP) {
            if (event.button.button == SDL_BUTTON_LEFT) {
                m_mouseButtonStates[LEFT] = false;
            }

            if (event.button.button == SDL_BUTTON_MIDDLE) {
                m_mouseButtonStates[MIDDLE] = false;
            }

            if (event.button.button == SDL_BUTTON_RIGHT) {
                m_mouseButtonStates[RIGHT] = false;
            }
        }

        if (event.type == SDL_MOUSEMOTION) {
            m_mousePosition->setX(event.motion.x);
            m_mousePosition->setY(event.motion.y);
        }
    }
}

 

Share this post


Link to post
Share on other sites

Maybe you can like search for overlap outside the object and for Release inside of them? (code below)

GameObject* LastDragged = nullptr;
void Game::update() {
	//if released, check it's current overlaps and give it an higer height since it is on top
	if (!LastDragged->IsDragged)
	{
		for (auto& Obj : m_gameObjects)
		{
			if (IsOverlapping(LastDragged,Obj))
			{
				//give higher height to LastDragged
			}
		}
	}
	else
	{
		//Mak a list of who is dragged outside the objects themselves
		vector<GameObject*> Overlaps;
		for (auto& Obj : m_gameObjects)
		{
			if (IsMouseOverlapping(Obj))
			{
				Overlaps.push_back(&Obj);
			}
		}

		//inside of this set the dragged bool to true only for the one on top
		//and return the currently dragged
		LastDragged = FindMaxHeight(Overlaps);
	}

	//update position
	for (int i = m_gameObjects.size() - 1; i >= 0; i--) {
		m_gameObjects[i]->update();
	}
}

And as you said use an height to set the last released as the one on top.

But I am a begginner so take this as a grain of salt and listen to more experienced and efficient suggestions :)

My code above seems not the best one since you iterate trough the objects list multiple times, but seems the easiest for sure.

Edited by MarcusAseth

Share this post


Link to post
Share on other sites

Rather than having each piece move itself based on input logic, create some sort of pointer object that represents the player (who picks up, moves and drops pieces). On mouse button down, have the pointer capture all overlapping puzzle pieces into an array by iterating over them and running the same intersection logic you had placed in the puzzle piece classes. Find out which piece in the array has the highest height (however you're keeping track of draw order should work fine), then set that piece (only that piece) to follow the mouse cursor until a mouse button release event.

 

EDIT: just read marcus's post, seems like he's getting at the same sort of idea.

Edited by cmac

Share this post


Link to post
Share on other sites

Each movable object should have a value, height, heights start at zero. Each movable object should have an Area (or other mechanism) which describes how large or small the object is, so that you do not place large objects over small ones and obscure them. Instead always place larger objects on bottom and smaller ones on top, this modifies the height value. When the mouse clicks an object, get the list of objects that are intersecting, sort through the list and get the top most object, the one with the largest height, and hang onto that object as it is the selected object. When the user stops dragging and releases the object, go through and generate the intersection list, insert the object into it, sort the list based on Area to generate new height values for objects in the list.

Edited by h8CplusplusGuru

Share this post


Link to post
Share on other sites

So I've been trying to make this work, but I only got it to work for shape that has highest y value. Missing something, can't pin point what >.<

This is modified update method:

std::vector<int> heights;
void Game::update() {
    for(int i = 0; i < pieces.size(); i++) {
        heights.push_back(pieces[i]->getPosition().getY());
    if (InputHandler::Instance()->getMouseButtonState(LEFT)) {
        if (intersects(pieces[i]->getPosition().getX(), pieces[i]->getPosition().getY(), 100,
                        InputHandler::Instance()->getMouseButtonPosition()->getX(),
                        InputHandler::Instance()->getMouseButtonPosition()->getY())) {

            int it = *std::max_element(std::begin(heights), std::end(heights));

            if (pieces[i]->getPosition().getY() == it) {
                pieces[i]->setSelected(true);
            }
            if (!InputHandler::Instance()->getMouseButtonState(LEFT)){
                pieces[i]->setSelected(false);
            }
        }
    } else {
            if(InputHandler::Instance()->getMouseButtonState(LEFT)){
                pieces[i]->setSelected(true);
            }
            if(!InputHandler::Instance()->getMouseButtonState(LEFT)){
                pieces[i]->setSelected(false);
            }
        }

        heights.clear();
        heights.push_back(pieces[i]->getPosition().getY());
        pieces[i]->update();
    }
}

 

 

Share this post


Link to post
Share on other sites

First question: why do you store all the heights of all pieces on every update? If you have 5000 pieces seems expensive, only objects under the mouse should be considered.

Furthermore, on the line:

 heights.push_back(pieces[i]->getPosition().getY());

why you store the Y into heights? Shouldn't X,Y be your coordinate on the grid (top down view) and Z the height?

Share this post


Link to post
Share on other sites

I have only 7 pieces, but yeah I agree, that should be put elsewhere.

I treat y as height since sdl2 doesn't have z value. I dunno how to invent that(unless it can be anything I want like index in the array or something like that).

Share this post


Link to post
Share on other sites
22 minutes ago, Dzuvan said:

I have only 7 pieces, but yeah I agree, that should be put elsewhere.

I treat y as height since sdl2 doesn't have z value. I dunno how to invent that(unless it can be anything I want like index in the array or something like that).

You can do it like this:

class Piece{
	SDL_Point position;
	int height;
  public:
  //...
  
  int getZ()const {return height;}
};

SDL has no saying in what you put inside your objects :)

Edited by MarcusAseth

Share this post


Link to post
Share on other sites

So, this is the solution I've come up with,  It kinda works but sometimes pieces still go together.

Z values are going from one to n of pieces. So first piece has z value 1, second 2 and so on...

std::vector<GameObject*> overlaps;
std::vector<int> zs; //heights

void Game::update() {
    // Grab pieces under button and push them in vector.
    if (InputHandler::Instance()->getMouseButtonState(LEFT)){
        for(int i = 0; i < pieces.size(); i++) {
            if (intersects(pieces[i]->getPosition().getX(),pieces[i]->getPosition().getY(), 100,
                        InputHandler::Instance()->getMouseButtonPosition()->getX(),
                        InputHandler::Instance()->getMouseButtonPosition()->getY())) {
                overlaps.push_back(pieces[i]);
            }
        }
    }

    if (InputHandler::Instance()->getMouseButtonState(LEFT)) {
        for (GameObject* o : overlaps) {
            zs.push_back(o->getZ());
            int it = *std::max_element(std::begin(zs), std::end(zs));
            if (o->getZ() == it) {
                o->setSelected(true);
            }
        }
    }
    else {
        for (GameObject* o : overlaps) {
            o->setSelected(false);
            overlaps.clear();
            zs.clear();
        }
    }

    for(int i = 0; i < pieces.size(); i++) {
        pieces[i]->update();
    }
}

 

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now


  • Announcements

  • Forum Statistics

    • Total Topics
      628381
    • Total Posts
      2982360
  • Similar Content

    • By noodleBowl
      So I have some quick questions about constructors and pointers

      Question on constructors:
      1. I'm working on this Sprite class and in general sprites need some kind of texture in order to display to the screen. So I have my class looking like this:
      class Sprite { public: Sprite(Texture* texture); ~Sprite(); Texture* texture; //Other class stuff } Now in the current state of my Sprite class would I be correct in that I cannot use it as a member in a different class, because it has no no-arg (default) constructor? That the only way this class could be a member is if it was a pointer?
      class SpriteContainer { public: SpriteContainer(); ~SpriteContainer(); Sprite singleSprite; //**BAD** not allowed because Sprite has no no-arg (default) constructor Sprite* singleSpritePtr; //This is allowed because its a pointer std::vector<Sprite> sprites; //**BAD** not allowed because Sprite has no no-arg (default) constructor std::vector<Sprite*> spites; //This is allowed because its a vector of Sprite pointers } I guess my really problem here is that I struggle with when a variable/class should be a pointer or not
      2. How do you make an abstract class where there are no methods that should be pure virtual functions?
      All the functions of the base class have there implementation, but I also do not want this class to be instantiated
      //Should not be able to instantiate this class. Should be an Abstract class class BaseClass { public: BaseClass() { x = 0; }; virtual ~BaseClass() { }; int x; void coolMethod() { ++x; } } //Can instantiate this class. coolMethod can be called from this class class DerivedClass : public BaseClass { public: DerivedClass(); ~DerivedClass(); }  
      Questions about smart pointers:
      1. I have never used these before, but are smart pointers ok when working with COM objects?
      Will the smart pointer automatically call a COM object's Release function or should I wrap my COM object and explicitly call Release in the wrapper class' destructor?
      2. Lets say there is a case where I wanted to give back the user of some APIs a pointer to a resource, but I also want them to know that they don't have to worry about cleaning it up. EG There is resource loader that will clean up all loaded resources once the program shuts down. The user has to do/worry about nothing.
      What type of smart pointer should I use in a case like this? Should it be a Shared pointer? Or should I make the resource a Unique pointer and then return to the user a raw pointer?
    • By Geri
      Anime Maker is an ultimately simple and but powerfull crossplatform software to create animated cartoons and anime.

      Download your favorite anime fanworks (characters, backgrounds) from internet, then open them with Anime Maker to create an Anime from them!

      Anime Maker offers shot based timeline management with 27 image layers, simple BONE ANIMATION for moving your heroes, dozens of effect including fire, water, snowfall, fountain, and refraction.

      Make your own anime for amatheur or professional movie competitions! Create your own ultimate fanfictions from your favorit anime, impress your friends, create your own anime seasons for televisions, or video sharing sites!
       
      Your career as an anime artist starts today!
       
       
       
      Anime Maker offers all the feature that required to create an anime, including the folowing: 

      - Time-line and shot based video edition with copy/paste/delete abilities
      - Support for bmp, jpg, png formats with alpha map, and automatic alpha-map generation
      - 27 independent graphics layer to create your movie
      - 3 sound channel on the timeline
      - Wav and ogg files for audio
      - Mouse and touchscreen for input
      - AVI video export with antialiasing
      - x86 and ARM based computers and mobile phones
      - Windows, Linux and Android compatibility
      - 8 invididual bones on all layers, with 4 joints each
      - Real time motion movement recording for realible animation
      - Dozens of fire and particle effects with your own textures
      - Layer refraction effects (water waves, etc)
      - Anime Maker supports multiple CPU cores for great performance
      - Dedicated mouth speak layer management
      - More stable than video software usually
      - Very small memory footprint, and powersaving
      - Ability to morph, move, resize, color, blend your layers in real-time
      - WYSIWYG editor: what you see is what you will get on export
      - Supports cutting exported video into multiple files
      - Tooltips as easy usage guide
      - Instant start and quit
      - Multimedia cacheing to avoid memory waste
      - 16:9 ratio with custom shot sizes optimized for Anime
      - Colored and shadowed subtitles
      download:
      http://AnimeMaker.tk
      http://gerigeri.uw.hu/animemaker/index.html
       
       
       
       
       
    • By Ravenmore
      Hi guys! 
      With Black Friday just around the corner, all my asssets are 66% off in the Black Friday Bundle =)

      https://itch.io/s/9180/ravenmores-black-friday-bundle

      Quality stuff like in the attached images, for super cheap. Grab it while it's hot


    • By potente
      Hello everyone.
      I have [digital] modeling from Vaughan, dance music manual from Snowman, is it there anything similar for 2D graphics?
      Thanks in advance for any help.
       
      Bye, Ivano.
    • By Kazuma506
      I am trying to recreate the combat system in the game Life is Feudal but make it more complex. The fighting system works by taking in the direction of the mouse movement and if you press the left click it will swing in that direction, though stab, overhead, left (up, down, left right) and right are the only swings that you can do. If you wanted to you could also hold the swing by holding left click so you are able to swing at the perfect moment in the battle. I want to change this so add in more swing directions but I also want to code this from scratch in Unreal. Can anyone give me any pointers or maybe a few snippets of code that work in Unreal that could help me start to implement this type of system?
       
       
  • Popular Now