Tip:
Highlight text to annotate it
X
While we were designing the Building Maker, we wanted to make sure we could add any number of floors to our buildings.
This unpredictable amount means that each floor's script controller had to be generated on the fly.
In this part, we'll show how to create a script controller in MaxScript.
We'll start off with the following script, which clears the scene, and produces the three cubes we had created in part 1.
We'll be using this script as a starting point to test different script controller configurations.
As a side note, you may have noticed that we’ve started and ended the script with paired parentheses.
This is to avoid echoing our script’s commands in the maxscript listener window.
Now, only the result of the last command will be echoed in the listener.
With our cubes created, we can call float_script() to create a script controller.
The MaxScript functions associated with this float_script object provide a very similar interface to the script controller window.
Let's assign this script controller to the green box's z_position controller.
We’ll also need to explicitly create two new bezier_float controllers.
One for the purple box’s height, and one for the yellow box’s height, since these controllers aren’t initialized by default when a box is created.
If you're not too familiar with controllers, you can imagine them as objects which contain animation data.
In the case of a bezier_float controller, the animation data is stored as a sequence of decimal values, which have been keyed at various times.
The controller's value at a specific time is computed by interpolating two keys using a bezier curve.
Objects usually have a wide variety of animatable properties, which are also referred to as either subanims, or "tracks". We'll use these two words interchangeably.
A track can be connected to a controller to animate the value of that track.
So far, in our script, we've connected two bezier_float controllers to the bottom box heights, and one float_script controller to the top box's z-position.
Recall that in part 1, our script controller required two input variables, namely the purple box and yellow box’s height.
We can use four functions to create input variables: addConstant, addTarget, addObject, and addNode.
Each of these functions correspond to one of the four buttons in the script controller interface
All these functions have specific uses, so it's important to cover which functions do what.
For a more detailed explanation of these functions, you can consult the Script Controllers topic in the MaxScript reference.
We'll look at the addObject function first, which is equivalent to the Add Controller button.
The first argument is the name of the variable, which could be anything. Here, we’ve chosen to set it to “purpleBox”.
The second argument is a reference to the purple box height controller.
“b1.height.controller” corresponds to the bezier_float controller connected to the purple box’s height
Let’s also add a variable for the yellow box’s height.
Now, we can set the expression, or the “script” to the following value.
Note that this string can be any valid MaxScript code, including loops, scripted functions, and so on.
In this expression, you’ll recognize the call to “amax”, which returns the maximum value in its input array.
The difference compared to part 1 is that we’re accessing the controller’s value itself instead of the object's track
When we run the script, we can change the height of the purple or yellow box, and the green box will stay above the tallest one.
The interesting behavior related to the addObject function is when either of the height controllers are re-assigned.
"In the listener window, type b1.height.controller = bezier_float() to change the purple box's height controller. "
Notice that the purple box’s height no longer affects the green box’s z-position.
That’s because the script controller is still referencing the purple box’s original bezier_float controller, which is no longer driving the purple box’s height.
Now let's look at the addTarget function to see the difference.
To picture the distinction between tracks and controllers, addTarget is the equivalent of the Add Track button, which, as the name implies, references an object’s track instead of its controller.
If we run the following utility script, we can print the nested tracks of the selected box.
You can find this script in the description section of this video.
Here, we can see that the purple box's height track is actually nested under the Object__Box track.
To access this nested track, we'll use the following sequence of square brackets in the Listener.
When we press enter, the result is the purple box's height sub-animatable object.
Now that we know how to access an object's track, let's get back to our script to assign these height tracks to new variables.
We'll comment our previous code out first, to help us compare each approach.
Use the addTarget function to assign the height tracks to their respective variables.
We also need to change the expression, since we're no longer dealing with controllers.
When we run the script, the z-position of the green box is still driven by the purple and yellow box heights
However, if we run b1.height.controller = bezier_float() in the listener, the purple box continues to affect the height of the green box, since the script controller is only referencing the track, and not the controller itself.
This is exactly what we're looking for, however for the sake of completeness, let's take a look at the addNode function next.
To continue comparing each function, let's comment our previous code out.
Use the addNode function to assign each variable to their respective nodes.
In Max, nodes contain the transformation properties of an object, including its translation, rotation, and scale.
We'll access each node's height property in the expression to pick the tallest box.
After we run the script the z-position of the green box doesn't immediately update.
In fact, it only updates when one of the box's transforms are modified.
This isn't very convenient for our situation, but it can be useful to create custom point constraints.
Finally, let's look at the addConstant function.
In this case, the variables created with the addConstant function contain the fixed value of the given property.
After we run the script, since the purpleBox and yellowBox variables are constant, the green box's z-position will not change when either of the box heights are modified.
Now that we've covered what each function does, we can revert back to using the addTarget function, which produces the desired effect.
In the next part, we'll show how to convert this short script into a general-purpose function to stack shapes one on top of the other.