How to Create Custom UI Screens in Unciv: A Complete Developer Guide

Extend the abstract BaseScreen class, compose widgets on the provided Stage using the shared skin, and push your screen onto the stack via UncivGame.pushScreen() to create fully functional custom interfaces in Unciv.

Unciv's user interface is built on LibGDX's Scene2D framework, making it straightforward to create custom UI screens by following the established inheritance pattern found throughout the codebase. Whether you are building a new mod configuration panel or a statistics dashboard, understanding how to properly extend BaseScreen and integrate with the screen management system is essential for any contributor or modder working with the yairm210/Unciv repository.

Understanding the BaseScreen Architecture

Every full-screen interface in Unciv inherits from BaseScreen, defined in core/src/com/unciv/ui/screens/basescreen/BaseScreen.kt. This abstract class provides the foundational lifecycle methods—render(), resize(), show(), hide(), and dispose()—along with a pre-configured Stage and a shared Skin instance. The Stage handles input processing and actor rendering, while the Skin ensures visual consistency with the game's default font styles and drawable assets.

Screen navigation is managed by UncivGame in core/src/com/unciv/UncivGame.kt. This singleton maintains a screen stack and exposes pushScreen(), popScreen(), and replaceCurrentScreen() to transition between views. When you create a custom screen, you instantiate it with a reference to the UncivGame instance and push it onto this stack to display it.

Step-by-Step Implementation

1. Subclass BaseScreen

Create a new Kotlin file in the appropriate package under core/src/com/unciv/ui/screens/. Your class must extend BaseScreen and accept the game instance as a constructor parameter.

package com.unciv.ui.screens.custom

import com.badlogic.gdx.scenes.scene2d.ui.Label
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.TextButton
import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.UncivGame
import com.unciv.ui.components.extensions.onClick

class MyCustomScreen(val game: UncivGame) : BaseScreen() {

    init {
        // Configure the root table to fill the viewport
        val root = Table()
        root.setFillParent(true)
        stage.addActor(root)

        // Add UI elements using the shared skin
        val title = Label("My Custom Screen", BaseScreen.skin)
        root.add(title).padBottom(20f).row()

        val closeButton = TextButton("Close", BaseScreen.skin)
        closeButton.onClick { game.popScreen() }
        root.add(closeButton)
    }
}

2. Compose the UI Hierarchy

Use Table containers to layout your widgets. The stage field provided by BaseScreen is where all actors must be added. Always reference BaseScreen.skin for constructors of Label, TextButton, SelectBox, and other Scene2D widgets to maintain the game's visual theme. For complex layouts involving side panels or scrollable lists, study ModManagementScreen.kt in core/src/com/unciv/ui/screens/modmanager/ModManagementScreen.kt, which demonstrates split-pane designs and list handling.

3. Register with the Screen Stack

To display your screen, call pushScreen() from the UncivGame instance. This adds your screen to the top of the stack and pauses the previous screen.

// Example: Opening the custom screen from an existing button
val openButton = TextButton("Open Custom View", BaseScreen.skin)
openButton.onClick {
    game.pushScreen(MyCustomScreen(game))
}

To close your screen and return to the previous view, invoke game.popScreen(). This triggers the hide() lifecycle method on your screen and show() on the restored screen.

4. Handle Resources and Lifecycle

If your screen loads custom textures, fonts, or other disposable resources, override the dispose() method to release them and prevent memory leaks. The base implementation handles disposal of the Stage, but you must explicitly dispose any assets loaded outside the shared skin.

override fun dispose() {
    // Dispose custom textures here if applicable
    super.dispose()
}

Key Reference Implementations

Study these existing screens to understand different UI patterns:

Best Practices for Custom Screens

  • Reuse the shared skin: Instantiate all widgets with BaseScreen.skin to ensure your UI matches Unciv's theme and supports user-defined mods that override default styles.
  • Separate concerns: Keep game logic inside game.gameInfo or dedicated controller classes. The UI layer should only handle presentation and fire events; avoid heavy calculations on the main thread.
  • Test cross-platform: UI scaling differs between desktop and Android. Verify that your Table layouts adapt correctly by testing on multiple resolutions, and use BaseScreen.stage.viewport.update(width, height, true) in resize() if you require custom viewport handling.
  • Follow naming conventions: Place screen classes in core/src/com/unciv/ui/screens/yourpackage/ and name files ending in Screen.kt for consistency with the existing codebase.

Summary

Frequently Asked Questions

What is the difference between a Screen and a Popup in Unciv?

A Screen is a full-viewport interface that occupies the entire display and is managed by the screen stack in UncivGame. A Popup is a modal overlay that appears above the current screen, typically dimming the background and capturing input until dismissed. Screens extend BaseScreen, while popups usually extend Popup or BasePopup and are added directly to a screen's stage.

How do I pass data between custom screens?

Pass data through constructor parameters when instantiating your screen class. For example, MyCustomScreen(game, gameInfo, selectedCivilization) allows you to carry state from the calling context. Avoid static singletons for screen-specific data to prevent memory leaks and threading issues.

Can I use custom fonts or textures in my UI screen?

Yes. Load custom assets in your screen's init block or show() method using LibGDX's AssetManager or direct texture loading. You must override dispose() to call texture.dispose() or font.dispose() on these custom assets, as BaseScreen only manages the default skin and stage resources.

Where should I add a button to open my custom screen?

Add entry points in existing UI hierarchies such as the main menu, the options menu (core/src/com/unciv/ui/popups/options/OptionsPopup.kt), or the mod manager. Create a TextButton with an onClick listener that calls game.pushScreen(MyCustomScreen(game)). Ensure you import your new screen class at the top of the file where you add the entry point.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →