Detect swipe gesture without lifting the finger - java

I am developing a game for android with libgdx and I have a little problem, I want to make it trigger a function when the user swipes, this I achieve. The problem is that it runs the function when you finish swiping (when you lift your finger). How can I make the function run while the swipe is doing?
This is the current code for the gesture listener:
private static class DirectionGestureListener extends GestureAdapter{
DirectionListener directionListener;
public DirectionGestureListener(DirectionListener directionListener){
this.directionListener = directionListener;
}
#Override
public boolean fling(float velocityX, float velocityY, int button) {
if(Math.abs(velocityX)>Math.abs(velocityY)){
if(velocityX>0){
directionListener.onRight();
}else{
directionListener.onLeft();
}
}else{
if(velocityY>0){
directionListener.onDown();
}else{
directionListener.onUp();
}
}
return super.fling(velocityX, velocityY, button);
}
}
And the game scene:
Gdx.input.setInputProcessor(new SimpleDirectionGestureDetector(new SimpleDirectionGestureDetector.DirectionListener() {
#Override
public void onUp() {
/*something*/
}
#Override
public void onRight() {
/*something*/
}
#Override
public void onLeft() {
/*something*/
}
#Override
public void onDown() {
/*something*/
}
}));

I looked into GestureDetector.java of libgdx source code itself. fling() will be executed whenever event of touchUp() happened. Thus aligned with what you experienced.
I think an option to make this work is to implement such gesture detection yourself by extending InputAdapter class or implementing InputProcessor interface class, then work on touchDown(), and touchDragged() to have an intended effect as you aimed for.
The idea is to keep track of touch id as it firstly touched on the screen inside touchDown() (its 3rd parameter is int button which is what you're looking at), then use that id to check and further operate inside touchDragged(). If id matches, then for simple approach, you can check whether user touched and moved for pre-defined distance by comparing it against original touching position.
Let's say we want a swipe gesture only when user firstly touches the screen, moves it for (at least) distance we pre-defined set, and within pre-defined duration. So if user firstly touches the screen, moves finger around but still not more than pre-defined distance, then finally moves far enough within duration we've set, this still won't be treated as swipe as our conditions set that it must be from first intention (first touch) to do such gesture. This means we calculate distance against original touching point, not moved point. Of course, you can customize the conditions to suit your need too.
From above, conditions to regard it as swipe gesture can include following (you can adapt these yourself)
distance it moved compared to original touching point
duration it took to move (might be 150ms, etc), longer duration user can be more relaxing not to act fast to make it registered as swipe gesture
only 1 swipe at a time can be taken into effect i.e. if user uses 2 fingers (2 touches) to swipe at the same time, only first one (or last one) can be used as swipe effect, etc.
Treating it separately for x, y direction is per your need. If so, you have to add handling code inside touchDragged() to check for both direction and send you corresponding event i.e. swipe-x, or swipe-y per se. Then you hook it up by calling one of your method to let your game knows.

Related

Android Studio Drag and Drop Multiple Objects Simultaneously

I am making a racing game with 2 cars, each of which to be controlled by a different player on the same device. I used the OnTouchEvent method to move the cars by drag and drop. I can't test the code on my laptop because my laptop can't do multi-touch and only allows me to control one car at a time (I only have the emulator from Android Studio, and no physical Android device). So what I currently know is that this code allows the cars to be moved one at a time. I was wondering if my code actually makes it so that the cars can be moved at the same time (which is what I want)or if they can only be moved one at a time:
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
// press down
case MotionEvent.ACTION_DOWN:
// conditionals check which car is tapped on
if (car.getCollisionShape().contains((int)event.getX(), (int)event.getY())) {
car.setActionDown(true);
} else if (car2.getCollisionShape().contains((int)event.getX(), (int)event.getY())) {
car2.setActionDown(true);
}
break;
// finger moved along screen
case MotionEvent.ACTION_MOVE:
// moves the cars horizontally where the fingers are moved
if(car.getActionDown()) {
car.setPosition(event.getX(), car.y, true);
} else if (car2.getActionDown()) {
car2.setPosition(event.getX(), car2.y, true);
}
break;
// finger released
case MotionEvent.ACTION_UP:
// conditionals check which car is tapped on
if (car.getCollisionShape().contains((int)event.getX(), (int)event.getY())) {
car.setActionDown(false);
} else if (car2.getCollisionShape().contains((int)event.getX(), (int)event.getY())) {
car2.setActionDown(false);
}
break;
}
return true;
}
Inside the ACTION_DOWN, a car's boolean, which signals if the car is currently being hold down or not, becomes true.
Inside, the ACTION_MOVE, the car's location is shifted to wherever the event.getX() and event.getY() are
Unfortunately, your code doesn't seem to be designed properly for handling multi-touch operations. To make multi-touch work, pointer-IDs are required, which can be obtained by using MotionEvent.getPointerId().
Steps to handle multi-touch gestures are roughly outlined as follows.
Get pointer-ID in ACTION_DOWN and map the ID to the car instance according to hit test result.
And in subsequent actions (ACTION_MOVE, ACTION_UP), determine for which car the MotionEvent is targeted, based on the pointer-ID. And then apply changes to the relevant car instance.
See the official training documentation for handling multi-touch gestures.
By the way, emulator has a little function to simulate multi-touch by mouse (mainly for testing pinch and spread gestures). You may be able to utilize it for testing your app. See Pinch and spread section in this table to know how to use it.

How do action events work with players' turns, need help showing which card the user played before the computer shows what card it played

Im creating an uno game and it is almost fully functional and i'm using javaFX to create it. It is Computer vs User and the problem is that when a user clicks on a card button, the middle card is supposed to be replaced with that card and then the computer goes, however what ends up happening is that the computer goes right away and so you only see the computer's card played in the middle. I am sure this has something to do with how i structured my code specifically the action event pertaining to the button clicked. However, since this is my first ever java GUI and i am fairly new to java i am struggling to understand how to structure it so that i am able to show the user's move. Also due to this, i am unable to implement a skip card even though i have tried using a boolean and creating another method i think the main problem lies within how the main gameloop and the actionevent inside is set up.
This method is called and it checks for a button click and then gets the index of the button in the player's arraylist/hand/ The boolean was my attempt at implementing a skip card but what kept happening is the computer would always play right after the user's move when a skip card was played.
public void gameLoop(){
for(int i =0; i<buttonList.size();i++){
buttonList.get(i).setOnAction((ActionEvent)->{
indexOfHandButton = buttonList.indexOf((Button)ActionEvent.getSource());
humanMakeMove();
if(!isReverseTurn()) {
computerMakeMove();
}
});
}
}
This method used the index given from the gameloop actionevent and makes the actual move in the backend. It then clears the hbox containing the users hands and calls showhumanHand which fills it up with the updated cards and similarily with the middle card. The if statements to try and implement the skip cards have not worked, i have also tried creating a new action even inside the if statement and creating a method or recursively calling humanMakeMove but of course the index will be the same and will cause an error.
public void humanMakeMove() {
String result = human.makeMove(game, theDeck, indexOfHandButton, computer);
if(result.equals("skip")){
setReverseTurn(true);
}
hbHumanHandBtn.getChildren().clear();
hbMiddleCard.getChildren().remove(middle);
middle = generateMiddleCard();
hbMiddleCard.getChildren().add(middle);
showHumanHand();
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>" + buttonList);
System.out.println(result);
if (middleCard.getType() != null) {
if (middleCard.getType().equals("skip") && isReverseTurn()) {
System.out.println(isReverseTurn());
gameLoop();
setReverseTurn(false);
}
}
}
So my main question is how can i restructure this or add something that allows for the user's card to show in the middle before the computer goes right away, i have tried a timer/sleep method too. And my skip card doesnt work but i think that has to do with how i set up my action listener which is messing up two problems. Thank you for reading this and let me know if you have any input!

Showing drop targets when drag starts

