I am adding bodies with fixtures to a box2d world in libgdx.
I want to detect if the user has touched (clicked) an object.
How do I do this? thanks
You should use libgdx Stage in order to detect touch events on Actors (what you are referring to them as Objects here). The best practice is to map a box2d body to a stage actor which makes it quite simple to do things like that.
To detect touch:
Implement touchDown method of the InputProcessor interface such that:
You have to transform your screen coordinates to stage coordinates using stage.toStageCoordiantes(...) method.
Use the transformed coordinates to detect hit on the Actor (Object) on stage using stage.hit(x, y).
stage.hit(x, y) will return you the actor if a hit is detected.
Hope that helps.
User touches the body only if he touches some of Fixture's contained into this body. This mean, you can check every Fixture of Body using testPoit() method:
public class Player {
private Body _body;
public boolean isPointOnPlayer(float x, float y){
for(Fixture fixture : _body.getFixtureList())
if(fixture.testPoint(x, y)) return true;
return false;
}
}
Next, you need to create InputAdapter like this:
public class PlayerControl extends InputAdapter {
private final Camera _camera;
private final Player _player;
private final Vector3 _touchPosition;
public PlayerControl(Camera camera, Player player) {
_camera = camera;
_player = player;
// create buffer vector to make garbage collector happy
_touchPosition = new Vector3();
}
#Override
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
// don't forget to unproject screen coordinates to game world
_camera.unproject(_touchPosition.set(screenX, screenY, 0F));
if (_player.isPointOnPlayer(_touchPosition.x, _touchPosition.y)) {
// touch on the player body. Do some stuff like player jumping
_player.jump();
return true;
} else
return super.touchDown(screenX, screenY, pointer, button);
}
}
And the last one - setup this processor to listen user input:
public class MyGame extends ApplicationAdapter {
#Override
public void create () {
// prepare player and game camera
Gdx.input.setInputProcessor(new PlayerControl(cam, player));
}
Read more about touch handling here
Related
So, I have created a texture, and then a sprite.
On my render() method, I am check for user input. If the user has touched/clicked, then I want my sprite to rotate 90 degrees ONCE.
Right now the rotation works. However, it rotates multiple times per click!
How can I make it rotate only once per touch? I have a feeling that I might have to use delta time, and that occurs because the render method is being called frequently, but I don't know how to fix it... Thanks!
public class MyGame extends ApplicationAdapter {
SpriteBatch batch;
Texture img;
Sprite sprite;
#Override
public void create () {
batch = new SpriteBatch();
img = new Texture("badlogic.jpg");
sprite = new Sprite(img);
}
#Override
public void render () {
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.begin();
sprite.draw(batch);
batch.end();
if (Gdx.input.isTouched()) {
rotateRight();
}
}
private void rotateRight() {
sprite.setRotation(sprite.getRotation() - 90);
}
}
Right now you are polling input inside of your render method. Polling simply checks the status of the input (is it touched or not) and does not care for any actual "event" occurred.
Instead of this you need to look for input events via event handling as this will give you access to the actual event of the screen being touched or untouched. You can do this by implementing InputProcessor which will give you access to override a bunch of touch event methods so you can execute your rotate method only on the event of the touch.
Example:
public void update()
{
Gdx.input.setInputProcessor(new InputProcessor() {
#Override
public boolean TouchDown(int x, int y, int pointer, int button) {
if (button == Input.Buttons.LEFT) {
rotateRight();
return true;
}
return false
}
});
}
Don't forget to call your update() method from your render method. I put it in a separate function just for readability and not to clog up your rendering code.
You can also have your class implement InputProcessor and override the methods in your class so you do not have to do it inline as I did above.
if (Gdx.input.justTouched() && Gdx.input.isTouched()) {
rotateRight();
}
I'm making a simple RTS game. I've created separate Stages for map and UI, and used scene2D Table class for side panel. The problem is, when i hover my side panel, if there is an actor (building) under panel at the moment, it fires its mouseover event. Click events work properly.
Here is my building class input listeners:
public class Building extends Actor {
addListener(new InputListener(){
public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
System.out.println("Click");
return true;
}
public void enter(InputEvent event, float x, float y, int pointer, Actor fromActor){
((Building)event.getTarget()).hover = true;
}
Here is my panel class listeners
public class SidePanel extends Table {
panelBg = new Image(skin,"side-panel");
addListener(new InputListener(){
public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
System.out.println("Click");
return true;
}
public void enter(InputEvent event, float x, float y, int pointer, Actor fromActor){
System.out.println("Enter");
}
});
addActor(panelBg);
}
}
Then side panel is added to UI class stage:
public class UI {
public UI(){
stage = new Stage();
sidePanel = new SidePanel();
stage.addActor(sidePanel);
Gdx.input.setInputProcessor(stage);
}
And finally i added UI to main class:
#Override
public void create () {
ui = new UI();
CP =new InputMultiplexer();
CP.addProcessor(ui.stage);
CP.addProcessor(gameStage);
Gdx.input.setInputProcessor(CP);
}
Not sure where the problem is, because click event works fine;
One quick solution would be to have your Building class check if the mouse was also over the SidePanel. For example, you could change your Building listener's enter method to something like this:
if (mouseIsOverSidePanel)
((Building)event.getTarget()).hover = true;
Your SidePanel listener's enter method can be something like this:
mouseIsOverSidePanel = true;
And your leave method in your SidePanel can be:
mouseIsOverSidePanel = false;
This is mostly pseudo code so it won't work without declaring the variables (obviously) but it should give you a basic idea of how to fix your problem.
I am creating an Android Game using LibGdx. It is a platformer and the map is tiled based. To test the movements of the player I used Key inputs and the desktop version of the game works fine. I created some buttons in scene2d and added them as an actor to the scene so that the game has movement buttons when played on Android devices. The buttons work as "System.out.print" shows. Problem is: the buttons and the player are each created in a different class. I can't seem to modify the velocity (and so the movement) of the Player from the class that holds the buttons. For that I need to change the velocity and speed etc. to static, which gives me strange errors on an Android device (Player won't show, or disappears after a frame). I am not sure how to fix this and what is the actual cause of this error. Here is some of the code of the different Classes:
Main Class (MyGdxGame) only included one button as an example.
public class MyGdxGame extends Game implements ApplicationListener {
private Skin skin;
private Stage stage;
#Override
public void create() {
setScreen(new Play());
skin = new Skin(Gdx.files.internal("ui/defaultskin.json"));
stage = new Stage();
Gdx.input.setInputProcessor(stage);
//Button Right
TextButton buttonRight = new TextButton("Right", skin, "default");
buttonRight.setWidth(50f);
buttonRight.setHeight(50f);
buttonRight.setPosition(Gdx.graphics.getWidth() /2 - 250f, Gdx.graphics.getHeight()/2 - 200f);
buttonRight.addListener(new ClickListener(){
public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
System.out.println("Hold");
return true;
}
public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
System.out.print("Released");
}
});
stage.addActor(buttonRight);
}
Play Class
public class Play implements Screen {
private TiledMap map;
private OrthogonalTiledMapRenderer renderer;
private OrthographicCamera camera;
private Player player;
#Override
public void render(float delta) {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
camera.position.set(Gdx.graphics.getWidth() / 2, Gdx.graphics.getHeight() / 2, 0);
camera.update();
renderer.setView(camera);
renderer.render();
renderer.getSpriteBatch().begin();
player.draw(renderer.getSpriteBatch());
renderer.getSpriteBatch().end();
}
#Override
public void resize(int width, int height) {
camera.viewportWidth = width;
camera.viewportHeight = height;
}
#Override
public void show() {
map = new TmxMapLoader().load("maps/map.tmx");
renderer = new OrthogonalTiledMapRenderer(map);
camera = new OrthographicCamera();
player = new Player(new Sprite(new Texture("data/jack2.png")), (TiledMapTileLayer) map.getLayers().get(0));
player.setPosition(2 * player.getCollisionLayer().getTileWidth(), 10 * player.getCollisionLayer().getTileHeight());
}
Player Class
public class Player extends Sprite implements InputProcessor{
// the movement velocity //
public Vector2 velocity = new Vector2();
public float speed = 60 * 2, gravity = 60 * 1.8f;
private boolean canJump;
private TiledMapTileLayer collisionLayer;
private String blockedKey = "blocked";
public Player(Sprite sprite, TiledMapTileLayer collisionLayer){
super(sprite);
this.collisionLayer = collisionLayer;
}
#Override
public void draw(SpriteBatch spriteBatch) {
update(Gdx.graphics.getDeltaTime());
super.draw(spriteBatch);
}
So the button has a working ClickListener, but I don't know how it can modify the players velocity. Any help is welcome.
You just need to have some way to access that player instance from the class containing the button. In my games I make a static World class which can be accessed from anywhere in the program. Through the world class I can access the player instance. That's just how I do it, you may find another way. You say your program has errors on android device when making certain parameters static? Do you mean it compiles but it just doesn't work the way you expect? If that's the case, there is probably just a bug in how you're modifying the position or velocity of the player.
I am using libGDX on Android. I set up the Stage and added a CustomActor with a custom draw. It's working properly. However, I am not able to get any logs for the touchDragged method via the InputListener for this actor. Even the code within it does not run.
Here is the required exposure to the code:
public class CustomActor extends Actor {
public CustomActor() {
this.setListener(new InputListener() {
#Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
return true;
}
#Override
public void touchDragged(InputEvent event, float x, float y, int pointer) {
//This log doesn't print up!
Gdx.app.log("CustomActor","touchDragged");
//This code doesn't work either
Vector2 v = CustomActor.this.localToParentCoordinates(new Vector2(x,y));
CustomActor.this.setPosition(v.x, v.y);
}
});
}
}
Can anyone help me out here on what am I missing ?
Add the Stage you are using as InputProcessor to receive the events else the Stage can't forward the event to the actors.
Gdx.input.setInputProcessor(stage);
Should do it.
try using public boolean addListener (EventListener listener) {...} method of Actor. Which version of libgdx are you using? I think actor does not have "setListener" method.
Check your actor bounds. (width & height)
I've created a simple test with a touchDown() event on an image actor. The event works on the stage but not on the actor.
Here is the code:
I set the InputProcessor in the constructor of the parent class (AbstractScreen).
Gdx.input.setInputProcessor(this.stage);
In the sub class:
private TextureAtlas atlas;
private Image boardImg;
public void show(){
super.show();
atlas = new TextureAtlas(Gdx.files.internal("data/packs/mainMenu.atlas"));
AtlasRegion board = atlas.findRegion("board");
boardImg = new Image(board);
boardImg.addListener(new InputListener(){
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button){
Gdx.app.log("aaaa", "down");
return true;
}
});
stage.addActor(boardImg);
stage.addListener(new InputListener() {
public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
Gdx.app.log("aaaa", "stage down");
return true;
}
});
}
#Override
public void resize(int width, int height) {
super.resize(width, height);
boardImg.setPosition(stage.getWidth()/2 - boardImg.getWidth()/2, stage.getHeight()/4);
}
I'm testing both on the desktop and on the android (same results).
I get the "stage down" event but not the event of the actor. I've tried also without having the stage event.
Do all of these works:
boardImg.setTouchable(Touchable.enabled)
return false in stage listener or clear its listener.
do not call setInputProcessor(this.stage) in parent class instead call in child class.
I think the problem is because the touchDown() method of the stage returns true, and so the event will be "consumed" and won't be propagated to its children, the actors, and so the touchDown() method of boardImg will not be called.
Image doesn't receive touch events because it has no size.
Read documentation carefully:
Note that the actor must specify its bounds in order to receive input
events within those bounds.
Just know that boardImg = new Image(board) won't set width and height of actor for you. So you need to make it manualy. Assume you work with pixel perfect sizes:
boardImg = new Image(board);
boardImg.setWidth(board.getWidth());
boardImg.setHeight(board.getHeight());
That will do the trick. Good luck.