0
\$\begingroup\$

I created a class template named PlayField<T>, where T is the type of the GridObject. I wrote this class using Cocos2d-x.

The point of this class is to make it easy for me to create a grid for games like "Tetris", "Bejeweled" or "Minesweeper". One of the main goals for this class is to place it anywhere by a position offset. At the time of creation, each GridObject will position itself according to this offset. This way, I could make the grid center align perfectly, or adjust the position horizontally or vertically depending on the layout.

I am able to do this easily with SFML, since the coordinate system there is top-left. The coordinates system in Cocos2d-x is bottom-left, which has given me a challenge, writing up a simple framework for creating the grid.


One of the key functions of my class is the ability to convert the pixel position of the mouse or touch input into a grid position. For example, 245 x 245 returns grid position of 4 x 4.

Clicking on each GridObject should only return positive coordinates, so I can use this to access them via a 2D vector. However, clicking on the bottom-most part of the grid returns a negative coordinate for the Y-axis. I think I made some wrong calculations, since I am new to programming bottom-left coordinates, and having a difficult time doing so.

Why does selecting a tile return a negative coordinate?


Here is my template class:

(...)

template<class T>
PlayField<T>::PlayField(const cocos2d::Vec2 & position, const cocos2d::Size & gridSize, 
    int offset) : mGridSize(gridSize), mOffset(offset), mGridPositionOffset(position), 
    mDrawNode(nullptr)
{
    mGridPositionOffset.y = mGridPositionOffset.y - (gridSize.height * offset);
}

template<class T>
PlayField<T>::~PlayField()
{

}

template<class T>
void PlayField<T>::initialize(cocos2d::Scene * scene)
{
    for (int y = 0; y < mGridSize.height; ++y)
    {
        mGrid.push_back(vector<unique_ptr<T>>());

        for (int x = 0; x < mGridSize.width; ++x)
        {               
            cocos2d::Vec2 actualPosition;
            actualPosition.x = (x * mOffset) + mGridPositionOffset.x + (mOffset / 2.0f);
            actualPosition.y 
                = mGridPositionOffset.y + ((y + 1) * mOffset) + (mOffset / 2.0f);

            T * gridObject = createGridObject(cocos2d::Vec2(x , y), scene);
            mGrid[y].push_back(unique_ptr<T>(gridObject));

            onGridObjectCreated(gridObject, actualPosition , cocos2d::Vec2(x , y));
        }
    }

    onGridCreationFinished(mGrid);
}

template<class T>
bool PlayField<T>::isWithinBounds(const cocos2d::Vec2 & position)
{
    if ((position.x >= 0 && position.x < mGridSize.width)
        && (position.y >= 0 && position.y < mGridSize.height))
        return true;

    return false;
}

template<class T>
cocos2d::Vec2 PlayField<T>::convertToGridPosition(const cocos2d::Vec2 & position)
{       
    cocos2d::Size gridPixelSize 
       = cocos2d::Size(mGridSize.width * mOffset, (mGridSize.height-1) * mOffset);

    cocos2d::Vec2 ipos;
    ipos.x = (mGridPositionOffset.x + gridPixelSize.width)  - position.x;
    ipos.y = (mGridPositionOffset.y + gridPixelSize.height) - position.y;

    cocos2d::Vec2 gpos;
    gpos.x = static_cast<int>(ipos.x / mOffset);
    gpos.y = static_cast<int>(ipos.y / mOffset);

    //gpos = cocos2d::Vec2(mGridSize.width - 1 , mGridSize.height - 1) - gpos;

    gpos.x = (mGridSize.width - 1) - gpos.x;

    return gpos;
}

template<class T>
int PlayField<T>::getOffset() const
{
    return mOffset;
}

(...)

This is how I initialize the grid; I set a small size so that I can simplify testing:

