Today I am going to show you and teach you about my custom quantity selector input menu part.

image

And here is an example of how it can look in game.

image

- First it has a UI Box. This is the UI box that will contain the quantity selectors. Just set up a UI box like you want it and select it here.
- Then featured, is an information display update component allowing the display to update based on your needs.
- Then you have the option to limit the amount of points you can put into the boxes. This is useful for creating an attribute point system, where you only have a certain number of points to spend.
If chosen, this will add a default UIStringInputContent in the last position of the controls. You can customize the text by
changing the limit text option in the editor.
-Then finally you have the quantity selector input fields themselves. Each input field has a name tied to it which can be used for finding and referencing a quantity selector. You can set the initial, minimum and maximum values of the quantity selector. There are settings for if you are using a vertical or horizontal input and you can choose a custom input if you don't want to use the default integer input prefab that you have set up under UI Settings.

So how can we use this. Well this menu part requires two classes. QuantitySelectorMenuPart and QuantitySelectionAlternative. Let's look at the QuantitySelectionAlternative first:


public class QuantitySelectionAlternative : BaseData
{
[EditorHelp("Name", "The name of this quantity selection.", "")]
[EditorFoldout("Base Settings", "Set the name and base settings of this quantity selection.", "")]
[EditorEndFoldout]
public string name = "";


[EditorFoldout("Quantity Changes", "Define how quantity can be changed.\n" +
"Changing quantity uses integer value inputs.")]
[EditorHelp("Initial Value", "The initial value of the attribute box.", "")]
public int initialValue = 1;
[EditorHelp("Minimum Value", "The minimum value of the attribute box.", "")]
public int minValue = 1;
[EditorHelp("Maximum Value", "The maximum value of the attribute box.", "")]
public int maxValue = 20;
// quantity input
// horizontal change
[EditorHelp("Use Horizontal Change", "Change the quantity via horizontal input of the used UI box.")]
public bool useHorizontalChange = true;

[EditorHelp("Loop Horizontal", "Loop horizontal quantity changes.")]
[EditorCondition("useHorizontalChange", true)]
public bool loopHorizontal = false;

[EditorHelp("Horizontal Change", "The value that will be added/subtracted from the field per input.", "")]
[EditorEndCondition]
public FloatValue<GameObjectSelection> horizontalChange = new FloatValue<GameObjectSelection>(1);

// vertical change
[EditorHelp("Use Vertical Change", "Change the quantity via vertical input of the used UI box.")]
public bool useVerticalChange = false;

[EditorHelp("Loop Vertical", "Loop vertical quantity changes.")]
[EditorCondition("useVerticalChange", true)]
public bool loopVertical = false;

[EditorHelp("Vertial Change", "The value that will be added/subtracted from the field per input.", "")]
[EditorEndCondition]
public FloatValue<GameObjectSelection> verticalChange = new FloatValue<GameObjectSelection>(10);

// custom input
[EditorSeparator]
public UICustomInputSettings customInput = new UICustomInputSettings();

// main content
[EditorFoldout("Input Content", "Define the content that will be displayed in the quantity input field, e.g. quantity values.")]
[EditorEndFoldout(2)]
[EditorLabel("<value> = current quantity, <min> = minimum quantity (usually 0), <max> = maximum quantity")]
[EditorLanguageExport("InputOption")]
public Content quantityContent = new Content("Quantity", "value", "<value>/<max>");

public QuantitySelectionAlternative()
{

}
}


This is the quantity selector itself. It extends BaseData allowing the editor to recognize it when we set up the actual menu part. It is just properties, there are no methods. Simple right? The reason we use this class over the regular quantity selection, is to trim it down a bit. If you choose to use QuantitySelection as the class you derive from then note you will have extra options, such as a second UI Box option for the quantity selector itself. You will also have to rewrite the code presented. Also some options will be missing such as initial, minimum and maximum values.

