Writing Controller Scripts



3ds Max interprets the text you type into the Script text box as the body of a MAXScript block expression, so you can type as many expressions as you want on as many lines as you want and they are evaluated in turn and the value of the last expression is taken as the controller value. This value must yield the right type for the controller, Float for float, Point3 for position, Quat for rotation, etc.

The value returned from a script controller may not necessarily be the same value seen in Track View or in the command panels, nor the same value returned by accessing the property value in MAXScript. Part of a MAXWrapper property definition is a scaling value that is applied when reading or setting the true value stored in a controller. For example, the slice_to and slice_from properties associated with many of the geometry primitives is displayed in degrees. The actual value stored in the controller associated with these properties are in radians. This scaling factor is an internal property of the MAXWrapper property, and not an internal property of the controller. Both 3ds Max and MAXScript automatically apply the specified scaling factor when accessing properties, so this scaling is normally invisible to the user. The exception is when script or expression controllers are used. For these controllers, the data value returned must be the unscaled value, as 3ds Max will then apply the scaling factor to the output value. In the documentation of the MAXWrapper classes, any scaling applied to a property is shown. If there is no scaling listed, no scaling occurs for that property. For example, a portion of the Capsule documentation is:

<Capsule>.radius Float default: 0.0 -- animatable

<Capsule>.height Float default: 0.0 -- animatable

<Capsule>.slice_from Float default: 0.0 -- animatable, angle

<Capsule>.slice_to Float default: 0.0 -- animatable, angle

From this, a scripted controller would return unscaled values for the radius and height properties, and radians for slice_from and slice_to. The scaling types, stored value meaning, and scaling value applied to the true controller value are as follows:

Angle -- value stored in radians, output scaled by 57.29578

Percentage -- value stored as fraction (0 to 1), output scaled by 100

For properties that have a Color value type, the controller output is automatically scaled by 255. If a point3_script is assigned to one of these properties, each component value should stored as fraction (0 to 1). Remember that converting a MAXScript Color value to a Point3 value does not apply a scaling factor - red as point3 returns a value of [255,0,0]. Therefore you must explicitly scale a Color value by dividing by 255 if it is output by the script controller.

The body of a script controller’s script is any valid MAXScript expression that evaluates to the proper data type, and can contain global and local variables, functions, and structure definitions. The script is compiled in its own local scope, and the locals are only visible inside the scope of the script controller. Script controller local variables are heap-based locals, which are slightly different from normal (stack-based) locals. Normal locals are visible in the current scope and have a lifetime of one execution of that scope. Heap-based locals are also visible only in the current scope, but have a lifetime equal to the lifetime of the top-level expression where they are defined. A script controller’s locals are created the first time the script is executed and are kept permanently in the heap (unless and until you redefine the script). This means you can store values in local variables during one execution of the script controller and these values will still be present at the next evaluation. You cannot access the script controller’s locals from another utility or from Listener, because they are not executing within the scope of the script controller. See Scope of Variables for more information.

As a special case, you can exit a script controller’s script using a return <expr>.

A controller is always evaluated by 3ds Max with respect to a specific animation time. This might be the current time slider or incrementing frame time if an animation is playing or a render is under way.

In the case of Script controllers, the time being evaluated is used to establish an automatic ‘at time’ context around the controller script, so any properties you access (outside of other explicit ‘at time’ expressions) yield the correct values for the current controller evaluation time. This means you don’t have to do anything special in your scripts to work at the correct time. You can access the evaluation time if you need to with the standard MAXScript variable, currentTime. You can also reference scene property values at other times by using explicit ‘at time’ expressions in your scripts, as in normal MAXScript programming.

Remember that MAXScript lets you write multiline string literals, if you need.


--A position script keeping the object at the center of all other objects

--in the scene as they move about:

local pos = [0,0,0]

for o in objects where o != $foo do

pos += o.pos

pos / (objects.count - 1)

The above script computes the average position of all objects except the current one (written as $foo here) by setting up a local variable that iterates through all objects except $foo, accumulates a total position vector, and computes the average in the last line, which is the final result of the script.


--A position script keeping the object attached to the highest vertex in a

--given object:

local high_index = 1, high_z = (getVert $foo 1).z

for i in 2 to $foo.numVerts do

if (getVert $foo i).z > high_z then


high_index = i

high_z = (getVert $foo i).z


getVert $foo high_index

The above script runs over the vertices in $foo remembering the index of the vertex with the largest z and returns that vertex’s coordinates as the new position.

Limitations in versions prior to 3ds Max 8

Script controllers are not automatically updated when you interactively modify objects that they depend on unless you define an explicit dependency using dependsOn or assign the scene node to a variable.

If you move the time slider or if you animate the changes and then play the animation, the changes are reflected automatically. Because the scripts can refer to other objects in very indirect ways or conditional ways, it is not possible for MAXScript to automatically determine the objects a script depends on. These objects must always be specificied explicitly.