LibGDX flappy bird clone, Scene2d actor (button) - java

So, I am developing a flappy bird clone. I created a button which bounces the bird. What I also want for this button is to draw something (totally irrelevant at this point). But not each time the button is hit. I already tried this with:
if (Gdx.input.isTouched()) {
batcher.draw(birdRed, bird.getX(), bird.getY(),
bird.getWidth() / 2.0f, bird.getHeight() / 2.0f,
bird.getWidth(), bird.getHeight(), 1, 1, 1);
}
But this is now what I want. I want specifically to draw something when button is hit first time. I hope you understand my problem. Thanks for your help.

boolean touched = false;
...
if (Gdx.input.isTouched() && !touched) {
touched = true;
batcher.draw(birdRed, bird.getX(), bird.getY(),
bird.getWidth() / 2.0f, bird.getHeight() / 2.0f,
bird.getWidth(), bird.getHeight(), 1, 1, 1);
}

If you use button actor then you can handle change event and use some boolean flag to keep it's first time pressed state.
private boolean wasPressed = false;
Somewhere in button initialization code:
Button pushBirdButton = new Button();
pushBirdButton.addListener(new ChangeListener() {
#Override
public void changed(ChangeEvent event, Actor actor) {
wasPressed = true;
}
});
And then you can always check was you button pressed or not.

Related

The animation for my programatically created button acts weird in my application

