Is it possible in LibGDX's `scene2d` API to ha

2019-07-23 17:43发布

问题:

I am making a program using the amazing libGDX+scene2d API and I structured it as follows:

  • I have a single MyGame instance, holding a single PolygonSpriteBatch instance.
  • There is an abstract MyScreen class, holding a MyStage class (see below)
  • Then there are lots of different screen classes that inherit from MyScreen, and instantiate each other at will.

(in all cases, removing the "My" gives you the name of the respective library class that it extends)

This model worked fine, until I encountered some problems to perform actions between screens using the Action system. I decided then that it would be a good idea to have a single OmnipresentActor belonging to MyGame that, as the name says, is present in every scene. So I modified MyStage to look more or less like this:

public class MyStage extends Stage {
public MyStage(MyGame g) {
    super(new FitViewport(MyGame.WIDTH, MyGame.HEIGHT), g.batch);
    addActor(game.omnipresentInvisibleActor);
}

@Override
public void clear() {
    unfocusAll();
    getRoot().clearActions();
    getRoot().clearListeners();
    removeActorsButNotListenersNorActions();
}

public void removeActorsButNotListenersNorActions() {
    for (Actor a : getActors()) if (a.getClass()!= OmnipresentInvisibleActor.class) a.remove();
}

It followed a painful debugging phase, until I found out the following:

public PresentationScreen(MyGame g) {
    // super() call and other irrelevant/already debugged code
    System.out.println("PRINT_BEFORE: "+ stage.getActors().toString()); // OmnipresentActor is there
    mainMenuScreen = new MainMenuScreen(game);
    System.out.println("PRINT_AFTER: "+ stage.getActors().toString()); // OmnipresentActor is not there anymore, but was added to the mainMenuScreen

the "PRINT_BEFORE" statement shows that the stage holds the omnipresentActor. In "PRINT_AFTER" it isn't there anymore, whereas mainMenuScreen is indeed holding it. So my question, now more precise:

does scene2d prevent this to happen, or am I doing something wrong here?

Answers much appreciated! Cheers

回答1:

An actor can only be a member of one stage: Thanks to @Tenfour04 for confirming that. The explanation is quite clear after doing a little research:

Stage.addActor() looks like this:

(here the github code of Stage.java)

/** Adds an actor to the root of the stage.
     * @see Group#addActor(Actor) */
    public void addActor (Actor actor) {
        root.addActor(actor);
}

whereas root is simply initialized as a group in the Stage constructor: root = new Group();.

And Group.addActor() looks like this:

(here the github code of Group.java)

/** Adds an actor as a child of this group. The actor is first removed from its parent group, if any. */
public void addActor (Actor actor) {
    if (actor.parent != null) actor.parent.removeActor(actor, false);
    children.add(actor);
    actor.setParent(this);
    actor.setStage(getStage());
    childrenChanged();

}

So in the tree first lines is the answer: when creating the new stage, if the actor to add already has a parent, it is removed from its current parent. So, There are two possible solutions to the problem I enounced:

SOLUTION 1: Override addActor removing the if statement, or any other alteration of the library, which I'm not sure if it would work. I rather think this could be very problematic, for instance it could prevent the stages from disposing correctly

SOLUTION 2: Change the design so you don't need an omnipresent actor, nor changing/reimplementing the libraries. For the moment this is what I've done based on this answer, it isn't very clean but it works so far:

1) In the MyScreen class added the following fields:

private boolean watchingTemp;
private Actor watchActorTemp;
private Action actionTemp;

2) Then added this method:

public void addActionOnStageAfterActorEndsHisActions(Actor actor, Action action) {
    watchActorTemp = actor;
    actionTemp = action;
    watchingTemp = true;
}

3) then in the render method, I added the following:

if (watchingTemp && !watchActorTemp.hasActions()) {
        watchingTemp = false;
        stage.addAction(actionTemp);
    }

4) finally, when wishing to perform an action at a screen transition (and eventually disposing the first one), you can do something like this: I use something similar when clicking on a door between screens

public void movePlayerTHENgotoNewScreen(float xPos, float yPos, whatever else...) {
    game.player.walkToAnyPoint(xPos, yPos);
    yourFavoriteScreen.addActionOnStageAfterActorEndsHisActions(game.player, gotoNewScreen(wathever else...));
}

Hope it helps!