I needed access to the ACTION_UP that occurs after the ACTION_MOVE event, and the SimpleOnGestureListener didn't directly support that, so I tried to implement custom touch handling in an activity's onTouchEvent method, in order to get more familiar with it.
The activity is an alarm clock's alert screen (appears when an alarm goes off).
There are 5 kinds of gestures I need to handle. They are:
Single tap - Visual feedback
Double tap - Snooze the alarm
Swipe along the Y access - visual feedback based on distance swiped.
ACTION_UP after an ACTION_MOVE with a distance of less than 50% of the screen's Y axis - visual feedback.
ACTION_UP after an ACTION_MOVE with a distance of greater than 50% of the screen's Y axis - dismiss the alarm.
The function I wrote doesn't work correctly, and I'm pretty sure that it is because the variable "startEvent" is being set on every call to onTouchEvent, when it should be only set on the ACTION_DOWN events of a swipe or the first tap of a double tap.
Here is my code:
private final int DOUBLE_TAP_TIMEOUT = 500; // ms
private boolean isScrolling = false; // flag used to detect the ACTION_UP after a scroll
private boolean possibleDoubleTap = false; // flag used to detect a double tap
private MotionEvent startEvent; // the event that starts the gesture we are trying to detect
private float yPercentScrolled; // the percent the along the (Y axis / 2) that has been scrolled.
#Override
public boolean onTouchEvent(MotionEvent event){
switch (event.getActionMasked()){
case(MotionEvent.ACTION_DOWN):
Log.d("MOTION", "DOWN");
// reset the startEvent given the proper conditions
if(
(!isScrolling && !possibleDoubleTap)
|| (possibleDoubleTap && event.getEventTime() - startEvent.getEventTime() <= DOUBLE_TAP_TIMEOUT)
){
startEvent = event;
}
return true;
case(MotionEvent.ACTION_UP):
Log.d("MOTION", "UP");
if(isScrolling){
isScrolling = false;
if(yPercentScrolled >= 1f){
dismissAlarm();
}
else {
layout.setBackgroundColor(bgColor);
startEvent = null;
possibleDoubleTap = false;
isScrolling = false;
}
}
else if(possibleDoubleTap){
// if we have a double tap
if(event.getEventTime() - startEvent.getEventTime() <= DOUBLE_TAP_TIMEOUT){
snoozeAlarm();
return true;
}
// if we don't have a double tap, do nothing
}
else if(!possibleDoubleTap){
possibleDoubleTap = true;
layout.setBackgroundColor(Color.parseColor("#000000"));
}
return true;
case(MotionEvent.ACTION_MOVE):
Log.d("MOTION", "MOVE");
if(!isScrolling){
isScrolling = true;
}
else {
yPercentScrolled = handleScroll(event, startEvent);
}
return true;
default:
Log.d("MOTION", "other:" + String.valueOf(event.getActionMasked()));
return super.onTouchEvent(event);
}
}
Any ideas on why "startEvent" is being reset when it shouldn't? There are no "MOTION:DOWN" in logcat when it is being reset, so this on is stumping me. This is also the only location where startEvent is being assigned to something other than null.
Thanks.
it seems that startEvent = event is assigning a reference to object event instead of the object itself. so when the value of object event changed. the value of startEvent is also changed.
Try to take and store just the value that you need (in your case is the X, Y, and EventTime) instead of the whole event object.
Related
I want to observe onFling Function to detect Velocityx and Velocity Means Swiping force/Velocity. In android we attach this to the android view.But donot find a way how to call this in Jetpack compose or alternative function for this in jetpack compose...?
Please take the Animation-in-Compose codelab to understand better, but for now, here's how you can achieve something similar
private fun Modifier.swipeToDismiss(
onDismissed: () -> Unit
): Modifier = composed {
// This `Animatable` stores the horizontal offset for the element.
val offsetX = remember { Animatable(0f) }
pointerInput(Unit) {
// Used to calculate a settling position of a fling animation.
val decay = splineBasedDecay<Float>(this)
// Wrap in a coroutine scope to use suspend functions for touch events and animation.
coroutineScope {
while (true) {
// Wait for a touch down event.
val pointerId = awaitPointerEventScope { awaitFirstDown().id }
// Interrupt any ongoing animation.
offsetX.stop()
// Prepare for drag events and record velocity of a fling.
val velocityTracker = VelocityTracker()
// Wait for drag events.
awaitPointerEventScope {
horizontalDrag(pointerId) { change ->
// Record the position after offset
val horizontalDragOffset = offsetX.value + change.positionChange().x
launch {
// Overwrite the `Animatable` value while the element is dragged.
offsetX.snapTo(horizontalDragOffset)
}
// Record the velocity of the drag.
velocityTracker.addPosition(change.uptimeMillis, change.position)
// Consume the gesture event, not passed to external
change.consumePositionChange()
}
}
// Dragging finished. Calculate the velocity of the fling.
val velocity = velocityTracker.calculateVelocity().x
// Calculate where the element eventually settles after the fling animation.
val targetOffsetX = decay.calculateTargetValue(offsetX.value, velocity)
// The animation should end as soon as it reaches these bounds.
offsetX.updateBounds(
lowerBound = -size.width.toFloat(),
upperBound = size.width.toFloat()
)
launch {
if (targetOffsetX.absoluteValue <= size.width) {
// Not enough velocity; Slide back to the default position.
offsetX.animateTo(targetValue = 0f, initialVelocity = velocity)
} else {
// Enough velocity to slide away the element to the edge.
offsetX.animateDecay(velocity, decay)
// The element was swiped away.
onDismissed()
}
}
}
}
}
// Apply the horizontal offset to the element.
.offset { IntOffset(offsetX.value.roundToInt(), 0) }
}
I would not recommend trying to comprehend this code just from here, you should probably refer to the documentation at hand, while taking the codelab as well. This implements a swipe-to-dismiss functionality on a simple list item in a compose-sample app, the code to which you will find linked to in the start of the codelab. Here are all the sample apps published for compose, for your reference.
So in my game I want to have it so the longer someone holds down on the screen the higher my character jumps. However I don't know how to check if someone is holding down on the screen.
My current attempt is to do this:
And run it every frame in the update method
public void handleInput(float dt) {
if (Gdx.input.isTouched()) {
if (sheep.getPosition().y != sheep.maxHeight && sheep.getPosition().y == sheep.minHeight) {
sheep.jump(1);
}
if (sheep.getPosition().y == sheep.maxHeight && sheep.getPosition().y != sheep.minHeight) {
sheep.jump(-1);
}
}
}
I am suggesting two way to detect long touch, choose one according to your requirement.
You can use longPress method of GestureListener interface to detect there is a long press or not. By default longPress duration is 1.1 seconds that mean user have to touch the screen equal to this duration, to fire a longPress event.
#Override
public boolean longPress(float x, float y) {
Gdx.app.log("MyGestureListener","LONG PRESSED");
return false;
}
Set your implementation as InputProcessor.
Gdx.input.setInputProcessor(new GestureDetector(new MyGestureListener()));
longPress only gets called one time after holding the screen for X time. so it's better to create own logic and check how long user touched the screen.
if (Gdx.input.isTouched()) {
//Finger touching the screen
counter++;
}
And on touchUp of InputListener interface make jump according to value of counter and reset value of counter to zero.
#Override
public boolean touchUp(int screenX, int screenY, int pointer, int button) {
//make jump according to value of counter
counter=0; //reset counter value
return false;
}
Set your implementation as InputProcessor.
Gdx.input.setInputProcessor(new MyInputListener());
My method some reason doesnt work I want it so when I click tab it grabs the mouse and moves the player but when clicked again I want it to release the mouse and stop the players rotation I tried putting a print statement into the movePlayerWithMouse method but it just returns true the only thing that does change is the mouse each time I click tab it releases and grabs any help?
Heres the method I use:
boolean movePlayerWithMouse = false;
while(Keyboard.next()) {
if(!Keyboard.getEventKeyState()) {
if(Keyboard.getEventKey() == Keyboard.KEY_TAB) {
movePlayerWithMouse = !movePlayerWithMouse;
Mouse.setGrabbed(!Mouse.isGrabbed());
}
}
}
if(movePlayerWithMouse = !movePlayerWithMouse) {
rotY -= Mouse.getDX() / SENSITIVITY;
}
I try to improve the movement of my figures but i dont find the real reason why they stutter a bit. I am moving them with an SequenceAction containing an MoveToAction and an RunnableAction that does reset the moveDone flag so there can be started a new move.
The game itself is gridbased so if a move is done the squence starts a move to the next grid depending on the direction. So here is how it looks like:
note that this is inside of the Act of the figure
....//some more here
if (checkNextMove(Status.LEFT)) //check if the position is valid
{
status = Status.LEFT; //change enum status
move(Status.LEFT); // calls the move
screen.map.mapArray[(int) mapPos.x][(int) mapPos.y] = Config.EMPTYPOSITION;
screen.map.mapArray[(int) (mapPos.x - 1)][(int) mapPos.y] = Config.CHARSTATE;
mapPos.x--;
moveDone = false;
}
//... same for the up down right and so on.
//at the end of this checking the updating of the actor:
// methode from the absctract to change sprites
updateSprite(delta);
super.act(delta); // so the actions work
//end of act
And here is the move Method that does add the Actions
protected void move(Status direction)
{
// delete all old actions if there are some left.
clearActions();
moveAction.setDuration(speed);
//restart all actions to they can used again
sequence.restart();
switch (direction)
{
case LEFT:
moveAction.setPosition(getX() - Config.TILE_SIZE, getY());
addAction(sequence);
break;
case RIGHT:
moveAction.setPosition(getX() + Config.TILE_SIZE, getY());
addAction(sequence);
break;
case UP:
moveAction.setPosition(getX(), getY() + Config.TILE_SIZE);
addAction(sequence);
break;
case DOWN:
moveAction.setPosition(getX(), getY() - Config.TILE_SIZE);
addAction(sequence);
break;
default:
break;
}
}
The figures dont really move smothy.
Anyone does see a misstake or isnt it possible to let them move smothy like this?
It always stutter a bit if a new move is started. So i think this might not work good. Is there a differnt approach to move them exactly from one Grid to an nother? (Tried it myself with movementspeed * delta time but this does not work exactly and i struggeled around and used the Actionmodel)
it seems to make troubles with the one Frame where it does not move for example.
Here is an mp4 Video of the stuttering:
stuttering.mp4
just to mention, the camera movement is just fine. it's smothy but the figure stutters as you can see i hope
If you use your moveAction each move, you should call moveAction.restart() to reset the counter inside it:
// delete all old actions if there are some left.
clearActions();
sequence.reset(); // clear sequence
// add movementspeed. Note it can change!
moveAction.restart();
moveAction.setDuration(speed);
UPDATE:
Now issue occurs, because you call restart of SequenceAction after clearActions(). If your clearActions() removes all actions from SequenceAction then restart will be called for empty SequenceAction. So, do this instead:
//restart all actions to they can used again
sequence.restart();
// delete all old actions if there are some left.
clearActions();
moveAction.setDuration(speed);
Okay so i solved it myself. It has nothing todo with changing sequences around. It has something todo with the updatefrequency of the act() of the figures. The MoveToAction interpolates between the 2 points given by time. So if the last update, updates the MoveToAction by "to much time" it would need to go over the 100% of the action. So it would move to far but the action does not do this it set the final position and thats what it should do. Thats why it does not look fluent.
So how to solve this issue?
By decreasing the updatetime the "to far movement" decreases too because the steplength is getting smaller. So i need to increase the updatespeed above the 60fps. Luckily i got an thread for my updating of the figures and positions and so on. GameLogicThread. This ran on 60fps too. So i solved the stuttering by increasing it's frequence. up to around 210fps at the moment so the "overstep" is minimal. You can't even notice it.
To Show how my thread works:
public class GameLogicThread extends Thread
{
private GameScreen m_screen;
private boolean m_runing;
private long m_timeBegin;
private long m_timeDiff;
private long m_sleepTime;
private final static float FRAMERATE = 210f;
public GameLogicThread(GameScreen screen)
{
m_screen = screen;
setName("GameLogic");
}
#Override
public void run()
{
m_runing = true;
Logger.log("Started");
while (m_runing)
{
m_timeBegin = TimeUtils.millis();
// hanlde events
m_screen.m_main.handler.processEvents();
synchronized (m_screen.figureStage)
{
// now figures
if (m_screen.m_status == GameStatus.GAME)
{
m_screen.character.myAct(1f / GameLogicThread.FRAMERATE);// and here it is ;)
m_screen.figureStage.act(1f / GameLogicThread.FRAMERATE);
}
}
m_timeDiff = TimeUtils.millis() - m_timeBegin;
m_sleepTime = (long) (1f / GameLogicThread.FRAMERATE * 1000f - m_timeDiff);
if (m_sleepTime > 0)
{
try
{
Thread.sleep(m_sleepTime);
}
catch (InterruptedException e)
{
Logger.error("Couldn't sleep " + e.getStackTrace());
}
}
else
{
Logger.error("we are to slow! " + m_sleepTime);
}
}
}
public void stopThread()
{
m_runing = false;
boolean retry = true;
while (retry)
{
try
{
this.join();
retry = false;
}
catch (Exception e)
{
Logger.error(e.getMessage());
}
}
}
}
I am using this method in AndEngine to determine the scene being touched by the user.
#Override
public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent) {
if(pSceneTouchEvent.isActionDown()) {
Log.e("Arcade", "Scene Tapped");
//Simulate player jumping
}
return false;
}
What i want to do is when the scene is tapped, i want to allow the player to jump.
Now two things for this would it be better to use PathModifier, or MoveYModifier considering it is landscape mode?
If either please provide an example of such.
Thanks
EDIT:
ive managed to use Physics to simulate a jump using this..
#Override
public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent) {
if(pSceneTouchEvent.isActionDown()) {
Log.e("Arcade", "Scene Tapped");
final Vector2 velocity = Vector2Pool.obtain(mPhysicsWorld.getGravity().x * -0.5f,mPhysicsWorld.getGravity().y * -0.5f);
body.setLinearVelocity(velocity);
Vector2Pool.recycle(velocity);
return true;
}
return false;
}
As you said in the answer by changing the gravity. The only issue is, when the user keeps touching the screen the sprites keep going up and up and up. How can i set it where the user can only click once and cant make him jump again until the sprite hits the ground, which is a rectangle?
Use the MoveYModifier. remember, you can register as many modifiers as you want. So if, for example, the game a platform game and the character always moves on the X axis, and he can jumpt if he wants (Like Gravity Guy or Yoo Ninja, although these games change the gravity which is something else).
You could do like:
Entity playerEntity = ..//It doesn't matter if the player is a sprite, animated sprite, or anything else. So I'll just use Entity here, but you can declare your player as you wish.
final float jumpDuration = 2;
final float startX = playerEntity.getX();
final float jumpHeight = 100;
final MoveYModifier moveUpModifier = new MoveYModifier(jumpDuration / 2, startX, startX + jumpHeight);
final MoveYModifier moveDownModifier = new MoveYModifier(jumpDuration / 2, startX + jumpHeight, startX);
final SequenceEntityModifier modifier = new SequenceEntityModifier(moveUpModifier, moveDownModifier);
playerEntity.registerEntityModifier(modifier);
EDIT:
For your second question:
Create a variable boolean mIsJumping in your scene; When the jump starts - set it to true. If the user taps the screen and mIsJumping == true, don't jump.
Now, register a ContactListener to your PhysicsWorld. Whenever there is contact between the player and the ground, set mIsJumping to false.
There are many samples of using ContactListeners in AndEngine forums, a quick search yields some. If you need an example, you can ask for one :)
EDIT 2: ContactListener sample:
Have 2 variables to hold IDs for the player and the ground: private static final String PLAYER_ID = "player", GROUND_ID = "ground";
When you create the ground body and the player body, set their IDs as the user data: playerBody.setUserData(PLAYER_ID); and groundBody.setUserData(GROUND_ID);
Create a ContactListener as a field in your scene:
private ContactListener mContactListener = new ContactListener() {
/**
* Called when two fixtures begin to touch.
*/
public void beginContact (Contact contact) {
final Body bodyA = contact.getFixtureA().getBody();
final Body bodyB = contact.getFixtureB().getBody();
if(bodyA.getUserData().equals(PLAYER_ID)) {
if(bodyB.getUserData().equals(GROUND_ID))
mIsJumping = false;
}
else if(bodyA.getUserData().equals(GROUND_ID)) {
if(bodyB.getUserData().equals(PLAYER_ID))
mIsJumping = false;
}
}
/**
* Called when two fixtures cease to touch.
*/
public void endContact (Contact contact) { }
/**
* This is called after a contact is updated.
*/
public void preSolve(Contact pContact) { }
/**
* This lets you inspect a contact after the solver is finished.
*/
public void postSolve(Contact pContact) { }
};
Lastly, register that contact listener: physicsWorld.setContactListener(mContactListener);
EDIT 3:
To move your sprite over the X axis, you can apply a force using Body.applyForce method, or apply an impulse using Body.applyLinearImpulse method. Play around with the arguments and find what works the next.
The vector should consist a X part only; Try Vector2 force = Vector2Pool.obtain(50, 0);. Then apply the force this way: body.applyForce(force, body.getWorldCenter());.