Skip to content

Latest commit

 

History

History
177 lines (141 loc) · 5.85 KB

gui.md

File metadata and controls

177 lines (141 loc) · 5.85 KB

GUI

Return to table of contents

Arcade provides a wide array of gui components, read the GUI Section to see more information about those. This section is dedicated to how those gui components can be integrated within minigames.

All the gui elements are handled by a MinigameUIManager, you can add all of your gui components to this manager, and it will ensure that players will be displayed the components, and they will be updated for those players. If a player joins the minigame, it will correctly update all the player's ui, and if a player leaves, all the ui will be removed.

Adding & Removing UI Components

We simply construct our ui component then add it to the manager using one of the respective methods, once registered, the minigame will handle any ticking or updating that is needed for that component:

val minigame: Minigame = // ...

val bossbar: CustomBossbar = // ...
minigame.ui.addBossbar(bossbar)

val nametag: PlayerNameTag = // ...
minigame.ui.addNameTag(nametag)

val sidebar: Sidebar = // ...
minigame.ui.setSidebar(sidebar)

val display: PlayerListDisplay = // ...
minigame.ui.setPlayerListDisplay(display)

Note

You can add as many bossbars and nametags as you wish, however, there can only every be one sidebar and one player list display.

We can also remove any elements with their respective methods:

val minigame: Minigame = // ...

val bossbar: CustomBossbar = // ...
minigame.ui.removeBossbar(bossbar)
minigame.ui.removeAllBossbars()

val nametag: PlayerNameTag = // ...
minigame.ui.removeNametag(nametag)
minigame.ui.removeAllNametags()

minigame.ui.removeSidebar()

minigame.ui.removePlayerListDisplay()

There are also two additional things that the ui manager controls, that is the Countdown and ReadyChecker, these are used in minigames by default when unpausing to check that all players are ready, and then a countdown begins. You may also want to use these for other applications. These will have default implementations, but you can overwrite them.

val minigame: Minigame = // ...
    
minigame.ui.countdown = TitledCountdown.titled(Component.literal("My Titled Countdown!"))

// Utility method for countdowns specifically for minigames
// this will default to using the minigame scheduler and
// also only display this for the minigame players by default
minigame.ui.countdown.countdown(minigame, 10.Seconds, 1.Seconds).then {
    println("Minigame Countdown!")
}

minigame.ui.readier = // ...
    
// We can then use this to check if players are ready
minigame.ui.readier.arePlayersReady(minigame.players.playing).then {
    println("Playing players are ready!")
}

Phased UI Components

Previously in the Scheduling Section we briefly talked about the BossbarTask, the purpose of this task is to be able to display a bossbar for a certain phase of our minigame and even if we backtrack to a previous phase the bossbar will be removed appropriately.

Each gui component has a respective task that does this:

  • BossbarTask
  • NameTagTask
  • PlayerListTask
  • SidebarTask

Let's take a look at a more concrete example. Let's say we've got a timer bossbar that lasts 10 minutes denoting a minigame phase change. There are a couple issues with this; what if we want to step into the next phase before the 10 minutes is up, the bossbar would not automatically disappear. Okay well we could solve this by simply storing a bossbar as a field as always removing it in the end method of our phase:

class ExampleMinigame(
    server: MinecraftServer,
    uuid: UUID
): Minigame(server, uuid) {
    // ...

    var bossbar: MyCustomTimerBossBar = MyCustomTimerBossBar()

    // ...
}

enum class ExamplePhases(
    override val id: String
): Phase<ExampleMinigame> {
    Grace("grace") {
        override fun initialize(minigame: ExampleMinigame) {
            minigame.ui.addBossbar(minigame.bossbar)
        }
        
        override fun end(minigame: ExampleMinigame, next: Phase<ExampleMinigame>) {
            minigame.ui.removeBossbar(minigame.bossbar)
        }
    }
    // ...
}

But this feels very clunky, especially if you have multiple different gui components that you want to manage. Not to mention, this will also make serialization more difficult if that's something you're also aiming for.

Instead, what we can do is create an instance of BossbarTask and schedule it as a cancellable task which will run if cancelled:

enum class ExamplePhases(
    override val id: String
): Phase<ExampleMinigame> {
    Grace("grace") {
        override fun initialize(minigame: ExampleMinigame) {
            val duration = 10.Minutes
            val bossbar = MyCustomTimerBossBar()
            bossbar.setDuration(duration)

            val task = BossbarTask(minigame, bossbar)
            minigame.scheduler.schedulePhasedCancellable(duration + 1.Ticks, task).runIfCancelled()
        }
    }
    // ...
}

If we take a peek at how the BossbarTask is implemented we will see why this works:

open class BossbarTask<T: CustomBossbar>(
    private val minigame: Minigame,
    val bar: T
): Task {
    init {
        this.minigame.ui.addBossbar(this.bar)
    }

    final override fun run() {
        this.minigame.ui.removeBossbar(this.bar)
    }
}

It simply adds the bossbar when the task is created, then when the task is run it will remove the bossbar. So when scheduling this as a task, we will guarantee that it will be removed after the specified time. And because we scheduled it as phased cancellable that means we can wrap this task in a cancellable task which in the case the phase abruptly changes will notify the task, calling runIfCancelled tells the task to run if it's been notified of the cancellation and so the bossbar will be removed.

See the next section on Resource Packs