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?
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.