This is a multi part post and is the second part:
- Overview for character creation
- Dealing with complex nested options – (This post)
- Applying the options to skeletal mesh (using n-hance assets)
- Connect character creation to custom server
- Populate character selection screen from server
In this episode we’re going to cover the nested options that go into character creation screen. This is a universal topic so may be useful in many other parts of the game, but we will specifically focus on the character creation part.
The assets used here are from N-Hance, I am not affiliated with them but like their content.
Specifically it is this part that we’re focusing on:
In order to do this work, you will need 2 widgets. First is the root widget for the character creation screen and the other is for the option toggling – because the options will be set out dynamically.
Create character widget
So first you have a horizontal box, this is not essential, you could have a simple canvas panel instead of it as root component.
Next you add a size box
component, which you will need to align to the right of the screen and stretch it vertically.
Next, add a border
component, to which you will add styling too, as well as adding necessary padding.
My border was part of the 4k full fantasy GUI asset pack
– but you can use others, its a styling pack so will not have impact to the code we write.
Next, I added a vertical box
to fit a couple components – a title, the options, the character name and create button.
I will focus specifically on the custom options part.
So part of the Horizontal box, we have a border
component which will draw a nested table view. This is optional. But inside that you have a scroll box
component. This is where we will start adding the nested options.
The scrollbox
component is key here, but it could’ve been others, for example uniform grid panel
or similar. I think the scroll option would work nicely here therefore would advise that one, but use whatever suits you application more.
Ok now you have the design sorted out, before we go into the blueprints, let’s create the component which will go inside the scrollbox.
Toggle options widget
The toggle widget is as described. A widget which will allow you to toggle options.
It will contain a ‘Title’ and the ‘option’ value. It will also have 2 buttons, to toggle left and right.
It will not do processing inside this widget itself, but provide callbacks for when those buttons are pressed.
This is similar to design pattern in React js
– a UI library for web development.
The blueprints for this widget are relatively straight forward
First create some variables, CurrentValue
and Title
, both as String
– see part 1
in screenshot above.
Make them both Instance editable
and Expose on spawn
so that you can fill them out part of constructor for this widget
Next create a custom event
(right click -> custom event) and call it Update value
– this is to update the dynamic value that’s written in the widget.
See 2
in blueprints for populating this custom event – effectively setting Current Value
to the textbox inside the widget.
Next, see 3
for populating the constructor – this is populating the static Title
field which is static. If you want it to be dynamic, you can add it to Update value
custom event too.
Then we want to create some event dispatchers
– see part 4
. Create two event dispatchers, LeftClick
and RightClick
.
Finally in 5
we link those events to the button presses. This means that when a user clicks on the left button, an event is fired which we will utilize in the parent widget to handle the change.
Preparing the data – structures
Now we want to prepare the data that we will be traversing.
This part can be very tricky to get right, so I’d suggest you draw some things on paper to see if it will all fit together. i.e. dry run your solution.
What approach did I take?
For my create character options, I think the core of the nesting relies on the race
+ gender
of the character.
For example, what hair meshes are available?
- Human + male = [option1, option2, option3]
- Human + female = [option4, option5, option6]
- Orc + male = [option7, option8, option9]
- and so on…
I hope you see what I am saying here – the base for the options for me in my use case, was race + gender – it could potentially be different in yours.
Furthermore, note that option
will also potentially want a dual value, i.e. user facing name
and internal key
. User facing names can have spaces and special characters etc. But keys should avoid those.
Note that it will get a bit more complicated in future (when applying meshes to character) when you will also have to evaluate items that the character has equipped.
i.e. you will evaluate face mesh + material (based on race + gender + skin option) – then you have to worry about whether the player is wearing a helmet, which is yet another option. Simplifying these options will be key and we will cover some of this as we go through this post and part 3 when we’re applying these to the meshes.
Ok so now that I know what I want my options to be and their data type, I can start preparing the data structures to support this.
First I create a new structure (Right click in Content browser
-> Blueprints
-> Structure
). I’d suggest creating a separate models
folder to keep these in.
Inside the RaceGenderPair
we simply have two string fields, for Race
and Gender
. This will be used as a key (composite key) for our Map of data that’s to come. In reality, what we’re aiming for is creating a structure like this: Map<RaceGenderKey, OptionsArr>
.
Next, we create the OptionsArr
structure:
In this structure, I utilised a previously created structure called Tag
and made it array (this is because Map in UE will not allow you by default to stick array of values inside the value field of Map).
For reference, the Tag
structure is a name
and value
pair, create this if you don’t already have it:
This is useful in many places, particularly when displaying values to users and linking to internal data structures.
Ok now you have prepared the models required, you will want to start populating the actual options.
Preparing the data – available options
Create a Blueprint Function Library
called something like CreateCharacterOptions
– or whatever you like.
Inside this blueprint, we will start defining what options will be available for the character creation screen.
We will start with simple GetAvailableRaces
function.
Create a local variable here to store all available options for races. This local variable is of the TagStructure
array type.
Inside this variable I populate all available races, for you it could be more, or less even.
For example, I have data for:
- name = human (key for data structures etc, could’ve been just ‘h’)
- value = Human (human readable name, to display in toggle widget etc)
Next, let’s create the available genders. The input variable here is Race
which is a String
and output is TagStructure Array
.
The gender options above are based off race. You could just make it same as race
where you just provide the options out, but some genders may be different. For example, what if you had a robot
in your game as an option, it may not require to follow same structure as others.
In my case though, both human
and dk
have both male
and female
options.
Next is where it starts getting interesting.
Preparing options for hair styles
Here is where we start seeing and utilizing the power of composite keys.
We create a function for GetAvailableHairMeshes
and the input for this function we have input of RaceGenderPair
structure and output of Tag Structure Array
.
Inside the HairMeshes
variable, which is a Map
of RaceGenderPair
for key and OptionsArr
for output, we define multiple options for hair meshes.
Have a look, the initial key is: race: human, gender: m
(defined in options from previous 2 functions) and the value contains an array of valid options for this selection: h_m_hair_1
, h_m_hair_2
-> h_m_hair_4
.
Then you define the options for all other available keys, i.e. where gender is female
etc.
Do note that your skeletal meshes and materials (typically color differenece) can be disjointed.
So for example, hair style 1
can have color 1
or color 2
etc.
This is also why you have skeletal meshes and materials on different functions, so below is one for the hair materials:
So in a very similar fashion, you have a variable for HairMaterials
which accepts a RaceGender
key and OptionsArr
as value.
Then the hair material can contain values: h_m_color_1
which has user facing value of Blonde
.
Handling skin
This part is also kind of interesting, as I combined multiple data points here.
Note that in above, the key is the Race Gender
pair as usual, and the value is also the Tag Structure Array
. However if you look into the values, you will notice that I actually combine the data of race + gender into skin.
i.e. the race + gender is combined for the skin style.
This will be important as we will want to start aggregating some options as you get closer to applying these data to skeletal meshes. But you can do it many ways, this is just one of them.
Optional data
Finally we have some race/gender locked options. In this case I have human male beards
Note that for the keys I only put human male. For human female I left it blank, and its effectively the same for my other race dk
. The empty values will be handled in the widget.
Finally let’s cover Classes
just for completeness.
As you can see, you can actually make classes race/gender locked. E.g. in WoW where you have some classes only available to certain races.
Alternatively, you can simply remove the key and return all options to simplify this.
Back to Create Character Widget
Ok now that we have the data prepared, let’s go back to the Create character widget
that was built in the beginning, this one:
Let’s now start looking at the blueprints for this class, they will appear quite big and intimidating, but if we look piece by piece then will not be so bad.
First, let’s check the variables that I created:
The first part I want to point out, is that for each of the options we will have 3 variables. The options array, the toggle widget reference, and the current index that selection is on.
In retrospect, there’s a way to keep this cleaner. That is to have 3 Maps of <String, Object> where object is the variable type.
For example, I have RaceWidget
of type ToggleWidget
. I could instead have: Map<String, ToggleWidget>
where the String key = "RaceWidget"
.
Note I specifically didn’t do this – I had it like that originally and it made my variable section a lot cleaner, however every time I wanted to use the variable I needed to fetch it in my blueprints, which made my blueprints look more complicated. So its a tradeoff, choose one you prefer.
Ok let’s start looking at the constructor. This is relatively long function, but is fairly straightforward once you understand what its aiming to do.
Here it is broken down into three images just for better clarity.
We can ignore section 1, we will be covering this in next post – it is spawning the skeletal mesh that we will be modifying.
In section 2 we get the available races and setting it to the local variable. Remember races
did not require a key, its base data. Note that these are the functions we created in Preparing the data
section.
Section 3 we get the genders
. Remember we utilized the race
as a key, which potentially you may not use. Again update/promote these to local variables.
Part 4 is important as this is where we create the RaceGenderPair
key which will be used to reference other data options.
This is a local function created here by pressing the plus icon:
We will go through all of the functions covered here eventually, some covered in next post.
The content of this function is:
Simply put, you find the values from race / gender options based on current index of each and make the RaceGenderPair
structure – setting it as a local variable.
The function for GetTagName
and GetTagValue
are as follows:
The GET is by ref. On UE4 you may not have ‘IS EMPTY’ which you can change to size > 0
.
They’re very simple functions, but since we use them quite often in our blueprints, its much neater to refactor them into their own function calls.
Note that the return for empty is not really necessary, but it can avoid any unexpected error messages when data is missing. Empty data will be handled in several ways.
Ok now we can move to the next part, of creating the actual widgets for toggling.
All of these create
calls are custom events. Furthermore, they’re all relatively similar, so once you get the hang of one, you should be able to understand all of them!
Ok so it may seem like a lot is going on, but it can be split into 3 basic parts.
- Create the custom event for
create race selection
. This will only be called once. Its responsible for creating the toggle widget, we get theTag Value
(user facing value) from theRaceOptions
variable (0th index as its initializing), that we populated in constructor. Set the title based on the selection, in this caseRace
. Once the widget is created, add it to theScrollBox
by callingAdd Child
function on it.
Note if the scrollbox is not visible in your variables list, go back to the designer, and ensure its selected here like so
2. In the toggle widget we created 2x event dispatchers. They were for LeftClick
and for RightClick
. That’s what we see here for part 2, we created the toggle widget and using that reference, we bind to those two events.
3. Create two custom events, for LeftClickRace
and RightClickRace
. These are what handle the actual button clicks from the toggle widget. You will see that we have a function to Update Index
, Resolve Changes
and update all options
.
Update index is a simple function to help us increment/decrement index. This is just to keep things neater as this functionality will be used in multiple places.
This was my implementation, I wonder if there’s a simpler way.. It’s really mainly adding or subtracting 1 from the index value. It also checks if the index value is going below/above allowed (i.e. can’t be -1 or larger than size of array).
Resolve changes
– this function is updating the toggle widget. Remember that on the toggle widget we’re displaying the currently selected value. This function sets that up.
We simply get the array of options, the index and use the Get Tag Value
function to obtain the user facing name. From the toggle widget, we set the current value
(inside the toggle widget) and call the update value
method, which was also used in the constructor.
We also call Update Gender Race Key
function, that we covered few steps above. The main reason for that is that this universal resolve changes
function can be called on any options, including race and gender. It can either be called here, or after race/gender resolvers.
Finally, Update all options
is another custom event inside the Customize Character
blueprint.
In this custom event, we call individual resolvers on each of the options which affect the appearance. I.e. race
and gender
does not directly affect the appearance, what it does is modify the hair options, the skin options, facial features, etc – then it gets applied to the mesh.
The Update character appearance struct
will be covered in the next post.
The rest of the options
The rest of the options past race actually follow the same principles. This is because we used generic components to build the functionality. The only additional piece is the ResolveGender
, ResolveHairMesh
etc.
As you can see, the Create<Widget>
, LeftClick<Option>
, RightClick<Option>
are the same, but we also have a resolver here too.
Furthermore we have 1 additional check in facial feature
widget which we will cover in last screenshot.
And now we covered all the fundamental functions for the rest of the options, so here they are for reference.
Note that on this one, after getting the available facial feature options, we check if the length of that array is larger than 0. If not, we hide this widget.
And that’s it! Hopefully you managed to follow along and understood the principles shared here, there’s a lot of data to go through but this way you should save more time long term as it should be easier to work with.
The next post will cover processing this data to decide the skeletal meshes and the material to apply to the character option.
Best of luck!