I'm having a hard time getting events working with my Actor in libgdx. I'm using nightly builds.
My stage is setup in the show() method of a Screen subclass:
stage = new Stage(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), true);
Gdx.input.setInputProcessor(stage);
TestActor actor = new TestActor();
stage.addActor(actor);
And my actor class looks like:
class TestActor extends Actor {
private Sprite sprite;
private TextureAtlas atlas;
public TestActor() {
atlas = new TextureAtlas(Gdx.files.internal("textures/images-packed.atlas"));
sprite = atlas.createSprite("logo-96");
setTouchable(Touchable.enabled);
addListener(new InputListener() {
public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
Gdx.app.debug(TestGame.TAG, "TestActor.touchDown()");
return true; // must return true for touchUp event to occur
}
public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
Gdx.app.debug(TestGame.TAG, "TestActor.touchUp()");
}
});
}
#Override
public void draw(SpriteBatch batch, float parentAlpha) {
Color color = getColor();
batch.setColor(color.r, color.g, color.b, color.a * parentAlpha);
batch.draw(sprite, getX(), getY());
}
}
The events don't seem to fire. Oddly enough, I've used built-in UI widgets like the TextButton and can get those events to fire just fine. Can anybody see what I'm doing wrong?
You should also setBounds to your actor.
best way to do it (if you want the same size as your texture)
add these lines to your constructor :
setWidth(sprite.getWidth());
setHeight(sprite.getHeight());
setBounds(0, 0, getWidth(), getHeight());
notice you can also set the location of the bounds with the first 2 parameters.
Related
I'm currently making a 2d infinite runner esque game that has a release date of Halloween. I've nearly finished the game itself and I've just added a main menu and am currently in the process of making a pause menu. I'm having a big issue with setting the screens though. Setting the screen from my main menu into the game works perfectly fine but when I set the screen back to the main menu a few weird things happen:
Nothing in the spriteBatch renders, everything is just a black box (MainMenuScreen)
If I choose to go back to the game I find that not everything is reset like when the screen is initially set to GameScreen
Here's a video of the issue
Here's the code for how I switch screens:
From when the game is first opened to main menu:
public class RadiationPigeon extends Game {
public static final float PPM = 100;
public static SpriteBatch batch;
#Override
public void create () {
batch = new SpriteBatch();
setScreen(new MainMenuScreen(this));
}
#Override
public void render () {
super.render();
}
#Override
public void dispose () {
batch.dispose();
}}
From MainMenuScreen to GameScreen:
private RadiationPigeon radiationPigeon;
public MainMenuScreen(RadiationPigeon radiationPigeon){
this.radiationPigeon = radiationPigeon;
}
playButton.addListener(new InputListener() {
public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
return true;
}
public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
radiationPigeon.setScreen(new GameScreen(radiationPigeon));
//I give GameScreen radiationpigeon so that the screen can be set back to MainMenu
}
});
From GameScreen to MainMenu:
private RadiationPigeon radiationPigeon;
public GameScreen(RadiationPigeon radiationPigeon){
world.setContactListener(new ContactListener());
Timer timer = new Timer();
timer.schedule(new BatAttackChanceCounter(), 0, 1000);
this.radiationPigeon = radiationPigeon;
}
pauseButton.addListener(new InputListener() {
public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
return true;
}
public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
//paused = true;
radiationPigeon.setScreen(new MainMenuScreen(radiationPigeon));
}
});
Just for reference, here's my render method of both screens:
MainMenuScreen:
#Override
public void render(float delta) {
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
stage.act();
RadiationPigeon.batch.begin();
stage.draw();
RadiationPigeon.batch.end();
}
GameScreen:
#Override
public void render(float delta) {
update(delta);
Gdx.gl.glClearColor(0, 0,0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
RadiationPigeon.batch.setProjectionMatrix(pigeoncam.combined);
RadiationPigeon.batch.begin();
//RadiationPigeon.batch.draw(pausedScreen, pigeoncam.position.x - (250 / RadiationPigeon.PPM), pigeoncam.position.y - pigeoncam.viewportHeight / 2, 5, 5);
RadiationPigeon.batch.end();
b2dr.render(world, pigeoncam.combined);
stage.draw();
hud.stage.draw();
}
After lots of trial and error I finally resolved my issue. There was nothing wrong with how I set my screens (for anyone having trouble with setting screens the code I provided works). The problem was with the actual buttons and images. I had made some of them static because I needed to use the same buttonstyles and fonts in GameScreen. I thought that would save memory, however, once they were accessed outside the screen in which they were created (MainMenuScreen) they would lose all their values essentially making them empty without making them null.
A lack of understanding on how buttons worked is what caused the issue
EDIT:
Turns out a lack of understanding on how static variables work was the real issue. On android when you minimize an android app the java cycle doesn't stop and as such the items are "kept alive"
I want to use the exit() method in the InputListener to see wheter the cursor is inside the button or not.
Here is the explanation in the libGDX docs.
public void exit(InputEvent event, float x, float y, int pointer, Actor toActor)
Called any time the mouse cursor or a finger touch is moved out of an actor.
But when I put my cursor on the button and then move it outside the button, the method is not called. I am testing it by a System.out.println("exited"); and I get nothing in the console.
EDIT:
LibGDX Version: Latest Stable Nightlies
InputListener implementation:
//This button class is a custom class to make button creation easier. This is the constructor.
public Button(Vector2 position, String packLocation, String text, Stage stage, BitmapFont font, Color color) {
//Removed buttonStyle creation etc. to shorten the code.
button = new TextButton(text, buttonStyle);
button.setPosition(position.x, position.y);
stage.addActor(button);
Gdx.input.setInputProcessor(stage);
pressed = false;
button.addListener(new ClickListener() {
#Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
pressed = true;
return true;
}
#Override
public void touchUp(InputEvent event, float x, float y, int pointer, int button) {
pressed = false;
}
#Override
public void exit(InputEvent event, float x, float y, int pointer, Actor toActor) {
System.out.println("exited");
}
});
}
EDIT:
Hovering over the button also does not change the texture of the button as I set it to like so:
buttonStyle.over = skin.getDrawable("over");
But clicking does.
After searching for a couple hours I finally found what was missing. stage.act(); had to be called in the render method. This both gave functionality to the texture change when we hover over the button and also the enter/exit methods in the InputListener.
public void render(float delta) {
Gdx.gl.glClearColor(0, 0, 0.2f, 1);
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
stage.act(delta);
batch.setProjectionMatrix(camera.combined);
batch.begin();
if (lifeCount > 0) {
/*
When stage.draw is called here, it only displays the exit button.
The game still operates, but everything is invisible.
If I leave the game on, the game over screen is shown,
*/
stage.draw();
text.setColor(1.0f, 1.0f, 1.0f, 1.0f);
text.draw(batch, score + scoreCount, 25, 100);
text.draw(batch, lives + lifeCount, 25, 120);
text.draw(batch, speed + raindropSpeed, 25, 80);
batch.draw(bucketTexture, bucket.x, bucket.y);
for (Rectangle clearDrop : clearDrops) {
batch.draw(clearDropTexture, clearDrop.x, clearDrop.y);
}
for (Rectangle healthDrop : healthDrops) {
batch.draw(healthDropTexture, healthDrop.x, healthDrop.y);
/*
If I place stage.draw here, health drops are invisible.
This also happens if I place it in the raindrop for-loop and the
cleardrop for-loop
*/
}
for (Rectangle raindrop : raindrops) {
batch.draw(raindropTexture, raindrop.x, raindrop.y);
}
/*
If I place stage.draw here, the bucket, score, life, and speed
display correctly. The drops are still invisible.
*/
} else {
pause();
raindrops.clear();
game.setScreen(new GameOver(game));
}
batch.end();
What I have been trying to do is have an exit button in the top right corner of the GameScreen, although drawing the stage which the button resides in gives me difficulties (see comments in code).
Here is my code for the exit button and stage (resize()):
if (stage == null)
stage = new Stage(width, height, true);
stage.clear();
Gdx.input.setInputProcessor(stage);
TextButtonStyle styleQuit = new TextButtonStyle();
styleQuit.up = skin.getDrawable("buttonnormal");
styleQuit.down = skin.getDrawable("buttonpressed");
styleQuit.font = text;
quitButton = new TextButton(" ", styleQuit);
quitButton.setWidth(128);
quitButton.setHeight(128);
quitButton.setX(800 - 128);
quitButton.setY(480 - 100);
quitButton.addListener(new InputListener() {
public boolean touchDown(InputEvent event, float x, float y,
int pointer, int button) {
return true;
}
public void touchUp(InputEvent event, float x, float y,
int pointer, int button) {
Gdx.app.log(RainCatcher.LOG, "Quit Button Pressed");
game.setScreen(new MainMenu(game));
}
});
stage.addActor(quitButton);
And the rest (in show())
atlas = new TextureAtlas("gamebuttons.pack");
skin = new Skin();
skin.addRegions(atlas);
text = new BitmapFont();
Is there any special trick to allow a stage to be rendered alongside with the falling raindrops, bucket, and text? My friends and I have been stumped and couldn't find a solution anywhere.
Move stage.draw() after batch.end() or before batch.begin()
This is not the right approach you're taking in my opinion. If you're using a stage in libgdx, then you should benefit from other components of Scene2d. So, rather than drawing your raindrops and other game entities separately (and making your job complicated), you should make them actors and add them to stage wherever you need and then draw the stage in the render.
For example:
public class RaindDrop extends Actor {
TextureRegion region;
public RaindDrop () {
region = new TextureRegion(...);
}
public void draw (SpriteBatch batch, float parentAlpha) {
Color color = getColor();
batch.setColor(color.r, color.g, color.b, color.a * parentAlpha);
batch.draw(...);
}
}
So on for other entities. Initialise and add them to your stage.
Here's the official wiki to read more:
https://code.google.com/p/libgdx/wiki/scene2d
I've created a simple test with a touchDown() event on an image actor. The event works on the stage but not on the actor.
Here is the code:
I set the InputProcessor in the constructor of the parent class (AbstractScreen).
Gdx.input.setInputProcessor(this.stage);
In the sub class:
private TextureAtlas atlas;
private Image boardImg;
public void show(){
super.show();
atlas = new TextureAtlas(Gdx.files.internal("data/packs/mainMenu.atlas"));
AtlasRegion board = atlas.findRegion("board");
boardImg = new Image(board);
boardImg.addListener(new InputListener(){
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button){
Gdx.app.log("aaaa", "down");
return true;
}
});
stage.addActor(boardImg);
stage.addListener(new InputListener() {
public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
Gdx.app.log("aaaa", "stage down");
return true;
}
});
}
#Override
public void resize(int width, int height) {
super.resize(width, height);
boardImg.setPosition(stage.getWidth()/2 - boardImg.getWidth()/2, stage.getHeight()/4);
}
I'm testing both on the desktop and on the android (same results).
I get the "stage down" event but not the event of the actor. I've tried also without having the stage event.
Do all of these works:
boardImg.setTouchable(Touchable.enabled)
return false in stage listener or clear its listener.
do not call setInputProcessor(this.stage) in parent class instead call in child class.
I think the problem is because the touchDown() method of the stage returns true, and so the event will be "consumed" and won't be propagated to its children, the actors, and so the touchDown() method of boardImg will not be called.
Image doesn't receive touch events because it has no size.
Read documentation carefully:
Note that the actor must specify its bounds in order to receive input
events within those bounds.
Just know that boardImg = new Image(board) won't set width and height of actor for you. So you need to make it manualy. Assume you work with pixel perfect sizes:
boardImg = new Image(board);
boardImg.setWidth(board.getWidth());
boardImg.setHeight(board.getHeight());
That will do the trick. Good luck.
I would like to remove the body when the item is clicked. As you see i have it set up as a wallfixture.
When the item is clicked the sprite is removed, now i just would like to remove the body also..
public void addSprites(Scene scene, int x,int y,int width,int height,String type,Body body){
Sprite sprite = null;
if(type.equals(TAG_ENTITY_ATTRIBUTE_TYPE_VALUE_WOOD)) {
sprite = new Sprite(x, y, width, height, this.wood, this.getVertexBufferObjectManager()){
#Override
public boolean onAreaTouched(final TouchEvent pSceneTouchEvent, final float pTouchAreaLocalX, final float pTouchAreaLocalY) {
mScene.detachChild(this);
mPhysicsWorld.destroyBody(woodBody);
return true;
}
};
final FixtureDef wallFixtureDef = PhysicsFactory.createFixtureDef(0, 0.5f, 0.5f);
woodBody = PhysicsFactory.createBoxBody(this.mPhysicsWorld, sprite, BodyType.StaticBody, wallFixtureDef);
mScene.registerTouchArea(sprite);
Log.e("TYPE", "Wood");
}
scene.attachChild(sprite);
}
What i want to do is when the item is clicked i would like to remove the sprite & the body it is attached to. The code i have works but the only problem is it removes ALL of the bodies, i use the method to attach items to a level, so its a total of 3 items, and when one is clicked the body is removed from ALL of the sprites, when it should just be the one clicked.
Anyone know how to go about doing this?
I am not sure but probably that happens because you use the same variable for all bodies
woodBody = PhysicsFactory.createBoxBody(this.mPhysicsWorld, sprite, BodyType.StaticBody, wallFixtureDef);
To do good connection between body and sprite I suggest you to extend Sprite and declare body variable inside your class. For example I do this in that way
public class Ball extends Sprite{
final FixtureDef ballFixtureDef = PhysicsFactory.createFixtureDef(1.0f, 0.0f, 0.0f, false, Main.CATEGORYBIT_BALL, Main.MASKBITS_BALL, (short)0);
Body body;
float velocityX, velocityY;
int type;
public Ball(float pX, float pY, TextureRegion pTextureRegion, PhysicsWorld pWorld, float velocityX, float velocityY, int type)
{
super(pX, pY, pTextureRegion);
this.type = type;
this.velocityX = velocityX;
this.velocityY = velocityY;
body = PhysicsFactory.createCircleBody(pWorld, this, BodyType.DynamicBody, ballFixtureDef);
body.setUserData(Ball.this);
pWorld.registerPhysicsConnector(new PhysicsConnector(this, body, true, true));
}
}
and method for destroy sprite with body
private void destroyBall(final Ball ball)
{
this.runOnUpdateThread(new Runnable(){
#Override
public void run() {
final Body body = ball.body;
mPhysicsWorld.unregisterPhysicsConnector(mPhysicsWorld.getPhysicsConnectorManager().findPhysicsConnectorByShape(ball));
mPhysicsWorld.destroyBody(body);
mScene.detachChild(ball);
ballsList.remove(ball);
}});
}
and you just create object in that way
Ball b = new Ball(float pX, float pY, TextureRegion pTextureRegion, PhysicsWorld pWorld, float velocityX, float velocityY, int type)
{
#Override
public boolean onAreaTouched(final TouchEvent pSceneTouchEvent, final float pTouchAreaLocalX, final float pTouchAreaLocalY)
{
destroyBall(this);
}
}
I'm not exactly sure, but I think you need to flag items or objects that need to be removed first. Then when the game updates it should go through all of the objects and check which ones are flagged and then remove them. I'm new to this too but here goes nothing:
So a good way to do this in AndEngine is to first make an IUpdateHandler like so:
/**
* This wil get called every frame and is placed inside of your
* main game activity or where ever you want.
**/
public IUpdateHandler getCollisionUpdateHandler(){
return new IUpdateHandler(){
#Override
public void onUpdate(float pSecondsElapsed) {
// loop through all of your sprites
for(int i=0; i<GameActivity.this.spriteList.length; i++) {
//if sprite is flagged for deletion, begin removal
if( GameActivity.this.spriteList.get(i).isDelete()){
// get the next sprite to delete
final mSprite Object = GameActivity.this.spriteList.get(i);
// can remove it from the list now
Game.this.spriteList.remove(i);
final Scene scene = GameActivity.this.mEngine.getScene();
final Body body = GameActivity.this.mPhysicsWorld.getPhysicsConnectorManager().findBodyByShape(Object);
mPhysicsWorld.destroyBody(body);
//scene.getLayer(0).removeEntity(Object);
//scene.getLayer(1).removeEntity(Object);
scene.detachChild( Object );
}
}
}
}
}
Then simply register that update handler. Like so:
mScene.registerUpdateHandler( this.getCollisionUpdateHandler() );
So now whenever you want to delete a sprite (when it's pressed) then you simply change your method to:
#Override
public boolean onAreaTouched(final TouchEvent pSceneTouchEvent, final float pTouchAreaLocalX, final float pTouchAreaLocalY) {
MainActivity.spriteList.add( this );
this.delete = true;
return true;
}
Hope that helps. Here's where I got it from: (I simply tried to make it fit your problem :) )
www.andengine.org/forums/tutorials/box2d-collision-and-removal-t523.html