I'm trying to achieve making drop targets visible (or adding them) as soon as the user starts a drag.
The documentation explains that this should be handled in onDrag when ACTION_DRAG_STARTED is received, which could then be used to say highlight the View as being able to accept the drag.
However, my view (which is actually a LinearLayout) should look different when no drag is going on, and show drop targets when a drag is initiated.
Normal look:
[Item A][Item B]
When drag starts it should look like:
[ ][Item A][ ][Item B][ ]
Where the empty parenthesis represent locations where the drag can be dropped.
I've tried the following things to achieve this:
1) Dynamically add views
When top-level container receives ACTION_DRAG_STARTED, dynamically add the drop target views. Problem: the newly added views never receive ACTION_DRAG_STARTED themselves (or any other events) and so they cannot accept the drop.
2) Have hidden drop targets
Always have View.GONE drop targets in between the real items available all the time, and just make them View.VISIBLE when the drag starts:
if(event.getAction() == DragEvent.ACTION_DRAG_STARTED) {
// Make all containers visible:
for(int i = 0; i < cc.getChildCount(); i++) {
cc.getChildAt(i).setVisibility(View.VISIBLE);
}
}
Problem: apparently View.GONE also means the view does not receive events. Same thing with View.INVISIBLE.
So, what are my options? Using say View.VISIBLE and doing some dynamic resizing when the drag starts/end? Seems really silly...
Any better suggestions?
You need to change the visibility just before calling startDrag() or startDragAndDrop(). I demonstrate that in this sample app (from this chapter of this book, FWIW).
In that sample, if you run it on a tablet, I will initiate a drag-and-drop operation on a long-click of an item in a RecyclerView:
#Override
public boolean onLongClick(View v) {
if (listener!=null) {
listener.onStartDrag();
}
ClipData clip=ClipData.newRawUri(title.getText(), videoUri);
View.DragShadowBuilder shadow=new View.DragShadowBuilder(thumbnail);
itemView.startDrag(clip, shadow, Boolean.TRUE, 0);
return(true);
}
But before I call startDrag(), I let a registered listener know that I am about to start the drag. That listener is the hosting activity, which makes my "hotspot" drop target visible:
#Override
public void onStartDrag() {
info.setVisibility(View.VISIBLE);
}
The net effect is akin to a home screen, where specific "actions" appear (e.g., uninstall) when you start the drag.
I tried your second approach initially, and it didn't work. My assumption is that calling startDrag() or startDragAndDrop() basically captures the roster of visible drop targets, and so changes to that roster (new widgets, newly-visible widgets) have no effect after this point.

gridworld help-moving critters to open spot

Alright so I have a project due for computer science, and I need help with grid world. I have the code written so I can click on a critter, but I want to click on the critter once, then click on an open spot on the grid to move the critter to. But it doesn't work. I need to get this working, and I don't know whats wrong with my code. Take a look. thanks for all your help
Actor t;
public boolean locationClicked(Location loc)
{
Grid<Actor> gr = getGrid();
t = gr.get(loc);
Actor j;
//Location second;
if (t != null)
{
setMessage("Click on " + t);
numOfClicks++;
if(t instanceof BlackCheckers || t instanceof RedCheckers)
{
if(numOfClicks==0)
{
secondClick(second);
}
}
}
else
{
setMessage("Click on nothing");
}
return true;
}
Location second;
public void secondClick(Location second)
{
this.second=second;
Grid<Actor> op=getGrid();
Actor te=op.get(second);
if(te==null)
{
t.moveTo(second);
}
}
This is beyond what I know about Gridworld, but I found something that might help.
How You Can Intercept Mouse Clicks and Keystrokes
The World class has student-friendly mechanisms for intercepting mouse clicks and keystrokes. No knowledge of AWT events is required.
When the user clicks on a grid location, the locationClicked method of the World is called. By default, that method returns false, which tells the framework to initiate the default action, namely to move the selection square and to show the constructor or method menu.
To intercept the mouse click, override the locationClicked method. Carry out any desired action and return true. The grid location on which the user clicked is passed as a parameter. Typical actions include flipping tiles, populating empty locations, and so on.
Sometimes, you need to ask the user for additional information after the mouse click. The easiest method is to use a JOptionPane.
Let’s consider a common situation. In a game, a user selects a piece. You want to ask where the user wants to move the piece. You can wait for another mouse click. That means,
your locationClicked method needs to keep track of the click state (piece selection vs. target selection). Or you can enumerate all legal targets and call JOptionPane.showOptionDialog.
When the user hits a key, the keyPressed method of the World is called. By default, that method returns false, which tells the framework to initiate the default key action. If the user hit a cursor key, the selection square is moved. If the user hit the Enter key, the constructor or method menu is shown. All other keys are ignored.
To intercept the keystroke, override the keyPressed method. The method receives the current location and the keystroke string, encoded in the same format that is used by the java.awt.KeyStroke class. Example keystroke strings are "INSERT" or "alt shift X". Your keyPressed method should check the keystroke string. If the string matches a keystroke that you want to intercept, carry out any desired action and return true. Return false for all other keystrokes. It is a good idea to return false for the cursor keys and the Enter key. Otherwise, the standard actions are disabled for your world.
Source (pg 16)

Andengine: destinguish between OnAreaTouched and OnSceneTouched