So I am facing a weird bug I cannot explain - I cannot even reproduce it sometimes.
Basic context:
I have an application, which lists objects. Every object has a name and a point value. For every object, the addCustomSpinner function creates a "ticket" (a custom view, kind-of-spinner) and shows them in a scrollview so the user can select the one needed. There are four different 'containers' for four different kind of objects - so the layout can be populated with four kind of "ticket" package.
The data for the objects are collected from a database. The addCustomSpinner is called with a for cycle for every object in the database, and - Important - before the for method, the Layout it populates with the tickets is cleared (removeAllViews).
Inside addCustomSpinner, everything is created as "new" - like the button in question.
addCustomSpinner creates this button and adds a new onClickListener. Inside onClickListener, a new boolean is created - this is used to show a different animation when the button is clicked again. On first click (boolean = true), the arrow turns 180 degrees and faces upwards, on second click (boolean = false) the arrow turns 180 degrees and faces downwards. Works like a charm, until...
The bug I am facing:
Sometimes - as I already mentioned, not every time - if I click the button for one "ticket", then leave it 'opened' and click on an another one, and leave it 'opened' also, THEN I choose to populate the layout with a different kind of "ticket" package - The arrow faces upwards by default on every ticket in every package! Sometimes - again, just sometimes - with the same pattern I can turn it back, but it happens just "by accident".
I don't understand how the animation and state of the buttons can be connected, if every created ticket is new, every button is new, every onClickListener is new, and every boolean inside onClickListener is new. And if these are connected somehow, then why can that be that every behavior is "unique" for the buttons, nothing else shows any connection - even this is just a "sometimes" bug, a pretty rare one.
Can anybody help me why this happens?
What I tried:
Well, tried to trace the issue - but since it happens just by accident, I have no clue, I just searched if I can do anything else than the boolean to add different animation for the clicks. Sadly using ObjectAnimator is not a good solution for me - not the same result at least, since my animated arrow not only rotates, but it also changes its color. Shapeshifter seemed like a good idea to create animations easily, but now as I see it, maybe a simple rotation will be my ultimate solution.
Here's the code for the button:
customButton.setOnClickListener(new View.OnClickListener() {
boolean isCustomButtonClicked = true;
#Override
public void onClick(View v) {
if (isCustomButtonClicked) {
customButton.setImageResource(R.drawable.avd_anim_arrow_blue_back);
Drawable d = customButton.getDrawable();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (d instanceof AnimatedVectorDrawable) {
animArrowAnim = (AnimatedVectorDrawable) d;
animArrowAnim.start();
}
}
routeWhoClimbed.setVisibility(View.VISIBLE);
isCustomButtonClicked = false;
} else if (!isCustomButtonClicked) {
customButton.setImageResource(R.drawable.avd_anim_arrow_blue);
Drawable d = customButton.getDrawable();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (d instanceof AnimatedVectorDrawable) {
animArrowAnim = (AnimatedVectorDrawable) d;
animArrowAnim.start();
}
}
routeWhoClimbed.setVisibility(GONE);
isCustomButtonClicked = true;
}
}
});
EDIT:
The full addCustomSpinner():
private void addCustomSpinner(Routes mRouteItemToAdd, String placeName) {
//creating a new View for my custom layout created in xml
View customRoutesView = new View(this);
LinearLayout.LayoutParams customViewParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
customRoutesView.setLayoutParams(customViewParams);
customRoutesView = LayoutInflater.from(this).inflate(
R.layout.custom_view_layout, routeLayout, false
);
//Setting up the views inside the custom view
ImageView imageViewDiffImage = customRoutesView.findViewById(R.id.routeDiffImageView);
TextView textViewRouteName = customRoutesView.findViewById(R.id.routeNameTextView);
TextView textViewRouteDiff = customRoutesView.findViewById(R.id.routeDiffTextView);
ImageButton customButton = customRoutesView.findViewById(R.id.customButton);
RadioButton climberNameOne = customRoutesView.findViewById(R.id.climberNameOne);
RadioButton climberNameTwo = customRoutesView.findViewById(R.id.climberNameTwo);
Button climbedItButton = customRoutesView.findViewById(R.id.climbed_it_button);
RadioGroup climberNameRadioGroup = customRoutesView.findViewById(R.id.climberNameRadioGroup);
RadioGroup climbingStyleRadioGroup = customRoutesView.findViewById(R.id.styleNameRadioGroup);
RelativeLayout routeWhoClimbed = customRoutesView.findViewById(R.id.routeWhoClimbedRelativeLayout);
imageViewDiffImage.setImageResource(R.mipmap.muscle);
textViewRouteName.setText(mRouteItemToAdd.name);
textViewRouteDiff.setText("Difficulty: " + (int) mRouteItemToAdd.difficulty);
climberNameOne.setText(climberName1);
climberNameTwo.setText(climberName2);
routeWhoClimbed.setVisibility(GONE);
//Here comes the button with the animated image
customButton.setOnClickListener(new View.OnClickListener() {
boolean isCustomButtonClicked = true;
#Override
public void onClick(View v) {
if (isCustomButtonClicked) {
customButton.setImageResource(R.drawable.avd_anim_arrow_blue_back);
Drawable d = customButton.getDrawable();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (d instanceof AnimatedVectorDrawable) {
animArrowAnim = (AnimatedVectorDrawable) d;
animArrowAnim.start();
}
}
routeWhoClimbed.setVisibility(View.VISIBLE);
isCustomButtonClicked = false;
} else if (!isCustomButtonClicked) {
customButton.setImageResource(R.drawable.avd_anim_arrow_blue);
Drawable d = customButton.getDrawable();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (d instanceof AnimatedVectorDrawable) {
animArrowAnim = (AnimatedVectorDrawable) d;
animArrowAnim.start();
}
}
routeWhoClimbed.setVisibility(GONE);
isCustomButtonClicked = true;
}
}
});
//Button, works like an 'OK' or something, and I have no
//problem with this
climbedItButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int checkedNameButton = climberNameRadioGroup.getCheckedRadioButtonId();
int checkedStyleButton = climbingStyleRadioGroup.getCheckedRadioButtonId();
RadioButton checkedNameRadioButton = (RadioButton) findViewById(checkedNameButton);
RadioButton checkedStyleRadioButton = (RadioButton) findViewById(checkedStyleButton);
String checkedName = (String) checkedNameRadioButton.getText();
String checkedStyle = (String) checkedStyleRadioButton.getText();
addClimbToDatabase(user.getUid(), checkedName, mRouteItemToAdd, placeName, checkedStyle);
}
});
//And finally, I add this new "ticket" with the custom view to the layout i want to show it. Again, this also works like a charm, no problem here.
routeLayout.addView(customRoutesView);
}
Ultimately, I did not manage to understand the problem throughly, but I was able to eliminate it.
So during my fixing tries I narrowed down the problem to the animated drawable state - credit to #avalerio for his pro tip, but the answer wasn't addig an id to the button. I think somehow and sometime, the state of the first animation (turning the arrow 180 degrees) stuck in the end position - causing the other views using this animatedDrawable showing it in end position on start.
.reset() did not help, since it resets the animatedVectorDrawable object, not the animation xml drawable state. My solution is a kind of workaround, but it is working: when the custom-view 'ticket' is created with the animated-drawable-imagebutton, I set the imageResource of the button to a not-animated xml drawable - this drawable is basically the start position of my animated-drawable. This way, when the 'tickets' are generated, the imagebutton is 'hardcoded' in the start position.
Not elegant, but works. BUT(!) I would really appreciate if someone could explain to me how this weird behavior is possible - just sometimes, randomly, with no pattern I can reproduce intentionally.