mScreenSize = cocos2d::Director::getInstance()->getOpenGLView()->getFrameSize();
cocos2d::Size gridSize = cocos2d::Size(3, 3);

cocos2d::Vec2 actualPosition;
actualPosition.x = (mScreenSize.width / 2.0f) - ((gridSize.width  * 50) / 2.0f);
actualPosition.y 
    = ((mScreenSize.height / 2.0f) + (((gridSize.height-1) * 50) / 2.0f) - 74.0f);

mJewelBoard.reset(new JewelBoard(actualPosition, this));

The JewelBoard constructor automatically sets the grid into a size of 3 x 3. with a tile size of 50 x 50 pixels in its constructor. The size of the game screen is 600 x 550. I set the position of the JewelBoard to the center using the actualPosition variable. Notice that I subtracted 74.0f, so it goes little bit downward to match the background borders.

That is where I notice that the grid coordinates being returned are negative, on the y-axis. I must have missed something, in calculation. Also take note that getting the position of the touch and mouse location is starting at the top-left. Also note that I prefer my 0 x 0 to start at the bottom-most part. as well. to keep it aligned with my other code. That way, I don't have to do flip-axis conversions.

Here is the picture to better understand my situation:

In a grid of jewels, the bottom line returns a position of "0, -2", while two rows up returns a position of "0, 0".

What has gone wrong? Why does selecting a tile return a negative coordinate?

\$\endgroup\$
  • \$\begingroup\$ You should accept your answer, to tell other users that that solved the problem. \$\endgroup\$ – Gnemlock May 17 '17 at 3:56
1
\$\begingroup\$

The fast and simple solution is just to increment all returned Y values by 2.

I'm not seeing an obvious error although I can't see the whole situation. I suspect when you subtract 74 pixels to offset the grid you either did or didn't also subtract 74 pixels from every pixel input to methods.

It may also have something to do with your offsets or your additions and subtractions to the y coordinate.

I can help you debug the program though, you start with your incorrect result and then work backwards until you find your error.

In this case you are receiving an incorrect index from I assume the method convertToGridPosition. So you can write a print statement to console or to screen to ensure that it is getting an accurate position input. If yes then your error is in that method, otherwise it is before that method. You work backwards and then forwards through the offending method until you find the statement that is incorrect.

\$\endgroup\$
  • \$\begingroup\$ " I suspect when you subtract 74 pixels to offset the grid you either did or didn't also subtract 74 pixels from every pixel input to methods." This I have to check as well. Thanks for the remarks! \$\endgroup\$ – Neon Warge Jun 29 '15 at 12:54
1
\$\begingroup\$

I can't quite spot the error in your code, but this looks odd.

cocos2d::Size gridPixelSize = cocos2d::Size(mGridSize.width * mOffset, (mGridSize.height-1) * mOffset);

Why the - 1 on height?

Also, in general to do something like this, you should abstract away the screen coordinates entirely. Make a hierarchical system so that when handling clicks, you only have to deal with local coordinates, and never global screen coordinates, except when getting the click from the user.

Something like this, in pseudocode:

