Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
This tutorial provides a brand new way to implement CrystalCoat for any kind of vehicle and to make any part of a vehicle customizable by the user during runtime. The only limit is your imagination !
Created: Jun ?? 2024 by @hgyi56 Last documented update: -
Modification policy
The author of this tutorial does not allow other users to modify it without his permission. Instead users shall contact the author using the links provided above and post comments on the relevant pages.
From the in-game perspective, this is a technology developed by Rayfield manufacturer that enables a vehicle to get colored dynamically. You can think of it either as a huge matrix composed of tiny LEDs that would cover most parts of the vehicle allowing its user to select a color at any time.
Or it could also be a holographic technology that would cover the vehicle with some kind of 3D projection of a new color.
Now from the modder's perspective, this feature is made possible by applying InkWidgets onto mesh components.
InkWidgets were initially developed in order to display animated content for TVs, computer monitors or vehicle UI. From game v2.11 they are now also used to display colored shapes wrapped on a mesh dynamically.
In order for your mod to work you need to install dependencies from Nexusmods:
Keep in mind that this is my way of implementing CrystalCoat and there may be other ways to do it.
This tutorial assumes that you and your users will install either Crystal Coat Fix v2.12 or Enhanced Vehicle System.
Either of these mods contain a script part that is currently required for CrystalCoat mods based on this tutorial to work perfectly.
They will still run without one of them but the result will be degraded.
Well it depends on what you intend to do. This tutorial is incremental. It means that you start at the beginning and you can eventually stop at some point if what you have created satisfies your goal.
Although I explain things step by step it may not be exhaustive otherwise this tutorial would become very long. Here is a list of skills that will help you to go through this tutorial.
Usage of WolvenKit. Using its tools, adding game files into your project, modifying array elements, knowing how to pack and install a mod, importing and exporting files from WolvenKit.
Usage of MlsetupBuilder (MLSB). Installing and preparing the tool, importing and exporting mlsetup files, modifying a material layer.
Usage of Blender. Installing the Cyberpunk related extension. Importing and exporting a entity or mesh file, creating a submesh.
Usage of Adobe Photoshop (or another image editor). Have a basic knowledge of layers, channels, know how to use some tools like the magic wand, gradient tool, fill a selection, create a selection, create a layer style, apply a filter.
Scripting basics. Know how to navigate into a JSON file, YAML file, REDS file, perform a replacement of a string, append a block by copy-pasting it from the tutorial.
Usage of a good text editor. I recommend using Visual Studio Code and installing the redscript-ide extension and the redscript syntax highlight extension.
You can test my mod Crystal Coat for Rayfield Caliburn to have an idea of what can be done.
Things you will be able to do after learning this tutorial:
Add CrystalCoat to any kind of vehicle.
Define a secondary color.
Customize the color picker widget with your brand and vehicle image.
Create multiple paint aspects and use any of them during runtime.
Create multiple appearances for all the vehicle parts and use any of them during runtime.
Clean the rust and dust from your vehicle parts and windows.
Modify window glass.
Create any amount of customizable components on your vehicle. They can be sub-parts of existing component.
Customize the vehicle appearance when CrystalCoat is ON and when it is OFF during runtime.
Allow anyone to add translations to your mod.
Display vehicle part images in the mod settings menu.
Create a default settings package for your mod settings to allow mod community admins to customize the mod's default settings for their users.
Each mesh component that you want to make colorable needs its own WorldWidgetComponent. For example you want the body_01_painted_custom
component to be colored so you need to create a new item in the component list with type WorldWidgetComponent.
Now we will set it up for CrystalCoat. Some of its fields shall use specific values.
For both the meshTargetBinding
and parentTransform
fields you shall create a handle. To do this click on the field, then on the right create a handle and select the only available type. Finally write the name of the mesh component into the bindName
field of it. In this example the component name is body_01_painted_custom
.
The name of the WorldWidgetComponent can use a pattern that will allow the script to handle any component for CrystalCoat. In order for this to work you must always use a name like this:
For this example the name will be visual_customization_body_01_painted_custom
.
Now for the widgetResource
field you must give it the InkWidget file that defines how the color will be wrapped onto the mesh.
The game uses a InkWidget that is customized for the Rayfield Caliburn or for the Rayfield Aerondight vehicles so it is unlikely to be usable as it is for any other vehicle without displaying unmatching patterns.
The details of how this InkWidget is structured and how to modify it will be covered into another paragraph. We will start in easy mode for now so we want a InkWidget that can be used on any part of any vehicle.
I have modified the InkWidget from the Rayfield Caliburn of the game v2.12a
so it will display only a primary color on the entire UV map.
I will explain into another paragraph how the InkWidget is structured, how it works and how you can modify it to define a secondary color for your specific vehicle.
This ZIP archive contains 6 files:
primary_color.inkatlas
: this file uses a simple white square texture from the base game in order to apply a primary color for widgets.
secondary_color.inkatlas
: this file will be useful to add a secondary color mask to the widget.
secondary_color.xbm
: the mask texture associated with the InkAtlas.
vvc_car_appearance_widget.inkwidget
: this widget is meant to apply both a primary color and a secondary color on the vehicle. The secondary color will be applied using a mask. This is the most advanced widget we will use at the end of the tutorial.
vvc_full_primary_only.inkwidget
: we will use this widget at the beginning. It only applies a primary color on the entire component it is bound to.
vvc_full_primary_and_secondary.inkwidget
: this widget applies both a primary color and a secondary color on the entire component it is bound to. It does not use a mask file for the secondary color.
You must deploy these files into your project's unique folder, for example into a widget
folder. Then we need to update the relative to the InkAtlas files into the three widgets.
When it comes to modifying a resource path into a InkWidget file it is more reliable to convert the widget into JSON and to modify the text file.
Right-click on the InkWidget file and click on Convert to JSON
. This will create a clear-copy of the file as readable JSON into the raw
folder. Open it with a text editor and replace the 3 occurrences of:
By your relative path to primary_color.inkatlas
(you must use double-backslashes \\
). Next, replace the 2 occurrences of:
By your relative path to secondary_color.inkatlas
(you must use double-backslashes \\
).
Then save the file and right-click on the readable JSON file in the raw
folder of the Project Explorer and click on Convert from JSON
to rebuild the InkWidget file.
Repeat this process with the other InkWidget files
vvc_full_primary_and_secondary.inkwidget
contains 5 occurrences of primary_color.inkatlas
and none of secondary_color.inkatlas
.
vvc_full_primary_only.inkwidget
contains 3 occurrences of primary_color.inkatlas
and none of secondary_color.inkatlas
.
Now your InkWidget files are ready to be used by any component. For now we will only use the vvc_full_primary_only.inkwidget
. Copy the relative path to your InkWidget file and write it into the widgetResource
field of the WorldWidgetComponent.
You now have a WorldWidgetComponent properly configured for CrystalCoat. All you have to do in order to work with another mesh component is to duplicate this WorldWidgetComponent and then update its meshTargetBinding > bindName
and parentTransform > bindName
fields with the new component name.
You can finally test your mod !
Wait ! My doors are not colored ! This is not good !
This is great ! Now we need to apply CrystalCoat to all 6 doors and the side mirrors which correspond to these components:
Driver door: door_fl_a
Front passenger door: door_fr_a
Back left door: door_bl_a
Back right door: door_br_a
Trunk left door: trunk_a
Trunk right door: trunk_b
Left mirror: mirror_fl_01
Right mirror: mirror_fr
All you have to do is to follow this tutorial for each part of the vehicle.
Wait ! This is cheap CrystalCoat ! What kind of green is this ?
As you can see the base color of the vehicle under the CrystalCoat is still present and modifies the resulting color. When applying CrystalCoat on a vehicle we shall use dark gray or black color as the base color. I will cover this modification later in the tutorial.
Now you have added all 6 doors and side mirrors to CrystalCoat, you need to make the new components detachable along with their original component.
Use the Tweak Browser and look into the vehicle record in the destruction > detachableParts
. Then look for the parts that correspond to the one you have duplicated.
In this picture you can see the TrunkLeft
detachable part that corresponds to the trunk_a
component. As we have duplicated this one to create the trunk_a_painted_custom
, we need to add it into the list of components for this detachable part.
In order to create a YAML file dedicated to detachable parts, right-click on the detachableParts
array and create a TweakXL override.
Then right-click on the TrunkLeft
element and select TweakXL > Copy to clipboard
. Finally erase the YAML file content with this data.
We want to append a new element to the components array.
Then repeat the same operation for all relevant detachable parts of the list. You should now have a YAML file with this content.
Concerning the trunk_a
part, you can see that it is also present into the destruction > deformableParts
array.
So you need to perform the same operation that we did for the body_01_painted_custom
component.
In order to clean a vehicle part we need to add its mesh file to our project, then assign this new file to the component in the APP file.
Next, identify the material being used by this component by looking into the APP file at the meshAppearance
field of the component. Then in the mesh file find the relevant chunk.
Here are the vehicle parts to clean for the Mahir Supron:
tire_01_fl_a
tire_01_fr_a
tire_01_bl_a
tire_01_br_a
wheel_01_fl_a
and *_custom
wheel_01_fr_a
and *_custom
wheel_01_bl_a
and *_custom
wheel_01_br_a
and *_custom
bumper_f_01
bumper_b_01
engine
junk_fuelcell_01
body_01
and *_custom
door_fl_a
and *_custom
door_fr_a
and *_custom
door_bl_a
and *_custom
door_br_a
and *_custom
mirror_fl_01
and *_custom
mirror_fr
and *_custom
By chance the tires mesh file already contains a clean
appearance for the Mahir Supron so simply use it in the APP components meshAppearance
field.
For the other ones we need to edit their material setup.
Add the mlsetup being used by the material to your project and add a suffix to it so we know what it is all about: _clean
. Using its original name and appending a suffix tells us that this file is meant to be accurate to the original except that we remove the rust and dust from it.
Assign this new mlsetup file to the material definition. Then open the mlsetup file into MLSB. Load the 3D model of the corresponding mesh and identify dirt / rust / dust layers. Turn their opacity to 0 and export the file to JSON. Then import it back into WolvenKit.
Mlsetup files are shared between multiple components so once you have edited the mlsetup you will just have to assign it to the other components that use the same mlsetup.
Wait ! I can see yellow on the doors plastic parts ! Also the aluminium on wheels is messy !
We know that the Mahir Supron is using layer 0 as the paint layer which covers the entire vehicle. This implies that if additional layers are not precisely drew then the underlying layers will be visible. Depending on how the game developers have created layers it may be more or less accurate.
From what I have seen on multiple vehicles they seem to have used brush painting to create masks which I find it to be a messy way to create layers except for rust and dust which are messy by design.
Instead we must use Blender to select the mesh parts that shall be impacted by a layer and then export the UV layout associated with this selection.
Finally create a mlmask layer using this UV layout. The result will be perfect to the pixel. It also depends on the layer resolution because if the layer image is too small (like 256x256 on some vehicles) then it will lack precision. I prefer to use 1024x1024 layers which is the case for most vehicles anyway.
Open one of your doors mesh file and look in the poor_06
material. Add the mlmask file to your project and add the _clean
suffix to it.
Export the mlmask, the 6 doors mesh files and the body mesh file using the Tools > Export Tool
. Plastic parts are defined in the original component for each component, so you don't need to export _custom
components files.
Import all the mesh files into Blender, hide unused chunks and select all the chunks that include plastic parts (one per component). Then go into Edit mode
using the Tab
key and select all the areas affected by plastic. You can move the different mesh objects to make things easier.
Then use the UV Editing
view in the menu bar to display the UV layout that corresponds to your selection. Use this menu to export the layout to a PNG image UV > Export UV Layout
.
Then be sure to set a size of 1024x1024 and to set a Fill Opacity
of 1.00. Also name your PNG file layer11.png
so we know which layer is associated with this layout.
Now into a image editor like Adobe Photoshop open the mask layer 11 from your mlmask PNG files set. Then import the UV layout you have just created into that project and create a layer style on it to fill it with white.
Then merge layers and save the PNG file in-place. Import the mlmask layers set back into WolvenKit using the Tools > Import Tool
. Finally affect this new mlmask file to impacted components into their poor_06
material at localMaterialBuffer > materials > poor_06 > MultilayerMask
.
body_01
door_fl_a
door_fr_a
door_bl_a
door_br_a
As you can see the aluminium parts of wheels have some black artifacts. This is due to a messy layer mask. So you just need to repeat the same operation as above for the _insert
mesh file of the wheels.
You need to fix the layer 7 of the mlmask. Then assign the new mlmask to the coated
and poor_01
materials of the _inserts
mesh file.
We only need to update these 2 materials for wheels because these are the only materials that are using the original mlmask of the game.
You must update the AppearanceVisualController
list when you create a new component or if you modify its name, mesh appearance or mesh file path.
Now test your mod and see that all plastic parts and wheels aluminium parts are clean !
Awesome ! Now I want to create a secondary color !
In order to get a nice result for our CrystalCoat implementation we need to use some scripting. This script will also allow us to go deeper into the customization later.
Download this template, rename it with your mod name, and place it into your project at this location.
Then edit the script using VSCode. You need to perform some replacements into the file to make it work for your mod. To replace text use CTRL + H.
Perform case-sensitive replacements to avoid mistakes.
First you need to define the name of your module at the top of the file. The module name acts as a namespace for your types definition so they won't collide with another mod. It also allows other mods to check if your mod is installed.
You must use a unique combination of words to define your module name. I recommend you to use your nickname followed by your mod name.
In order to change this perform a global replacement using CTRL + H. Replace My_Nickname.My_Mod_Name
using the case-sensitive option.
You should have replaced 3 occurrences.
Next you need to use your vehicle record name. In this tutorial I am using the Mahir Supron with this record name Vehicle.v_standard25_mahir_supron_player
.
If you are working with another vehicle then perform a global replacement of this sequence with yours. You should have replaced 1 occurrence.
Do you remember what I told you about how does the game use appearances when it loads a vehicle ?
The script needs to monitor entity loading and it must hook only if this is the vehicle entity that your mod is working with. This is why you need to tell the script what is the entity ENT file and what is the entity appearance name to look for.
You need to replace mahir_supron__basic_player_01
by the base appearance name of the vehicle you can find in the vehicle record using the Tweak Browser at the field named appearanceName
. You should have replaced 1 occurrence.
You need to replace the entity file path by the one from the base game, that is to say by the one that is actually loaded by the game during runtime (not your own file !). Remember that our own ENT file only serves the purpose of having its content appended to the base game file.
You should have replaced 1 occurrence.
You must use double-backslashes (\\
) for file paths in the script file.
This script can be extended to support multiple vehicles so you will find an enumerated type.
If you are not creating a mod for the Mahir Supron you should replace the model name in this type definition and also into the code. Replace the type definition manually, then replace usage occurrences by looking for .Supron
You should have replaced 2 usage occurrences.
In this section I will explain the minimal steps required in order to enable the feature on the vehicle. I will use the Mahir Supron vehicle as an example. You can buy this vehicle at the autofixer.
First you need to make your vehicle entity aware of the CrystalCoat feature. To do this you need to use TweakXL. Open WolvenKit and look into the Tweak Browser. Type your vehicle record name and right-click on it. Then create a TweakXL Override.
This will generate a new YAML file with the record name into your project at resources/r6/tweaks
. Open it and write this content. If needed, replace the vehicle record name at line 1 with yours. We will fill the customizableAppearance
field later.
This will tell the game to allow this vehicle to toggle CrystalCoat and to display the color picker widget. Now in the vehicle entity (*.ent
) file you need to create a new appearance dedicated to CrystalCoat for your vehicle.
The game will load the vehicle using its base appearance that is defined in the vehicle record at the field appearanceName
. This is what it does for all entities.
If the vehicle supports CrystalCoat, the game will assemble the entity during loading by swapping the appearance silently with the customizableAppearance
.
What is important to note is that the appearance being used will still be the base appearance. But the components being loaded and visually present on the screen will be the customizable ones.
This is why I am saying that the customizableAppearance
is being used silently.
When you toggle the CrystalCoat feature the game plays a distortion effect on the vehicle. This must be appended into the ENT file in the components > EffectSpawner3546 > effectDescs
array.
The easiest way is to copy the effect entry from the Rayfield Caliburn's ENT file and paste it into yours.
If you are editing an existing vehicle of the game, add the ENT file into your project and place it into a folder with a unique name to prevent collision with other mods.
Create a root folder named <mynickname>_modding
and place everything into it.
Open the ENT file in WolvenKit. Click on the appearances
array and then create a new appearance on the right.
Now define a unique name for the CrystalCoat appearance and write it down into the two fields:
appearanceName
name
It is not mandatory to use the same name in both fields but it will make things more simple. I would advise you to define a name that will not collide with another mod. You could for example use your nickname followed by the vehicle model followed by a CrystalCoat suffix:
Don't forget to write this appearance name into your YAML file in the customizableAppearance
field ! This field corresponds to the name
field of the entity appearance (not appearanceName
).
You must also set the path to the APP file of the vehicle which contains the actual appearance definition. The appearanceName
field must correspond to the appearance name that you will define into the APP file.
If you are modifying an existing vehicle of the game you should not replace the ENT file of the base game with yours or this will prevent other modders from adding new appearances to this vehicle.
Instead you shall use ArchiveXL to append your entity appearance to the base game ENT file dynamically. Create a new XL file by using the WolvenKit menu File > New File
. Click on ArchiveXL then select the XL file type.
This will create a new file into resources
folder. Add this content into it.
On the left side you must use the relative path to your own ENT file. Right-click on your ENT file in WolvenKit and copy its relative path then paste it here.
On the right side you must use the relative path to the base game ENT file. Find it using the Asset Browser and copy-paste its relative path.
Now you have created the top-level appearance you need to actually define it. This part is done into the APP file. Find the APP file using the Asset Browser and add it to your project. Then place it into your unique folder.
You shall duplicate the base appearance of the vehicle and change its name with what you have defined previously in the appearanceName
field of the entity appearance. Then remove all appearances from the file leaving only the one you have just created.
Then you shall add engine\ink\textures\4x4_transparent.xbm
into the resolvedDependencies
array of the appearance.
CrystalCoat consists of displaying a colored widget above a mesh. The problem is that the mesh itself uses a multilayered material definition defined in a mlsetup file.
This file defines the material layers applied on associated mask layers (mlmask file) to get the final result we have on the screen for this mesh. Each layer also uses a color.
The Mahir Supron uses a yellow / light brown color for the painted areas of the vehicle. We need to modify this color and to use a gray color instead so it will be neutral for any CrystalCoat tint.
Generally all the painted parts of the vehicle use the same mlsetup / mlmask combination. For the Mahir Supron you can find these files in the material definition in the parameters MultilayerMask
and MultilayerSetup
.
Add the mlsetup file to your project and place it into your unique folder. Then rename it with a simple name like coated.mlsetup
. Then assign it to all your *_painted_custom
components in their respective coated
material definition: localMaterialBuffer > materials > coated > MultilayeredSetup
.
Then right-click on the file and convert it to JSON. You must install the MlsetupBuilder plugin for WolvenKit in order to modify a mlsetup file.
To do so go into HOME in the menu bar and then Plugins
to display the plugins menu. Install MlsetupBuilder and restart WolvenKit. Follow this tutorial to properly setup this tool.
Then right-click on the coated.mlsetup.json
file and choose Open in MlsetupBuilder
. Import the layers into the editor by clicking on the orange button.
If you have correctly setup MlsetupBuilder you can display a 3D model of the vehicle in the tool. To do so click on the Models library
tab then search for "Supron" and click on the body part.
Go back into the Editor
tab and click on each layer until you find the one that corresponds to the painted areas. Layers are ordered from 0 (top) to 19 (bottom) and are added on top of each other (1 is applied above 0, etc). Each layer is linked with the mask that uses the same number of the mlmask file.
If layer 0 and layer 1 cover the same areas then layer 1 will cover layer 0. Sometimes the paint-related layer is the layer 0 that always covers everything, but then all additional layers will cover non-painted areas of the vehicle so in the end, by substracting these layers area to the layer 0 you get the painted areas. In this case we would be interested by the layer 0 to modify the paint color.
You can also look at the color used by the layer to find the relevant one. We know that the Mahir Supron uses a yellowish/brown paint.
For the Mahir Supron the layer responsible for the painted areas is the layer 0.
We shall use a dark gray or a somewhat black color to replace the base vehicle color.
For this particular case we want to keep the stripes on the sides of the vehicle so we need to have a lighter gray color for layer 0 and a darker gray color for layer 2.
You can use the code 720cfa_d42857
for layer 0 and 3eaf77_69039f
for layer 2. Enter these codes into the text input next to the colored rectangle for each layer then immediately hit ENTER to validate the new color and immediately click on Apply edits
at the top of the layers list before working on the next layer.
If you want to keep everything else identical you can just export the file using File > Mlsetup > Export
and erase the JSON file and then convert it back into mlsetup in WolvenKit.
On some computers when exiting MlsetupBuilder tool the computer may crash with a BSoD. In order to avoid this crash I have experienced that using CTRL + R to reload the instance and then using the menu to exit File > Exit
tends to avoid crashes.
Wait ! My vehicle still has rust and garbage all over it ! This is bad work !
As we are modifying the material definition we can also clean the painted parts.
The different rust marks on the vehicle are part of some layers. In order to remove them we only need to change the opacity of the relevant layers to 0. Click on each layer to identify messy ones and modify their Opacity
slider to 0 then click on Apply edits
before working with another layer.
This is the list of layers to make invisible: 7, 8, 13, 15, 16, 17, 18.
Export the file using File > Mlsetup > Export
and erase the JSON file. Finally convert it back into mlsetup in WolvenKit.
The paint is now fixed to keep a neutral color that will suit any CrystalCoat tint. I have also cleaned the rust and dust.
Wait ! Are you kidding ? My windows are covered with crap !
You may have noticed that windows are still dirty and their tint feels a bit too clear. In order to fix this we need to add all the window mesh files into the project and then modify their material definition.
Window components are the following:
body_01_window_bl_01
body_01_window_br_01
window_bl
window_br
window_fl
window_fr
window_f
trunk_a_window_01
trunk_b_window_01
Assign your custom files to these components and then open the window_fl
mesh file. Go into the preloadLocalMaterialInstances
array and look into the glass
material definition.
This mesh is using a MI file as the base material. MI files are instances of a MT file bundled with predefined parameters so it can be reused easily on multiple components.
Add this MI file to your project and rename it window_glass.mi
. Then edit the file and change these parameters into it.
Then save the file and assign it to all your window mesh files. Also remove any existing parameter present in the values
array of the material definition. To quickly empty an array right-click on it and select Delete All Items in Array/Buffer
.
Your windows are now clean and have a tint that feels more realistic / more visible.
Wait ! When I choose the color it says "CrystalCoat by Rayfield" and a sports car is displayed !
Also secondary color does not work !
When you know how to implement CrystalCoat for one mesh component you can apply the same work on the other components.
I will use the example of the body_01
component which is the biggest one of the vehicle. The idea is always the same: submesh the component into multiple ones so you can customize as many sub-parts of the component as you wish and treat them automatically with a script.
On the picture above of the Mahir Supron, you only want to color the light brown areas which correspond to the painted parts of the vehicle. Coloring the entire mesh wouldn't make sense.
We will isolate the painted parts into a new component with a single chunk. First go into the APP file and duplicate the body_01
component into a new one called body_01_painted_custom
. Adding a common suffix to all our customizable components will be helpful for the scripting part of the mod.
The name of all the customizable components of the vehicle will end with _custom
. Add the mesh file of the body_01
to your project and duplicate it. Then add the _painted
suffix to the duplicated file so we know what it contains. Finally assign these files to the body_01
and body_01_painted_custom
components.
You should now have this project structure.
Export both mesh files and then open one of them in Blender. You can see that the interesting chunk is the first one with index 0 so you can hide the other ones.
Select the faces that correspond to the painted areas and press P
then choose Selection
to create the submesh. Now export the new submesh alone into the mesh file that corresponds to body_01_painted_custom
and then export the other submeshes to the base mesh file.
Be careful not to duplicate submeshes between multiple files !
Import both files back into WolvenKit. Now we need to configure the new mesh component to allow it to be colored.
First open the APP file and set the meshAppearance
of body_01_painted_custom
to standard
.
Then open the mesh file of body_01_painted_custom
and remove all appearances. Create two appearances named respectively standard
and coated
.
The standard
appearance will have a single chunk that uses the original material poor_06
. This appearance will be used when CrystalCoat is turned off. The coated
appearance will use a single chunk named coated
. This appearance will be used when CrystalCoat is turned on.
Next, we know that the original material for this mesh is named poor_06
so remove all entries from localMaterialBuffer > materials
and materialEntries
arrays except this one. Then duplicate it in both arrays and name the new ones coated
.
You must rebuild the index of the materialEntries
array after you have finished to reorganize it. Right-click on it and choose Recalculate child index properties
.
You should now have this appearance list.
Check coated
material and see that it only contains the MultilayerMask and MultilayerSetup parameters using the multilayer_vehicle_destruction.mi
as the base material.
CrystalCoat requires the mesh to use a specific base material in order to be colorable. So you need to modify the base material of coated
material with this one.
CrystalCoat is meant to apply a color on vehicle parts but it must look like something pretty, with a glossy coating. To do this add the following parameters in the values
array. The easiest way is to copy the existing parameters from the Rayfield Caliburn vehicle and paste them into yours.
Open this mesh file from the Rayfield Caliburn and go into localMaterialBuffer > materials > premium_ext_07_customizable > values
. Then copy this list of parameters by right-clicking on them and choosing Copy Selection From Array/Buffer
.
Then right-click on the values
array of your vehicle's body coated
material and choose Paste Selection To Array/Buffer
.
When you perform one of these operations on a component into the APP file you must terminate your work by updating the list of components in the entVisualControllerComponent
named AppearanceVisualController
at the end of the component list.
Add or remove a component
Modify a component's name, mesh file path or mesh appearance.
As we can modify a lot of components, the most reliable way to update the list is to right-click on the APP component list and then choose Regenerate Appearance Dependencies
. This will rebuild the entire list in the AppearanceVisualController
.
Each vehicle has a list of deformable parts. If you look into the vehicle record using the Tweak Browser into the destruction
element you will see that the body_01
component is a deformable part.
As we have divided it into multiple components we need to add the duplicates to the list of deformable parts. To do this we must create a TweakXL override. Right-click on the deformableParts
array and create a TweakXL override. This will create another TweakXL file into the folder resources/r6/tweaks
.
We want to create a new entry, but as you can see the entry contains other entries. We must proceed from bottom to top in order to create our new deformable part.
The lowest level of entry is the zones
array. Deploy it and right-click on the first entry then click on TweakXL > Copy to clipboard
.
Edit your new YAML file and replace its content with it. Lines starting with #
are comments. They help to visually organize our file so it can be read more easily.
This is an existing entry we want to duplicate so add the suffix _1
after its name. Do the same for the second zone entry.
Then we are going to duplicate the level above zones
which is the deformable part entry. Append the data into the file and append the suffix to its name. Then use the previous zone entries you have duplicated to populate the zones
array of this new deformable part. Don't forget to update the component name too.
You should now have this file content.
Now we have duplicated the deformable part entry, we can finally append our new entry to the deformableParts
array. Using !append
tells TweakXL not to erase the entire array content but instead append the element into it.
You see that the YAML file is defined in a reversed order. First we define low-level entries (zones), then as we go through the file we define the deformable part and finally we append it into the top-level array.
This is just like building a house: you build it from the basement up to the roof.
All we need to do is to perform the same steps as we did for the roof. Except that we won't make this component colorable with CrystalCoat. Instead it will only be able to use predefined appearances.
Duplicate the body_01_roof_custom
component and rename it to body_01_sunvisor_custom
, then duplicate its mesh file and replace its suffix by _sunvisor
. Affect this new mesh file to the new component.
Export the new mesh file and the body_01
mesh file. Open the body_01
mesh file into Blender and submesh the component. Export the new submesh alone into the sun visor mesh file. Finally export all the other submeshes into the base mesh file. Finally import both files back into WolvenKit.
Open the new mesh file and remove all appearances except standard
and black
. Remove the corresponding materials from localMaterialBuffer > materials
and materialEntries
arrays. When you are done, right-click on the materialEntries
array and refresh the index by choosing Recalculate child index properties
.
You must update the AppearanceVisualController
list when you create a new component or if you modify its name, mesh appearance or mesh file path.
Now we need to add two settings into our SettingsPackage
class to handle both CrystalCoat ON and OFF states.
Notice that we are using the EMeshAppearanceStandard
enumerated type for both fields. This is because we don't want the user to be able to choose Coated
or Painted
values in any case.
We need to add new localized strings for our new settings. Export your en-us.json
file then add these elements into it and update their secondary keys.
Then import it back into CR2W by right-clicking on it and select Convert from JSON
. Repeat this process with any other language you have set.
The _1
suffix is already being used for body_01_painted_custom
component. And the _2
suffix is already being used for body_01_roof_custom
component.
As our component is individually customizable by the user we need to connect the mod settings with the components processing just like we did for the roof.
To do this modify the Utils.CustomizeMesh
method. In the first switch
block of the method, add a new else if
block under the existing if
block. The new content is on the lines 7 to 9.
Now you can test your mod and see that the sun visor can be customized for both CrystalCoat states.
My vehicle is so fancy now ! But wait ! Those wheels feel very cheap !
Concerning the wheels we want to make them colorable with CrystalCoat but we would like to select their paint aspect independently of the rest of the vehicle. So we can have a vehicle with metallic paint and wheels using the glossy paint.
For the Mahir Supron all the wheels use the same mesh file so it will be easier. The wheel components are these ones:
wheel_01_fl_a
wheel_01_fr_a
wheel_01_bl_a
wheel_01_br_a
Duplicate the wheel_01_fl_a
component to create two new components named wheel_01_fl_a_painted
and wheel_01_fl_a_inserts
. Set their meshAppearance
to standard
.
Add the mesh file to your project and duplicate it twice. Add theses suffixes to your new mesh files _painted
and _inserts
. Assign all three files to your three components:
wheel_01_fl_a
wheel_01_fl_a_painted
wheel_01_fl_a_inserts
Repeat this process for the other wheels using the same mesh files. If your front wheels and back wheels are using two different mesh files then repeat this process for both groups.
Export your three mesh files and import one of them into Blender. Hide all submeshes except the relevant one you want to work with.
Submesh these parts for the _inserts
component.
Select this new submesh alone and export it to the _inserts
mesh file. Submesh these parts for the _painted
component.
Select this new submesh alone and export it to the _painted
mesh file. Then select all the other submeshes and export them to the base mesh file.
Import your three mesh files back into WolvenKit. Now duplicate one of your WorldWidgetComponents named visual_customization_<some_component>
for the painted component and another one for the inserts component. Rename them and update their meshTargetBinding > bindName
and parentTransform > bindName
fields with their new component name.
You must update the AppearanceVisualController
list when you create a new component or if you modify its name, mesh appearance or mesh file path.
We need to create four mod settings to handle wheels. Two for CrystalCoat OFF state and two more for CrystalCoat ON state.
As usual in the following code you need to replace the mod name and secondary keys for translation. See that CrystalCoat OFF settings are using EMeshAppearanceStandard
type while CrystalCoat ON settings are using EWheelAppearanceCC
type because we want to replace the Painted
value with a list of paint aspects directly. So we also need to use the relevant translation entries above the field.
Then create the new EWheelAppearanceCC
enumerated type.
As we have 4 wheels to link with our new settings we need to add this code (lines 10-21) in the Utils.CustomizeMesh
method.
We need to add new localized strings for our new settings. Export your en-us.json
file then add these elements into it and update their secondary keys.
Then import it back into CR2W by right-clicking on it and select Convert from JSON
. Repeat this process with any other language you have set.
Open your _inserts
mesh file so we can create the materials and appearances. Delete all appearances and create these ones with a single chunk (appearance name -> chunk name).
standard
-> poor_01
metallic
-> metallic
glossy
-> glossy
coated
-> coated
black
-> black
In the materialEntries
and localMaterialBuffer > materials
arrays remove all entries except poor_01
. Then copy the existing materials from another of your mesh files concerning metallic
, glossy
, coated
and black
materials.
You must rebuild the index of the materialEntries
array after you have finished to reorganize it. Right-click on it and choose Recalculate child index properties
.
For the coated
material, reuse the original mlmask file from the poor_01
material. Add its associated mlsetup file to your project and rename it to wheel_coated.mlsetup
. Assign this new file to localMaterialBuffer > materials > coated > MultilayerSetup
. Then right-click on the file and select Convert to JSON
.
Open it in MLSB and disable the layers that make the wheel dirty: 15, 17, 18. To do so turn their opacity to 0. Then export the file to JSON and import it back into WolvenKit.
Concerning this one as this is a textured material we need to define its Tiles
value into the mlsetup file because depending on the mesh UV, the texture resolution will be different.
Duplicate the existing metallic.mlsetup
and name it wheel_metallic.mlsetup
. Then assign it to localMaterialBuffer > materials > metallic > MultilayerSetup
.
Edit the file into MLSB and set its Tiles
value to 8 on the layer 0. Save the file and import it back into WolvenKit. Now we also need to define a new mlmask file for the wheels.
We cannot reuse the existing painted.mlmask
file because it uses layer 0 and layer 2. Indeed the Mahir Supron has side stripes thanks to layer 2.
Duplicate the painted.mlmask
file and name it wheel_painted.mlmask
. Export it and replace the layer 2 image by a black image using the same size and name. Then import the mlmask back into WolvenKit.
Assign it to your metallic
material into localMaterialBuffer > materials > metallic > MultilayerMask
.
For this one we don't need to duplicate the mlsetup. Simply assign your wheel_painted.mlmask
file into localMaterialBuffer > materials > glossy > MultilayerMask
.
Finally concerning the black
material this is exactly the same process as the glossy
one.
Now you can test your mod and see the result !
If your wheels stay black when you activate CrystalCoat, switch between the third-person perspective (TPP) and the first-person perspective (FPP) so widgets will be updated.
This is fantastic ! My vehicle now looks premios ! But wait ! It is all dirty on the tires and around the vehicle !
Once we have created our customization mechanism we can create as many options as we want and extend this to any sub-part of the vehicle.
It would be a great addition to allow the roof to use a glossy black plastic finish don't you think ?
First duplicate the mesh file of the body_01_painted_custom
component. Modify its file name suffix from _painted
to _roof
. Then export both the body_01
mesh file and this new file.
Then import the body_01
mesh file into Blender so we can submesh it.
Select all the faces of the roof component then press P
and select Selection
to create the new submesh. Export this submesh alone into the new roof mesh file. Then export all the other submeshes to the body_01
mesh file.
Then import both mesh files back into WolvenKit. Now in the APP file duplicate the body_01_painted_custom
component into a new one named body_01_roof_custom
.
Assign your new mesh file to it. Also duplicate its widget named visual_customization_body_01_painted_custom
into a new one named visual_customization_body_01_roof_custom
. This widget will allow us to eventually apply paint or coating on the roof.
Then into this new widget modify the meshTargetBinding > bindName
and parentTransform > bindName
with the new component name.
If you test your mod you will see that the roof will now be synchronized with all the other painted components. Although this is a cool feature, this is not exactly what we want.
What we want is to be able to choose what to do with the roof. So we need to append something to our script and to mod settings.
You must update the AppearanceVisualController
list when you create a new component or if you modify its name, mesh appearance or mesh file path.
First we want to allow the user to choose the roof appearance. Append this code snippet into your SettingsPackage
class at the end of it. Notice that I have added a new translation entry for the Black
value at line 13.
As usual replace the secondary keys prefix and the mod name with yours. You can see that the roofAppearance
field is using the EMeshAppearanceCC
type which is the list of available appearances we have defined before.
We want to allow the roof to use a glossy black material, so add a Black
value to the EMeshAppearanceCC
type.
Now in the Utils.CustomizeMesh
method, we need to add some intelligence in order to treat each customizable component independently from the others. At least for some of them. Replace the first switch
block by this new one.
Replace the vehicle model at line 2 if necessary. You can see that we are looking for a specific component named body_01_roof_custom
, so we can make the link with its corresponding mod setting.
For all the other components we use the else
block that works like before. Now if you look closely at line 5 you can see that if CrystalCoat is disabled we force the roof appearance to be standard. If CrystalCoat is enabled we use the mod setting value.
As we have created a new mod setting and a new mesh appearance we need to add new localized strings into our en-us.json
file and also in any other language file you have defined. Right-click on your en-us.json
file and choose Convert to JSON
.
Edit the clear-copy created in the raw
folder and add these new entries into it. Replace the secondary keys prefix with yours.
Then right-click on the en-us.json.json
file from the raw
folder and choose Convert from JSON
to save your changes.
Finally we need to edit the roof mesh file to add the new black appearance into it and to create the corresponding material. Open the file and duplicate one of the existing appearances. Then name it black
and rename its chunk to black
.
Now into the materialEntries
array duplicate the last item and name it black
. In the localMaterialBuffer > materials
array right-click on the glossy
entry and choose Copy From Array/Buffer
. Then right-click on the array and choose Paste Into Array/Buffer
in order to append it to the end.
We are using the glossy
material because it is the closest to the one we want to create as we want to create a glossy black material. But we need to edit two parameters to get a better result CoatTintFwd
and CoatTintSide
. We should set colors that are close to the one we want. As we are creating a black material set CoatTintFwd = (red:0, green:0, blue:0, alpha:255)
and CoatTintSide = (red:77, green:77, blue:77, alpha:255)
.
Also as this material is not colorable by CrystalCoat, use the basic multilayer file in the base material field.
Now duplicate the glossy.mlsetup
file and name it black.mlsetup
. Then assign it to the material into its MultilayerSetup parameter. Right-click on the black.mlsetup
file and convert it to JSON. Open it in MlsetupBuilder by right-clicking on it.
We are going to use only the first layer and turn all the other ones invisible by setting their opacity to 0. By chance the glossy paint layer #0 is already configured properly. Simply export the file using File > Mlsetup > Export
and erase the JSON file. Then import it back into mlsetup in WolvenKit.
You can use either painted.mlmask
or the original mlmask file of the vehicle for this material because both files use the layer 0. In any case we will ignore the other layers because their opacity is null. We only need a mlmask that uses the layer 0, and this layer is used by all mlmask files because the layer 0 is the base layer.
For a better clarity I recommend you to use the original mlmask file when it is possible so if you come back later to modify your mod you won't be asking yourself why you would have used that mlmask.
The _1
suffix is already being used for body_01_painted_custom
component.
Now you can test your mod and play with the new mod setting so you can see how it behaves.
Wait ! What if I want to have a customized roof while CrystalCoat is turned off ?
Well this raises the question of customizing the vehicle for two contexts: with and without CrystalCoat. It is possible to do this with some modifications to the script.
First rename the roofAppearance
field to roofAppearanceCC
using a case-sensitive replacement. This CC
suffix means that it concerns the CrystalCoat ON state. You should have replaced 2 occurrences.
Next, modify your existing secondary keys for this field so they have a -on-
part into them.
Modify your secondary keys in your languages JSON files too and add new keys for the OFF state.
Import your JSON files back into CR2W. Now add a new roofAppearance
field by duplicating the existing one and change the secondary keys of it so they have a -off-
part into them.
Wait !
We are defining a new mod setting for the CrystalCoat OFF state but the values Coated
and Painted
can be chosen by the user. These values make no sense if CrystalCoat is turned off.
To fix this we need to create a second enumerated type that excludes these values.
Create the new mod setting for CrystalCoat OFF state.
Then into the Utils.CustomizeMesh
method in the if
block that concerns the body_01_roof_custom
component change the line by this one (line 2).
Now you can test your mod and configure the roof appearance for both the CrystalCoat ON and OFF states.
This is really great ! I think the sun visor feels cheap ! Can you do something for this ?
Let's say we want to give the user some choices to customize his vehicle. It would be great to allow for multiple paint aspects just like in real life.
If we want to allow the user to choose between multiple paint aspects we need to create a choice mechanism. Mod Settings is made for this purpose. So let's create a new settings class into our script.
Let me explain what lies into the following code snippet. The SettingsPackage
class contains the settings you will see in the Mod Settings menu of the game. Here you have two settings paintAspectEnabled
and paintAspect.
The first one is a toggle that allows to turn on the custom paint feature. Indeed you still want to be able to use the default CrystalCoat we have already created which is what the base game is doing. The second setting will be enabled only if the first one is enabled too because I have set a dependency
attribute (line 23).
The second setting uses a enumerated type EPaintAspect
which is defined on top of the snippet. This enum will contain any paint aspect we want to define. To begin I have added a single one that corresponds to a metallic paint.
The second class is the MyModSettings
class that we will call anywhere into the script to access our settings. This class contains the settings package instance into its settings
field.
In order for this code to work into your mod you need to perform some case-sensitive replacements. First replace occurrences of "MyModName"
(including quotes) by your mod name (with quotes) as you want it to appear in the ModSettings mods list. You should have replaced 2 occurrences.
Now you need to replace MyNickName-MyModName
by what you may have already used during to define your secondary keys for translation. You should have replaced 7 occurrences.
Finally replace MyNickName.MyModName
by your module name which is at the very top of the script file. You should have replaced 1 occurrence.
You also need to add a new Painted
value into the EMeshAppearanceCC
enumerated type so we can handle paint materials.
Finally we need to revise our Utils.CustomizeMesh
method that actually swaps mesh appearances so it can accept a new one.
In the switch
statement at line 7 we look for the vehicle model. You may need to update it if you are not using the Mahir Supron.
What this code block does is select the relevant mesh appearance depending on the CrystalCoat state. If it is turned off then it will use the standard
mesh appearance. Otherwise if the custom paint toggle is enabled it will use the selected paint aspect (metallic
) and if the toggle is disabled it will use the default coated
mesh appearance you already know.
Then the for
loop at line 24 looks for a corresponding WorldWidgetComponent associated with the current mesh component if one exists.
And finally the final switch
statement at line 31 is actually affecting the new mesh appearance to the component.
You should have replaced 6 occurrences.
We have just created the mechanism that allows the user to choose a different paint. Now we need to create the new paint material and add it into our mesh files.
Duplicate your existing coated.mlsetup
file and name it metallic.mlsetup
. Then convert it into JSON and open it in MlsetupBuilder (MLSB). Then in MLSB import layers to the editor by clicking on the orange button.
We know that our paint layer is 0 so we just need to modify this layer. Into the material template field use this one.
This is the default metallic car paint material of the game. Then set these values into the material settings. Apply edits on the left before doing anything else or your changes will be lost.
Finally export the file and erase the JSON file. Then convert the JSON file back into mlsetup in WolvenKit.
Add a new mesh appearance in the appearances
array with name metallic
containing a single chunk named metallic
too.
Now you need to update the mesh file for each of your *_painted_custom
components and add a new metallic
material into them.
To do this duplicate the last entry of the materialEntries
array and name it metallic
then right-click on the array and choose Recalculate child index properties
.
Then in the localMaterialBuffer > materials
array right-click on the coated
material and select Copy from Array/Buffer
. Then right-click on the array and select Paste into Array/Buffer
so it will be added to the end.
Now we need to set the material parameters. Open the metallic
material and modify its MultilayerSetup
field with the relative path to the new metallic.mlsetup
file you have created.
Next we need to add several parameters into this material. To do this click on the values
array and on the right click on Create Item In Array
. Then for each parameter you will need to use the relevant type.
Simple numbers like CoatFresnelBias
or Opacity
are using the Scalar
type.
RGBA colors like CoatTintFwd
are using the Color
type.
Fields that use a XBM file path like GlobalNormal
are using the Texture
type.
For each parameter you need to set its name and its value. Then you will be able to copy these parameters or even the entire metallic
material into the other mesh files as they all use the same mlmask and mlsetup in the case of the Mahir Supron.
Otherwise you need to copy the parameters into the existing material you have previously duplicated.
Now lets see the result.
Nice ! But I can see some stairs in the paint on the back doors !
As we have isolated all the painted parts it allows us to use a dedicated mlmask that will only allow the paint layer to be displayed. All of our painted components will use this new mlmask.
Copy any existing mlmask and rename it painted.mlmask
. For example use the one from the Mahir Supron.
Use the export tool Tools > Export Tool
to generate PNG images for each layer in the raw
folder. Then go into this folder and see that there are 20 layers as PNG files.
We know that the layer 0 is responsible for the painted areas. Then all we need to do is to use a full white image for layer 0 and a full black image for all the other layers so we are sure that they won't conflict with our paint.
In the case of the Mahir Supron the side stripes are created by the layer 2 so we need to keep the layer 2 image unchanged.
The layer 0 is already a full white image so there is no need to change it. Either find another layer that is already full black or create one with an image editor software using the same image size. Then duplicate this file for all the other layers except layer 2 and reuse their name.
Then import the layers back into the mlmask file using the Tools > Import Tool
.
Then affect your new mlmask file to all the painted components in the metallic
material in the field MultilayerMask
.
Then test the mod and see if the problem is solved.
Now all of our painted components are fixed. This metallic paint is beautiful !
For the Mahir Supron we could have kept the original mlmask file and instead turn the opacity for all layers except layer #0 and layer #2 to 0 into the mlsetup file. This could have worked because the Supron is using the base layer 0 as the paint layer and this layer is always a full white image so there is no UV drawing problem.
But some other vehicles use a specific layer for the paint, thus they draw all the painted parts into that layer. In this case, if the drawing is messy and lacks precision the only way to fix it is to modify the mlmask layer. This is why I have chosen to modify the mlmask so you can use a solution that works in all cases.
What if I want to add another paint aspect ? Like a glossy paint ?
The work is essentially the same as the metallic paint we have just created. As we already have created the user choice mechanism before we can now simply add a new paint type into the script.
At line 8 in this code snippet we need to add a new runtimeProperty
to translate the new enum value.
In the en-us.json
language file we need to add a new localized string entry. Do the same for any other language file you may have defined.
Duplicate the existing metallic.mlsetup
file and name it glossy.mlsetup
. Then open it in MLSB and replace layer 0's template file with this one.
Then set these parameters for layer 0.
Then save the file and import it back into WolvenKit.
For all painted components duplicate the metallic
appearance and replace its name and chunk with glossy
. Then duplicate the materialEntries
metallic
entry and name it glossy
then duplicate the associated localMaterialBuffer > materials
metallic
entry.
Finally assign the new glossy.mlsetup
file into this material. Keep all the parameters identical to the metallic paint.
All you have to do now is to install the mod and select the new paint aspect in the mod settings !
Wonderful ! I can't believe it ! Well do you think that I could add other premios stuff to this vehicle ?
Creating a custom color picker widget for your vehicle requires to handle an image editor like Adobe Photoshop or a free alternative and to have a bit of creativity in order to get a decent result. I will explain in details how I do so you can do it more easily.
The color picker widget displayed in the game is defined by a InkWidget file that you must copy into your project so we can modify it.
This InkWidget is a bit complicated to edit visually in WolvenKit so you will want to convert it to JSON and edit it with a text editor. Right-click on the InkWidget file and convert it to JSON.
Then into the readable JSON file perform the following search using a case-sensitive research.
You must include quote characters ( "
) into the search.
For each of these keywords (including quotes) you will find exactly 4 results.
Results 1 and 2 concern the inner color circle
Results 3 and 4 concern the outer color circle.
As we do not have defined a secondary color yet, we want to hide the inner circle. For results 1 and 2 of these researches you need to change the visible
field that is a few lines under the matching word. Change its value to "visible": 0
.
You should have performed 8 modifications.
Now we must make the inner circle unselectable both for mice and gamepads. To do this search for inkCircleInnerWidget
. You will find 2 occurrences. For each of them you need to go a few lines up from the matching word until you see isInteractive
field.
Change its value to "isInteractive": 0
. You should have performed 2 modifications.
But this won't be enough for gamepads because they trigger the circle activation from code.
We need to complete this by some code. Append this code into your project's script file. It will prevent gamepads from triggering the inner circle when switching modes.
We want to update the title of the widget so it will display CrystalCoat™ by <manufacturer>
for your vehicle. For this we need to create a localized resource that can be translated to other languages.
Create a new folder for localization data in your project, then add a File > New File
of JSON type named en-us.json
into that folder. Now append this text into your existing XL file to allow your mod to handle localized strings.
Update the relative path to your JSON file in this text.
Use English language as the default language if the user's language is not found. The first language code in the list will be the default fallback language. Then you can add any other language under it in any order with its own JSON file.
Convert your en-us.json
file into readable JSON by right-clicking on it then choose Convert to JSON
. Write this content into it and update these elements:
Update the manufacturer name by one of your choice if needed (line 23).
Create a new secondary key to use into the secondaryKey
field (line 26). A secondary key is a unique identifier to identify the localized string. It is the same key across all languages. You should use something unique like a combination of words separated by dashes. For example use your nickname followed by your mod name followed by what your key is referring to: mynickname-MyModName-colorpicker_title
You can then convert your readable JSON file back into CR2W by right-clicking on the en-us.json.json
file into the raw
folder then choose Convert from JSON
.
You can repeat the same process for any other language that you know:
Create a dedicated JSON file
Add the JSON file entry into the XL file using a new language code.
Convert the JSON into readable JSON.
Modify the JSON file content using the same secondary key as the other files.
Convert the readable JSON back into CR2W.
InkWidget files need to use a primary key in order to identify a localized string. So we cannot directly use the secondary key into the InkWidget file.
Instead, we need to generate a hash number of our secondary key using a specific algorithm and then use this resulting number into the InkWidget file.
To create the hash number go into WolvenKit and use the menu Tools > Hash Tool
. Copy your secondary key and paste it into the Text
field of the new window.
As soon as your enter a text value into the Text
field, you will see lot of numbers automatically generated into the other fields. This is what hashing is all about: translating an input data into a number that is unique and consistent regarding the input data.
This means that as long as you enter the same text you will always obtain the same hash number. Thus we can say that the hash number represents your input text. This operation is made possible by the hash algorithm (FNV1A64) that is a complex mathematical algorithm that you really don't want to know about.
What you need here is to copy the CName
field value. Then, in your readable JSON file corresponding to the InkWidget, search for LocKey#96050
and replace it with LocKey#<your hash number>
.
On the example above, the new LocKey would be LocKey#17052470697381502790
. You should have replaced 2 occurrences.
You can now convert your JSON file back into InkWidget.
You must not write the generated hash number into the en-us.json
file or into any other JSON language file.
Now we want the user to display our custom color picker widget when he his using the relevant vehicle. To do this append the following code to your script and replace the widget path (line 16) by the relative path to yours using double-backslashes (\\
).
You may also need to modify the vehicle model ESupportedVehicle.Supron
that is right above it (line 15).
Now you can test your mod.
Wait ! There is still a sports car displayed ! This is not my vehicle !
The most accurate way to represent the actual vehicle into the widget is to use the 3D model of the vehicle unless you would be an artist able to create a wonderful artwork by hand.
To do this we must export it from WolvenKit and import it into Blender.
Into WolvenKit create a new temporary project, find the ENT file of the vehicle in the Asset Browser then add it to your project.
Do not use your CrystalCoat project to perform this step or it will be populated with dozen of undesired files !
Select the ENT file in your project then go into the menu Tools > Script Manager
. Select Export_Vehicle_Ent
and click on the green arrow next to it to run the script. Then wait for it to finish adding files to your project.
Open Blender and in the import menu select Cyberpunk Entity (.json)
. Then in the dialog window find the *.ent.json
file of your vehicle into the raw
folder of the temporary project.
Before validating, be sure to enter the entity appearance to load in the upper right corner. For the Mahir Supron we want to load mahir_supron__basic_player_01
. This is the value in the appearanceName
field of the vehicle record.
Uncheck "With Materials" before validating or the import will be unnecessarily long.
Now hide the armature and remove all decals you see around the vehicle. The idea is to obtain a clean 3D model and take a screenshot of it. Then we will dump the project.
Decals are all these flat rectangle elements. Click on them and remove them all. Generally they are grouped together for each component so selecting one will select a group of decals.
If you notice that some elements are not placed correctly like the license plate you can simply remove them, if this is a window it is better to move it to the right place.
Once the vehicle is clean and ready, create a plane mesh under its wheels that we will use as a background. Then make this plane to display as green to make the image editing easier.
To display it as green, select the plane then on the right menu go into the materials tab (red sphere icon) click on New
to add a material to the plane.
Then in the material definition go into the Viewport Display
section and set the color to full green.
Now activate the TexturePaint
mode in the upper left corner of the viewport, place the camera from the top by clicking on the Z axis node in the upper right corner of the viewport. Zoom and center the view on the vehicle then take a screenshot of it. Be sure to keep a few space around the vehicle model.
I will use Adobe Photoshop in order to prepare the vehicle image but you can certainly do the same with another image editor. However you will have to find the corresponding tools.
First isolate the model by leaving a few space around. Then use the magic wand to select the green areas.
Some vehicles may also have holes from the top view. In this case do not forget to select these green areas too.
Once you have selected all the green areas, invert the selection by right-clicking on it and select "Invert" (I don't use English so I may be wrong on the exact word).
The selection will now select everything except the green areas. Press CTRL + C to copy the model then on the lower right corner create a new layer by using the +
icon. Then press CTRL + V to paste the model into it so you get rid of the green background.
You can now delete the layer containing the green background.
Now we want to make this model look a bit more like a drawing so what I do is first increase the brightness of the picture by 50%.
Next apply a filter on the picture to make it look a bit more like a drawing. Use the Filter > Filter Gallery...
menu. Then select Artistic > Paint Daubs
(French is "Barbouillage") with parameters Brush Size = 2
, Sharpness = 20
and Brush Type = Simple
.
Finally reduce the Saturation of the image by 100% to remove pink borders.
Our model is now ready to be used into WolvenKit.
Create a backup of this model picture so you will be able to reuse this picture later to create a mask for the secondary color.
In order to use our model into the color picker widget we need to use a XBM texture file and to modify it. Add this one to your project and then use the Tools > Export Tool
to convert it into TGA.
Open the TGA file into your image editor. You will see that it uses 5 channels: RGB, Red, Green, Blue and Alpha. When you click on the RGB channel it displays all the three colored channels together while if you click on the Alpha channel it will display another image with only gray shades.
When we modify something in the picture we need to modify it both in the RGB channel and also in the Alpha channel. When we manipulate layers they will only apply in the RGB channel.
On the picture we can see multiple parts. The idea is to replace the main model by our vehicle and then eventually to replace the secondary color mask with ours or to hide it. Finally replace the light beam, and both the headlight and tail light elements. We will leave the white square mask and the Cosmetic_Troll data code as they are.
We can eventually make the picture larger if our elements do not fit into it but we must keep the same vertical size so the widget will display them correctly. Especially concerning the vehicle and the secondary color mask parts.
Now use the RGB channel and paste your model layer in the image. Then resize it while keeping its aspect ratio and make it the same vertical size as the original image.
When your model is placed and resized correctly select the original layer eventually unlock it if necessary and then select a large area to cover the entire original vehicle part. Then fill the areas with black color to remove the original vehicle picture.
Then go into the Alpha channel and remove the vehicle part using a black area.
Now go back into the RGB channel, select your model layer then hold CTRL key and left-click on the layer image to create a selection around it.
Then go into the Alpha channel and fill your selection with white color. It should be placed at the exact same position as the model image on the RGB channel.
Finally go back into RGB channel, then layers tab and merge the model layer with the base layer.
Now you know how to modify one part of the texture for both the RGB and Alpha channels. As we currently don't have a secondary color we can remove the secondary color mask from both channels.
All you have to do is to fill the secondary color mask area with black color on both channels. The next step is to modify the headlight element.
First we want to know where is the element into the game when headlights are turned on. For the Mahir Supron there are several light elements the create.
To keep things simple, we will only create a single light component on the right of the vehicle. In the image editor let's create a simple shape in a new layer at the same position as the main headlight component.
Now we have a new headlight component in a dedicated layer, let's move it at the position of the existing one and replace it for the RGB channel like we did for the previous part.
Concerning the Alpha channel we will do differently. Once the element is placed correctly, duplicate the layer using CTRL + J in the RGB channel and create a style on it to make it full white. To do so right-click on the new layer and select the first element of the list. Then add a color coating with white color.
Then merge the style you have just created with the layer itself by right-clicking on it and choose this option.
Now your alpha element is placed right over the RGB element. Select the alpha element layer then hold CTRL and left click on the layer image to create a selection around your element. Then press CTRL + C.
Go into the Alpha channel and paste using CTRL + V. Your pasted element shall be at the same location as the one in the RGB channel. Now you can click anywhere to validate. Go back into the layers tab and delete the one you have just created.
Finally switch back and forth from RGB channel to Alpha channel to check that both elements are at the same location. You should see a white element in the Alpha channel while it is a little bit darker one in the RGB channel.
You can finally merge the headlight layer with the base layer.
Now we are going to repeat the same process for the tail light element. Except that instead of filling it with gray color, we are going to use the gradient tool with a dark red and a light red color.
Create a new layer, then create a simple selection at the tail light location and select the gradient tool. Select the basic gradient and fill the selection.
Now place the element at the location of the existing one. Right-click on the layer and add a new style with a red external glowing effect in order to create a similar effect as the original element.
Now remove the original tail light element as usual (both in RGB and Alpha channels) and place the new one at the same spot. Before merging the style with the layer, duplicate the layer and modify its style to have a white glowing effect and a white filling color for the alpha element.
Then merge the style of the alpha element, then select both this layer and the base layer and merge them.
Now create a rectangle selection that goes from the right border of the picture until you have the alpha element into it. Then copy the selection, go into the Alpha channel and paste it so it will be at the same location.
Go back into the RGB channel and delete the alpha element by selecting the spot and filling it with black color. Show the red tail light element back and merge its style then merge the layer with the base layer.
The last element we need to change is the light beam. Essential it will consist in modifying its base area so it complies with the shape of our headlight element. On some vehicles it is also necessary to enlarge it if the headlight component is larger.
First create a new layer containing a copy of the light beam by selecting a rectangle along the top and right borders.
Now click on the base layer and use the magic wand tool to select the headlight component area then move the selection by dragging it (not the headlight element) up to the light beam until it is all into it. Click on the new light beam layer. Also hide the base layer to better see the result. Then hit the DELETE key to remove pixels in the selection for our new layer. Then delete the remainings below it too.
Then toggle the visibility of the light beam element to OFF and turn the base layer back ON. Remove the original light beam both in the RGB and Alpha channels as we did for the previous parts.
Then toggle the new light beam layer back ON and merge it with the base layer. The select the area of the light beam and paste it in the Alpha channel.
You should now have this final result.
Your image should not be larger than 1200 pixels or the InkWidget won't display the texture parts correctly.
Now save the picture as TARGA / TGA image and be sure to select 32-bits/pixel and import it back into WolvenKit using the Tools > Import Tool
.
Now you have imported the TGA file back into XBM we must use a InkAtlas file in order to create texture parts from our XBM file.
Add this file into your project at the same location as the XBM.
Then open it and set the relative path to your XBM file in place of the three elements of the slots
array.
Now go into the Part Mapping
tab and see that eventually all parts are already well placed so you won't have to modify parts. In the case you need to modify some of them. Let's explain how it works.
In the Part Mapping tab, click on a part then see on the right there are informations like Left, Top, Width, Height using pixel unit. The rectangle of the texture part is defined like this:
Left: number of pixels between the left border of the image and the left border of the part
Width: number of pixels between the left border of the part and the right border of the part
Top: number of pixels between the top border of the image and the top border of the part
Height: number of pixels between the top border of the part and the bottom border of the part
But the issue is that you cannot set the new part area from here, you can only play with the pixel values to define a new visual area that you find better. You cannot save this information like this. But instead once you have finished to define your new part area, remember the pixel values of Left, Width, Top, Height fields.
Then know the size of your texture image. Let's say the picture size is 960 x 850 pixels. You can calculate the ratio between your part area borders and the image size.
Let's use the example of the headlight element. Its rectangle area is Left: 770, Width: 170, Top: 240 and Height: 40 then you have to calculate into a 0:1 UV space the equivalent of these coordinates.
On the horizontal axis, 0 is the left border of the image, 1 is the right border of the image. On the vertical axis, 0 is the top border of the image, 1 is the bottom border of the image. Then you find this result:
Left: 770 / 960 = 0,8020833333333333
Right: (770 + 170) / 960 = 0,9791666666666667
Top: 240 / 850 = 0,2823529411764706
Bottom: (240 + 40) / 850 = 0,3294117647058824
The decimals are important in order to keep precision. Then go back into the first tab and open the first slot element then update the fields with these values to define the new part area.
If you want to check the new visual area after you have modified the coordinates you need to save the file and reopen it. Do this for all required parts until none of them overlaps another.
Finally remove the elements 1 and 2 of the slots
array and duplicate the first element twice so they are all 3 identical.
Before we can test the result, we need to actually use the InkAtlas file into the InkWidget file. To do this convert your InkWidget into JSON then replace all occurrences of this path by the relative path to your InkAtlas file using double-backslashes (\\
).
Then convert your JSON file back into InkWidget. You can now test your mod.
Wait ! Light components do not correspond to what is on the actual vehicle !
Well we are almost there ! As you can see the widget automatically duplicate the light components symetrically for headlights, tail lights and light beams. We can also see that it applies a mirror effect on half of them.
For this vehicle we need to use only one of each and to place them differently. This must be done into the InkWidget file.
For this step we can edit the widget directly in WolvenKit. Open the file and go into this element hierarchy libraryItems > package > rootWidget > children > container > children > CenterGroup > children > Car > children
.
Here you have the list of elements related to the vehicle.
CarColorPrintC1
is the main model
CarColorPrintC2
is the secondary color mask
CarColorPrintLights1
and 2 are the tail lights
CarColorPrintLightsFront1
and 2 are the headlights
CarColorPrintLightsFront1Beam
and 2Beam are the light beams.
We will keep the right headlight component as it is the closest to the actual location we need. But as you noticed it has been mirrored so we will change this and its location.
Go into CarColorPrintLightsFront1
which is the left headlight and uncheck the visible
field so it will be hidden.
Into the CarColorPrintLightsFront2
set mirrorType
to NoMirror
. Then into layout > margin
you will set its relative position in pixels. There is no fast method to find the right values you need to try new values and save the file, install the mod and test the mod and so on until it matches.
If you are creating the same mod as me, I will save you some time and give you my values for the Mahir Supron. Left: 99, Top: -432.
Go into CarColorPrintLights1
which is the left tail light and uncheck the visible
field so it will be hidden.
Into the CarColorPrintLights2
set mirrorType
to NoMirror
. Then modify the position into layout > margin
. For the Mahir Supron I have used Left: 138, Top: 352.
Go into CarColorPrintLightsFront1Beam
which is the left light beam and uncheck the visible
field so it will be hidden.
Into the CarColorPrintLightsFront2Beam
set mirrorType
to NoMirror
. Then modify the position into layout > margin
. For the Mahir Supron I have used Left: 109, Top: -528.
Now let's see the result.
Wonderful ! My color picker widget is now perfect !
But wait ! I am a bit disappointed with my CrystalCoat. I would like to have a more premios result !
My Supron feels cheap !
to do so.
to do so.
As the sun visor component comes from the body_01
component it must be added to the list of deformable parts of the vehicle. to do so. Except that you will add a _3
suffix to your new elements in the YAML file.
to do so.
to do so.
As the roof component comes from the body_01
component it must be added to the list of deformable parts of the vehicle. to do so. Except that you will add a _2
suffix to your new elements in the YAML file.
Now the code is ready, we need to add new localized strings to our existing JSON files. In the following content you need to replace the MyNickName-MyModName
occurrences by what you have used previously. If you don't have created a translation file yet, then .
If you test the mod now you may see that details of the outer circle have disappeared. This may occur if you modify the file from WolvenKit. To fix this convert your InkWidget into JSON and . For each of the keywords, set the field visible: 1
for results 3 and 4.
When you install the mod and you go into the mod settings menu, you will see that you have default values. When you modify these values you can then click on a button to reset all values to default.
These default values are defined by what is written into the script for each field. It is up to you to define default values that you find relevant for your mod.
Some users are community administrators that deploy a collection of mods to a lot of users. They may want to add your mod to their collection, but for some reason they would prefer to customize the default settings for their community.
This is the purpose of this chapter.
If you look at the script you will find a SettingsPackage
class. This class contains the mod settings definition. Default values are what stands after the =
sign for each field.
Creating a custom settings package requires to create a new mod just like translations also require to create a standalone mod.
These extension mods shall not contain anything from the original mod otherwise it will cause trouble with updates, and it will also violate intellectual property of the original mod author.
When you create a translation mod you simply need to create a XL file and one JSON file per additional language you are providing. This way you don't duplicate anything from the original mod and you won't erase any file from it.
Settings package mod works in a similar way.
If you want other mods to be able to override your default settings you need to add a few changes to your script. At the top of your file, remember that you have defined a module name. This is what uniquely identifies your mod for any other script.
Right below the module statement, you must add the following code snippet. The if
line checks if another installed mod is defining a new settings package. If none is installed then the import
statement won't be executed.
If one is installed then it will replace your SettingsPackage
class with the one defined into the other mod. You must replace the MyNickName.MyModName
string by your module name.
Next, just before the SettingsPackage
class definition you must add a new line. This line is the opposite of the previous one above. If the settings package mod is not installed then your default class will be used.
That's it ! Your mod is now able to replace its default settings values with those of another mod. Now you should create a ready-to-use template file that other users will be able to download.
To do so create a new script file that you will make available for download to your users into a ZIP archive for example SettingsPackageTemplate.zip
.
In this REDS file you will simply add this content. Use the same module name as your original script except that you add .SettingsPackage
at the end.
Then the import
statement loads all the types definition from your original mod so the class below can use these types. And finally the SettingsPackage
class must be a exact duplicate of the original mod's class.
If you update the original mod and you rename / add / remove / change type of any setting field then you must update the template and any user that has created a custom settings package mod must update its mod with your new template.
In order to create a new settings package you need to create a new mod project, then add a script file. You can either create it by following the previous paragraph or you can simply download the template file made available by the author on the original mod's page.
Rename the REDS file with your new mod name and place it into resources/r6/scripts/<mod name>/
folder.
Then modify the fields to define the default values. Default values are what stands after the =
sign of each field. If you need more details on the types used by fields, refer to the original mod's script.
That's it ! Finally package your mod and install it so it will override the default values. When installing or removing a settings package mod, it will erase the custom values defined by the user and revert to the new default values.
You can name your custom settings mod as you want because the mod loading order won't matter.
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.
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.
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.
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.
And the mlsetup parameters are these ones.
Next, for each impacted mesh file in the matte
material remove all parameters except the MultilayerMask and MultilayerSetup.
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.
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.
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.
body_01_roof_custom: Bottom:0,25, Left:0, Right:1, Top:0
body_01_sunvisor_custom: Bottom:0,5, Left:0, Right:1, Top:0,25
wheel_01_painted_custom: Bottom:0,75, Left:0, Right:1, Top:0,5
wheel_01_inserts_custom: Bottom:1, Left:0, Right:1, Top:0,75
Now save the file and go into the Part Mapping
tab to check the result. You should be able to select your pictures individually.
In order for the mod settings menu to use your pictures and display a widget we need to add some code to create this new feature.
First add the new class into your script. Then perform some case-sensitive replacements into it.
Replace MyNickName.MyModName
with your module name (line 13).
Replace path\\to\\vehicle_picture.inkwidget
with the relative path to yours. You must use double-backslashes (\\
) at line 18.
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.
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.
The CrystalCoat feature allows to define a primary color that shall cover the painted areas of the vehicle and then a secondary color that goes on top of the primary color.
The idea of the secondary color is to create shapes, patterns or to cover only some parts of the vehicle. There are two approaches that you can combine as you wish in order to create a secondary color.
The most simple way is to apply the secondary color on a complete mesh so the combination of multiple components will give a nice result. This can work especially with vehicles that use a organic design such as Rayfield vehicles.
If a component subset could make sense to use the secondary color then you can submesh it and apply this approach with it.
On this picture, the silver front wings of the vehicle have been submeshed to they can be colored entirely without affecting the rest of the body part.
With conventional vehicles that consist in a box with 4 square doors, this approach won't give a nice result most of the time. Except for wheels.
The approach that allows the most possibilities is to work with a UV layout.
When you want to apply a 2D resource onto a 3D model you need to project your mesh onto a plane. Think of it as a spotlight that projects the shadow of an object on the ground. Although this is not exactly what is happening.
The mesh is made of vertices so you simply decide where you want to place these vertices on the plane. The classic way to use this technic looks a bit like a spotlight projection as you can see on the picture above. It also makes it easier for a human to understand what is going on.
This type of projection is particularly useful if you want to apply 2D resources across multiple parts at once. In the case of CrystalCoat you may want to create oblique stripes across the body, roof and doors of the vehicle. You can do it easily with the Mahir Supron.
The red stripes on this picture would not necessarily be red but they represent shapes that you can create to define a secondary color pattern.
The easiest way to create such mask is to use a image editor like Adobe Photoshop to create shapes and to transform them (rotate, shear, scale, translate) onto the UV layout of the component.
Most of the time the components of a vehicle are sharing the same UV layout that is to say they are all sharing the UV space without overlapping each other. In this case they use the same mlmask file in their material definition.
You will notice that wheels are always using a dedicated UV layout so you won't be able to use the same InkWidget to apply a secondary color for both the painted areas of the vehicle and for its wheels.
In the case your vehicle uses a different UV layout for each component it becomes more difficult to define shapes that will apply across multiple parts.
And moreover when the UV layout is different you always need to use a new InkWidget because your patterns are bound to a specific UV layout. The red stripes on the picture above make sense only for this unique UV layout. Any other one wouldn't suit.
There is a third way to create a secondary color pattern. It is the same idea as to create a mask but instead of editing a picture into a image editor you can create shapes directly into the InkWidget file. This is what is being done for the Rayfield vehicles in the base game.
The InkWidget file for the Rayfield Caliburn contains 16 shapes for the secondary color pattern.
This way of creating shapes is the most difficult because you can't even work with a UV layout to know what you are doing. You must try and test multiple times until the shapes are applying correctly on the vehicle. Also there is no benefit to use this approach.
I recommend you to work on a mask using a image editor.
You could use the mlmask file from the game and extract the mask layer corresponding to the vehicle paint. But you will notice that masks from the base game are quite messy and even contain imperfections. Also if the paint layer is the layer 0 then you won't be able to use this technic.
In order to create a clean UV layout for your secondary color pattern, you should create your own mask using the mesh files. Export all the mesh files involved in the vehicle paint using the menu Tools > Export Tool
.
In the case of the Mahir Supron I will export all the _painted
files except the wheel one. Then I will also export the roof mesh file. Import them all into Blender and uncheck the With Materials
option so the import will be faster.
You can only import mesh components that are sharing the same UV layout together. That is to say they use the same mlmask file. Otherwise they may overlap on one another.
Select all the meshes using the A
key then press TAB
to go into Edit mode
. Finally select the UV Editing
view in the menu bar.
You can see on the left that your UV layout has been generated. Export it to PNG using the menu UV > Export UV Layout
. Define the size to 1024x1024 and set the Fill Opacity
to 1.00.
Open Adobe Photoshop or another image editor and create a new project with a image size of 1024x1024. Then fill it with black color by using the menu Edit > Fill
. Import the PNG file you have just created with Blender. It should be imported into a new layer.
Create a new style on the UV layout to fill it with white. Now you have created your mask for the painted areas of the vehicle.
The next part is to define your secondary color pattern by adding shapes on top of your mask using dynamic objects (like the Rectangle tool) or any other technic as long as they belong to a dedicated layer. I recommend that you use red shapes so you can see them better.
Before exporting the mask we will fill them with white.
You can also use any other tool to create shapes such as the magic wand to select color areas or other selection tools. You can even use external images, logos and even text. Once your pattern is ready create a style on it to fill it with white just like you did previously. Then hide all the other layers except the pattern so you have something like this.
Although you can use any kind of shape to define a secondary color pattern like logos and text you should keep in mind that the InkWidget resolution on the vehicle is low so you won't have a lot of details.
Finally export the image as PNG. Now it is time to export your secondary_color.xbm
texture to PNG using the Tools > Export Tool
. Erase the PNG export with your PNG mask using the same file name.
Then use the Tools > Import Tool
with the following settings to import your mask back into the texture file.
From the beginning of this tutorial you are using the vvc_full_primary_only.inkwidget
into all of your WorldWidgetComponents so you can only display a primary color on your components.
Now you have defined a secondary color pattern, you must use the vvc_car_appearance_widget.inkwidget
for all components except for wheels. You need to update the widgetResource
field of your WorldWidgetComponents using the relative path to this InkWidget.
The _painted
WorldWidgetComponents for wheels will still be using the vvc_full_primary_only.inkwidget
.
We want to apply the secondary color on wheels inserts too. But as the wheels are using a dedicated mlmask (UV layout) we would need to duplicate the InkWidget file, and also the secondary_color.xbm
and the secondary_color.inkatlas
files.
Then update their relative path into the new dedicated InkWidget and finally create a mask for the wheels using a image editor.
We can obtain the same result by simply using the vvc_full_primary_and_secondary.inkwidget
on the _inserts
WorldWidgetComponents of the wheels.
visual_customization_wheel_01_fl_a_inserts_custom
visual_customization_wheel_01_fr_a_inserts_custom
visual_customization_wheel_01_bl_a_inserts_custom
visual_customization_wheel_01_br_a_inserts_custom
At the beginning of this tutorial we have removed the ability for the user to select a secondary color. Now we are going to restore this feature.
Remove the code you have added in this paragraph of the tutorial.
Right-click on the vehicle_visual_customization.inkwidget
and export it into JSON. Then perform these replacements in the opposite way to restore the inner circle of the widget. Finally import the JSON file back into InkWidget.
Now let's test the mod again.
For a component to use the secondary color you must set its appearance to either Coated
or Painted
in the mod settings. Concerning wheels parts you need to set their appearances to eitherMetallic
or Glossy
in the mod settings.
Wait ! I don't see the secondary color pattern on the color picker !
Remember that the texture vehicle_visual_customization_car_preview.xbm
that contains the vehicle model into the color picker widget has a part dedicated to the secondary color.
All you have to do is to follow this paragraph to modify the secondary color mask instead of the vehicle model. The secondary color mask should be a selection of the original model. The following picture is the mask I have created. Visible parts of the model will use the secondary color if it is defined.
The most accurate way to create the mask is to duplicate the section that contains the vehicle model. Then keep only the areas that should be affected by the secondary color.
Your image should not be larger than 1200 pixels or the InkWidget won't display the texture parts correctly.
Concerning the secondary color pattern I have choosen for the Mahir Supron, it cannot be easily seen on the color picker. This will depend on your choices.
I have added a lot of customizable parts in the mod settings how can I make it more user-friendly ?