Nathan
Nathan

Reputation: 232

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.

Upvotes: 4

Views: 7327

Answers (4)

Nathan
Nathan

Reputation: 232

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{}

Upvotes: 0

zeg
zeg

Reputation: 586

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();
      }

      ...

}

Upvotes: 3

Madmenyo
Madmenyo

Reputation: 8584

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();

Upvotes: 0

Angel Angel
Angel Angel

Reputation: 21688

@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)

Upvotes: 9

Related Questions