Hi Everyone,
in the game I develop with AndEngine, there are a lot of sprites running around. Now every of this sprites has a TouchArea registered to the scene, because I display some informations about the sprite when it is touched. The Scene itself has a OnSceneTouchListener that I use for moving the camera around and to zoom.
My problem is, that every time the user moves the camera (by touching the display somewhere and moving his finger around) the OnAreaTouched() method of any sprite, that is accidentally under the finger, gets called, when the movement is finished (finger gets lifted). I already limited the triggering events to event.getAction()==UP (before it was a real mess of called touchAreas) but this is not enough. If the user is zooming or moving the camera, the sprites touchAreas should not be activated.
Is there any way I can distinguish between an OnAreaTouched-event and an OnSceneTouched-event? Which one is called first and can I suppress the other?
This is my OnSceneTouched() method (simplified):
public boolean onSceneTouchEvent(Scene scene, final TouchEvent event) {
boolean isZooming = event.getMotionEvent().getPointerCount() >= 2;
if (event.getAction() == MotionEvent.ACTION_DOWN) {
// REMEMBER FIRST TOUCHPOINT, TO KNOW IN WHICH DIRECTION TO MOVE
this.touchPoint = new Point(event.getMotionEvent().getX(), event.getMotionEvent().getY());
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
if (isZooming) {
// DO SOME ZOOM STUFF
} else {
// DO SOME MOVEMENT STUFF
}
return true;
}
OK, actually this is not very interesting - but as you can see I always return true to signal that the touch event was handled. Still the OnAreaTouched() gets called
This is a typical OnAreaTouched() Method of a sprite:
public boolean onAreaTouched(final TouchEvent touchEvent, float touchAreaLocalX, float touchAreaLocalY) {
if (touchEvent.getAction() == TouchEvent.ACTION_UP) {
// DISPLAY INFORMATION ABOUT THE SPRITE
return true;
}
return false;
}
You see, there is nothing special to it. So I hope someone can help me out here to find a solution how to suppress the OnAreaTouch-event when the OnSceneTouch-event should be used. Maybe I can somehow catch the event.getAction()==UP in the OnSceneTouched()-Method??
I hope I could explain the problem good enough for you to understand (sorry, it's not that easy for me : ). Any help is much appreciated, and thank you for you time!
regards
Christoph
edit:
After experimenting with MahdeTo's suggestion to tag the event somehow I found out the following:
the TouchEvent that triggers the OnSceneTouchEvent() Method is not the same as the one triggering the OnAreaTouched() Method.
OnAreaTouched() gets called 20 ms later than OnSceneTouchEvent()
the event calling OnAreaTouched() starts actually when the user puts his finger down on the display (than he moves it around and the OnSceneTouchEvent() gets called multiple times), when he then lifts his finger the first event stops and gets handled. (I tried it out by measuring the time)
So I came up with the solution to measure how long a touch event lasted. If the event is longer than 200 ms, I guess the user wanted not to simply click but move or zoom (because these actions usually take longer). So now the OnAreaTouched() method gets only called when someone really meant to click and not accidentally swiped over the area.
but it's still not a good solution and I would really appreciate if anyone knows more about controlling such events.
thank you
I have recently written a fairly complicated application using touch controls - pressing buttons on the screen, pinch zooming, rotating and moving objects with two fingers and more. What I ended up doing was iteratively improving the application and adding rules controlling how to respond to different combinations of touch events. Whenever something was wrong, I recorded the sequence of TouchEvents and added rules so that the misbehaving action was handled.
You can also create boolean switches that prevent the onAreaTouched to execute, just add a condition that, for example if you want to touch an object, checks whether doNotTouchObject is true or false.
Another thing I found useful were touch mode switchers. I wrote several methods that completely change the behavior when user touches the screen. I believe you can switch them on the go, so it should be possible to call scene.setOnAreaTouchListener(touchListener) while you are touching the screen.
Since un/registering touch areas is blazing fast, simply unregistering certain touch areas while you perform some action is a possibility. After the action is completed, reregister them again.
I used a combination of all of the above and it works rather well, but it is essential to keep the code clean, otherwise debugging or implementing new features will be a pain.
maybe you can use the TouchDetector.
use the clickDetector instead of process TouchUp event, then only the detected click event is processed by the sprites;
use the scroll detector for the Scene Scrolling too;
enable the SceneTouchListenerBinding and TouchAreaBinding will also help you to by pass all the unintended in-progress events.

Categories

Resources