// A container is a rectangle that can be moved around on the screen.
// Children of the container move with it.
class Container
     // A rectangle (x, y, w, h) relative to the parent coordinate system
     Rectangle local_bounds;
     // A rectangle (x, y, w, h) relative to the screen.
     Rectangle global_bounds;
     // If the parent is null, all coordinates are global. Otherwise,
     // local coordinates are relative to the parent.
     Container parent;
     // A container may have zero or more children.
     List<Container> children;

     // Computes the global bounds of a rectangle based on its parent
     void ComputeGlobalBounds():
          if (parent == null):
              global_bounds = local_bounds;
          else:
              global_bounds = (parent.global_bounds.x + local_bounds.x,
                               parent.global_bounds.y + local_bounds.y,
                               local_bounds.w,
                               local_bounds.h);

     // Gets the local coordinates of a point on the screen.
     Point GetLocalCoords(Point global_coords):
         return (global_coords.x - global_bounds.x, global_coords.y - global_bounds.y);

     // Called when the mouse is clicked (or a touch event, etc.). The coords
     // are global screen coordinates.
     void OnClick(Point global_coords):
         OnLocalClick(GetLocalCoords(global_coords);

         foreach(Container child in children):
             child.OnClick(global_coords);

     // Called from OnClick
     void OnLocalClick(Point global_coords):
          // Do something....

Then for a grid, you would do something like this:

// This container puts all of its children in a grid.
class GridContainer extends Container:
    // The children are laid out in a grid of this size.
    int num_cells_x;
    int num_cells_y;

    // Get the size of a child in pixels.
    float GetChildSizeX():
       return local_bounds.w / num_cells_x;

    float GetChildSizeY():
       return local_bounds.h / num_cells.y;

    // Add a child at the specified grid coordinate.
    void AddChild(Container child, int x, int y):
        children.Add(child);
        float size_x = GetChildSizeX();
        float size_y = GetChildSizeY();
        child.local_bounds = (size_x * x, size_y * y, size_x, size_y);

Now, whenever OnClick is called, the appropriate child will automatically get a callback.

\$\endgroup\$
  • \$\begingroup\$ This is very feasible. I think I may have tried this already, do you mean that each element should detect their own click? If so, I have done this, and I am having trouble with cocos2dx framework making each sprite swallow the event but that is another topic to discuss. Also as I have done that, the bottom-most row doesn't detect any bound which is weird because every sprite on screen should have a bounding box, but it doesn't seem to be the case. The bottom-most row doesn't detect point containment in a bounding box. That was frustrating actually. If I have done, I might have made workaround. \$\endgroup\$ – Neon Warge Jun 29 '15 at 13:37
0
\$\begingroup\$

I just figured it out. The mGridPositionOffset is pointing to the top-most corner of the grid. So that makes it (grid.height + mGridPositionOffset.height). As you can notice. That was very wrong. The mGridPositionOffset is already accounting the height of the gridBox plus the grid height. I just figured it out when I make realtime and actual measurement. That was painstaking. Yes, the formula I wrote works, its just that it over accounted the height of the grid which threw the calculations off the expected output. The mGridPositionOffset already accounted the height of the grid since it is on the top-left most corner. What I did is to make sure that the offset actually starts on the bottom-most part of the grid. So the coordinate system is consistent all over the place.

To make that happen, consider the changes I made:

    template<class T>
cocos2d::Vec2 PlayField<T>::convertToGridPosition(const cocos2d::Vec2 & position)
{       
    //if (!isWithinPixelBounds(position))
    //  return cocos2d::Vec2(-1, -1);

    // TODO fix y-axis not aligning properly

    cocos2d::Size gridPixelSize = cocos2d::Size(mGridSize.width * mOffset, mGridSize.height * mOffset);

    cocos2d::Vec2 ipos;
    ipos.x = (mGridPositionOffset.x + gridPixelSize.width ) - position.x;
    ipos.y = ((mGridPositionOffset.y - gridPixelSize.height) + gridPixelSize.height) - position.y;

    CCLOG("Position : %f , %f" , ipos.x , ipos.y);

    cocos2d::Vec2 gpos;
    gpos.x = static_cast<int>(ipos.x / mOffset);
    gpos.y = static_cast<int>(ipos.y / mOffset);

    gpos.x = (mGridSize.width - 1) - gpos.x;
    gpos.y = (mGridSize.height - 1) - gpos.y;

    return gpos;
}

Also, I made sure that the touch/mouse position actually starts on the bottom as well:

cocos2d::Vec2 touchPosition = touch->getLocationInView();
touchPosition = cocos2d::Director::getInstance()->convertToGL(touchPosition);

Now the grid can now throw the correct coordinates wherever the grid is on the screen!

Hope it helps.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.