• Advertisement

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
Advertisement

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


  • Advertisement
  • Advertisement
  • Popular Tags

  • Advertisement
  • Popular Now

  • Similar Content

    • By francoisdiy
      So I wrote a programming language called C-Lesh to program games for my game maker Platformisis. It is a scripting language which tiles into the JavaScript game engine via a memory mapper using memory mapped I/O. Currently, I am porting the language as a standalone interpreter to be able to run on the PC and possibly other devices excluding the phone. The interpreter is being written in C++ so for those of you who are C++ fans you can see the different components implemented. Some background of the language and how to program in C-Lesh can be found here:

      http://www.codeloader.net/readme.html
      As I program this thing I will post code from different components and explain.
    • By Manuel Berger
      Hello fellow devs!
      Once again I started working on an 2D adventure game and right now I'm doing the character-movement/animation. I'm not a big math guy and I was happy about my solution, but soon I realized that it's flawed.
      My player has 5 walking-animations, mirrored for the left side: up, upright, right, downright, down. With the atan2 function I get the angle between player and destination. To get an index from 0 to 4, I divide PI by 5 and see how many times it goes into the player-destination angle.

      In Pseudo-Code:
      angle = atan2(destination.x - player.x, destination.y - player.y) //swapped y and x to get mirrored angle around the y axis
      index = (int) (angle / (PI / 5));
      PlayAnimation(index); //0 = up, 1 = up_right, 2 = right, 3 = down_right, 4 = down

      Besides the fact that when angle is equal to PI it produces an index of 5, this works like a charm. Or at least I thought so at first. When I tested it, I realized that the up and down animation is playing more often than the others, which is pretty logical, since they have double the angle.

      What I'm trying to achieve is something like this, but with equal angles, so that up and down has the same range as all other directions.

      I can't get my head around it. Any suggestions? Is the whole approach doomed?

      Thank you in advance for any input!
       
    • By isu diss
      I'm trying to duplicate vertices using std::map to be used in a vertex buffer. I don't get the correct index buffer(myInds) or vertex buffer(myVerts). I can get the index array from FBX but it differs from what I get in the following std::map code. Any help is much appreciated.
      struct FBXVTX { XMFLOAT3 Position; XMFLOAT2 TextureCoord; XMFLOAT3 Normal; }; std::map< FBXVTX, int > myVertsMap; std::vector<FBXVTX> myVerts; std::vector<int> myInds; HRESULT FBXLoader::Open(HWND hWnd, char* Filename, bool UsePositionOnly) { HRESULT hr = S_OK; if (FBXM) { FBXIOS = FbxIOSettings::Create(FBXM, IOSROOT); FBXM->SetIOSettings(FBXIOS); FBXI = FbxImporter::Create(FBXM, ""); if (!(FBXI->Initialize(Filename, -1, FBXIOS))) { hr = E_FAIL; MessageBox(hWnd, (wchar_t*)FBXI->GetStatus().GetErrorString(), TEXT("ALM"), MB_OK); } FBXS = FbxScene::Create(FBXM, "REALMS"); if (!FBXS) { hr = E_FAIL; MessageBox(hWnd, TEXT("Failed to create the scene"), TEXT("ALM"), MB_OK); } if (!(FBXI->Import(FBXS))) { hr = E_FAIL; MessageBox(hWnd, TEXT("Failed to import fbx file content into the scene"), TEXT("ALM"), MB_OK); } FbxAxisSystem OurAxisSystem = FbxAxisSystem::DirectX; FbxAxisSystem SceneAxisSystem = FBXS->GetGlobalSettings().GetAxisSystem(); if(SceneAxisSystem != OurAxisSystem) { FbxAxisSystem::DirectX.ConvertScene(FBXS); } FbxSystemUnit SceneSystemUnit = FBXS->GetGlobalSettings().GetSystemUnit(); if( SceneSystemUnit.GetScaleFactor() != 1.0 ) { FbxSystemUnit::cm.ConvertScene( FBXS ); } if (FBXI) FBXI->Destroy(); FbxNode* MainNode = FBXS->GetRootNode(); int NumKids = MainNode->GetChildCount(); FbxNode* ChildNode = NULL; for (int i=0; i<NumKids; i++) { ChildNode = MainNode->GetChild(i); FbxNodeAttribute* NodeAttribute = ChildNode->GetNodeAttribute(); if (NodeAttribute->GetAttributeType() == FbxNodeAttribute::eMesh) { FbxMesh* Mesh = ChildNode->GetMesh(); if (UsePositionOnly) { NumVertices = Mesh->GetControlPointsCount();//number of vertices MyV = new XMFLOAT3[NumVertices]; for (DWORD j = 0; j < NumVertices; j++) { FbxVector4 Vertex = Mesh->GetControlPointAt(j);//Gets the control point at the specified index. MyV[j] = XMFLOAT3((float)Vertex.mData[0], (float)Vertex.mData[1], (float)Vertex.mData[2]); } NumIndices = Mesh->GetPolygonVertexCount();//number of indices MyI = (DWORD*)Mesh->GetPolygonVertices();//index array } else { FbxLayerElementArrayTemplate<FbxVector2>* uvVertices = NULL; Mesh->GetTextureUV(&uvVertices); int idx = 0; for (int i = 0; i < Mesh->GetPolygonCount(); i++)//polygon(=mostly triangle) count { for (int j = 0; j < Mesh->GetPolygonSize(i); j++)//retrieves number of vertices in a polygon { FBXVTX myVert; int p_index = 3*i+j; int t_index = Mesh->GetTextureUVIndex(i, j); FbxVector4 Vertex = Mesh->GetControlPointAt(p_index);//Gets the control point at the specified index. myVert.Position = XMFLOAT3((float)Vertex.mData[0], (float)Vertex.mData[1], (float)Vertex.mData[2]); FbxVector4 Normal; Mesh->GetPolygonVertexNormal(i, j, Normal); myVert.Normal = XMFLOAT3((float)Normal.mData[0], (float)Normal.mData[1], (float)Normal.mData[2]); FbxVector2 uv = uvVertices->GetAt(t_index); myVert.TextureCoord = XMFLOAT2((float)uv.mData[0], (float)uv.mData[1]); if ( myVertsMap.find( myVert ) != myVertsMap.end() ) myInds.push_back( myVertsMap[ myVert ]); else { myVertsMap.insert( std::pair<FBXVTX, int> (myVert, idx ) ); myVerts.push_back(myVert); myInds.push_back(idx); idx++; } } } } } } } else { hr = E_FAIL; MessageBox(hWnd, TEXT("Failed to create the FBX Manager"), TEXT("ALM"), MB_OK); } return hr; } bool operator < ( const FBXVTX &lValue, const FBXVTX &rValue) { if (lValue.Position.x != rValue.Position.x) return(lValue.Position.x < rValue.Position.x); if (lValue.Position.y != rValue.Position.y) return(lValue.Position.y < rValue.Position.y); if (lValue.Position.z != rValue.Position.z) return(lValue.Position.z < rValue.Position.z); if (lValue.TextureCoord.x != rValue.TextureCoord.x) return(lValue.TextureCoord.x < rValue.TextureCoord.x); if (lValue.TextureCoord.y != rValue.TextureCoord.y) return(lValue.TextureCoord.y < rValue.TextureCoord.y); if (lValue.Normal.x != rValue.Normal.x) return(lValue.Normal.x < rValue.Normal.x); if (lValue.Normal.y != rValue.Normal.y) return(lValue.Normal.y < rValue.Normal.y); return(lValue.Normal.z < rValue.Normal.z); }  
    • By Terry Jin
      Hi everyone! 

      I am from an indie studio that has received funding for our concept and is ready to create the next generation 2D Pokemon-inspired MMORPG called Phantasy World. This ad is for a volunteer position but hopefully will transition into something more. Our vision is to create a game that draws inspiration from the series but is dramatically different in both aesthetics and gameplay as the work would be our own.
       
      We are hoping that you can help us make this a reality and are looking for game developers familiar with the unreal engine and would be happy to work on a 2D top down game. Sprite artists are also welcome as we are in desperate need of talented artists! Join our discord and let's have a chat! https://discord.gg/hfDxwDX

      Here's some of our in game sprites for playable characters while moving around the game world! Hope to see you soon!
       


    • By Sean Meredith
      Hi all, I am starting to develop a tactics game and ran into a problem I had not thought of. I began by drawing a screen with a hex grid, and this is no big deal. I got that working fine. But, I realized it didn't look quite right. This is because in most strategy games, you're not looking straight down. There is a bit of a tilt. Attached is an example of what I mean. The hexagons on bottom are larger than the hexagons on top, and I'm unsure of how to go about adding this effect. Especially when you consider that some maps may be of different sizes. 
      I'm not sure if this is the right place to post something like this, but it seems as though some sort of linear transformation would be applied? No? I don't even know where to begin in a problem like this.
      Thanks.

  • Advertisement