Search code examples
libgdxscreenactorstage

LibGDX Stage and Actor, Events and Actor properties


I'm just starting android game development with LibGdx framework. I read many online tutorial so far and the more I read the more I got confused: ApplicationListener, ApplicationAdapter, Stages, Game, Screens, Actors, Sprites, Images... not mentioning Input and Gesture listeners of all king).

I finally understood what kind of "model" I should use for the game I have in mind (a kind of puzzle game): Game, Screens, Stage and Actor.

So here is my first code.

This is the main application (Game):

package com.my.game1;
import com.badlogic.gdx.Game;

public class MyGame extends Game {

    @Override
    public void create () {
        setScreen(new StarterScreen());
    } 
}

This is the main screen class:

package com.my.game1;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.scenes.scene2d.Stage;

public class StarterScreen implements Screen {

    private Stage stage;
    private float screenW, screenH;
    private Tess tessera;

    @Override
    public void show() {
        tessera = new Tess("image.png");
        stage = new Stage();
        screenW = stage.getViewport().getWorldWidth();
        screenH = stage.getViewport().getWorldHeight();
        Gdx.input.setInputProcessor(stage);
        stage.addActor(tessera);
    }

    @Override
    public void render(float delta) {
        Gdx.gl.glClearColor(0,0,0,1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
        stage.act();
        stage.draw();
    }

    @Override
    public void resize(int width, int height) {
        // TODO Auto-generated method stub

    }

    @Override
    public void pause() {
        // TODO Auto-generated method stub

    }

    @Override
    public void resume() {
        // TODO Auto-generated method stub

    }

    @Override
    public void hide() {
        dispose();
    }

    @Override
    public void dispose() {
        stage.dispose();
    }  

}

And the following is the class that extends Actor:

package com.my.game1;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.utils.ActorGestureListener;

public class Tess extends Actor {

    private Texture texture;
    private boolean selected = false;

    public Tess (String img) {
        this.texture = new Texture(Gdx.files.internal(img));
        this.setBounds(0f, 0f, this.texture.getWidth(), this.texture.getHeight());
        this.setOrigin(this.texture.getWidth() / 2, this.texture.getHeight() / 2);
        this.setScale(0.25f);
        this.addListener(new ActorGestureListener() {
            public void tap(InputEvent event, float x, float y, int pointer, int button) {
                ((Tess)event.getTarget()).toggleSelect();
                ((Tess)event.getTarget()).setColor(0.5f, 0f, 0.5f, 1f);
            }
        });
    }

    @Override
    public void draw(Batch batch, float alpha){
        batch.draw(texture, 0, 0);
    }

    public void finalize() {
        this.texture.dispose();
    }

    public void toggleSelect(){
        this.selected = !this.selected;
        if (this.selected == true)
            this.setColor(0.5f, 0f, 0.5f, 1f);
        else
            this.setColor(0f, 0f, 0f, 0f);          
    }
}

The screen shows correctly the actor, but I cannot set the Actor's position or its scale, nor the "tap" event seems to get detected; and the color doesn't change.

What I did wrong?


Solution

  • Several things were wrong. First, just on the side, you don't want to call dispose() from the Screen's hide() method. hide() can be called simply when the screen is turned off, or when the app is switched to the background, and disposing of the Screen during that would cause serious issues on resume.

    With that out of the way, here's what your Actor should have looked like:

    package com.my.game1;
    
    import com.badlogic.gdx.Gdx;
    import com.badlogic.gdx.graphics.Texture;
    import com.badlogic.gdx.graphics.g2d.Batch;
    import com.badlogic.gdx.graphics.g2d.Sprite;
    import com.badlogic.gdx.scenes.scene2d.Actor;
    import com.badlogic.gdx.scenes.scene2d.InputEvent;
    import com.badlogic.gdx.scenes.scene2d.InputListener;
    import com.badlogic.gdx.scenes.scene2d.Touchable;
    
    public class Tess extends Actor {
    
        private Sprite sprite;
        private boolean selected = false;
    
        public Tess (String img) {
            this.sprite = new Sprite(new Texture(Gdx.files.internal(img)));
            this.setTouchable(Touchable.enabled);
            this.setBounds(this.sprite.getX(), this.sprite.getY(), this.sprite.getWidth(), this.sprite.getHeight());
            this.setOrigin(this.sprite.getWidth() / 2, this.sprite.getHeight() / 2);
            this.setScale(0.25f);
            this.addListener(new ActorGestureListener() {
                @Override
                public void tap (InputEvent event, float x, float y, int pointer, int button) {
                    ((Tess)event.getTarget()).toggleSelect();
                }
            });
        }
    
        @Override
        public void draw(Batch batch, float alpha){
            sprite.draw(batch);
        }
    
        @Override
        public void positionChanged(){
            sprite.setPosition(getX(), getY());
        }
    
        public void toggleSelect(){
            this.selected = !this.selected;
            if (this.selected == true)
                sprite.setColor(0.5f, 0f, 0.5f, 1f);
            else
                sprite.setColor(0f, 0f, 0f, 0f);          
        }
    }
    

    First thing changed: you should use a Sprite, not a Texture, to handle color, drawing and transformations easily. Texture is possible, but is not as straightforward as Sprite is.

    Next, you need to call setTouchable(Touchable.enabled) inside the actor to actually enable hit detection. Without this, no touch events are passed to the Actor.

    After that, with setBounds(), you need to use sprite.getX() and sprite.getY(), to utilize the Sprite's positional values. Setting them to any arbitrary number seems to disable any touch capacity for that Actor.

    Another thing, if all of that had been OK, is that you were setting the color twice for each touch, once based on the selected field, and then immediately after straight to the dark purple, so I removed the second set and just used your toggle method.

    Next, since we have a Sprite now, we can use the draw() method attached to the Sprite itself and feed it the Batch, instead of calling the Batch's draw.

    Finally, when you want to change the position of the image, call setPosition on the actor itself, and utilize an override of the positionChanged() method to set the Sprite's position based on the Actor's new position.