UI:Build-a-Button Workshop

From Total War Modding

Lesson two! This time around, we build our button, select its text, give it some stuffing, and ship it off to our relatives for the holidays. We’re building a button!

Before we get into the nitty-gritty, we have to back up into the nit-grit and talk about one more general concept – creating components.

One More General Concept – Creating Components

In the previous lecture hall, we discussed finding in-game components, the root component, parentage/childhood, and printing out info. All of this was done with stuff that was already there. We’ll be actually creating a new component object in the game, and to do that we need to understand a few things.

Every UIC in the game is built off of what’s called a “layout file”. Note: this is not to be confused with the “layout” component we saw in the previous lesson. We’ll be seeing it a lot. We can find layout files by loading all CA packs, going into the ui/templates/ folder, and just scrolling on through. All of the files there have no file extension – they’re all hexidecimal files, which define a bunch of details about UIC’s created with these layout files. Each layout file can be called a bunch of times – there’s no limit to how many panel_title UICs there can be in the game, for instance.

We won’t yet get into how to read these files and to see what each of them does – we’re going to use ye good ol’ guess-and-check method. Whenever you make a new UIC in-game, your only option is to create one off of these layout files that predefine the deets, which you can change later on.

The command we use here is a method on UIC – it’s to create a direct child of a UIC. It goes like this:

UIComponent(uic:CreateComponent(id_of_child, layout_file_path))

I’m going to now make-up a path-from-root UIC, but I’ll choose a real layout file path, so you get the idea:

local root = core:get_ui_root()
local fake_uic = find_uicomponent(root, "fake_child", "this_isnt_real_cant_you_tell")

local child_uic = UIComponent(fake_uic:CreateComponent("real_child", "ui/templates/round_small_button"))

And what the above does is – it grabs the fake_uic, it creates a child UIC with the ID “real_child”, off the layout file “round_small_button”, and child_uic is now equivalent to that game object for the child. Pretty cool.

Before we go on, we’re going to use our skillz to figure out how to make a button on some existing row of buttons. Typically this is pretty easy. Find the row of buttons you want to add onto – say, the top right row of buttons with info and stuff, or the row of buttons on the bottom of the army docker – and then click one of those buttons. You’ll get the path-from-root for the button output – you just need to find the button’s parent, one step higher up the path, and use that as the parent of this new button.

With this, I advise using the DevTool Console. Have the mini-console setup while the UICs you’re messing with are on screen and active. Don’t call any UICs when they’re not on screen, because they might not exist. We’ll get into that later.

Try to find the template button layout file that matches the other buttons with the parent. I’ll give you a hint – the names of the layout files are pretty descriptive!

Return to me when you have a perfectly valid button that does nothing and says nothing!

Actual Usage

At this point, you have a perfectly valid button that does nothing and says nothing. We’re going to first give it a tiny bit of spunk, and then we’ll make it actually do something upon being clicked. What it says and what it does is up to you, but I’m going to try and provide the tools needed to do both those things.

First thing’s first, we want to change that image. We’re going to learn our second uic method here – SetImagePath(). It takes one arg (uh, for now) – the path to the image, INCLUDING the file extension. If we wanted to, say, give our child_uic the below file, we’d write the following:

local root = core:get_ui_root()
local fake_uic = find_uicomponent(root, "fake_child", "this_isnt_real_cant_you_tell")

local child_uic = UIComponent(fake_uic:CreateComponent("real_child", "ui/templates/round_small_button"))
child_uic:SetImagePath("ui/skins/default/advisor_beastmen_2d.png")
Isn't he handsome?

You probably don’t want to use the above example. Find a pretty image to put on the button. Tip: find one with the prefix icon_, those are all typically made for effect icons/buttons.

After that, let’s give it a tooltip. The method here is uic:SetTooltipText(tooltip_text, true). For a quick example:

-- same code as above
child_uic:SetTooltipText("I'm a very happy button", true)

Again, I’m not gonna tell you how to live your life, be creative and do some stuff. I will give you one tip though: using the character | in this method allows you create a vanilla-like tooltip, that expands after hovering over the button for sometime. I recommend testing out that – as well as testing out some various loc tags. Test a UI tr, test UI tagged images, test colored text.

And the last thing’s last – the actual usage. (I just used the title of the movie in a scene, bonus points?)

This part should remain relatively simple, though the actual nuts and bolts of usage will be left up to you. All we’re doing is establishing a listener for the button being clicked, and then we effect change.

-- same code as above pt.2
core:add_listener(
    "MyButtonListener",
    "ComponentLClickUp",
    function(context)
        return child_uic == UIComponent(context.component)
    end,
    function(context)
        out("It Worked!")
    end,
    true
)

Every time the button is pressed, I would find a new line in my script log saying “It Worked!”. How quaint.

I’ll take the time now to cover this. There are a few UIC listeners – we’ll probably cover most before long. All of them have the same two bits of data you can access – context.component, or context.string. The former can be used as in the listener to grab the UIC game object (I’m checking to see if the button pressed is the child_uic that we made above), while the latter can be used to check the ID of the UIC game object, quickly. In this instance, I could also write a conditional that looks like the following:

    function(context)
        return context.string == "real_child"
    end,

Your Turn

At this point, you’re off to the wild. Attempt creating a brand new button, giving it a new image, giving it an expandable tooltip, and making the button do something when you press it. Keep the “thing it does” relatively simple – output text, pan the camera, move a character. Just make sure the thing it does isn’t anything UI related, just cause game change for now.

Next up, we’re gonna get into a few more upgrades for our button – including giving that button varied tooltips and making it inactive, based on criteria!