Make actor clip child Image - libgdx

I want an actor that draws a drawable, but clips it to the size of the actor. I'm deriving this class from Widget, and using some hard-coded values as a simple test:
public class MyWidget extends Widget {
public MyWidget(Drawable drawable) {
setDrawable(drawable);
setSize(100, 100);
}
public void draw(Batch batch, float parentAlpha) {
clipBegin(getX(), getY(), 20, 20);
drawable.draw(batch, getX(), getY(), 500, 500);
clipEnd();
}
}
No clipping is performed though, the drawable spills out of the bounds of the actor. This actor is part of a Table, if it matters. I believe I am using the clipBegin() / clipEnd() methods incorrectly. What is the right way to do this?
Thanks

This is what I found based on the comments and my own experimentation. It's important to flush the batch before starting the clip and after drawing to ensure the proper drawing gets clipped.
public void draw(Batch batch, float parentAlpha) {
batch.flush();
if (clipBegin(getX(), getY(), 20.0f, 20.0f)) {
//do your drawing here
drawable.draw(batch, getX(), getY(), 500, 500);
batch.flush();
clipEnd();
}
}

Related

Actor in Stage Does Not Update the MoveTo XY Location

I am creating a game wherein an apple is being shot with an arrow. The apple's location is the XY location of the user input and the arrow actor has to move to that location using the code actor.moveto. The problem is the arrow only moves only once to the user input's direction. I know that the moveTo action of the actor is updated many times per second when I called stageArrow.act in the update method so I am wondering why the arrow only moves once. Here's my code:
appleclass.java
public class AppleClass implements Screen {
Arrow arrow;
private final MainApp app;
public Image ShotImage;
public AppleClass(final MainApp app){
this.app = app;
this.stageApple = new Stage(new StretchViewport(app.screenWidth,app.screenHeight , app.camera));
this.stageArrow =new Stage(new StretchViewport(app.screenWidth,app.screenHeight , app.camera));
arrow = new ArrowClass(app);
}
#Override
public void show() {
InputMultiplexer inputMultiplexer = new InputMultiplexer();
inputMultiplexer.addProcessor(stageApple);
inputMultiplexer.addProcessor(stageArrow);
Gdx.input.setInputProcessor(inputMultiplexer);
arrow();
}
public void arrow(){
arrow.isTouchable();
stageArrow.addActor(arrow);
arrow.addAction((moveTo(Gdx.input.getX(),Gdx.input.getY(),0.3f))); //===> only executes once.
arrow.addListener(new InputListener(){
public void enter(InputEvent event, float x, float y, int pointer, Actor fromActor){
if (Gdx.input.isTouched()){
ShotImage.setVisible(true);
}
}
});}
}
#Override
public void render(float delta) {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
update(delta);
}
public void update(float deltaTime){
stageApple.draw();
stageArrow.draw();
stageApple.act(deltaTime);
stageArrow.act(deltaTime);
}
ArrowClass.java
public class ArrowClass extends Actor {
MainApp app;
AppleClass appleClass;
public Texture arrowTexture;
public ArrowClass(final MainApp app){
this.app = app;
arrowTexture = new Texture("medievalarrow.png");
this.setSize(arrowWidth, arrowHeight);
this.setTouchable(Touchable.enabled);
this.setBounds(app.screenWidth*0.45f,0,arrowWidth,arrowHeight);
this.setOrigin(0,0);
}
#Override
public void draw(Batch batch, float parentAlpha) {
super.draw(batch, parentAlpha);
final float delta = Gdx.graphics.getDeltaTime();
this.act(delta);
app.batch.begin();
app.batch.draw(arrowTexture, getX(),getY(),getWidth(),getHeight());
app.batch.end();
}
}
Any help will be highly appreciated. Thanks.
I think the problem is because you are calling this.act(delta) in your ArrowClass' draw method. When you call Stage#act(), it will call the act method on all of its actors for you. Since you're calling it once when you draw and again when you update the stage, it's moving at twice the normal speed and that could be causing it to reach its destination prematurely.
A few other comments about your code, if I may:
First, you probably don't want two separate stages- unless you're using Scene2D.UI, you would normally have a single stage with Arrow and Apple added to it as actors.
Second, when you override Actor#draw(), you should use the batch it passes you to do the rendering instead of using the one from app. You also don't want to call begin() and end() inside your draw method- these are 'expensive' and you only want to call them once per frame. However, if you just use the batch that is passed to draw(), the Stage class will handle beginning and ending for you and you won't need to call them explicitly.
Third, you actually don't need to call super.draw(batch, parentAlpha) because it's an empty method in the Actor class.
Thus, your class could be simplified to the following:
public class ArrowClass extends Actor {
AppleClass appleClass; // You never set this; you may not need it
Texture arrowTexture;
public ArrowClass(MainApp app, arrowWidth, arrowHeight) {
arrowTexture = new Texture("medievalarrow.png");
this.setSize(arrowWidth, arrowHeight);
this.setTouchable(Touchable.enabled);
this.setBounds(app.screenWidth*0.45f,0,arrowWidth,arrowHeight);
this.setOrigin(0,0);
}
#Override
public void draw(Batch batch, float parentAlpha) {
batch.draw(arrowTexture, getX(),getY(),getWidth(),getHeight());
}
}

Some Actions do not work

I want to make a text appear in the center of the screen, indicating
the current level. It should appear gradually and after a while disappear gradually. I'm using scene2d with stages, actors.. so i would use Actions.
This is what i have now:
public class TextActor extends Actor {
private BitmapFont font;
private CharSequence charSequence;
public TextActor(CharSequence charSequence) {
font = new BitmapFont(Gdx.files.internal("fonts/white_standard_font.fnt"));
this.charSequence = charSequence;
}
#Override
public void act(float delta) {
super.act(delta);
}
#Override
public void draw(Batch batch, float delta) {
super.draw(batch, delta);
font.draw(batch, charSequence, getX(), getY());
}
}
In the class that creates the TextActor..
textActor.addAction(Actions.sequence(Actions.fadeIn(1f), Actions.delay(1f), Actions.fadeOut(1f), new Action() {
#Override
public boolean act(float delta) {
textActor.remove();
transitionInProgress = false;
gameState = GameState.RUNNING;
Gdx.input.setInputProcessor(stage);
return true;
}
}));
gameTable.addActor(textActor);
fadeIn, fadeOut, alpha.. don't work. I tried with "moveBy" and it works, so it seems a problem concerning the appearance of the Actor. There is something that escapes me.
The fade actions modify the alpha value of the Actor's color (getColor().a). You're drawing the font directly without applying the color associated with the actor.
Take a look at how Label.draw is implemented for a better understanding. In the meantime, just try adding this above your font.draw(...) call:
font.setColor(getColor())
Or, if you don't want to modify the entire color, just the alpha, try this:
font.getColor().a = getColor().a;
UPDATE:
Also note that you should apply the parentAlpha (second parameter of draw - labelled as delta in your example) to the final alpha:
font.getColor().a = getColor().a * parentAlpha
This allows your actor to fade if you change the alpha of the stage or any parents.

How to use SpriteBatch and ShapeRenderer in one screen?

