Search code examples
javagraphicslibgdxcontrols

Offset between click detection and button graphics in libgdx


I have a libgdx application that contains a class Button. The constructor of Button takes three arguements: Filename of graphics, position, and game (the latter being used for callbacks of various sorts).

The button scales itself based on the graphics provided, thus setting its width and height based on the properties of the graphics.

The main class, Game, when a click is detected compares the coordinates of the click up against the coordinates of the button combined with its width and height.

Now, the main issue is that there is a little bit of a horizontal offset between the button and the click coordinates, so the effect is that the graphics show up a few pixels to the right of the clickable area. I cannot for the life of me figure out the source of this discrepancy, so I would greatly appreciate some fresh eyes to see where I'm going wrong here.

Button, constructor and polling-method for clickable area.

public Rectangle getClickArea() {
    return new Rectangle(pos.x - (img.getWidth() / 2), pos.y + (img.getHeight() / 2), w, h);
}

public Button(String assetfile, int x, int y, Game game) {
    this.game = game;
    img = new Pixmap(new FileHandle(assetfile));
    pos = new Vector2(x, y);
    this.w = img.getWidth();
    this.h = img.getHeight();
}

A relevant snippet from InputHandler. It listens for input and passes on the event. Please note that the vertical click position is subtracted from the vertical size of the screen, as vertical 0 is opposite in InputHandler:

public boolean touchDown(int screenX, int screenY, int pointer, int button) {
    tracker.click(screenX, Settings.windowSize_Y - screenY);
    return false;
}

ClickTracker (referenced as tracker in the above snippet), the Class that does the actual comparison between clicks and clickables:

public void click(int x, int y) {
    Vector2 clickPos = new Vector2(x, y);
    for (Tickable c : world.getPaintables())
    {
        if (!(c instanceof Unit))
            continue;

         if (((Clickable)c).getClickArea().contains(clickPos)) {
             System.out.println("Clicked on unit");
         }


    }
    for (Clickable c : clickables)
    {
        if (c.getClickArea().contains(clickPos)) {
            c.clicked(x, y);
        }
    }

In short: The vertical alignment works as intended, but the horizontal is slightly off. The button graphics appear maybe around 10-20 pixels to the right of the clickable area.

I'll gladly post more info or code if needed, but I believe I have the relevant parts covered.


Edit:

As Maciej Dziuban requested, here's the snipped that draws the UI elements. batch is a SpriteBatch as provided by libgdx:

    for (Paintable p : ui) {
        batch.draw(new Texture(p.getImg()), p.getImgPos().x, p.getImgPos().y);
    }

the getImgPos() is an interface method implemented by all drawable items:

public Vector2 getImgPos() {
    return new Vector2(pos.x - (getImg().getWidth() / 2), pos.y);
}

It's worth noting that half of the horizontal image size is subtracted from the X pos, as X pos refers to the bottom center.


Solution

  • You have inconsistency in your position transformations:

    • Your clickArea's corner is pos translated by [-width/2, height/2] vector.
    • Your drawArea's corner is pos translated by [-width/2, 0] vector

    They clearly should be the same, so if you want your pos to represent bottom-center of your entity (as you've explicitly stated) you have to change your getClickArea() method to, so it matches getImgPos().

      public Rectangle getClickArea() {
          return new Rectangle(pos.x - (img.getWidth() / 2), pos.y, w, h);
      }
    

    Side note: as Tenfour04 noticed, you create new texture each frame and this is huge memory leak. You should make it a field initialized in constructor or even a static variable given some buttons share the texture. Don't forget to call dispose() on resources. For more powerful asset management check out this article (note it may be an overkill in small projects).