libGDX textured disappearing when tapping home button

I am developing a mobile game for Android with libGDX. I have noticed that when I click the home button and then go back to the game, the player has disappeared. All other actors attached to the stage exist, and when I remove the code that moves the player it is also drawn. It only disappears when it is moving. I have done so much debugging, and sometimes the position seems to update corretly but the player is invisible, but sometimes it is just NaN. I have for example tried to save position and velocity in pause function and provide the player with them in the resume function, but nothing helps.
This is what I am doing in the player update function:
// When these lines are removed, the app works perfectly
velocity.add(0.0f, GRAVITY);
velocity.scl(deltaTime);
position.add(velocity);
velocity.scl(1/deltaTime);
It doesn't even help if I re-create the player in resume function
player2 = new Player(resourceManager.getRegion("player"), new Vector2(320.0f, 350.0f), 300.0f);
Finally I tried to create completely distinct player object that is drawn after the home button is clicked. It is visible, but is does not move:
player2 = new Player(resourceManager.getRegion("player"), new Vector2(320.0f, 350.0f), 300.0f);
Intrestingly enough I was dealing with the same issue today , every texture (actors) remains in screen after resume but not the main actor which was the one who was moving (main actor in my case). After hours debuging I found that when the game state changes between pause and resume the render method (mine looks like this) will get 0 for deltaTime:
#Override public void render(float deltaTime) {
update(delta);
spriteBatch.setProjectionMatrix(cam.combined);
spriteBatch.begin();
spriteBatch.draw(background, cam.position.x - (WIDTH / 2), 0, WIDTH, HEIGHT);
....
spriteBatch.end();}
deltaTime is time elapsed from last render.
apparently there is no time elapsed from pause to resume hence 0.
down the chain of actor updates I was updating my main actor like this
velocity.scl(deltaTime);
position.add(MOVEMENT * deltaTime, velocity.y);
which was passed 0 at very next render after resume hence the NaN (Not a Number).
anyway not sure if there is a better way to go about this or not but this is how I did, a simple check for zero deltaTime in my main actor update method and replace with a very small amount .001f : (please note in subsequent update deltaTime wont be zero and this if statment only get called once and exactly after resume)
public void update(float deltaTime) {
if (deltaTime <= 0) {
deltaTime=.001f;
}
velocity.scl(deltaTime);
position.add(MOVEMENT * deltaTime, velocity.y);
...
}
Oh yeah, I had the same issue before.
What I did was in onPause(), which is called when you press home button, I destroyed all assets references.
Then in onResume(), which is called when you're back, I reassigned the assets again.
render() method continue called when you press home button on Android so it's seems that you player position continue updating with gravity so when you resume your game you not able to see you player on screen so use a flag on position update code and change flag in pause() and resume() method of ApplicationListener interface.
enum GameState{
PAUSED, RESUMED
}
GameState gameState;
#Override
public void create() {
gameState=GameState.RESUMED;
...
}
#Override
public void render() {
Gdx.gl.glClearColor(1,1,1,1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
if(gameState==GameState.RESUMED) {
velocity.add(0.0f, GRAVITY);
velocity.scl(deltaTime);
position.add(velocity);
velocity.scl(1/deltaTime);
}
}
#Override
public void pause() {
gameState=GameState.PAUSED;
}
#Override
public void resume() {
gameState=GameState.RESUMED;
}

Andengine onAreaTouched TouchEvent not working - ACTION_OUTSIDE & ACTION_CANCEL

I have a simple problem. I have a button on the screen. I want isActionDown to push the button down, and isActionUp to push the button back up again and make my character jump. But the problem is if I push the button down and drag away from it, the button stays pushed down. I would like the button to push back up again if the user drags away from it.
How can I create the desired affect? isActionCancel doesn't seem to do the trick.
private void addButton(){
goB = new AnimatedSprite(240, 364, ResourceManager.getInstance().go_region, vbom){
#Override
public boolean onAreaTouched(final TouchEvent event, final float x, final float y){
if (event.isActionDown()){
goB.animate(new long[]{ 250 }, new int[] { 1 }, true);
/*if(event.isActionCancel()){
goB.animate(new long[]{ 250 }, new int[] { 0 }, true);
}*/
}
if (event.isActionUp()){
goB.animate(new long[]{ 250 }, new int[] { 0 }, true);
fresh = true;
playerBody.applyLinearImpulse(new Vector2(0, 45), playerBody.getPosition());
}
if (event.isActionCancel() || event.isActionOutside()){
goB.animate(new long[]{ 250 }, new int[] { 0 }, true);
}
return true;
}
};
goB.setScale(3);
registerTouchArea(goB);
attachChild(goB);
goB.animate(250);
}
I'm having the hardest time with this, and I've tried all sorts of variations of the code above. Please let me know if you have the solution.
Thanks for your help!
Use the
setTouchAreaBindingOnActionDownEnabled(true);
setTouchAreaBindingOnActionMoveEnabled(true);
functions

Focus ScrollView to selected position programmatically - Android

I have a ScrollView with a lot buttons. Each button is enabled when the user unlocks that button/level. I would like to focus the ScrollView on the latest unlocked button/level. See the screenshot below.
I found some functions like scrollTo() that could focus the ScrollView either at top, button or something like that but I would like to focus the ScrollView at certain places like the button ID that says Level 8 in the screenshot below. How would I go about doing this?
I think this should be good enough :
yourButtonView.getParent().requestChildFocus(yourButtonView,yourButtonView);
public void RequestChildFocus (View child, View focused)
child - The child of this ViewParent that wants focus. This view will contain the focused view. It is not necessarily the view that actually has focus.
focused - The view that is a descendant of child that actually has focus
First you need to know the position of item in scroll that has to get focus once you know that you can use following code to make that item focus
final int x;
final int y;
x = rowview[pos].getLeft();
y = rowView[pos].getTop();
yourScrollView.scrollTo(x, y);
refer this question
I agree with the benefits of previous answers. However, by experience, I have found this to be more complex than this. The setSelectionFromTop is very sensitive and often breaks if it is executed too early. This may depend on different reasons but the two primary reasons are that
If executed from within Activity lifecycle methods the views have not been loaded/configured yet.
View modifications triggered after the list move action seems to break the move. Probably overwriting some value before the move has been finalized due to a recalculation of the views.
The reasoning seems to apply for both setSelectionFromTop and setSelection() methods although
I tested mostly with setSelectionFromTop. smoothScrollToPosition() seems to be more robust, probably because it by definition changes the list position delayed whn doing the smooth scrolling.
Look at this example source code:
// Wee need to pospone the list move until all other view setup activities are finished
list.post(new Runnable(){
#override
public void run() {
list.setSelectionFromTop(selectedPosition, Math.max(0, Math.round(list.getHeight() / 3))); // Make sure selection is in middle of page, if possible.
// Make sure you do not modify (this or other) parts of the view afterwards - it may break the list move
// activityButtons.setVisibility(View.GONE);
}});
see: http://developer.android.com/reference/android/widget/ListView.html#setSelectionFromTop(int, int)
'int selectedLevel' holds the currently selected level, information is held in a Level class.
'values' is a list with all your levels, displayed by the list.
Example:
ListView lv = (ListView)findViewById(android.R.id.list);
int i = 0;
for (Level l : values) {
i++;
if (l.level == selectedLevel) {
lv.setSelectionFromTop(i, 200);
break;
}
}
try this
sc.post(new Runnable() {
public void run() {
sc.smoothScrollTo(x, y);
}
});
x the position where to scroll on the X axis
y the position where to scroll on the Y axis
Use this code, taken from this answer
private final void focusOnView(){
new Handler().post(new Runnable() {
#Override
public void run() {
your_scrollview.scrollTo(0, your_EditBox.getBottom());
}
});
}
Use this code, taken from my another answer
scrollViewSignup.post(new Runnable() {
#Override
public void run() {
int scrollY = scrollViewSignup.getScrollY();
scrollViewSignup.scrollTo(0, 0);
final Rect rect = new Rect(0, 0, view.getWidth(), view.getHeight());
view.requestRectangleOnScreen(rect, true);
int new_scrollY = scrollViewSignup.getScrollY();
scrollViewSignup.scrollTo(0, scrollY);
scrollViewSignup.smoothScrollTo(0, new_scrollY);
}
});
This code tries to smooth scroll and uses the standard behaviour of system to position to an item. You can simply change smoothScrollTo with scrollTo if you don't want the system to smooth scroll to the item. Or, you can use the code below only.
scrollViewSignup.post(new Runnable() {
#Override
public void run() {
scrollViewSignup.scrollTo(0, 0);
final Rect rect = new Rect(0, 0, view.getWidth(), view.getHeight());
view.requestRectangleOnScreen(rect, true);
}
});
Use the desired code block after trying, or, according to the need.

Using PathModifier or MoveYModifier to simulate sprite jumping

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());.

Categories

Resources