Hello, I have a rather complex piece of my UI and I’m struggling to figure out how to get everything working. Here’s what I’m trying to do:
In my game players will be able to allocate talents. They do this from the talent screen. The talent screen should look something like this:
On the left we have the tooltip. On the right we have the talent button columns and talent buttons. Each button represents a talent. When the player presses a button for a specific talent, that talent will be allocated. When the player mouses over a talent button, the tooltip should display more data about that talent. Each column has a number representing the required level for a specific talent (1, 4, 7, and 10 are visible here.) but the max level for talents should be able to be arbitrarily high, and the maximum amount of talents at a given level should also be able to be arbitrarily high.
This means that the non-tooltip portion of the talent screen should be scrollable in all directions, ideally to be drug with the mouse. The final challenge is that this should be mostly computer generated. I want to define the talent buttons, the talent columns, the talent data, and a few other starting variables. From there the computer should be able to read the talent data and figure out what talent columns / buttons should go where and what text fields should change.
Here’s some stumbling blocks I’ve run into:
- I’ve tried turning my talent-columns and talent-buttons into uxml templates. My thought is that these are basically like prefabs and so I should be able to just spawn them like prefabs from code. However I’m running into several problems. When my talent column is made into a template it no longer “fills” the screen. Here’s an image of it happening:
You can see the first talent column appears correctly. This talent column is not a template. You can see the talent column-as-a-template to the right, which I have highlighted because otherwise you wouldn’t see it. If I go in and manually change the Size-Min-Height to 100% it then expands correctly. However I can’t get this change to stick for the template as a whole, I have to do it for each instance I drag in.
- It appears I have to add the content-container=“true” attribute directly to the uxml of my talent column to get it to work correctly. Is there a way to do this in the inspector or am I stuck manually editing uxml?
- How would I dynamically create (and correctly parent) the ui elements from code?
- Since my talent buttons are also templates, it is impossible to change the text on them without changing all of them, which is unacceptable since each talent button should display the name of its talent.
…there were some other questions I had but I cannot remember them. I get the sneaking suspicion that I am using the wrong tools for the job, like maybe instead of using templates I’m supposed to use something else here? I hate to ask questions like this, but I just can’t find any good resources on how to do complex ui pieces in UI Toolkit. Thank you.
So I bought a Udemy course (https://www.udemy.com/course/modern-unity-ui-with-ui-toolkit/ for those interested) and it answered most of my questions. For posterity I will post the answers here.
- This is a two-parter. First, this is happening because of flex rules on the template container element. This can be fixed by adding a USS selector that targets the template container for that template and changes the flex rules for it. I have no idea if this is the most idiomatic way of accomplishing this, but it is how I did it.
- I believe I have to set the content-container attribute manually in code. Oh well.
- Dynamically creating UI elements is a few steps but is very simple. Here’s the method used to build my talent screen, which shows off several elements of a dynamic ui including template instantiation / parenting, resource loading, and button mouse over / click functionality.
private void TalentUIElementSetup()
{
var talentsRoot = Object.FindObjectOfType<UIDocument>().rootVisualElement.Q("talents-root");
var talentColumnParent = talentsRoot.Q("talent-column-parent");
var talentColumnTemplate = Resources.Load<VisualTreeAsset>("talent-column");
var talentButtonTemplate = Resources.Load<VisualTreeAsset>("talent-button");
var tooltip = talentsRoot.Q<Label>("tooltip-text");
var talentRefundToggle = talentsRoot.Q<Toggle>("talent-refund-toggle");
var talents = TalentDefinitions.Talents;
var talentColumnDictionary = new Dictionary<int, TemplateContainer>();
foreach (var talent in talents)
{
// If necessary build the new column for this talent, otherwise get an existing column
if (!talentColumnDictionary.TryGetValue(talent.levelRequirement, out var column))
{
column = talentColumnTemplate.Instantiate();
talentColumnDictionary.Add(talent.levelRequirement, column);
var levelLabel = column.Q<Label>("level-label");
levelLabel.text = talent.levelRequirement.ToString();
column.style.flexShrink = 0; // I have to set this here like a damn fool
talentColumnParent.Add(column);
}
// Build the talent button info and add it to its column
var talentButton = talentButtonTemplate.Instantiate();
var button = talentButton.Q<Button>("button");
button.text = talent.name;
talentButton.Q<Label>("points").text = "0 / " + talent.maxTalentLevel.ToString();
// build the tooltip string
var tooltipString = $"{talent.name}\n\nRequirements:\n\n";
foreach (var requirement in talent.requires)
{
tooltipString += requirement.ToString() + "\n";
}
tooltipString += "\nGrants:\n\n";
foreach (var granted in talent.grants)
{
tooltipString += granted.ToString() + "\n";
}
tooltips.Add(button, tooltipString);
// Register the callback
button.RegisterCallback<MouseEnterEvent>(e => tooltip.text = tooltips[button]);
button.RegisterCallback<MouseLeaveEvent>(e => tooltip.text = "");
button.RegisterCallback<MouseUpEvent>(e => Debug.Log($"stat: {talent.stat}, refund: {talentRefundToggle.value}"));
button.RegisterCallback<MouseUpEvent>(e => SystemAPI.GetSingleton<NetworkClientSystem.Singleton>().SendRpc(
new TalentAllocationRequestRPC
{
deallocate = talentRefundToggle.value,
stat = talent.stat,
}));
column.Add(talentButton);
}
}
- The text can be easily changed through code as shown above.
Finally, here’s my talent screen as I envisioned it. Notice the addition of a refund checkbox and that I’m mousing over a button showing it’s tooltip:
It would still be nice to be able to click and drag the talent screen around instead of using the scroll bars but I’m sure I’ll figure that out eventually.
1 Like