-
Notifications
You must be signed in to change notification settings - Fork 0
Custom Gauntlet UI
This article assumes you have a solid understand of how the CampaignBehaviorBase class works. Please refer to the article on Campaign Behavior if you are unfamiliar with that topic.
There are several different components to creating an Gauntlet UI. The first part is a custom class that extends ViewModel. This part handles a lot of the internal logic of the UI and is part of the C# code (You can think of this as analogous to the javascript in a webpage). The second part is the GUI prefab which handles a lot of the formatting. This part is written in xml and is found in Mount & Blade II Bannerlord\Modules\MyModule\GUI\Prefabs (You can think of this as analogous to the html/css in a webpage). The third and somewhat optional part is a class extending CampaignBehaviorBase to handle the more in depth logic for the actions performed in the UI and how that affects the game as a whole.
To start off I am going to make a new Campaign Behavior class and add an instance of it to my submodule class and override the two methods inherited from CampaignBehaviorBase. I am also going to create a custom class extending ViewModel, but for now nothing more needs to be done with that class
public class CustomUnitsVM : ViewModel { }
In my custom campaign behavior class I added the following 3 fields
private static GauntletLayer layer;
private static GauntletMovie gauntletMovie;
private static CustomUnitsVM customUnitVM;
Now I add the 2 functions for creating and destroying the UI layer
public static void CreateVMLayer(string unitId)
{
if (layer != null) // prevent creating multiple instances of the same UI layer
{
return;
}
layer = new GauntletLayer(1000); //the value passed in can be thought of as a Z value in the html, used for determining which layer goes on top when multiple UIs are open
if (customUnitVM == null)
{
customUnitVM = new CustomUnitsVM();
}
customUnitVM.RefreshValues();
gauntletMovie = (GauntletMovie)layer.LoadMovie("CustomUnits", (ViewModel)customUnitVM); //the first parameter string must match the file name of your xml prefab file
layer.InputRestrictions.SetInputRestrictions();//prevents stuffs like pressing N to open encyclopedia while this UI screen is open
ScreenManager.TopScreen.AddLayer((ScreenLayer)layer);
layer.IsFocusLayer = true;
ScreenManager.TrySetFocus((ScreenLayer)layer);
}
public static void DeleteVMLayer()
{
ScreenBase topScreen = ScreenManager.TopScreen;
if (layer != null) //prevent crashes when deleting a null layer
{
layer.InputRestrictions.ResetInputRestrictions(); //hot keys for things like encyclopedia works again
layer.IsFocusLayer = false;
if (gauntletMovie != null)
{
layer.ReleaseMovie(gauntletMovie);
}
topScreen.RemoveLayer((ScreenLayer)layer);
}
layer = null;
gauntletMovie = null;
customUnitVM = null;
}
now in your Mount & Blade II Bannerlord\Modules\MyModule\GUI\Prefabs folder, create an new xml file. The name needs to be the same as the string you passed into layer.LoadMovie
Here is a basic starter template you can use for your xml prefab
<Prefab>
<Constants>
<Constant Name="Encyclopedia.Frame.Width" BrushLayer="Default" BrushName="Encyclopedia.Frame" BrushValueType="Width" />
<Constant Name="Encyclopedia.Frame.Height" BrushLayer="Default" BrushName="Encyclopedia.Frame" BrushValueType="Height" />
<Constant Name="Top.Height" Value="240" />
<Constant Name="Top.VerticalDivider.Height" Value="!Top.Height" />
<Constant Name="Top.VerticalDivider.Width" Value="15" />
<Constant Name="Top.VerticalDivider.Thin.Width" Value="7" />
<Constant Name="Reference.AlphaFactor" Value="0" />
</Constants>
<Window>
<Widget WidthSizePolicy="StretchToParent" HeightSizePolicy="StretchToParent" Sprite="BlankWhiteSquare_9" Color="#000000CC">
<Standard.PopupCloseButton HorizontalAlignment="Center" VerticalAlignment="Center" MarginTop="940" Command.Click="Close" Parameter.ButtonText="Leave" />
<Children>
<!--Background-->
<BrushWidget Id="TownManagementPopup" DoNotAcceptEvents="true" WidthSizePolicy="Fixed" HeightSizePolicy="Fixed" SuggestedWidth="!Encyclopedia.Frame.Width" SuggestedHeight="!Encyclopedia.Frame.Height" HorizontalAlignment="Center" VerticalAlignment="Center" MarginBottom="35" Brush="Encyclopedia.Frame" ReserveManagementPopup="ReservePopup">
<Children>
<!--title-->
<Widget WidthSizePolicy="Fixed" HeightSizePolicy="Fixed" SuggestedWidth="590" SuggestedHeight="155" HorizontalAlignment="Center" VerticalAlignment="Top" PositionXOffset="6" PositionYOffset="-18" Sprite="StdAssets\tabbar_popup" IsDisabled="true">
<Children>
<RichTextWidget WidthSizePolicy="CoverChildren" HeightSizePolicy="CoverChildren" HorizontalAlignment="Center" VerticalAlignment="Center" PositionYOffset="-25" Brush="Recruitment.Popup.Title.Text" Brush.FontSize="46" IsDisabled="true" Text="Title" />
</Children>
</Widget>
</Children>
</BrushWidget>
<!--Close Encyclopedia Button-->
<ButtonWidget Command.Click="Close" HeightSizePolicy ="Fixed" WidthSizePolicy="Fixed" SuggestedHeight="100" SuggestedWidth="100" VerticalAlignment="Center" HorizontalAlignment="Center" PositionYOffset="-465" MarginLeft="1440" Brush="Popup.CloseButton"/>
</Children>
</Widget>
</Window>
</Prefab>
If you are already familiar with HTML then most of the attributes shouldn't be hard to understand.
Note that the close button has a click command associated with it
Command.Click="Close"
The value of this attribute needs to be the same as the function name of a function defined in the corresponding ViewModel class.
Therefor in our custom ViewModel class we need to add the following function
public void Close()
{
CustomUnitsBehavior.DeleteVMLayer();
}
This is the same DeleteVMLayer function we wrote earlier
from earlier we can see that it is very easy to set text to a static value such as with the title text from ealier
<RichTextWidget WidthSizePolicy="CoverChildren" HeightSizePolicy="CoverChildren" HorizontalAlignment="Center" VerticalAlignment="Center" PositionYOffset="-25" Brush="Recruitment.Popup.Title.Text" Brush.FontSize="46" IsDisabled="true" Text="Title" />
But to have the text display a dynamic value we need to pass the information from the corresponding ViewModel class First we need to change the value of the attribute in the GUI prefab xml
Text="@Title"
The @ symbol here indicates that instead of displaying the value as a literal string it will try to get the return value of a function in our ViewModel class with the same name
so in our ViewModel class we would need to add something such as
private string _title;
[DataSourceProperty]
public string Title
{
get
{
return this._title;
}
set
{
if (value != this._title)
{
this._title= value;
base.OnPropertyChangedWithValue(value, "Title");
}
}
}
In the constructor for the ViewModel and in the overriden RefreshValues function you want to assign Title a value
public CustomUnitsVM()
{
This.Title = "Old Title";
}
public override void RefreshValues()
{
base.RefreshValues();
this.Title = "New Title";
}
This method can passing data from the ViewModel to the GUI prefab can be done with other attributes as well even the ones that expects a value other than a string such as the "IsVisible" attribute. Just make sure the return type of the function makes sense with what the value of the attribute should be.
Tooltips can be added my making use of the following attributes
Command.HoverBegin="ExecuteBeginHint"
Command.HoverEnd="ExecuteEndHint"
Like with the click command attribute the value corresponds to the name of the function in the corresponding ViewModel class
public void ExecuteBeginHint()
{
InformationManager.AddHintInformation("This is a tooltip");
}
public void ExecuteEndHint()
{
InformationManager.HideInformations();
}
To add images make use of the sprite attribute
Sprite="BlankWhiteSquare_9"
This works with both native sprites and custom sprites added in custom sprite sheets
- Campaign AI
- MobileParty
- Clan
- Kingdom
- Mission AI
- Agent
- Formation
- Team
- Decision Utility System
- Casting Behaviors