Search code examples
javaandroiduser-interfacelibgdxscene2d

Use actions instead Drawable with buttons in libGDX?


I'm developing a game in libGDX and I want to add custom buttons. When the button is pressed, I want to add an action like scaleBy, because it looks better like that in Android. I made a class called Trigger which is an empty actor with a listener that add actions to the actor when the Trigger is pressed, entered, released, exited... So I create a Group with two actors: a TextButton (Touchable.disabled) and the Trigger.

It actually works but I have a problem: I can't add groups in Dialog's button method.

I think a better solution is creating a class which extends TextButton or Button, but I don't know how to do it.

If you want the code, just tell.

Thanks.

EDIT: I tried to make the class:

private Runnable runnable;
private UIScreen screen;

public static TextButtonStyle transformStyle(Skin skin) {
    TextButtonStyle s = skin.get(TextButtonStyle.class);
    TextButtonStyle style = new TextButtonStyle(s.up, null, null, s.font);
    return style;
}

public static TextButtonStyle transformStyle(TextButtonStyle style) {
    TextButtonStyle s = new TextButtonStyle(style.up, null, null, style.font);
    return s;
}

public DTextButton(String text, Skin skin, UIScreen screen, Runnable runnable) {
    super(text, transformStyle(skin));
    this.runnable = runnable;
    this.screen = screen;
    addListener(new ButtonListener());
    setOrigin(Align.center);
    setTransform(true);
}

@Override
public void draw(Batch batch, float parentAlpha) {
    super.draw(batch, parentAlpha);
}

public class ButtonListener extends ClickListener {

    private boolean isPressed = false;
    private int lastButton = 0;

    public void press() {
        screen.setButtonPressed(true);
        UIFactory.applyPressedAction(DTextButton.this);
        isPressed = true;
    }

    public void release(){
        UIFactory.applyReleasedAction(DTextButton.this);
        isPressed = false;
    }

    @Override
    public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
        if(button == Buttons.LEFT)
            press();
        return true;
    }

    @Override
    public void enter(InputEvent event, float x, float y, int pointer, Actor fromActor) {
        lastButton = (Gdx.input.isButtonPressed(Buttons.LEFT)) ? Buttons.LEFT : -1;
        if(pointer == 0 && !isPressed && screen.wasButtonPressed())
            press();
    }

    @Override
    public void exit(InputEvent event, float x, float y, int pointer, Actor toActor) {
        if(toActor == DTextButton.this && lastButton == Buttons.LEFT){
            Gdx.app.postRunnable(runnable);
        }
        if(pointer == 0 && isPressed)
            release();
    }

    @Override
    public void touchUp(InputEvent event, float x, float y, int pointer, int button) {
        lastButton = button;
        if(pointer == 0 && isPressed && button == Buttons.LEFT)
            release();
        screen.setButtonPressed(false);
    }

}

UIScreen:

public interface UIScreen {

/**
 * Return if any UI is pressed.
 * @return return buttonPressed
 */
public boolean wasButtonPressed();

/**
 * Set if any UI is pressed.
 * @param pressed if pressed or not.
 */
public void setButtonPressed(boolean pressed);

}

UIFactory:

public static void applyPressedAction(Actor actor) {
    actor.addAction(Actions.scaleBy(-0.2f, -0.2f, 0.2f, Interpolation.pow2Out));
}

public static void applyReleasedAction(Actor actor) {
    actor.addAction(Actions.scaleBy(0.2f, 0.2f, 0.3f, Interpolation.swingOut));
}

So I only used style.up and I added the same Trigger's listener. The listener doesn't work well. Any suggestion?


Solution

  • I think you're over-complicating this. You can merely add an input listener to the button and make sure it's set to transform. And you probably want its origin centered. No need for subclasses or wrapper Groups or anything like that.

    Also, you want to scale to, not by, to avoid error building up as you change states multiple times.

    Here's a helper method:

    public static void makeButtonScale (final Button button, final float hoverScale, final float pressedScale, final float duration){
        button.setTransform(true);
        button.addListener(new ClickListener(){
            void scaleTo (float targetScale){
                button.setOrigin(Align.center);
                button.clearActions();
                button.addAction(Actions.scaleTo(targetScale, targetScale, duration, Interpolation.fade));
            }
    
            public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
                super.touchDown(event, x, y, pointer, button);
                scaleTo(pressedScale);
                return true;
            }
    
            public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
                super.touchUp(event, x, y, pointer, button);
                scaleTo(isOver() ? hoverScale : 1f);
            }
    
            public void enter (InputEvent event, float x, float y, int pointer, Actor fromActor) {
                super.enter(event, x, y, pointer, fromActor);
                scaleTo(isPressed() ? pressedScale : hoverScale);
            }
    
            public void exit (InputEvent event, float x, float y, int pointer, Actor toActor) {
                super.exit(event, x, y, pointer, toActor);
                scaleTo(1f);
            }
        });
    

    Example usage:

    makeButtonScale(myDialogButton, 1.2f, 1.5f, 0.2f);
    

    Note, this will not look very good unless you are using a linear mag filter on your texture atlas.