LibGDX: when actors act up

As mentioned in my Robogenesis after action report, I ran into something of a clumsy use-case during development. (Just to be clear: I think it's far more likely that the perceived clumsiness is because I don't know how to best employ LibGDX in this situation, and not due to any shortcoming in LibGDX itself.) I posed this question elsewhere, but I think I didn't do a sufficient job explaining, so that's what this blog post is all about.

The setup

To create story in Robogenesis, I borrowed a play directly out of countless JRPGs: dialogue is presented as an overlay with a close-up of the "face" that you're talking to on one side of the screen and their words on the other. It's a fairly simple little scene to put together in LibGDX and Scene2D. Where it gets complicated is in the transition. To add a little visual flare, I wanted elements to fade and slide onto the screen.

Animation showing the dialogue transition in Robogenesis.

To keep all of my Actors organized, I arranged them into three Groups (from front to back):

  1. dialogGroup: containing the dialogue actors and a full-size translucent 'shadow'
  2. gameGroup: containing all of the game UI
  3. backgroundGroup: containing the background (thin vertical stripes)

When the game is in progress, naturally dialogGroup's visibility is set to false. When I want to transition from the game to the dialog, I fade in the dialogGroup.

Inside the dialog group are (basically) three Actors:

  1. The BOSS radio
  2. The speech bubble
  3. The fullscreen shadow

The code

When I start showing the dialog group, I position the radio and speech bubble off screen, fade in the group, then slide in the radio and the speech bubble at the same time. The code that accomplishes that looks something like this:

stage.addAction(
  Actions.sequence(
      // ACTION #1
      ActionUtil(() => {
        ui.dialogGroup.setVisible(true)
        ui.dialogGroup.setColor(1f, 1f, 1f, 1f)
        ui.dialogShadow.setColor(1f, 1f, 1f, 0.8f)
        
        ui.theirSpeech.setX(ui.theirSpeech.position.x + 800)
        speaker.setX(-800)
      }),
      // ACTION #2
      Actions.addAction(Actions.moveBy(-800, 0, dialogDelay), ui.theirSpeech),
      // ACTION #3
      Actions.addAction(Actions.moveBy(800, 0, dialogDelay), speaker),
      // ACTION #4
      Actions.delay(dialogDelay * 2)
  )
)

First thing you should notice from this code snippet is that we're adding actions to the Scene2D stage. Why? I'm acting on a lot of different components, and stage provides one master location for all the actions to be sequenced.

Second, we're adding a sequence of four actions. Action #1 is a RunnableAction (I'm using some Scala syntax here, but trust me, it's just a plain old RunnableAction) which makes sure some preconditions are set (visibility and color of components) and sets the speech bubble and the radio actors just off screen.

Actions #2 and #3 slide the radio and speech bubble onto the screen over a time period defined by the float variable "dialogDelay".

After this sequence of actions, I go on to display text in the speech bubble and do other things, but as should be clear, I have to wait for this sequence to complete before doing anything else. This is where Action #4 comes in and is the basis of my quandry.

The quandry

Actions #2 and #3 are actions which add an action to a different actor. The "addAction" action by nature completes immediately. The result is that Actions #2 and #3 begin executing in parallel and the sequence moves on to Action #4. What I'd really like, what would make the most sense for me, is if there were a way to wait for Actions #2 and #3 to complete. Because it seems that LibGDX doesn't give you any functions to do this, I have to insert a delay that takes at least as long as the other actions I've assigned. Here's that re-phrased in a graph:

Action sequence diagram.

Setting a delay that runs in parallel, while functional, strikes me as a work-around. I can imagine a few situations where this parallel delay solution would not quite fit the bill. What if you don't know how long that delay needs to be (say these actions are being added by different components of the system and a delay is only sometimes needed)? What if you need to wait indefinitely for the player to click on another Actor or press a key?

Rephrased, the question is this: when adding a sequence of actions to a container (Group or Stage), how do you apply actions to other actors and wait for those actions to complete?

I'll follow up with another blog post when a good answer has been presented or if I manage to dig up with something new.