Lets look at the second class, the menu part itself, QuantitySelectorMenuPart:

[EditorSettingInfo("Quantity Selectors", "Displays quantity selectors for use.\n" +
"Provides attribute and ability selection menus")]
public class QuantitySelectorMenuPart : BaseMenuPart, IUIBoxControl, IUIBoxInput, IUIBoxInputChanged
{
[EditorHelp("UI Box", "The UI Box that will contain the quantity selection inputs")]
public UIBoxSelection attributeButtonUIBox = new UIBoxSelection();

[EditorSeparator]
public InformationUpdateSettings infoDisplay = new InformationUpdateSettings();

[EditorHelp("Limit Available Points ", "Limit the available points you can spend on the quantity selectors.")]
public bool limitAvailablePoints = false;

[EditorHelp("Availble Points", "How many points you can spend before the quantity selectors become disabled.")]
[EditorCondition("limitAvailablePoints", true)]
[EditorSeparator]
public int availablePoints = 10;

[EditorHelp("Limit text", "The text displayed on screen for the amount of available points you have to spend.")]
[EditorEndCondition]
public string availablePointsName = "Attribute Points:";

[EditorFoldout("Quantity Selection Fields", "Customize the quantity selection controls.", "")]
[EditorArray("Add Quantity Selector", "Adds a quantity selector to the menu.", "",
"Remove", "Removes this menu item.", "", isMove = true, isCopy = true,
foldout = true, foldoutText = new string[] {
"Quantity Selector", "A custom quantity selector control.", ""
})]
[EditorEndFoldout]
public QuantitySelectionAlternative[] item = new QuantitySelectionAlternative[0];


//in game components
protected IUIBox attributeBox;
protected int[] inputValues; //The received and current input value of the quantity selector.
protected int[] previousValues; //The previous iterations value of the quantity selector.
protected List<UIIntInputContent> input; //The quantity selector list itself.
protected QuantityData[] data; //Quantity data list. A quantity selector requires a quantity data component.
protected UIStringInputContent attributePointText; //The text content for the available points value.

public QuantitySelectorMenuPart()
{
}

public override bool IsFocused()
{
return this.attributeBox != null;
}

public override bool IsOpened
{
get { return this.attributeBox != null && this.attributeBox.IsOpen; }
}

public override bool IsClosed
{
get { return this.attributeBox == null; }
}

public override bool Controlable
{
get { return true; }
set
{
if (this.attributeBox != null)
{
this.attributeBox.IsControlable = value;
}
}
}

public override void ChangeCombatant(Combatant old)
{
throw new NotImplementedException();
}

public override void Refresh()
{
this.markRefresh = false;
this.Show();
}

public override void Show(MenuScreen s)
{
this.screen = s;
this.Show();
}

private void Show()
{
this.infoDisplay.UnregisterChanges();

Maki.Game.Variables.Set("AttributePoints", availablePoints);

//Create background UI box
if (this.attributeBox == null || this.attributeBox.IsClosingOrClosed)
{
attributeBox = attributeButtonUIBox.Create();
}
//Create quantity data for the quantity selectors
data = new QuantityData[item.Length];
for (int i = 0; i < item.Length; i++)
{
QuantityData qData = new QuantityData(ORK.Game.ActiveGroup.Leader, this.screen.pauseGame, null, null, item[i].maxValue, item[i].minValue,
null, 0, 0, QuantitySelectionMode.Buy, null, null, null, null, null);
qData.quantity = item[i].initialValue;
data[i] = qData;
}

//Create choices
this.input = new List<UIIntInputContent>();
inputValues = new int[item.Length];
previousValues = new int[item.Length];

if (this.attributeBox != null)
{
//Set up quantity selection components
for (int index = 0; index < this.item.Length; index++)
{
this.input.Add(item[index].quantityContent.GetContent<UIIntInputContent>());
this.input[index].InitValue(this.data[index].quantity, this.data[index].availableQuantity, this.data[index].maxQuantity,
item[index].useHorizontalChange ?
(int)item[index].horizontalChange.GetValue(this.data[index].combatant.Call) :
0);
this.input[index].ChangeVertical = this.item[index].useVerticalChange ?
(int)item[index].verticalChange.GetValue(this.data[index].combatant.Call) :
0;
this.input[index].LoopHorizontal = this.item[index].loopHorizontal;
this.input[index].LoopVertical = this.item[index].loopVertical;
item[index].customInput.Use(this.input[index]);
this.attributeBox.AddInput(index, this.input[index]);
}

//Add the available points control if enabled
if (limitAvailablePoints)
{
UIText attributeDescription = new UIText(availablePointsName);
attributePointText = new UIStringInputContent(availablePoints.ToString(), attributeDescription, null);
attributePointText.IsActive = false;
this.attributeBox.AddInput(item.Length + 1, attributePointText);
}

//Set UIBox settings and open
this.attributeBox.Control = this;
this.attributeBox.InPause = this.screen.pauseGame;
this.attributeBox.IsControlable = true;
this.attributeBox.IsFocusable = true;
this.attributeBox.SchematicContext = this.screen.Combatant;
this.attributeBox.SelectedIndex = 0;
this.attributeBox.Open();

//Register the update information
this.infoDisplay.RegisterChanges(this.ResetInputs);

//Create our value arrays for the quantity selectors
for (int i = 0; i < input.Count; i++)
{
inputValues[i] = input[i].InitialValue;
previousValues[i] = input[i].InitialValue;
}
}
}

/*
============================================================================
Tick functions
============================================================================
*/
public virtual bool Tick(IUIBox origin)
{
return false;
}

public virtual bool UnfocusedTick(IUIBox origin)
{
return false;
}

public virtual bool NotControlableTick(IUIBox origin)
{
return false;
}
/*
============================================================================
State change functions
============================================================================
*/
public void BoxOpened(IUIBox origin)
{

}

public void BoxClosed(IUIBox origin)
{
//Unsubscribe to the registered events
this.infoDisplay.UnregisterChanges();

if (origin == this.attributeBox)
{
attributeBox = null;
}
}
public override void Close(bool closeImmediately)
{
if (UIBoxSetting.IsActive(this.attributeBox))
{
if (closeImmediately)
{
//Unsubscribe to the registered events
this.infoDisplay.UnregisterChanges();
this.attributeBox.Control = null;
this.attributeBox.CloseImmediately();
this.attributeBox = null;
}
else
{
attributeBox.Close();
}
}
}

public void FocusGained(IUIBox origin)
{

}

public void FocusLost(IUIBox origin)
{

}

public void OutOfBoxClicked(IUIBox origin)
{

}

/*
============================================================================
Ok button functions
============================================================================
*/
public bool IsOkButtonVisible(IUIBox origin)
{
return false;
}

public bool IsOkButtonActive(IUIBox origin)
{
return false;
}

public void Accepted(IUIBox origin)
{
}
/*
============================================================================
Cancel button functions
============================================================================
*/
public bool IsCancelButtonVisible(IUIBox origin)
{
return false;
}

public bool IsCancelButtonActive(IUIBox origin)
{
return false;
}

public void Canceled(IUIBox origin)
{
if (this.attributeBox == origin)
{
this.screen.Close();
}
}

public void InputAccepted(IUIBox origin, int index)
{

}

public void InputSelected(IUIBox origin, int index)
{

}

public void InputChanged(IUIBox origin, int index)
{
//Make sure we are checking a quantity selector
if (index <= item.Length)
{
if (this.attributeBox == origin && this.input != null)
{
//Are we limiting available points
if (limitAvailablePoints)
{
if (inputValues[index] - input[index].Value > 0) //We are gaining a point
{
availablePoints += inputValues[index] - input[index].Value;
this.data[index].quantity = this.input[index].Value;
inputValues[index] = input[index].Value;

UpdateStatusValues(index);

previousValues[index] = inputValues[index];
}
else
{
if (availablePoints > 0) //Points are available, set the variables and data.
{
availablePoints += inputValues[index] - input[index].Value;
this.data[index].quantity = this.input[index].Value;
inputValues[index] = input[index].Value;

UpdateStatusValues(index);
}
else if (availablePoints <= 0) //No available points so dont increase the quantity selector.
{
this.data[index].quantity = previousValues[index];
inputValues[index] = previousValues[index];
input[index].Value = previousValues[index];
availablePoints = 0;
}

previousValues[index] = inputValues[index];
}

//We reset the inputs to update text accordingly.
ResetInputs();
}
else //No limit imposed, just update the values.
{
this.data[index].quantity = this.input[index].Value;
inputValues[index] = input[index].Value;

UpdateStatusValues(index);

previousValues[index] = inputValues[index];

//No need to reset the inputs because we arent trying to limit the value or display the available points input field.
}
}
}
}

//Resets the inputs of the UI box so that they are accurate and up to date.
public void ResetInputs()
{
attributeBox.ClearInputs();

//Add the quantity selectors as they are.
for (int index = 0; index < this.item.Length; index++)
{
this.attributeBox.AddInput(index, this.input[index]);
}

//If limit available points add the available points input field.
if (limitAvailablePoints)
{
UIText attributeDescription = new UIText(availablePointsName);
attributePointText = new UIStringInputContent(availablePoints.ToString(), attributeDescription, null);
attributePointText.IsActive = false;

this.attributeBox.AddInput(item.Length + 1, attributePointText);
}

}
}


