I use a screen stack system to handle this kind of thing. Think of your screens as objects containing individual update and render logic that can be stacked on top of each other. These screens are then updated top -> bottom so that the most recent screen always has control. They are rendered bottom -> top so that overlays (pause screens/transparent screens as you say) and other effects can be implemented.
I do this using two classes:
ScreenService.java (handles individual screen objects):
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.utils.Disposable;
import org.jetbrains.annotations.NotNull;
import java.util.Stack;
/**
* The screen service class.
*/
public class ScreenService implements Disposable {
private final Stack<Screen> screenStack;
/**
* Constructs a ScreenService instance.
*/
public ScreenService() {
screenStack = new Stack<>();
}
/**
* Adds the specified screen to the service.
*
* @param screen
* The screen.
*/
public void add(@NotNull final Screen screen) {
screen.create();
screen.resize(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
screenStack.push(screen);
}
/** Releases all resources of this object. */
@Override public void dispose() {
for(Screen screen : screenStack) {
if(screen == null) continue;
screen.dispose();
}
screenStack.clear();
}
/**
* Draws all screens in the service.
*/
public void draw() {
for(Screen screen : screenStack) {
if(screen == null) continue;
screen.draw();
}
}
/**
* Pauses all screens in the service.
*/
public void pause() {
for(Screen screen : screenStack) {
if(screen == null) continue;
screen.pause();
}
}
/**
* Called when the game window is resized.
*
* @param width
* The new game window width (in pixels).
* @param height
* The new game window height (in pixels).
*/
public void resize(final int width, final int height) {
for(Screen screen : screenStack) {
if(screen == null) continue;
screen.resize(width, height);
}
}
/**
* Resumes all screens in the service.
*/
public void resume() {
for(Screen screen : screenStack) {
if(screen == null) continue;
screen.resume();
}
}
/**
* Updates the screen service.
*
* @param tt
* The total amount of time, in seconds, since the game started.
* @param dt
* The total amount of time, in seconds, since the last update.
*/
public void update(final float tt, final float dt) {
boolean coveredByOtherScreen = false;
for(int i = screenStack.size() - 1; i >= 0; i--) {
Screen screen = screenStack.get(i);
if(screen == null) {
screenStack.remove(i);
continue;
}
screen.update(tt, dt);
if(coveredByOtherScreen) remove(screen);
if(!screen.isOverlay()) coveredByOtherScreen = true;
}
}
/**
* Removes the specified screen from the service.
*
* @param screen
* The screen.
*/
private void remove(@NotNull final Screen screen) {
screen.dispose();
screenStack.remove(screen);
}
}
Screen.java (implements individual screen logic):
import com.badlogic.gdx.utils.Disposable;
import com.spacehorde.util.GameServices;
import org.jetbrains.annotations.NotNull;
/**
* An abstract screen class.
*/
public abstract class Screen implements Disposable {
private final GameServices gameServices;
/**
* Constructs a Screen instance.
*/
protected Screen(@NotNull final GameServices gameServices) {
this.gameServices = gameServices;
}
/**
* Called when the screen is created. Used for initialization.
*/
protected void create() { }
/**
* Draws the screen.
*/
protected abstract void draw();
/**
* Gets the game services.
*
* @return The game services.
*/
protected GameServices getGameServices() {
return gameServices;
}
/**
* Determines if the screen is an overlay or not. Overlays will not cause screens below it to automatically exit.
*
* @return {@code true} if the screen is an overlay; otherwise, {@code false}.
*/
protected abstract boolean isOverlay();
/**
* Pauses the screen.
*/
protected void pause() { }
/**
* Called when the screen is resized.
*
* @param width
* The new game window width (in pixels).
* @param height
* The new game window height (in pixels).
*/
protected void resize(final int width, final int height) { }
/**
* Resumes the screen after a pause.
*/
protected void resume() { }
/**
* Updates the screen.
*
* @param tt
* The total amount of time, in seconds, since the game started.
* @param dt
* The total amount of time, in seconds, since the last update.
*/
protected abstract void update(final float tt, final float dt);
}