This technical art brief from Lab42 immediately appealed to me because of the potential for multiple systems in one project. This feels like a programming project disguised as an art brief which is exactly what I am after to improve my technical skills this year.
The objective is to create a character customizing tool in UE5. For this project I should take a premade default character with a set of simple animations and create a tool which allows the player to do the following:
Change colour properties on the materials
Change attached and skinned meshes
Ensure these changes work with the existing animations
Create tools/scripts within the editor which allow these options to be set up and changed
I'm going to focus on making this tool as easily editable as possible by a hypothetical developer who wants to add more options in the future. I will make sure my graphs are annotated and I might even create some documentation to go with the system to act as a sort of manual for future developers to use.
At first glance this project seems deceptively simple to me, at least in terms of the logic. I need to create a system that swaps out meshes and materials, presumably using some kind of array that can be rearranged and added to at any point without breaking the system. I'd also like to use some kind of dynamic material parameters so that at least some colours can be fully customised as opposed to just chosen from a preset menu.
This is the modular character kit I am going to use to demonstrate my system, however I plan to make it so the tools are project agnostic and the meshes and materials can be easily replaced by another hypothetical developers resources.
Source: https://www.unrealengine.com/marketplace/en-US/product/stylized-male-character-kit-casual [date accessed: 30th September 2024]
For this project I have chosen to use an asset pack for the modular character as spending time creating my own during such a short time allocation for this project (5 weeks) doesn't seem like the most efficient use of my time.
The first thing I needed to figure out was how to set up a character blueprint. Prior to this project I had never created or used any UE blueprints so I'm hoping that through this project I will be able to learn a lot. I started off by opening the default Unreal Engine third person map and examining the blueprint to figure out what it does. It held the mesh and camera as well as the input/movement control system. I figured out how to replace the default mannequin mesh with the SCK default model but he was still one solid mesh, I needed him to be built of modular parts.
Once I had assembled the character I needed to make sure the animations would stay in sync as the character moved; I found out about the leader pose component from this tutorial, and then followed the UE documentation to implement it in 5.4, and as this kit is rigged to the default skeleton I didn't have to retarget the animations themselves.
My first attempt at assembling the mesh components (hint: check the right skeleton is selected)
My blueprint set up using the Unreal default third person template as a base to work from. I added each skeletal mesh component as a child under the original mesh and then deleted the mannequin.
I set the head as the leader bone so that the rest of the components would match it in all the default animations. This seems to be the easiest method to set up and as this is just a character creator system the high render thread cost shouldn't be a problem. The selected character options could always be saved using a mutual reference and then used to build a more efficient merged skeletal mesh if it needed to be more efficient in actual gameplay.
When I first started making this system it seemed logical. Then I found out you can't have multidimensional arrays in unreal (i.e an array of arrays) and it got more complicated. This is a record of how I initially approached this problem with no experience in Unreal Blueprints. Please know I came up with something much better afterwards and this is detailed later on in this blog.
This was the mesh swapping system: I set up an array of meshes containing the options and then used an integer variable which increments to cycle through them - very simple but not exactly the easiest to edit. If I wanted to add more hair mesh options I'd have to manually go into the correct array and add another element which isn't as effecient as I would like.
This was my original material swapping system which is, in my humble opinion, horrendous. If I ever added a new hair mesh and then needed to incorperate it's material into this system I would need to add a whole new branch - this was when I knew there needed to be a better way to do this.
What this shows is that on the button press the reference integer is incremented (or decremented if the prev buton is pressed) and then based on that value compared to the length it decided if the reference integer needs to loop back around in order to display the next material in which case it's set to 0 but if not then the value is just passed into the change hair function.
This is how the CC looked at this point. Cycling through materials and meshes on button presses.
Reflection:
Looking back this is an incredibly convoluted system, but it did it's job - taught me the basics of the functions I needed to understand how the mesh and material swapping systems work in UE, along with the basics of UI creation. Now I know I can create a much better system.
The inspiration for this system came from, of all places, Stardew Valley. I was creating a new character when I found myself trying to google for pictures of all the shirt options because theres over 100 tiny pixel shirts and you can't preview or compare them, you can only cycle through them. This was incredibly annoying but made me decide I wanted to do better as I had plenty of screen space to play with.
For this system I needed to learn about data tables, data tables required me to learn about structs and for the struct to work properly I needed to create an enumeration (basically an integer array but with names instead of numbers) to hold the various body part categories that I wanted to be able to change. In the struct I could then put the body part options, a picture of the option, and a reference to the mesh and materials it would use, and I could put all of the data in one place rather than in separate arrays as it had been before. This was what I was envisioning when thinking about a multidimensional array system.
In terms of implementing this system into an interactable UI I knew I needed to somehow read from the table and draw clickable buttons that would run the swapping function with the data read from the table to create that button. Thankfully this tutorial showed how to create an empty button in its own UI widget and then use versions of that empty button with data references from the table to create child buttons in scroll boxes that could be added to indefinitely without running out of screen space. This is a far better system in terms of both usability for a player and editablity as a developer. Now any new assets are all added in one place, the data table, and if a new body part slot such as an accessory slot or hat slot needed to be added it would be simple enough to add to the Enum and then to the data table using the existing structure.
The enum I created to reference bodyparts,
Struct to build data table from with all info needed for button functions. I added the reference/name variable for the saving system later on.
What the completed data table looks like.
Here is where the data is passed through from the button (which fills these variables from the data table upon construction) to the character blueprint where the mesh/material swapping actually takes place. Ref is something I added later to help with the save/load.
Constructing each button with the relevant image: the button being the target and the image variable being passed in from the data table when it is called.
This is where the buttons are constructed as children to the relevant scroll box. For each row in the data table it checks the value of the enum to sort the rows into relevant body parts. Then for each entry with that enum a new button is drawn as a child of the scroll box with the mesh, thumbnail image and material being passed from the data table to the variables created in the empty button widget. This way the entries in the data table could be jumbled up but they would still draw to the relevant UI menu sections.
changing mesh, materials and setting reference as variable for saving with data from the button
This is what the UI was looking like now. The same basic functionality as before but much more accessible and intuitive.
Now this was a really fun little widget to create. I wanted to be able to customise colour along a gradient rather than just using preset buttons. I figured the easiest material to apply this to would be the hair as I could just multiply a colour into the white base version to change the albedo output. Originally I was going to use a flat texture for the widget but then I discovered that it is possible to procedurally generate the texture as a gradient using only math functions! How cool is that!?
The UI menu has a widget option for a 2-Dimensional slider which was exactly what I needed. The X-axis would represent the hue, the Y axis the value and then I would control saturation with a separate slider. This essentially means all the possible colours are mapped as coordinates on a three dimensional grid - this, as I discovered, is refered to as HSV. I needed to be able to output this coordinate location as a set of numbers that could be read as a colour vector. Thankfully somebody else had already figured out the formula for RGB to HSV, which you can read here. This was fairly straight forward to convert into UE nodes:
The function as a written formula on this blog.
The same maths translated into nodes. I actually ended up outputting this value as a vector variable stored in a material parameter collection. This way I could plug it straight into the hair material and have it update live in game.
Updating the materials on the saturation and value sliders as well as the preview box to match the currently selected colour was a bit more complicated but this helpful tutorial [this part is from 22:50 onwards] explains the right order to set up the values in. However I encountered an issue that my saturation slider would randomly break and stop updating. I have no idea why and still don't but a workaround was to create a material instance and assign that to the slider instead of the master material. I really don't know how that worked but it seems to have solved the issue so I'm going to attribute it to a bug.
For the skin I blended the parameter into the subsurface colour, where as for the hair as it is white as a base anyway I just multiplied it into the albedo channel.
Lastly I set up this branching system to allow the player to customize both hair and skin colour. More possibilities for customization could be added and the branch node could be swapped for a switch on Enum that is assigned on a button press or something like that.
This is how the finished widget looks.
Changing the hair colour using the widget in real time.
Saving and loading was fairly easy to set up initially as UE has a lot of prebuilt nodes to run the process. I just created a savegame blueprint with all my variables that needed saving in, then cast from that to the player blueprint where all the data was stored and wrote each variable to its slot in the savegame. Loading was a bit more convoluted as I had to read the savegame variables then write that to a second variable that's used within the swapping function.
However, then I decided to overhaul the entire data system and replace my arrays with one big table so there was no longer a simple value to store and read from the variables. I needed to come up with a new system. The way I came up with was to create a reference column in the data table with the variables name in, this way I could call and save that name and get the relevant row from the table. That row then held all the information I needed to load the mesh and it's material so in a way it ended up seeming both more complex and more efficient at the same time. I definitely prefer this new system though. The material parameters are easy to save and load as they are
Original load based on arrays (very straight forward)
New load with data table (a bit more complex but still works the same)
Initially I was trying to come up with a way to move the camera in, out and around the character on the stage. Then I realised it would be a lot easier to just rotate the character and swap between several static cameras in the close and wide positions. This was the system I came up with:
A screenshot showing my camera and lighting set up.
By moving the character instead of the camera I could control the lighting easier as I didn't have to worry about it working from multiple angles.
I set up one wide shot with the whole body in view and then three close up cameras, one for each body part that is swapped - the head, torso and legs.
The basic logic of the system is to take the Y value of the mouse when it scrolls to zoom, and then using that value assign the correct camera. If it's above the body assign the face cam, if it's below the body assign the legs cam and if it's neither above or below the body then assign the body cam.
I initially tried to set this up in a blueprint but I was having issues with instigators and converting between actors and components so I decided that since this is a very simple character creator I would use the level blueprint just this once.
Debug to track Y value so I could figure out the value ranges for each camera.
Check mouse Y then assign the relevant camera.
Then blend to the assigned camera using the camera assigned at the moment they scrolled.
The character rotation system just rotates the character mesh on mouse click and drag. I am sure there are probably better ways to create this kind of effect but I like this way for its simplicity.
Camera zoom and character rotation in action.
I updated button layout for clarity, pulling the customise and reset buttons next to the colour widget as that's what they affect and increasing the size of the save button and placing it in the bottom right so it reads as the last thing to press. I placed the random button as far away as possible from all other buttons so that it is less likely to be accidentally clicked.
The randomise system for the hair and skin colour simply generates random floats for the RGB values of both parameters. I clamped the range slightly to keep the colours a bit more usable but still incredibly wacky for fun.
Also the buttons now change colour when hovered, and these colours invert when clicked. They're also a slightly nicer shape just to add some extra visual interest.
For randomising the meshes I generated random row names from the data table.
I then filtered these random outputs based on their bodypart enum value and ran the relevant swap function. This way nobody will end up with legs for hair and a torso for legs. I used a simple while loop with a counter variable i to allow enough passes that there's a good chance all 3 components will be randomised without being too costly.
Here is the randomise system in action.
Key changes:
I rescaled all the buttons to be consistent in size
I added individual buttons to remove each custom colour option
I moved the reset clothing button down to the bottom to match the other major buttons
I rescaled the scroll boxes to show part of the next item in the list to imply scrollability more clearly
Set the Customise Skin/Hair buttons to change colour when pressed to show which is currently enabled
These are all really small changes but I think it gives the UI a much more professional and balanced look and definitely improve the overall usability from a player standpoint.
I think this project has been a success. Before this I had no blueprint experience I feel significantly more confident in tackling tool and system creation in Unreal Engine which was one of my main goals when undertaking this brief; I have definitely learned a lot. I think my late decisions to add a camera zoom and rotation system really bring this project together and make the user experience much more interactive and satisfying. I think the UI is clear and well presented aesthetically even if it is a bit plain. Entire teams develop UI aesthetics in AAA games so for mine to at least be neat and functional I think meets the requirements of the brief. I could have been a bit more creative in it's presentation but that just wasn't where my priorities were for this project - I was focused much more on the technical aspects.
The tool functions really well and I think I did a good job at making it project-agnostic i.e the meshes, materials and even character model can be easily swapped. One issue I just realised is if the character is swapped to one of a very different scale to the original then the camera system may not zoom into the relevant points but that is an edge case which could be rectified by the artist manually changing the camera locations. The usability overall from a developer perspective was something I prioritised. I'm glad I reworked the project early on to pull all assets from one single data table rather than relying on multiple separate arrays for each mesh category and material category. This is a much cleaner solution and has made the documentation easier to follow.
The biggest thing I would like to improve for this project would be the camera system. Currently it operates out of the level blueprint which is definitely not the most efficient way of doing this. It's not a big problem for a project like this because it is a singular contained level and presumably once the player saves their character and progresses to the main game they would no longer need to use this camera system. However if this was a system more dynamically integrated into a game rather than just an initial set up then I would definitely need to change how this works. I know how I would do this; I would create a new custom gamemode and use a pawn blueprint for the camera. I did start attempting this but the logistics of integrating a new gamemode so late into the project proved risky and I decided not to pursue it for now.
Another thing I would improve is again on the technical side and that is I would find a way to set up and use a blueprint interface rather than casting data back and forth between all my various blueprints. This would just be a lot neater and more professional overall and the only reason I didn't set it up to begin with was because I didn't know it existed! For all my future projects the interfaces seem to be the way to go especially for more complex systems that require multiple variables being passed around like in this project.
In terms of my time management and organisation for this project I think it was effective. I struggled to plan out this project initially because I had little experience working with these kinds of tools so I had no reference to pull from, however I had a clear list of features and systems that I could work through in order of priority. One thing I didn't consider was how certain non-essential but fundamental features may have benefited from being built earlier in development, like the camera system for example. This wasn't part of the brief so I didn't prioritize it but if it had been built earlier I think it would have been a much smoother process overall. This is something I am going to take forwards into my FMP and prioritise not just based on final impact and marking criteria but also foundational importance as well such as how many other systems will rely on it.