LibGDX: Multiple Cameras

Camera for Game | Camera for UI

Vladislav Shesternin
3 min readJun 20, 2024
Camera for Game | Camera for UI

When there is a World in the game and there is a UI, then there is a problem of how to make the game world move and the UI is in place (that is, it moves together with the camera and is in the same place), so to speak, how to fix the UI. In this article, we will consider one of the solutions to this problem, namely, how I do it.

I do everything on the screen using viewports, it is very convenient and I recommend it to you.

1️⃣ Let’s create our abstract screen class with a Viewport and a scene to display the UI:

abstract class AdvancedScreen(
val WIDTH : Float = 1000f,
val HEIGHT: Float = 500f
) : ScreenAdapter(), AdvancedInputProcessor {

val viewportUI by lazy { FitViewport(WIDTH, HEIGHT) }
val stageUI by lazy { Stage(viewportUI) }

val inputMultiplexer = InputMultiplexer()

override fun resize(width: Int, height: Int) {
viewportUI.update(width, height, true)
}

override fun show() {
stageUI.addActorsOnStageUI()

Gdx.input.inputProcessor = inputMultiplexer.apply { addProcessors(this@AdvancedScreen, stageUI) }
Gdx.input.setCatchKey(Input.Keys.BACK, true)
}

override fun render(delta: Float) {
stageUI.apply {
viewport.apply()
act()
draw()
}
}

override fun dispose() {
log("dispose AdvancedScreen: ${this::class.java.name.substringAfterLast('.')}")

stageUI.dispose()
inputMultiplexer.clear()
}

open fun Stage.addActorsOnStageUI() {}

}

This code can be used to create your own screens, you just have to follow it:

class DemoUIScreen: AdvancedScreen() {

private val img = Image(/*yourImage*/)

override fun Stage.addActorsOnStageUI() {
addActor(img)
img.setBounds(10f, 10f, 100f, 100f)
}

}

2️⃣ Now let’s create an abstract screen class so that there is a camera for both the UI and the Game World:

Since the world will usually be used as a Box2D world, we will adjust the screen for Box2D.

abstract class AdvancedBox2dScreen(
val worldUtil: WorldUtil,
val uiW : Float = 1000f,
val uiH : Float = 500f,
val boxW : Float = 10f,
val boxH : Float = 5f,
): AdvancedScreen(uiW, uiH) {

private val viewportDebug by lazy { FitViewport(boxW, boxH) }

private val viewportBox2d by lazy { FitViewport(uiW, uiH) }
val stageBox2d by lazy { AdvancedStage(viewportBox2d) }

override fun show() {
stageBox2d.addActorsOnStageBox2d()
super.show()

inputMultiplexer.addProcessor(stageBox2d)
}

override fun resize(width: Int, height: Int) {
viewportDebug.update(width, height, true)
viewportBox2d.update(width, height, true)
super.resize(width, height)
}

override fun render(delta: Float) {
worldUtil.update(delta)

stageBox2d.apply {
viewport.apply()
act()
draw()
}
super.render(delta)

worldUtil.debug(viewportBox2d.camera.combined)
}

override fun dispose() {
log("dispose AdvancedBox2dScreen: ${this::class.java.name.substringAfterLast('.')}")

worldUtil.dispose()
super.dispose()
}

open fun AdvancedStage.addActorsOnStageBox2d() {}

}

Class WorldUtil is a wrapping class for ease of working with World, if you don’t need it you can remove the particles you don’t need related to box2d and just use 2 cameras (world and ui).

Pay attention to this part of the code, if you forget to add an input processor, the world scene will not respond to touches:

override fun show() {
stageBox2d.addActorsOnStageBox2d()
super.show()

inputMultiplexer.addProcessor(stageBox2d)
}

3️⃣ To use two cameras, simply implement the class:

class DemoBox2dScreen: AdvancedBox2dScreen(WorldUtil()) {

private val imgWorld = Image(/*yourImageWorld*/)
private val imgUI = Image(/*yourImageIU*/)

override fun Stage.addActorsOnStageBox2d() {
addActor(imgWorld)
imgWorld.setBounds(10f, 10f, 100f, 100f)
}

override fun Stage.addActorsOnStageUI() {
addActor(imgUI)
imgUI.setBounds(100f, 10f, 100f, 100f)
}

}

That’s it, the point is simple:

  • if you need to add an actor to the game world, add on addActorsOnStageBox2d.
  • if you need to add an actor to the UI, add on addActorsOnStageUI.

PS. Vel_daN: Love what You DO 💚.

--

--