detecting sprite clicked on

Hi,

If I have a series of sprites, what is the best way to detect which you have clicked on?

I have a simple class for the sprite which has a draw method to render it from a texture region, I thought I could implement InputProcessor and catch this way,
but this seems to detect clicks on the whole spritebatch and I have other things in this sprite batch.

What I want it for is these sprites hold various tools etc.

Any advice is appreciated.

Thanks

Can you please give more information? I don’t see how this:

[quote]what is the best way to detect which you have clicked on?
[/quote]
…relates to…

[quote]What I want it for is these sprites hold various tools etc.
[/quote]

Sorry about that. To be more clear, what I basically want is when a sprite is clicked via left mouse button, I can get information about this sprite, such as it’s type etc.

Kind of like when you click on crafting items in various games.

I write out a collection of ClickBounds objects when I render my entities (because that’s when I know the screen coordinates) and then I iterate throught the collection to resolve any mouseOvers or clicks.

Well, the simplest solution would be to use Stage. Your sprite should extend Actor and should be added to the Stage. Then you add ClickListener like this:


    actor.addListener(new ClickListener(Input.Buttons.LEFT) {
      @Override
      public void clicked(InputEvent event, float x, float y) {
        // Your code goes here. :)
      }
    });

Or you could extend ClickListener and keep track of which Actor is clicked like this:


import com.badlogic.gdx.Input;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;

/**
 * @author Ivan Vinski
 * @since 1.0
 */
public class Example extends ClickListener {
  private Actor a, b, c, d;

  public Example() {
    super(Input.Buttons.LEFT); // You can use either this.
    a = new Actor();
    a.addListener(this);
    b = new Actor();
    b.addListener(this);
    c = new Actor();
    c.addListener(this)
    d = new Actor();
    d.addListener(this);
  }

  @Override
  public void clicked(InputEvent event, float x, float y) {
    int button = event.getButton(); // Or if you wish to also use other buttons, use this.
    if (button == Input.Buttons.LEFT) { // Using this conditional statement is redundant if you used constructor.
      Actor clickedActor = event.getTarget();
      if (clickedActor == a) {
        // ...
      } else if (clickedActor == b) {
        // ...
      } else if (clickedActor == c) {
        // ...
      } else if (clickedActor == d) {
        // ...
      }
    }
  }
}

Note: Keep in mind that this example does not work without Stage and adding all of the Actors to that Stage.

Plus, since you said you used Sprite class, this would be the way to do it with Actor:


import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.scenes.scene2d.Actor;

/**
 * @author Ivan Vinski
 * @since 1.0
 */
public class RegionActor extends Actor {
  private TextureRegion region;

  public RegionActor() {
  }

  public RegionActor(TextureRegion region) {
    this.region = region;
  }

  @Override
  public void draw(Batch batch, float parentAlpha) {
    Color color = getColor();
    batch.setColor(color.r, color.g, color.b, color.a * parentAlpha);
    if (region != null) {
      batch.draw(
          region,
          getX(), getY(), getOriginX(), getOriginY(),
          getWidth(), getHeight(), getScaleX(), getScaleY(),
          getRotation()
      );
    }
  }

  public TextureRegion getRegion() {
    return region;
  }

  public void setRegion(TextureRegion region) {
    this.region = region;
  }
}

Something like:


public class MySpriteActor extends Actor {
        private float x,y;
        private Texture texture;
        public MySpriteActor(float x, float y, Texture texture)
        {
            this.x = x; this.y = y; this.texture = texture;
        }

        @Override
        public void draw(Batch batch){
            batch.draw(texture, x, y);
        }
    }


private Stage stage;

stage = new Stage(Gdx.graphics.getWidth(),Gdx.graphics.getHeight(),true);
        
MySpriteActor myActor = new MyActor(x, y);
stage.addActor(myActor);

MyActor.addListener(new ClickListener(Input.Buttons.LEFT) {
      @Override
      public void clicked(InputEvent event, float x, float y) {
      
      }
    });


Is it possible to put the addListener into the MySpriteActor class instead? Thus being able to create an abstract class I inherit from? I’d try it out but at work at the moment.

Thanks

I see you are unfamiliar with the Scene2D provided by LibGDX. You will find this article more than helpful. [url=https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/scenes/scene2d/Actor.java]Actor.java[/url] already has all of the properties you wish to use except Texture/TextureRegion.

Of course. Here is the modified version of MySpriteActor.java.


public abstract class MySpriteActor extends Actor {
  private Texture texture;
  private boolean flipX, flipY;

  public MySpriteActor(float x, float y, Texture texture) {
    this.texture = texture;
    this.setPosition(x, y);
    this.setSize(texture.getWidth(), texture.getHeight()); // Initial size is 0f, 0f - nothing would get drawn.
    this.addListener(new ClickListener() {
      @Override
      public void clicked(InputEvent event, float x, float y) {
        MySpriteActor.this.clicked(event, event.getButton(), x, y);
      }
    });
  }

  protected abstract void clicked(InputEvent event, int button, float x, float y);

  @Override
  public void draw(Batch batch, float parentAlpha) {
    Color color = getColor();
    // Set this so texture is tinted with the color you can change with setColor() method.
    // Plus, if the action is Actions.fadeOut or Actions.fadeIn, make the actor change alpha/visibility.
    batch.setColor(color.r, color.g, color.b, color.a * parentAlpha);
    // Render the texture correctly even when it changes position, rotation, size, scale, etc.
    // Also useful if you're going to use Actions.
    if (texture != null) {
      batch.draw(
          texture,
          getX(), getY(), getOriginX(), getOriginY(),
          getWidth(), getHeight(), getScaleX(), getScaleY(),
          getRotation(),
          // It's like TextureRegion, what area of the Texture to draw.
          // Well, this is going to draw whole texture.
          0, 0, texture.getWidth(), texture.getHeight(),
          flipX, flipY
      );
    }
  }
}

And then you would do something like this in your screen:


public class Example implements Screen {
  private Stage stage;
  private Texture texture;
  private MySpriteActor actor;

  @Override
  public void show() {
    stage = new Stage();
    texture = new Texture(Gdx.files.internal("badlogic.jpg"));
    actor = new MySpriteActor(0f, 0f, texture) {
      @Override
      protected void clicked(InputEvent event, int button, float x, float y) {
        // ...
      }
    };
    stage.addActor(actor);
  }

  @Override
  public void render(float delta) {
    stage.act(delta); // Needs to be called for updating and input events.
    stage.draw(); // And needs to render.
  }

  @Override
  public void hide() {
    dispose();
  }

  @Override
  public void dispose() {
    stage.dispose();
    texture.dispose();
  }

  @Override
  public void resize(int width, int height) {
  }

  @Override
  public void pause() {
  }

  @Override
  public void resume() {
  }
}

Thanks Ivan,

Yes, not used Scene2D but seems extremely useful.

No problem. Glad to be able to help! :smiley:

Get familiar with Scene2D especially because of the useful GUI classes it provides. :wink: