overriding drawing order scene2D's stage
Asked Answered
I

2

8

You are given a complex Scene2D graph in libgdx with several Group's and Actor's. You want the user to select some Actors and draw those at the end so they appear focussed on top of any other Actors.

I would like to iterate over the Stage twice. The first time it draws the unselected Actors, the second time it draws the selected actors. I don't see any 'good' way to enforce this behaviour however.

I would prefer options that are clean. I would not like to copy an entire method implementation just for the sake of this small addition.

What doesn't work:

  • the Actor's toFront() method only works for it's siblings.
  • swapping places of Actor's in the Stage: this modifies the transformations the Actors have.

Scenario to think about: you have a Root with a group gA and a group gB. Group gA contains two images iA1 and iA2. Group gB contains one image iB. Given that Group gA is added first to the stage and that image iA1 overlaps with iB; now you want to select iA1 and make it appear over iB. I don't want to only call gA.toFront(); this would put the whole Group gA to the front, which means also iA2 is put to the front. Putting iA2 in front has the undesired effect of hiding parts of images inside Group gB

enter image description here

Idaliaidalina answered 17/2, 2014 at 21:8 Comment(9)
See #16130403 for some ideasSeptuagesima
Those ideas only work for all sibling within a group. Not between actors/groups on several depths on different branches in a scene graph.Idaliaidalina
If you set the z index of an Actor, by using toFront() or toBack() it changes the z-Index of that Actor and sorts the Actor list. Actors with a high z Index will be drawn at last, Actors with a low z Index will be drawn at first, and so in the back. If you call actor.toFront() in the select() method or however you call it, this Actor will be rendered the last. In the deselect() you can call toBack(). This works for Group to, as Group is an Actor subclass. It is not a 100% working thing but in this case this should be enough.Powdery
@Idaliaidalina it does work for groups inside of the scene graph. Since a Group is also an Actor you can compare it the same. Just give the Group the right depth. In that case the siblings of the group should have the same depth. Else it wouldnt work since you cant let the group drawn at different times inside of the Stage. (just calls the .draw from the Group) i Would recomend to add an insertionsort instead of the collection sort. it's much faster for a closely sorted list. (and your list wont change alot i think)Embolism
Maybe I wasn't clear on this, but these ideas don't take into account the following scenario: you have a root with a group gA and a group gB. Group gA contains two images iA1 and iA2. group gB contains one image iB. Given that group gA is added first to the stage and that image iA1 overlaps with iB; now you want to select iA1 and make it appear over iB. Your suggestions would put the whole group gA to the front, which means also iA2 is put to the front. Putting iA2 has undesired effects.Idaliaidalina
@Idaliaidalina as BennX said you can only sort Actors inside a Group. So the only solution i can think about is: If that special order is needed rremove that Actor from the group, add it to the Stage, order the Actors and Groups and draw them. As soon as you need the Actor back in the Group again, for some reason, remove it from the Stage and add it to the Group. It is pretty complicated, but should work. Otherwise, don't use Scene2D. I also like Scene2D but everything has a disadvantage to...Powdery
If you remove the actor and put it somewhere else in the scene graph, a part of the transformations it inherits from it's ascendants is lost. If you would take the parent (a Group) and put that somewhere else in the scene graph, then some other Actors inside that Group might be put on top of other Actors that you didn't want. So I disagree that would work.Idaliaidalina
What could work, but isn't a solution imo: 1) store a list lSelected with the selected actors and a list lNotSelected with the actors not selected and visible 2) for each actor in lSelected, set that actor to invisible and for each actor in lNotSelected set that actor to visible 3) call draw on the stage 4) for each actor in lNotSelected, set that actor to invisible and set each actor in lSelected to visible. 5) call draw on the stage 6) update lSelected and lNotSelected 7) go to 2Idaliaidalina
@Idaliaidalina Well your solution seems a working approach. You could set all selected to invisible and draw them yourself after stage.draw(). For my approach: Ofc if you remove an acotr of a group you need to change his position depending on the position of the group. This would be really complex but should work. But in your case i would try to do this without scene2d... This kind of sorting just won't be easy to integrate to scene2d...Powdery
R
1

There are two solutions-

1 - Stop using [multiple] groups. Sucks but this may be the easier option. If you look at how rendering/drawing is done you start at the root Group for a Stage and get its children and render them. For each of those children, if they are a Group then render that Groups children. ZIndex is nothing more than the order of children within a group. If you look at the Actor's setZIndex you can see why toFront or setZIndex only affect siblings.

public void setZIndex (int index) {
    if (index < 0) 
        throw new IllegalArgumentException("ZIndex cannot be < 0.");

    Group parent = this.parent;
    if (parent == null) 
        return;

    Array<Actor> children = parent.getChildren();
    if (children.size == 1) 
        return;

    if (!children.removeValue(this, true)) 
        return;

    if (index >= children.size)
        children.add(this);
    else
        children.insert(index, this);
}

2 - The only other option would be to change the drawing order of all the actors. You'd have to extend Stage and replace the draw method to draw based on a different order of your choosing. You'd probably have to incorporate a lot of the functionality from the Group.drawChildren method.

TLDR; The way things are implemented in LibGDX - a Group is a layer. If you don't want layers then either change what groups do or stop using groups.

Rubie answered 11/3, 2014 at 16:11 Comment(0)
A
0

You can work around this by overriding draw on the parent:

Group parent = new Group() {
    // Do not render the child as we will render it later
    @Override
    protected void drawChildren(Batch batch, float parentAlpha) {
        child.setVisible(false);
        super.drawChildren(batch, parentAlpha);
        child.setVisible(true);
    }

    // Render the child after the draw method so it's always on top
    @Override
    public void draw(Batch batch, float parentAlpha) {
        super.draw(batch, parentAlpha);
        child.draw(batch, parentAlpha);
    }
};

Group child = new Group();
parent.addActor(child);
Attract answered 30/10, 2018 at 3:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.