We have added customizable parts in the mod settings in this paragraph. So we can define parts appearance when CrystalCoat is ON and also when it is OFF. Most of the time, users will want to customize only one of the CrystalCoat states at a time.
So it would be nice to be able to hide all CrystalCoat ON/OFF settings. Mod Settings allows to do this natively using dependencies.
In the general settings category we need to add two toggle settings.
The toggle for CrystalCoat OFF settings is disabled by default. Because unless wanted by the user, you want your vehicle to revert to its original appearance and keep the custom settings for the CrystalCoat appearance.
You must replace the secondary keys and the mod name with yours. Then into your JSON language files you must define 4 new keys.
{"$type":"localizationPersistenceOnScreenEntry","femaleVariant":"Show CrystalCoat™ ON settings","maleVariant":"","primaryKey":"0","secondaryKey":"MyNickName-MyModName-general-enable_cc_on"},{"$type":"localizationPersistenceOnScreenEntry","femaleVariant":"Show settings concerning the CrystalCoat™ ON state.","maleVariant":"","primaryKey":"0","secondaryKey":"MyNickName-MyModName-general-enable_cc_on-desc"},{"$type":"localizationPersistenceOnScreenEntry","femaleVariant":"Show CrystalCoat™ OFF settings","maleVariant":"","primaryKey":"0","secondaryKey":"MyNickName-MyModName-general-enable_cc_off"},{"$type":"localizationPersistenceOnScreenEntry","femaleVariant":"Show settings concerning the CrystalCoat™ OFF state.","maleVariant":"","primaryKey":"0","secondaryKey":"MyNickName-MyModName-general-enable_cc_off-desc"}
Then for each setting that concerns the CrystalCoat OFF state, we need to add a new runtimeProperty to create a dependency with the OFF toggle setting we have just created.
Each setting that concerns the CrystalCoat ON state needs a new runtimeProperty to create a dependency with the ON toggle setting we have just created.
When you disable either one of the toggle settings, their associated settings we be hidden from the mod settings page. This will allow for a better readability of your settings page.
Display vehicle parts
Another cool feature would be to display the vehicle parts associated with each setting so the user easily understand what he is modifying.
This feature requires to use additional files to add to your project.
This ZIP archive contains 3 files:
vehicle_part.inkatlas: this file acts like an organizer for the following texture so the script can select only a portion of it.
vehicle_part.xbm: a texture that contains one vehicle picture for each setting.
vehicle_picture.inkwidget: this widget will display the vehicle image into the mod settings page.
Regroup these files into a folder then open the InkAtlas file and update its slots array by using the XBM texture file path into the texture field of each array element.
In order to create pictures for your settings you can use the photo mode of the game while your vehicle is using a specific appearance. In the example picture of the Caliburn above I have used a black matte paint with only the relevant component using a red glossy appearance.
Create a matte paint aspect
First lets create the matte paint aspect following this paragraph. I will only talk about the differences. Concerning the mlsetup, the base mltemplate file to use is this one.
Offset U = 0.0
Offset V = 0.0
Roughness In = null
Roughness Out = (0.2953,0.498)
Normals = (undefined)
Metalness Out = (0,0.5)
ColorCode = 3eaf77_d42857
µBlends texture = base\surfaces\microblends\default.xbm
µBlends tiles = 10,1000004
µBlends contrast = 1
µBlends normals = 0
µBlends offset U = 0
µBlends offset V = 0
Next, for each impacted mesh file in the matte material remove all parameters except the MultilayerMask and MultilayerSetup.
Create a red appearance
Now we want all customizable components to be able to use a red glossy appearance. In order to do this follow this paragraph. I will only talk about the differences here.
Customizable components are the roof, the sun visor, the wheels inserts and the wheels painted areas.
In order to a have a deep color we need to create a custom mltemplate and define color codes into it. Then use this mltemplate file into the mlsetup instead of the original one. Add this file to your project and open it in WolvenKit.
Go into overrides > colorScale > c2162c_null > v and set these values (1, 0, 0) for a deep red color. It uses a RGB float code. Index 0 is red, index 1 is green and index 2 is blue. Value 0 is black, 1 is white. Now save the file and copy its relative path.
In the mlsetup file use the path to your custom mltemplate file and then use the red color code we have modified c2162c_null. Then into the red material definition of the mesh files, set these parameters values.
Now our parts can all be turned into black matte paint and red except for the sun visor. So we need to allow the sun visor to use paint materials. Let's make all of our parts be able to use the same materials. It will offer more options to the users.
For this we need to modify the type used by the sun visor setting field related to CrystalCoat ON state.
Next we need to create a WorldWidgetComponent to enable CrystalCoat coloring on the sun visor.
Finally we need to copy the missing appearances, materialEntries and localMaterialBuffer > materials entries from the roof component and paste them into the sun visor mesh file.
We can simply copy them because both components have been created from the same body_01 component so they share the same mlmask.
We need to copy the coated, metallic, glossy and matte appearances and materials. Now our vehicle is ready for a photo shoot.
Create the vehicle parts texture
We have added a file named vehicle_parts.xbm to our project. This file contains a matrix of pictures for each of the customizable components of the vehicle. In order to populate this texture we will use Adobe Photoshop or another image editor.
Now take clean screenshots of the vehicle focused on the red part. We need one picture for each customizable component.
Into your image editor create a new project with a size of 2000x2000 pixels and a transparent background. We are going to append our pictures stick to each other from top to bottom and apply some color correction. As we only have 4 pictures we can simply put them one under the other.
Import your pictures one by one and resize them to 800px large by using Edit > Free Transform. Then use the Image > Auto Tone to fix the overall colors. You must have one picture per layer. So you need to apply the color correction on each of them seperately.
Now we need to enhance the red color to make it more visible. To do this select the areas that correspond to the red parts using the menu Select > Color Range. Then use a tolerance of 100 and once you are done, remove any part of the selection that wouldn't correspond to the red areas of the vehicle component.
Next use the menu Image > Adjustments > Vibrance and set a saturation of 100 and a vibrance of 50.
Next we need to crop the bottom corners so the images will fit into the widget. To do this create a rectangle selection with size 70x70 pixels. Then use Select > Transform Selection to rotate it to 45° so you get a diamond-shaped selection. Now save your selection using the menu Select > Save Selection and name it "big diamond".
Now move your selection to the bottom-right corner of the first picture so the center of the selection is aligned with the corner. Then select the corresponding layer, right-click on it and select Rasterize Layer. Then hit the DEL key to remove the pixels. Repeat this operation for the other pictures.
Now create a new selection with size 16x16 pixels and repeat the same operation with the bottom-left corner of all the pictures. You should now have this final result.
Export the vehicle_parts.xbm file into PNG using the Tools > Export Tool. Now erase the PNG file with your new texture. Then import it back into WolvenKit using these options.
Organize the texture parts
The next step is to open the vehicle_parts.inkatlas and define the different parts of the texture. To do this go into the slots > [0] > parts array. Edit the part_name element by changing its name with the component name body_01_roof_custom.
Then into the clippingRectInUVCoords field we need to define the texture area corresponding to the picture. UV coordinates goes from 0 to 1 in both axis. (0;0) is the top left corner of the texture, and (1;1) is the bottom-right corner. As we have defined 4 pictures vertically we must divide the parts as following.
Create duplicate this element in the parts array to create the other ones.
Next we need to add these two methods (outside of any class). Then perform some case-sensitive replacements into it.
Replace MyModName with the name of your mod that you have used in the runtimeProperty elements of your mod settings (line 49). This should be the name as it appears in the mod list.
Replace path\\to\\vehicle_parts.inkatlas by the relative path to yours. You must use double-backslashes (\\) at line 62.
Then as you can see we are using if blocks (lines 66-93) to identify settings category (line 66) and field (line 68) using secondary keys. You must use your own keys there. Then we assign the texture part name into the widget (line 70) so you may need to update these part names too with what you have set into the InkAtlas file.
You must trigger a vehicle part picture for both the CrystalCoat ON setting and the CrystalCoat OFF setting. Example at lines 68-69.
@wrapMethod(SettingsSelectorController)protected cb funcOnHoverOver(e: ref<inkPointerEvent>) ->Bool {let parent = this.GetRootWidget().parentWidget as inkCompoundWidget;let currentWidget = this.GetRootWidget()as inkCompoundWidget;let currentLabelWidget = currentWidget.GetWidgetByPathName(n"layout/labels/label");ifIsDefined(currentLabelWidget) {let currentText = (currentLabelWidget as inkText).GetText();// Look backward in the list for the closest categorylet i =0;let lastCategory: String="";while i < parent.GetNumChildren() { // Iterate over list elementslet listItem = parent.GetWidgetByIndex(i)as inkCompoundWidget;// Iterate over list items from 0 until the selected oneif currentWidget == listItem {break; }ifEquals(listItem.GetName(), n"settingsCategory") {let label = listItem.GetWidgetByPathName(n"layout/inkCanvasWidget4/label"); lastCategory = (label as inkText).GetText(); } i +=1; }let widget = this.GetRootWidget().parentWidget.parentWidget.parentWidget.parentWidget.parentWidget.parentWidget as inkCompoundWidget;ifIsDefined(widget) {// Check that the relevant mod is selected in the mod list widget = widget.GetWidgetByPathName(n"mod_settings")as inkCompoundWidget;let modList = widget.GetWidgetByPathName(n"mod_flex/mod_scroller/mods")as inkCompoundWidget; VehiclePart_WidgetHandler.Get(GetGameInstance()).modList = modList;let isValidModSelected =false;let i =0;while i < modList.GetNumChildren() {let child = modList.GetWidget(i)as inkCompoundWidget;let highlight = child.GetWidgetByPathName(n"txtValueHighlight");// Opacity = 1.0 means that the mod is selectedifEquals(highlight.GetOpacity(), 1.0)&&Equals((highlight as inkText).GetText(), "MyModName") { isValidModSelected =true;break; } i +=1; }// Display the picture of the vehicle partif isValidModSelected {let widgetHandler = VehiclePart_WidgetHandler.Get(GetGameInstance());let atlasResource = r"path\\to\\vehicle_parts.inkatlas";let texturePart: CName;let showWidget: Bool=true;ifEquals(lastCategory, GetLocalizedTextByKey(n"hgyi56-MyMahirSupron_Mod-body-cat")) {ifEquals(currentText, GetLocalizedTextByKey(n"hgyi56-MyMahirSupron_Mod-body-on-roof"))||Equals(currentText, GetLocalizedTextByKey(n"hgyi56-MyMahirSupron_Mod-body-off-roof")) { texturePart = n"body_01_roof_custom"; }elseifEquals(currentText, GetLocalizedTextByKey(n"hgyi56-MyMahirSupron_Mod-body-on-sunvisor"))||Equals(currentText, GetLocalizedTextByKey(n"hgyi56-MyMahirSupron_Mod-body-off-sunvisor")) { texturePart = n"body_01_sunvisor_custom"; }else { showWidget =false; }if showWidget { widgetHandler.ShowVehiclePart(atlasResource, texturePart, widget, this); } }elseifEquals(lastCategory, GetLocalizedTextByKey(n"hgyi56-MyMahirSupron_Mod-wheels-cat")) {ifEquals(currentText, GetLocalizedTextByKey(n"hgyi56-MyMahirSupron_Mod-wheels-on-painted"))||Equals(currentText, GetLocalizedTextByKey(n"hgyi56-MyMahirSupron_Mod-wheels-off-painted")) { texturePart = n"wheel_01_painted_custom"; }elseifEquals(currentText, GetLocalizedTextByKey(n"hgyi56-MyMahirSupron_Mod-wheels-on-inserts"))||Equals(currentText, GetLocalizedTextByKey(n"hgyi56-MyMahirSupron_Mod-wheels-off-inserts")) { texturePart = n"wheel_01_inserts_custom"; }else { showWidget =false; }if showWidget { widgetHandler.ShowVehiclePart(atlasResource, texturePart, widget, this); } } } } }returnwrappedMethod(e);}@wrapMethod(SettingsSelectorController)protected cb funcOnHoverOut(e: ref<inkPointerEvent>) ->Bool {let widgetHandler = VehiclePart_WidgetHandler.Get(GetGameInstance()); widgetHandler.shouldBeVisible =false; widgetHandler.FadeWidget(false);returnwrappedMethod(e);}
Now you can test your mod and see that when you hover on the settings they fade out the mod list and make a picture appear to illustrate what this setting is refering to.