I'm using SpriteBatch to draw textures and ShapeRenderer to draw some shape.
Here is my code in an actor
#Override
public void draw(Batch batch, float parentAlpha) {
batch.end();
Gdx.gl.glEnable(GL20.GL_ARRAY_BUFFER_BINDING);
Gdx.gl.glEnable(GL20.GL_BLEND);
Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
shapeRenderer.begin(ShapeType.Filled);
//change color
shapeRenderer.setColor(color);
shapeRenderer.rect(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
shapeRenderer.end();
Gdx.gl.glDisable(GL20.GL_BLEND);
batch.begin();
}
and call stage.draw() on the screen
#Override
public void render(float delta) {
Gdx.gl20.glClearColor(0, 0, 0, 0);
Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
Gdx.gl20.glEnable(GL20.GL_TEXTURE_2D);
stage.act(delta);
stage.draw();
//......
}
It's working but unpredictably throw exception:
STACK_TRACE=java.lang.IllegalStateException: SpriteBatch.end must be called before begin.
at com.badlogic.gdx.graphics.g2d.SpriteBatch.begin(SpriteBatch.java:164)
at com.badlogic.gdx.scenes.scene2d.Stage.draw(Stage.java:127)
at c.i.a.a(AbstractCardRoomRenderer.java:3078)
at c.i.s.a(TLMNCardRoomRenderer.java:1158)
at c.j.e.render(GameScreen.java:22)
at com.badlogic.gdx.Game.render(Game.java:46)
at com.badlogic.gdx.backends.android.AndroidGraphics.onDrawFrame(AndroidGraphics.java:422)
at android.opengl.GLSurfaceView$GLThread.guardedRun(GLSurfaceView.java:1522)
at android.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1239)
EDIT: For more details what I'm doing:
What I want is to draw a shape. Because the stage's batch is drawing so I have to end it for shape drawing. My code still work but sometime, another actor, I think, use stage's batch to draw something else. It make the stage begin its batch. So it conflict between begin and end.
For example, the actor draw method:
batch.end();
//drawing shapes
batch.begin() (somewhere else) <--- I think this code is call when stage call draw on other actor
//drawing completed
batch.begin()
EDIT:
If others' answer not suit you, please consider my workaround I post as an answer below.
#Override
public void draw(Batch batch, float parentAlpha) {
batch.end(); <--
Gdx.gl.glEnable(GL20.GL_ARRAY_BUFFER_BINDING);
../// other code
shapeRenderer.end();
Gdx.gl.glDisable(GL20.GL_BLEND);
batch.begin(); <--
I think the error is in you is calling, batch.end () before bacth.begin (); try to change the order
on the other hand, if the draw method. It is the stage class, you call him with the arguments you require, public void draw (Batch batch, float parentAlpha)
If you don't close all Renderers before opening a new one you will get a view without the previous ones
spriteBatch.begin()
... // render Textures
shapeRenderer.begin()
... // render Shapes
shapeRenderer.close()
spriteBatch.close()
this would cause a Screen without your spriteBatch-Textures ---
you already solved this problem by resorting your code to this
#Override
public void draw(Batch batch, float parentAlpha) {
batch.end(); // close A
...
shapeRenderer.begin(ShapeType.Filled); // open B
...
shapeRenderer.end(); // close B
batch.begin(); // open A
}
But in the very first batch.end() your code is not able to find any opened spriteBatch that can be closed, therefore you get an IllegalStateException
You have to call
batch.begin() one time before using the end()-method
(but be aware that you shouldn't begin a batch every frame)
the most simple solution i would recommend to solve the issue is the following:
class MyActor{
private boolean firstDraw = true;
#Override
public void draw(Batch batch, float parentAlpha) {
if(firstDraw)
{
batch.begin();
firstDraw=false;
}
batch.end();
Gdx.gl.glEnable(GL20.GL_ARRAY_BUFFER_BINDING);
Gdx.gl.glEnable(GL20.GL_BLEND);
Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
shapeRenderer.begin(ShapeType.Filled);
//change color
shapeRenderer.setColor(color);
shapeRenderer.rect(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
shapeRenderer.end();
Gdx.gl.glDisable(GL20.GL_BLEND);
batch.begin();
}
...
}
Like Angel * 2 is saying, your error is coming from calling a .end before .begin. Using multiple drawing batches is perfectly possible and being used often, but you have to use them in order and begin/end them properly. The following code is valid:
spriteBatch.begin();
spriteBatch.draw(..);
//more draw calls for this spritebatch
spriteBatch.end();
shapeRenderer.begin(..);
shapeRenderer.line(..);
//more draw calls for shaperenderer go here
shapeRenderer.end();
anotherSpriteBatch.begin();
anotherSpriteBatch.draw(..);
anotherSpriteBatch.end();
//You can also use the same batch again.
shapeRenderer.begin(..);
shapeRenderer.circle(..);
shapeRenderer.close();
I know this is my old question but I can see there are still new people using libgdx facing this error too. So I post my workaround as an answer:
The problem is that there is something break in between
batch.begin()
and
batch.end()
when the stage drawing
so if you use Stage to manage the batch, try-catch can save your time:
#Override
public void render(float delta) {
try {
stage.act(delta)
stage.draw()
} catch (Exception ex) {
if(stage.batch.isDrawing)
stage.batch.end()
}
}
** This is just a workaround to bypass some accidental error (e.g glyphlayout) in a frame and it should work fine in next frame. If there is any real problem in your code or resources, your render code will end up in the catch{}

LibGdx: How to implement gestures on Actor

I am trying to implement gestures on Actors in LibGdx using ActorGestureListener. The following code draws the actor, but the pan method is not called. Am I missing something?
I am using LibGdx 1.3.1.
public class MyApp extends ApplicationAdapter {
private Stage stage;
#Override
public void create() {
stage = new Stage(new ExtendViewport(Gdx.graphics.getWidth(), Gdx.graphics.getHeight()));
Gdx.input.setInputProcessor(stage);
final Shape circle = new Circle(); // Actor that draws a texture
stage.addActor(circle);
circle.addListener(new ActorGestureListener(){
public void pan (InputEvent event, float x, float y, float deltaX, float deltaY) {
Gdx.app.log("", "pan");
event.getTarget().moveBy(deltaX, deltaY);
}
});
circle.setTouchable(Touchable.enabled);
}
#Override
public void render() {
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
stage.act(Gdx.graphics.getDeltaTime());
stage.draw();
}
}
If you inherit from Actor, you need to set the bounds or it will not be click/touch-able!.
Simply set the bounds to match the texture your Actor contains.
//add this Set bounds the x, y, width, and height
circle.setBounds(0, 0,texture.getWidth(),
texture.getHeight());
//add this Sets the origin position which is relative to the actor's bottom left corner.
circle.setOrigin(0, 0);
stage.addActor(circle);

Incorrect click event when resize on libGdx

I want to use scene2D, in libGDX, to detect click on object.
I have this simple Game sample :
Stage stage;
#Override
public void create()
{
this.stage = new Stage(1280, 720, true);
MyActor actor = new MyActor();
Gdx.input.setInputProcessor(stage);
actor.setTexture(new TextureRegion(new Texture(Gdx.files.internal("plateau.jpg"))));
actor.setScale(0.1f);
stage.addActor(actor);
actor.addListener(new InputListener()
{
#Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button)
{
System.out.println("down"+Math.random());
return true;
}
});
}
#Override
public void render()
{
stage.act();
stage.draw();
}
MyActor is a simple class extending Actor with a rendertexture.
The Event is working fine at start.
But when resizing the frame, while the Actor is streched as wanted, the coordinates of the click are not updated and thus misplaced.
How to make the stage using the new size of the items as base to the event ?
You need to update the viewport of your stage in resize method of your application like this:
public void resize(int width, int height) {
stage.setViewport(stage.getWidth(), stage.getHeight(), false, 0, 0, width, height);
}
What worked for me was similar to what mentioned above but you have to modify it slightly:
public void resize(int width, int height) {
stage.getViewport().update(width, height);
}
You shouldn't use the Gdx.graphics.width and height but really use what resize() is providing to you.
You have to use camera.project() method for this purpose I think.
camera.project(Vector3 worldPoint);
Does this works for you?