So under InputChanged() is where we handle what we do when the quantity selectors change value. For example you could write code the initialize your status values according to the value inputted. An example of such code would be:

protected void UpdateStatusValues(int index)
{
Combatant player = ORK.Game.ActiveGroup.Leader;

//Switch on the quantity selectors name.
switch (this.item[index].name)
{
//We set up our status value references here. The name is important, you must match the name of the quantity selector to the attribute you are changing.
case "Strength":
StatusValueSetting strengthStatusValue = ORK.StatusValues.Get(5); //Match the index to your status value index.
StatusValue strengthStatus = player.Status.Get(strengthStatusValue);
strengthStatus.InitValue(input[index].Value);
strengthStatus.Owner.FireChanged();
break;
case "Dexterity":
StatusValueSetting dexterityStatusValue = ORK.StatusValues.Get(8);
StatusValue dexterityStatus = player.Status.Get(dexterityStatusValue);
dexterityStatus.InitValue(input[index].Value);
dexterityStatus.Owner.FireChanged();
break;
case "Vitality":
StatusValueSetting vitalityStatusValue = ORK.StatusValues.Get(6);
StatusValue vitalityStatus = player.Status.Get(vitalityStatusValue);
vitalityStatus.InitValue(input[index].Value);
vitalityStatus.Owner.FireChanged();
break;
case "Prowess":
StatusValueSetting prowessStatusValue = ORK.StatusValues.Get(10);
StatusValue prowessStatus = player.Status.Get(prowessStatusValue);
prowessStatus.InitValue(input[index].Value);
prowessStatus.Owner.FireChanged();
break;
default:
break;
}
}


The result: image

So hopefully this illustrates a bit about menu parts and how to make custom menu parts. I'm having a blast making this custom menu screen for my character creation and I thought by sharing it I could expand peoples coding knowledge and offer a new tool for people to use. Thanks for your time, have a good day!
Post edited by Vanisle on
  • Good job :)
    Please consider rating/reviewing my products on the Asset Store (hopefully positively), as that helps tremendously with getting found.
    If you're enjoying my products, updates and support, please consider supporting me on patreon.com!
Sign In or Register to comment.