I want to drag an Actor around with the Mouse in LibGDX.
My code:
// overrides in the ClickListener
#Override
public boolean touchDown(InputEvent event, float x, float y,
int pointer, int button) {
touching = true;
prevTouchPosition = new Vector2(x, y);
return super.touchDown(event, x, y, pointer, button);
}
#Override
public void touchDragged(InputEvent event, float x, float y,
int pointer) {
dragging = true;
lastTouchPosition = new Vector2(x, y);
super.touchDragged(event, x, y, pointer);
}
#Override
public void touchUp(InputEvent event, float x, float y,
int pointer, int button) {
touching = false;
super.touchUp(event, x, y, pointer, button);
}
// the update method
#Override
public void act(float delta) {
super.act(delta);
if(dragging){
Vector2 diff = new Vector2(lastTouchPosition).sub(prevTouchPosition);
translate(diff.x, diff.y);
prevTouchPosition = new Vector2(lastTouchPosition);
dragging = false;
}
}
The more I move the Actor around the worse it gets.
The idea is to keep the last two Mouse positions and use the difference between them to update the position of the Actor.
Anything incremental like this is going to suffer from roundoff errors. Moreover, I think you don't need to update prevTouchPosition every frame; in the absence of roundoff errors, it should not be changing. Note that your code only works if the object is not rotated or scaled.
A more robust drag algorithm works as follows:
upon touch down, touch point L in object-local coordinates:
G := L
upon touch move, touch point L in object-local coordinates:
setPosition(toGlobal(L) - toGlobal(G) + getPosition())
Note that we never update G during the drag operation; it is used to remember the point on the object where it was "grabbed", and we continuously align the object so that this point is under the current touch. Here, toGlobal(L) - toGlobal(G) gives you the "delta" vector in global coordinates, which we add to the current position.
Note that I'm assuming there's no object hierarchy, so the current position is itself in global coordinates. Otherwise you'll need to take the parent's transform into account as well.
Related
Faced a problem while trying to handle clicking on a moving image.
I used InputAdapter.touchDown() to handle the click and created Sprite for the image. Then I set the borders through Sprite.setBounds(). Further, in fact, the problem: if the coordinates in setBounds() are unchanged - the click is handled correctly. But if you change them (position.x++, for example) - the object comes into motion, but clicks are not read.
I can’t understand where the reason.
I tried to make a alterable variable outside the method, but this also didn't bring any result.
I tried using batch.draw(img) instead of img.draw(batch) - the effect is the same.
I tried to relocate Gdx.input.setInputProcessor() to the render() method, after img.setBounds() - nothing changed.
I even compared the coordinates of the Img and the Bounds area online, in motion - they are the same, as it should be.
Img and handler in constructor:
img = new Sprite(new Texture(finSize));
centerX = Gdx.graphics.getWidth()/2-img.getWidth()/2;
centerY = Gdx.graphics.getHeight()/2-img.getHeight()/2;
startPosition = new Vector2(centerX, centerY);
Gdx.input.setInputProcessor(new InputAdapter(){
#Override
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
if(img.getBoundingRectangle().contains(screenX, screenY))
System.out.println("Image Clicked");
return true;
}
});
Render:
public void render(SpriteBatch batch, float radius, float boost) {
speed+=boost;
nextX = radius * (float) Math.cos(speed); // Offset step X
nextY = radius * (float) Math.sin(speed); // Offset step Y
// Img is moving, but clicks is not handling
img.setBounds(startPosition.x+ nextX, startPosition.y + nextY, 100, 100);
// Handling clicks fine, but img is motionless
img.setBounds(startPosition.x, startPosition.y, 100, 100);
img.draw(batch);
// Checking coordinates - all's fine
System.out.println(img.getBoundingRectangle().getX());
System.out.println(startPosition.x + nextX);
System.out.println(img.getBoundingRectangle().getY());
System.out.println(startPosition.y + nextY);
}
So, I sequentially compared the XY coordinates of the image and the mouse click point and came to the conclusion that InputAdaper and Sprite consider Y differently - from above and from below. Therefore, X always coincided, and Y had a big difference in values.
As a result, I entered two corrected coordinates xPos \ yPos (Y subtracted from the total field height) for the center of the pic and in the touchDown() method, instead of comparing with BoundRectangle, simply compared the difference in the coordinates of the pic and the click modulo. If the result into the image size range - everything is ok.
Now clicks on the moving image works correctly.
public void render(SpriteBatch batch, float radius, float boost) {
speed+=boost; // rotational speed
nextX = radius * (float) Math.cos(speed); // Offset step X
nextY = radius * (float) Math.sin(speed); // Offset step Y
// set image size and position
img.setBounds(startPosition.x+nextX, startPosition.y+nextY, 100, 100);
img.draw(batch);
// Corrected coordinates of the image for InputAdapter coordinate system
xPos = img.getX()+img.getWidth()/2;
yPos = Gdx.graphics.getHeight()-img.getY()- img.getHeight()/2;
// Check the coincidence of the coordinates of the image area and the click point
Gdx.input.setInputProcessor(new InputAdapter(){
#Override
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
if((Math.abs(xPos-screenX)<=img.getWidth()) && (Math.abs(yPos-screenY)<=img.getHeight()))
{System.out.println("Image Clicked");}
return true;
}
});
}
I'm making a game where swiping and justTouched do different things. My problem is that when I swipe on the screen obviously the touchDown() method is also being triggered.
Im extending GestureAdapter:
#Override
public boolean touchDown(float x, float y, int pointer, int button) {
//move when screen is touched
if(life == 1 && overlaps == false) {
timeState = 0;
velocity.y = -120;
velocity.x = 100;
}
return super.touchDown(x, y, pointer, button);
}
#Override
public boolean fling(float velocityX, float velocityY, int button) {
if(velocityX > 10)
//do something
return super.fling(velocityX, velocityY, button);
}
You should not use just touched in this case because when player touched first time you cant know will it be just touch or swipe.
You must use justtouched with touch up method like this
when player justtouched save touched coordinates.
Check coordinates when player touched up.
If distance between this 2 points less than 50 pixels (or what limit you want to set) then you can assume its justtouched otherwise its swipe and you can do calculations about swiping.
public boolean touchDragged(int screenX, int screenY, int pointer) {
if(check) {
sprite.setPosition(screenX - sprite.getWidth() / 2, Gdx.graphics.getHeight() - screenY - sprite.getHeight() / 2);
rect.setPosition(screenX - sprite.getWidth() / 2, Gdx.graphics.getHeight() - screenY - sprite.getHeight() / 2);
}
return false;
}
this is my method in the my custom input processor class i use Input multiplexer in my main because i have 2 classes . Simultaneous drag won't move the sprite i can only move one sprite at a time.
My intention is to drag 2 sprites at the same time.
Thanks for your help and sorry for my bad English.
I don't know if this is the best approach, but a kinda-solution could be something like this:
1. Add a constant that let you know how many object you will allow to move at the same time:
private static final int MAX_TOUCHES = 4;
2. Add a collection with a fixed size, with this you'll manage all sprite that are currently being possible moving:
private final Array<Sprite> sprites = new Array<Sprite>(MAX_TOUCHES);
3. Now, in your class where you are handling touches, implement the touchDown(), touchDragged() and touchUp():
/**
* In the touchDown, add the sprite being touched
**/
#Override
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
// Just allow 4 sprites to move at same time
if(pointer >= MAX_TOUCHES) return true;
// Get the sprite at this current position...
Sprite sprite = getSpriteAtThisPosition(screenX, screenY);
// If sprite found, add to list with current pointer, else, do nothing
if(sprite != null) {
sprites.set(pointer, sprite);
}
return true;
}
getSpriteAtThisPosition() is just a method that return the first current sprite in that position, could return null.
/**
* In the touchDragged, move this sprite
**/
#Override
public boolean touchDragged(int screenX, int screenY, int pointer) {
// Just allow 4 sprites to move at same time
if(pointer >= MAX_TOUCHES) return false;
// Get the sprite with the current pointer
Sprite sprite = sprites.get(pointer);
// if sprite is null, do nothing
if(sprite == null) return false;
// else, move sprite to new position
sprite.setPosition(screenX - sprite.getWidth() / 2,
Gdx.graphics.getHeight() - screenY -
sprite.getHeight() / 2);
return false;
}
/**
* In the touchUp, remove this sprite from the list
**/
#Override
public boolean touchUp(int screenX, int screenY, int pointer, int button) {
// Just allow 4 sprites to move at same time
if(pointer >= MAX_TOUCHES) return true;
// remove sprite at pointer position
sprites.set(pointer, null);
return true;
}
The InputMultiplexer is not for dealing with two classes of object to move but to handle two input processors (or more) that you want to have - for example when you have more than one Stage and want to interact with player with each of them.
What you should do here would be to remember what pointer is attached to the touched object and then move it according to the pointer movement. The pointer is just id of for example finger enumerated from 0. So when you will touch screen with first finger it is 0 pointer, second one is 1 - BUT! If you will leave first finger keeping second the second is still 1 so it is perfect for this usage.
To handle remembering pointer id you also need to implement touchDown listener method
The code example would be like:
HashMap<Integer, Sprite> ids = new HashMap<Integer, Sprite>();
...
public boolean touchDown (int x, int y, int pointer, int button)
{
Sprite sprite = getSprite(x, y); //getSprite should iterate over all sprites in your game checking if x/y is inside one of them - you need to implement this one
ids.put(pointer, sprite);
return true;
}
public boolean touchUp (int x, int y, int pointer, int button)
{
ids.remove(pointer); //removing from hashmap
return false;
}
public boolean touchDragged (int x, int y, int pointer)
{
Sprite sprite = ids.get(pointer);
if(sprite != null)
{
sprite.setPosition... //and so on
}
return false;
}
For getting more information about multitouch handling you can check out this tutorial.
Basically, the sprite is spawned at a random time every (1,2 or 3 sec) and infinitely. I want the sprite to disappear once it's touched on the screen. (android touch event)
public void newEnemy(){
Sprite newEnemy=Pools.obtain(Sprite.class);
newEnemy.set(enemy);
newEnemy.setPosition(200, 700);
enemies.add(newEnemy);
}
public void update(){
deltaTime=Gdx.graphics.getDeltaTime();
timer+=1*deltaTime;
timer2+=1*deltaTime;
timer3+=1*deltaTime;
if(timer>=random){
newEnemy(); //spawn a new enemy
timer-=random;
random=rTime.nextInt(3)*1f+1;//create random time if timer>= initial random time;
}
You will need to set up a touch listener. Information on that here
You will then need to check if the touch location is within your sprite bounds.
A common way to do this would be to create a rectangle and check if the touch location is inside of the rectangle like this
Rectangle2D bounds = new Rectangle2D.Float(x, y, width, height);
`if(bounds.contains(`the touch x value`,` the touch y value`){`
//your code to remove the sprite
}
Alternately you could write your own method in sprite, this would be a better decision if all you needed was the contains method. That way, you don't have to import another library. (Note that it doesn't make much of a difference but it's good practice)
public boolean contains(int x, int y) {
return (x > this.x && y > this.y && x < this.x + this.width && y < this.y + this.height);
}
I'm using an actors with a stage as buttons. I can detect when touchDown/touchUp events occur just fine over the actor, but when the user taps on the actor and then proceeds to drag their finger off the actor, the touchUp event never fires. I tried to use the exit event instead, but it never fires. In my program the touchUp/touchDown events determine movement and also button color which depend on whether the button is being pressed or not. So I' left with a permanently "depressed" button until it's clicked down/up again.
Example of the code I'm using:
stage.addListener(new InputListener() {
public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
Actor actor = stage.hit(x, y, true);
if (actor != null){
System.out.println("touchDown: " + actor.getName().toString());
}
return true;
}
public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
Actor actor = stage.hit(x, y, true);
if (actor != null){
System.out.println("touchUp: " + actor.getName().toString());
}
}
public void exit(InputEvent event, float x, float y, int pointer, Actor toActor){
System.out.println("exit");
}
});
If you change
stage.addListener(new InputListener() {});
to
stage.addListener(new ClickListener() {});
it will recognize the TouchUp call. And it still will be able to handle the TouchDown, and Exit calls.
I had the same problem. I fixed it by creating boolean isDown variable as a field of my GameScreen class. Whenever touchDown occures on my background Image I make isDown variable true and when touchUp occures - isDown = false. That way touchUp will always occur. Then still in my GameScreen in render method i check if isDown is true, if it is, I check if the touch intersects with my actor:
if (isDown) {
if (pointIntersection(myActor, Gdx.input.getX(), Gdx.input.getY())) {
// do something
}
} else {
// reverse the effect of what you did when isDown was true
}
where pointIntersection method is:
public static boolean pointIntersection(Image img, float x, float y) {
y = Gdx.graphics.getHeight() - y;
if (img.x <= x && img.y <= y && img.x + img.width >= x && img.y + img.height >= y)
return true;
return false;
}
It's the only workaround I found to that. Still, it's not very pretty but works for me.