Skip to content
Tutorials

icon picker
Gauntlet UI Tutorial

Overview

This guide will teach you a variety of things including:
How to add a Brutton to the MapBar
How to Interact with objects to seamlessly Push and Pop Screens using Objects
How to create Custom Screens and ViewModels
————————————————————————————————————

Outline

Rationale
Required Knowledge
SetUp
Part I : Add Button to MapBar
Part II: Configure Navigation Elements
Part III: Setting up your Screen and View Model
Part IV: Configure SubModule and Launch!
————————————————————————————————————

Rationale

There are much simpler approaches to pushing Screens. However, working with the game objects the way the creators intend will ensure that your mod can scale without hiccups. Furthermore, once you get the hang of this, you will be able to fully take command over GauntletUI objects!
————————————————————————————————————

Required Knowledge

Before we begin, it is important that you know how to do a few things:
Setup a .NET (C#) environment in Rider or Visual Studio
Create basic Prefab Objects (Templates will be Provided)
How to install NuGet Packages in your respective Integrated Development Environment (IDE)
Setting up SubModules
If you need help in any of these areas, please consult these links:
If you are a total beginner and want to get started, consult my
————————————————————————————————————

SetUp

Create a base SubModule project (consult guide if needed) and name it ExampleUIMod
Install following the instructions on the website
————————————————————————————————————

Part I : Add Button to MapBar

The MapBar is the UI element you see all the time. It’s the bar that has your Inventory, Clan, Kingdom, and other buttons. So it’s a great place to seamlessly add in your own custom button.
Before we get started, it’s a good idea to use my ExampleButton template. Create a file called
ExampleButton.xml in this location. You may need to create this directory.
YourModule/GUI/PrefabExtensions/

Template Code:

now copy this template code and put it into the XML file.
<Widget UpdateChildrenStates="true" WidthSizePolicy="Fixed" SuggestedHeight="!LeftBar.Button6.Height" VerticalAlignment="Bottom" HorizontalAlignment="Right" HeightSizePolicy="Fixed" SuggestedWidth="!LeftBar.Button6.Width">


<Children>


<ButtonIconOffsetWidget UpdateChildrenStates="true" WidthSizePolicy="StretchToParent" HeightSizePolicy="StretchToParent" Id="w" PressedXOffset="!MapBar.ButtonIcon.XOffset" PressedYOffset="!MapBar.ButtonIcon.YOffset" HoveredCursorState="RightClickLink" Brush="MapBar.Left.Button6" DoNotPassEventsToChildren="true" Command.Click="ExecuteOpenScene" ButtonIcon="ClanIcon">


<Children>

<Widget WidthSizePolicy="Fixed" SuggestedHeight="!LeftBar.Icon7.Height" VerticalAlignment="Center" HorizontalAlignment="Center" HeightSizePolicy="Fixed" SuggestedWidth="!LeftBar.Icon7.Width" Id="ClanIcon" Brush="MapBar.Left.Icon7"/>

</Children>

</ButtonIconOffsetWidget>

</Children>

</Widget>

Configure Prefab Patch


1. In your C# Bannerlord Mod Project, create a folder called MapBar. This will contain all the elements associated with overriding it.
Make sure you’ve installed UIExtenderLib and create a new Class file and name it PrefabExtension inside the folder using the following code:
using UIExtenderLib.Interface;

[UIExtenderLib.Interface.PrefabExtension("MapBar",
"/Prefab/Window/Widget/Children/Widget/Children/ListPanel/Children/Widget")]
public class PrefabExtension : PrefabExtensionInsertAsSiblingPatch
{
public override string Name => "ExampleButton";
public override InsertType Type => InsertType.Append;
}

Let me explain what’s happening:
The XML file MapBar is the file to be overridden
The XPath Location (below is where the patching operation will occur. [Read more about XPath
]
"/Prefab/Window/Widget/Children/Widget/Children/ListPanel/Children/Widget"
The inherited class PrefabExtensionInsertAsSiblingPatch defines what type of patching will be done at the specified XPath
Name is the name of the XML file that the patcher will add/replace
InsertType is the type of sibling insertion that the patcher will perform.
To learn more about the options that are available to you, please consult the documentation

Configure ViewModel

Now that we have added the button using the patcher, we need to implement a custom action that will be taken when you click on it. To do this we will also use UIExtenderLib
Create a class file called CustomMapVM and inherit the BaseViewModelMixin<MapNavigationVM> class.
Now, copy this code to proceed
[ViewModelMixin]
public class CustomMapVM : BaseViewModelMixin<MapNavigationVM>
{
public CustomMapVM(MapNavigationVM vm) : base(vm)
{ }
// Add Your MapBar Button Methods Here
[DataSourceMethod]
public void ExecuteOpenScene()
{ }
}
This code is perfectly acceptable if all you want to do is execute a Game Cheat or (shameless-plug), but I bet you want it to link to another UI. If so, fear not, I have you covered :P.
————————————————————————————————————

Part II: Configure Navigation Elements

In order to get your button to open a new scene, it’s important to implement Navigation Elements that the game uses in order for it to occur seamlessly and in a scalable fashion. Before we begin, I will give a brief overview of how Screens are loaded by the :
A command is executed by the GameStateManager that pushes a GameState object onto a Queue of GameStates.
The jobs are then Dequeued and actions related to their JobTypes are performed.
In the case of a new scene, this eventually triggers a method called by the which sets the Screen associated with the GameState as an object.
After this, the method is called by the component of the screen
Subsequently, the method of the Screen is called and the movie (another name for the GUI you see on the screen) is loaded.
While there seems to be a lot of steps, we actually only need to few things to get this going! let’s first start by creating a CustomStateHandler.

Creating a CustomStateHandler

Very simple! Just create a new Interface file called CustomStateHandler in the NavigationElements folder and copy the following code. That’s it!
public interface CustomStateHandler
{
}

Creating a Custom

Create a class in the NavigationElements folder called CustomState
Copy the code verbatim
public class CustomState : GameState
{
private CustomStateHandler _handler;

public override bool IsMenuState
{
get { return true; }
}

public CustomStateHandler Handler
{
get { return this._handler; }
set { this._handler = value; }
}

}

Creating a CustomNavigationHandler

Create a new Interface file in the NavigationElements folder called CustomNavigationHandler and inherit the . Place any Method that you would like to perform Scene Opening events like so:
public interface CustomNavigationHandler : INavigationHandler
{
void ExampleOpenScene();
}

Extending the MapNavigationHandler

Now we want to extend the MapNavigation handler. the and your CustomNavigationHandler.
public class CustomNavigation : MapNavigationHandler, CustomNavigationHandler
{
}
Now, create a private attribute, call it _game, and set it with the constructor.

private Game _game;

public CustomNavigation()
{
this._game = Game.Current;
}
Finally, you want to connect your methods you specified in your CustomNavigationHandler to PushState methods. It is important to note that the CustomState depends on the name of the GameState class you created.
void CustomNavigationHandler.ExampleOpenScene(){
this._game.GameStateManager.PushState(this._game.GameStateManager.CreateState<CustomState>(), 0);
}

Now that we have navigation out of the way, all we need to do is configure our new UIElement!
————————————————————————————————————

Part III: Setting up your Screen and View Model

Creating a Custom ViewModel

Create a folder called UIElements. This will contain all your screens and view models.
Now create a new class file and name it CustomUI. This will contain both your Screen and ViewModel.
In that file create a ViewModel class and create a method to close the screen
public class CustomVM : ViewModel {

[DataSourceMethod]
private void CloseCustomScreen(){
Game.Current.GameStateManager.PopState(0);
}


}
That’s it for now.

Creating a Custom Screen

Now for what you’ve all been waiting for: Screens! In the same file, create a new class called CustomScreen which will inherit ScreenBase and IGameStateListener.
public class CustomScreen : ScreenBase, IGameStateListener{}
Next, we will associate our CustomState with this file by proceeding as follows
[GameStateScreen(typeof(CustomState))]
public class CustomScreen : ScreenBase, IGameStateListener {}
We shall now proceed to configure our GauntletLayer, DataSource, and GameState object. Reminder: this will be inside the class (brackets { } ).
private GauntletLayer _gauntletLayer;
private readonly CustomState _customState
private CustomVM _dataSOurce;
Next, we will configure our CustomState with the constructor
public CustomScreen(CustomState customState){
this._customState = customState;
this._customState.Listener = (IGameStateListener) this;
}
Now, we will configure the methods required by the
void IGameStateListener.OnActivate(){}
void IGameStateListener.OnDeactivate(){}
void IGameStateListener.OnInitialize(){}
void IGameStateListener.OnFinalize(){}

Let’s configure the method. You need to substitute “YourPrefabXML” with your Prefab of choice. If you just want to see if it works, you can use “ClanScreen”.
base.OnActivate();
this._gauntletLayer = new GauntletLayer(1,"GauntletLayer");
this._gauntletLayer.InputRestrictions.SetInputRestrictions(true,InputUsageMask.All);
this._gauntletLayer.Input.RegisterHotKeyCategory(HotKeyManager.GetCategory("GenericCampaignPanelsGameKeyCategory"));
this._gauntletLayer.IsFocusLayer = true;
ScreenManager.TrySetFocus((ScreenLayer) this._gauntletLayer);
this.AddLayer((ScreenLayer) this._gauntletLayer);

this._dataSource = new CustomVM();
this._gauntletLayer.LoadMovie("YourPrefabXML", this._dataSource);

Now the
this.OnDeactivate();
this.RemoveLayer((ScreenLayer) this._gauntletLayer);
this._gauntletLayer.IsFocusLayer = false;
ScreenManager.TryLoseFocus((ScreenLayer) this._gauntletLayer);
OnInitialize() can be left empty. Now let’s configure
this._dataSource = (CustomVM) null;
this._gauntletLayer = (GauntletLayer) null;
Great! Now we can test it out!
————————————————————————————————————

Part IV: Configure SubModule and Launch!

Go to your Main.cs file. This is where you setup your SubModule by inheriting MBSubModuleBase()
Configure UIExtenderLib as follows:
public class SubModule : MBSubModuleBase
{

private UIExtender _extender;
protected override void OnSubModuleLoad()
{
base.OnSubModuleLoad();
_extender = new UIExtender("ExampleUIMod");
_extender.Register();
}

protected override void OnBeforeInitialModuleScreenSetAsRoot()
{
base.OnBeforeInitialModuleScreenSetAsRoot();
_extender.Verify();
}

Build your .dll file and run!
Contact me on Discord for any questions:
Panopticon#3014
Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.