Custom Asset Shape Controller – Simple Car Example
A few questions came up after the first tutorial, so I wanted to explain the basic idea again with a more concrete example.
This time I will use a simple
car as the example.
The goal is to show how a
CustomUnityAsset can be prepared in Unity and then controlled from a VaM plugin.
The important point is:
Not everything happens in the C# code.
Some things must be prepared correctly in Unity first.
What We Want To Control
Imagine a car where we want to control:
- Steering wheel
- Front left door
- Front right door
- Rear left door
- Rear right door
- Front left wheel
- Front right wheel
- Rear left wheel
- Rear right wheel
For example:
Code:
Steering slider
↓
Steering wheel rotates
↓
Front wheels turn left or right
And:
Code:
Front Left Door slider
↓
Front left door opens
We could also rotate all four wheels together to simulate them spinning.
First: Unity And The Plugin Have Different Jobs
A simple way to think about it is:
Code:
UNITY
prepares what can move
```
↓
```
VAM PLUGIN
controls how it moves
Unity is where you prepare the structure of the asset.
The VaM plugin then finds those prepared parts and changes their position or rotation.
For example:
Code:
UNITY VAM PLUGIN
create separate door → find the door
set door pivot → rotate the door
create separate wheel → find the wheel
set wheel pivot → rotate the wheel
create steering wheel → find steering wheel
set correct rotation → connect it to a slider
If something moves incorrectly, the problem is therefore not automatically in the C# code.
Sometimes the Unity asset needs to be fixed first.
A Single Car Mesh Is Not Enough
If your complete car looks like this in Unity:
Code:
TutorialCar
└── CompleteCarMesh
then the plugin cannot independently move the doors, steering wheel and wheels.
There are simply no separate parts to control.
The car needs separate objects or Transforms for the parts that should move.
A very simple hierarchy could look like this:
Code:
TutorialCar
├── Body
├── SteeringWheel
├── Door_Front_Left
├── Door_Front_Right
├── Door_Rear_Left
├── Door_Rear_Right
├── Wheel_Front_Left
├── Wheel_Front_Right
├── Wheel_Rear_Left
└── Wheel_Rear_Right
The exact hierarchy does not have to look like this.
The important thing is that every part you want to control has its own Transform.
What Is A Transform?
For this tutorial, think of a Unity Transform as a movable point attached to an object.
A Transform contains three important properties:
For example:
Code:
rotate a door Transform
→ the door opens
rotate a steering wheel Transform
→ the steering wheel turns
rotate a wheel Transform
→ the wheel spins
change a Transform position
→ the object moves
The plugin does not need to understand what a "door" or a "wheel" is.
It only needs to know:
Code:
What is the Transform called?
and:
Code:
What should happen to that Transform?
Preparing A Door In Unity
Imagine that we want to open the front left door.
The door needs its own Transform.
A simple setup could look like this:
Code:
TutorialCar
└── Body
└── Door_Front_Left
└── DoorMesh
The object:
Door_Front_Left
is the object we want to rotate.
The visible mesh can be a child of that object.
The Pivot Is Important
A car door rotates around its hinge.
The Transform should therefore rotate around approximately the same point.
Conceptually:
Code:
GOOD
●---------------- Door
↑
hinge / pivot
When rotated:
the door opens naturally.
If the pivot is in the center:
Code:
BAD
-------●-------
↑
pivot
then the door rotates around its middle.
That is not really a C# problem.
The Unity asset is prepared incorrectly for the movement we want.
Using An Empty GameObject As A Pivot
You do not necessarily have to modify the imported mesh.
A very useful method is to create an empty GameObject in Unity.
For example:
Code:
Door_Front_Left_Pivot
└── Door_Front_Left_Mesh
Place:
Door_Front_Left_Pivot
at the hinge position.
Then make the visible door mesh a child of it.
Now the plugin only needs to rotate:
Door_Front_Left_Pivot
This method is useful for many movable parts:
Code:
Door_Pivot
SteeringWheel_Pivot
Wheel_Pivot
Trunk_Pivot
Hood_Pivot
Lever_Pivot
Handle_Pivot
Preparing The Steering Wheel
The steering wheel works in the same way.
For example:
Code:
SteeringWheel_Pivot
└── SteeringWheel_Mesh
Place the pivot around the steering column.
Then test the rotation directly in Unity.
If rotating the Transform gives you a natural steering wheel movement, the object is prepared correctly.
The plugin can later rotate exactly this Transform.
Preparing The Wheels
The wheels also need separate Transforms.
For example:
Code:
Wheel_Front_Left_Pivot
└── Wheel_Front_Left_Mesh
But a front wheel can actually have two different types of rotation:
For a cleaner setup, you can therefore use two nested Transforms:
Code:
Wheel_Front_Left_Steering
└── Wheel_Front_Left_Rotation
└── Wheel_Front_Left_Mesh
Then:
Wheel_Front_Left_Steering
controls left and right steering.
And:
Wheel_Front_Left_Rotation
controls the wheel spinning forward or backward.
The same setup can be used for the front right wheel.
The rear wheels usually only need the rotation Transform.
A Better Car Hierarchy
A more useful hierarchy for our example could therefore look like this:
Code:
TutorialCar
├── Body
│
├── SteeringWheel_Pivot
│ └── SteeringWheel_Mesh
│
├── Door_FL_Pivot
│ └── Door_FL_Mesh
│
├── Door_FR_Pivot
│ └── Door_FR_Mesh
│
├── Door_RL_Pivot
│ └── Door_RL_Mesh
│
├── Door_RR_Pivot
│ └── Door_RR_Mesh
│
├── Wheel_FL_Steering
│ └── Wheel_FL_Rotation
│ └── Wheel_FL_Mesh
│
├── Wheel_FR_Steering
│ └── Wheel_FR_Rotation
│ └── Wheel_FR_Mesh
│
├── Wheel_RL_Rotation
│ └── Wheel_RL_Mesh
│
└── Wheel_RR_Rotation
└── Wheel_RR_Mesh
You do not have to use these exact names.
They are only examples.
Clear names simply make the script easier to understand.
Test Everything In Unity First
Before writing the VaM controls, test the important movements directly inside Unity.
For example, select:
Door_FL_Pivot
and rotate it manually.
Check:
- Does the door rotate around the hinge?
- Does it open in the correct direction?
- Does another part move accidentally?
Then test:
SteeringWheel_Pivot
and:
Wheel_FL_Steering
If the movement already looks wrong inside Unity, it will also look wrong when the VaM plugin performs the same rotation.
Fix the Unity hierarchy or pivot first.
Rotation Axes Can Be Different
Not every asset uses the same axis.
One door may open around Y:
Code:
Quaternion.Euler(0f, angle, 0f);
Another may need Z:
Code:
Quaternion.Euler(0f, 0f, angle);
Another model may need X:
Code:
Quaternion.Euler(angle, 0f, 0f);
That depends on how the model was created and imported.
The easiest way to find out is to select the Transform in Unity and rotate it manually.
Whichever axis gives you the movement you want is usually the axis you should use in the plugin.
Now The VaM Plugin Can Take Over
Once the car is prepared correctly in Unity, the plugin side becomes much simpler.
We tell the script which Transforms it should look for.
For example:
Code:
private const string SteeringWheelNodeName = "SteeringWheel_Pivot";
private const string FrontLeftDoorNodeName = "Door_FL_Pivot";
private const string FrontRightDoorNodeName = "Door_FR_Pivot";
private const string RearLeftDoorNodeName = "Door_RL_Pivot";
private const string RearRightDoorNodeName = "Door_RR_Pivot";
private const string FrontLeftSteeringNodeName = "Wheel_FL_Steering";
private const string FrontRightSteeringNodeName = "Wheel_FR_Steering";
private const string FrontLeftWheelNodeName = "Wheel_FL_Rotation";
private const string FrontRightWheelNodeName = "Wheel_FR_Rotation";
private const string RearLeftWheelNodeName = "Wheel_RL_Rotation";
private const string RearRightWheelNodeName = "Wheel_RR_Rotation";
Then we create variables for the Transforms:
Code:
private Transform steeringWheel;
private Transform frontLeftDoor;
private Transform frontRightDoor;
private Transform rearLeftDoor;
private Transform rearRightDoor;
private Transform frontLeftSteering;
private Transform frontRightSteering;
private Transform frontLeftWheel;
private Transform frontRightWheel;
private Transform rearLeftWheel;
private Transform rearRightWheel;
Finding The Parts
We can use the same recursive search pattern from the original tutorial.
For example:
Code:
steeringWheel =
FindDescendantByName(loadedAssetRoot, SteeringWheelNodeName);
frontLeftDoor =
FindDescendantByName(loadedAssetRoot, FrontLeftDoorNodeName);
frontRightDoor =
FindDescendantByName(loadedAssetRoot, FrontRightDoorNodeName);
rearLeftDoor =
FindDescendantByName(loadedAssetRoot, RearLeftDoorNodeName);
rearRightDoor =
FindDescendantByName(loadedAssetRoot, RearRightDoorNodeName);
frontLeftSteering =
FindDescendantByName(loadedAssetRoot, FrontLeftSteeringNodeName);
frontRightSteering =
FindDescendantByName(loadedAssetRoot, FrontRightSteeringNodeName);
The useful part is that these objects can be nested deeper inside the hierarchy.
For example:
Code:
TutorialCar
└── Interior
└── Dashboard
└── SteeringWheel_Pivot
The recursive search can still find:
SteeringWheel_Pivot
Creating A Steering Slider
Now we can create a VaM slider.
For example:
Code:
public JSONStorableFloat steering;
Then during setup:
Code:
steering = new JSONStorableFloat(
"steering",
0f,
-35f,
35f
);
steering.setCallbackFunction += delegate
{
ApplySteering();
};
RegisterFloat(steering);
And create the UI control:
Code:
UIDynamicSlider steeringSlider =
CreateSlider(steering, false);
steeringSlider.label = "Steering";
Now every time the slider changes, this method is called:
One Slider Can Control Several Parts
This is one of the useful things about the car example.
One VaM control can affect several Transforms.
For example:
Code:
Steering slider
│
├── SteeringWheel_Pivot
├── Wheel_FL_Steering
└── Wheel_FR_Steering
The code could look like this:
Code:
private void ApplySteering()
{
if (!assetIsBound)
{
return;
}
```
float angle = steering.val;
if (steeringWheel != null)
{
steeringWheel.localRotation =
Quaternion.Euler(0f, 0f, -angle * 3f);
}
if (frontLeftSteering != null)
{
frontLeftSteering.localRotation =
Quaternion.Euler(0f, angle, 0f);
}
if (frontRightSteering != null)
{
frontRightSteering.localRotation =
Quaternion.Euler(0f, angle, 0f);
}
```
}
Now one slider moves three different parts.
The front wheels turn left and right.
At the same time, the steering wheel rotates.
The steering wheel uses:
because a steering wheel normally rotates more than the front wheels.
The exact value depends on your model.
Opening The Front Left Door
The door follows exactly the same basic pattern.
First create a value:
Code:
public JSONStorableFloat frontLeftDoorAngle;
Register it:
Code:
frontLeftDoorAngle = new JSONStorableFloat(
"frontLeftDoorAngle",
0f,
0f,
70f
);
frontLeftDoorAngle.setCallbackFunction += delegate
{
ApplyFrontLeftDoor();
};
RegisterFloat(frontLeftDoorAngle);
Create the slider:
Code:
UIDynamicSlider doorSlider =
CreateSlider(frontLeftDoorAngle, false);
doorSlider.label = "Front Left Door";
Then apply the rotation:
Code:
private void ApplyFrontLeftDoor()
{
if (!assetIsBound || frontLeftDoor == null)
{
return;
}
```
frontLeftDoor.localRotation =
Quaternion.Euler(
0f,
frontLeftDoorAngle.val,
0f
);
```
}
Now:
Code:
Front Left Door slider
↓
frontLeftDoorAngle
↓
ApplyFrontLeftDoor()
↓
Door_FL_Pivot
↓
door opens
The other three doors can use the same pattern.
Rotating All Four Wheels
We can also create one slider that rotates all four wheels.
For example:
Code:
public JSONStorableFloat wheelRotation;
Then:
Code:
wheelRotation = new JSONStorableFloat(
"wheelRotation",
0f,
-180f,
180f
);
wheelRotation.setCallbackFunction += delegate
{
ApplyWheelRotation();
};
RegisterFloat(wheelRotation);
And:
Code:
private void ApplyWheelRotation()
{
if (!assetIsBound)
{
return;
}
```
float angle = wheelRotation.val;
if (frontLeftWheel != null)
{
frontLeftWheel.localRotation =
Quaternion.Euler(angle, 0f, 0f);
}
if (frontRightWheel != null)
{
frontRightWheel.localRotation =
Quaternion.Euler(angle, 0f, 0f);
}
if (rearLeftWheel != null)
{
rearLeftWheel.localRotation =
Quaternion.Euler(angle, 0f, 0f);
}
if (rearRightWheel != null)
{
rearRightWheel.localRotation =
Quaternion.Euler(angle, 0f, 0f);
}
```
}
Now:
Code:
Wheel Rotation slider
│
├── Wheel_FL_Rotation
├── Wheel_FR_Rotation
├── Wheel_RL_Rotation
└── Wheel_RR_Rotation
All four wheels are controlled by one VaM value.
Again, the correct rotation axis depends on your model.
The Basic Pattern
Almost everything in this example follows the same pattern:
- Prepare the movable part in Unity
- Give it a useful Transform
- Make sure the pivot is correct
- Give the Transform a clear name
- Let the VaM plugin find that Transform
- Create a VaM slider or toggle
- Apply the slider value to the Transform
For example:
Code:
UNITY
Door_FL_Pivot
↓
correct hinge position
VAM
Front Left Door slider
↓
ApplyFrontLeftDoor()
↓
Door_FL_Pivot
↓
rotation changes
↓
door opens
What Unity Should Do
Unity should normally handle things like:
- separate movable parts
- parent and child relationships
- pivot positions
- local rotation axes
- mesh assignment
- basic mechanical structure
For example:
Code:
Door_FL_Pivot
└── Door_FL_Mesh
is something you prepare in Unity.
What The VaM Plugin Should Do
The plugin can then handle things like:
- find the Transform
- create sliders
- read slider values
- move or rotate the Transform
- show or hide objects
- control several Transforms together
- save control values
For example:
Code:
find Door_FL_Pivot
↓
read door slider
↓
change localRotation
Do Not Try To Fix Everything In C#
It is technically possible to compensate for some bad asset setups in code.
But that can quickly make the plugin unnecessarily complicated.
For example, if a door pivot is wrong, you could start calculating offsets and additional rotations in C#.
Usually this is much cleaner:
Code:
create an empty GameObject in Unity
↓
place it at the hinge
↓
make the door mesh a child
↓
rotate the helper Transform
A clean Unity hierarchy often saves a lot of code.
You Do Not Need To Understand The Whole Plugin
The complete plugin also contains code for:
- waiting for the CustomUnityAsset
- detecting when the hierarchy is ready
- finding deeply nested Transforms
- creating VaM UI
- saving values
- handling asset changes
- displaying status messages
You do not need to understand every part immediately.
For your first modification, concentrate on these questions:
- What do I want to move?
- Is it prepared correctly in Unity?
- What is its Transform called?
- Where does the script find that Transform?
- Which
Apply...() method changes it?
For example:
Code:
Door_FL_Pivot
↓
FindDescendantByName(...)
↓
frontLeftDoor
↓
ApplyFrontLeftDoor()
↓
localRotation
Once that pattern makes sense, adding more controls becomes much easier.
Want To Add Something Else?
Imagine that the car also has a trunk.
First prepare it in Unity:
Code:
Trunk_Pivot
└── Trunk_Mesh
Place the pivot near the trunk hinge.
Test the rotation directly in Unity.
Then in the plugin:
Code:
Trunk_Pivot
↓
find Transform
↓
create slider
↓
ApplyTrunk()
↓
rotate Transform
Exactly the same idea works for:
- hood
- glove box
- gear lever
- handbrake
- mirrors
- headlights
- seat adjustment
- buttons
- switches
And of course the technique is not limited to cars.
The car is simply an easy example because the expected movement of each part is obvious.
If Something Does Not Move
Check the simple things first:
- Is the CustomUnityAsset loaded?
- Is the plugin attached to the correct CustomUnityAsset atom?
- Does the Transform actually exist?
- Does the Transform name match the name in the script?
- Is the movable mesh really a child of that Transform?
- Is the pivot in the correct position?
- Are you rotating the correct X, Y or Z axis?
A very useful test is always:
Try the movement manually in Unity first.
If the Transform does not behave correctly there, fix the Unity setup before trying to fix the plugin code.
The Most Important Idea
The easiest way to remember the whole process is:
Code:
UNITY
creates the movable structure
```
↓
```
VAM PLUGIN
finds that structure
```
↓
```
VAM CONTROL
changes the Transform
Or even shorter:
Code:
Prepare in Unity
↓
Find in code
↓
Control in VaM
The cleaner the asset is prepared in Unity, the simpler the plugin code becomes.
Questions Or Problems?
I made this second example because a few questions came up about how the original tutorial can be adapted to an actual asset.
If something is unclear, does not work with your asset, or you are unsure what should be prepared in Unity and what should be done in the plugin, just write a question in the discussion.
If possible, include:
- what you are trying to move
- the relevant Transform names
- a screenshot or text version of the Unity hierarchy
- what currently happens
- what you expected to happen
Then I can look at the specific case and explain how I would set it up.
The car example is only there to make the general pattern easier to understand:
Code:
Prepare the part in Unity
↓
Find its Transform
↓
Connect a VaM control
↓
Change the Transform