Writing Your Own MAXScripts


This section presents the basics of the MAXScript language and shows you how to use the various parts of MAXScript in your own scripts. You can test any of these scripting commands using the MAXScript Listener window.

CROSS-REF 

Much of the discussion that follows will sound familiar if you've already read the material on expressions found in Chapter 32, "Animating with Constraints and Controllers." Expressions use many of the same constructs as MAXScript.

Variables and data types

A variable in MAXScript is sort of like a variable in algebra. It represents some other value, and when you mention a variable in an equation, you're actually talking about the value that the variable holds. You can think of variables in MAXScript as containers that you can put stuff into and take it out of later. Unlike variables in algebra, however, variables in MAXScript can "hold" other things besides numbers, as we'll soon see.

To put a value into a variable, you use the equal sign. For example, if you type

      X = 5 * 3 

in the MAXScript Listener window, Max evaluates the expression on the right side of the equal sign and stores the result in the variable named X. In this case, Max would multiply 5 and 3 and would store the result (15) in X. You can then see what is in X by just typing X in the Listener window and pressing Enter. Max then displays the value stored in X, or 15.

You can name your variables whatever you want, and naming them something that helps you remember what the variable is for is a good idea. For example, if you want a variable that keeps track of how many objects you're going to manipulate, the name "objCount" would be better than something like "Z."

Note 

Variable names can be just about anything you want, but you must start a variable name with a letter. Also, the variable name can't have any special characters in it, like spaces, commas, or quotation marks. You can, however, use the underscore character and any normal alphabetic characters.

Variables can also hold strings, which are groups of characters. For example,

      badDay = "Monday" 

stores the word "Monday" in the variable badDay. You can attach two strings together using the plus sign, like this:

      grouchy = "My least favorite day is" + badDay 

Now the variable grouchy holds the value "My least favorite day is Monday."

Try this:

      wontWork = 5 + "cheese" 

Max prints out an error because it's confused: You're asking it to add a number to a string. The problem is that 5 and "cheese" are two different data types. Data types are different classes of values that you can store in variables. You can almost always mix values of the same data type, but values of different types usually don't make sense together.

Note 

To see the data type of a variable, use the class of command. Using the previous example, you could type classof grouchy, and Max would in turn print out String.

Another very common data type is Point3, which represents a three-dimensional point. Following are a few examples of using points, with explanatory comments:

      Pos = [5,3,2]        -- Marks a point at (5,3,2)      Pos.x = 7            -- Change the x coordinate to 7                           -- Now the point is at (7,3,2)      Pos = Pos + (1,2,5)  -- Take the old value for Pos,                           -- move it by (1,2,5) to (8,5,7)                           -- and store the new value in Pos 

In addition to these basic data types, each object in your scene has its own data type. For example, if you use classof on a sphere object, Max prints out Sphere. Data types for scene objects are actually complex data types or structures, which means that they are groups of other data types in a single unit. The pieces of data inside a larger object are called members or properties. Most scene objects have a member called Name, which is of type String. The Name member tells the specific name of that object. Another common property is Position, a Point3 variable that tells the object's position.

Max has a special built-in variable that represents whatever object is currently selected. This variable is $ (the dollar sign), which is used in the following tutorial.

Tutorial: Using variables

In this tutorial, you learn more about variables in MAXScript by using them to manipulate an object in your scene.

To use variables to manipulate scene objects, follow these steps:

  1. Open the image from book Teapot.max file from the Chap 49 directory on the DVD.

    This file has a simple teapot object.

  2. Right-click on the title for the Left viewport, and choose Views image from book Extended image from book MAXScript Listener to open the MAXScript Listener window in the Left viewport.

  3. Select the teapot object, type $, and press Enter.

    Max displays information about the teapot. (Your numbers will probably be different depending on where you placed your teapot.)

  4. Type the following lines one at a time in the top pane to see the property values stored as part of the teapot object:

          $.position      $.wirecolor      $.radius      $.name      $.lid 
  5. Now type these lines, one at a time, to set the property values of the teapot object:

          $.lid = false      $.position.x = -20      $.segs = 20       

    Figure 49.12 shows the commands, their results in the MAXScript Listener window, and the resulting teapot object.

    image from book
    Figure 49.12: The script commands entered in the MAXScript Listener affect the objects in the viewports.

Program flow and comments

In general, when Max begins executing a script, it starts with the first line of the script, processes it, and then moves on to the next line. Execution of the script continues until no more lines are in the script file. (Later, we look at some MAXScript keywords that let you change the flow of script execution.)

Max lets you embed comments or notes in your script file to help explain what is happening. To insert a comment, precede it with two hyphens (--). When Max encounters the double hyphen, it skips the comment and everything else on that line and moves to the next line of the script. For example, in this line of MAXScript

      $Torus01.pos = [0,0,0]   -- Move it back to the origin 

Max processes the first part of the line (and moves the object to the origin) and then moves on to the next line after it reaches the comment.

Using comments in your MAXScript files is very important because as your scripts start to become complex, figuring out what is happening can get difficult. Also, when you come back a few months later to improve your script, comments will refresh your memory and help keep you from repeating the same mistakes you made the first time around.

Note 

Because Max ignores anything after the double hyphen, you can use comments to temporarily remove MAXScript lines from your script. If something isn't working right, you can comment out the lines that you want Max to skip. Later, when you want to add them back in, you don't have to retype them. You can just remove the comment marks, and your script is back to normal.

Expressions

An expression is what Max uses to make decisions. An expression compares two things and draws a simple conclusion based on that comparison.

CROSS-REF 

These same expressions can be used within the Expression controller. You can find details on this controller in Chapter 32, "Animating with Constraints and Controllers."

Simple expressions

The expression

      1 < 2 

is a simple expression that asks the question, "Is 1 less than 2?" Expressions always ask yes/no type questions. When you type an expression in the MAXScript Listener window (or inside of a script), Max evaluates the expression and prints true if the expression is valid (like the preceding example) and false if it isn't. Try the following expressions in the Listener window, and Max will print the results as shown in Figure 49.13 (you don't have to type in the comments):

      1 < 2           -- 1 IS less than 2, so expression is true      1 > 2           -- 1 is NOT greater than 2, so false      2 + 2 == 4      -- '==' means "is equal to". 2 + 2 is                      -- equal to 4, so true      2 + 2 == 5      -- 4 is NOT equal to 5, so false      3 * 3 == 5 + 4  -- 9 IS equal to 9, so true      3 * 3 != 5 + 4  -- '!=' means 'not equal to'. '9 is not                      -- equal to 9' is a false statement, so                      -- the expression is false      a = 23          -- store 23 in variable a      b = 14 + 9      -- store 23 in variable b      a == b          -- 23 IS equal to 23, so true 

image from book
Figure 49.13: Using the MAXScript Listener to evaluate expressions

Play around with simple expressions until you're familiar with what they mean and have an intuitive feel for whether or not an expression is going to evaluate to true or false.

Complex expressions

Sometimes you need an expression to decide on more than just two pieces of data. MAXScript has the and, or, and not operators to help you do this.

The and operator combines two expressions and asks the question, "Are both expressions true?" If both are true, then the entire expression evaluates to true. But if either is false, or if they are both false, then the entire expression is false. You can use parentheses to group expressions, so an expression with the and operator might look something like this:

      (1 < 2) and (1 < 3)    -- true because (1 < 2) is true AND                             -- (1 < 3) is true 

The or operator is similar to and, except that an expression with or is true if either of the expressions is true or if both are true. Here are some examples:

      (2 > 3) or (2 > 1)     -- even though (2 > 3) is false, the                             -- entire expression is true because                             -- (2 > 1) is true      (2 > 3) and (2 > 1)    -- false because both expressions are                             -- not true 

Try some of these complex expressions to make sure that you understand how they work:

      a = 3      b = 2      (a == b) or (a > b)    -- true because a IS greater than b      (a == b) and (b == 2)  -- false because both expressions are                             -- not true      (a > b) or (a < b)     -- true because at least one IS true      (a != b) and (b == 3)  -- false because b is NOT equal to 3 

The not operator negates or flips the value of an expression from true to false, or vice versa. For example:

      (1 == 2)               -- false because 1 is NOT equal to 2      not (1 == 2)           -- true. 'not' flips the false to true 

Conditions

Conditions are one way in which you can control program flow in a script. Normally, Max processes each line, no matter what, and then quits; but with conditions, Max executes certain lines only if an expression is true.

For example, suppose you have a script with the following lines:

      a = 4      If (a == 5) then      (       b = 2      ) 

Max would not execute the line b = 2 because the expression (a == 5) evaluates to false. Conditional statements, or "if" statements, basically say, "If this expression evaluates to true, then do the stuff inside the block of parentheses. If the expression evaluates to false, skip those lines of script."

Conditional statements follow this form:

      If <expr> then <stuff> 

where <expr> is an expression to evaluate and <stuff> is some MAXScript to execute if the expression evaluates to true. You can also use the keyword else to specify what happens if the expression evaluates to false, as shown in the following example:

      a = 4      if (a == 5) then      (        b = 2      )      else      (        b = 3      ) 

After this block of MAXScript, the variable b would have the value of 3 because the expression (a == 5) evaluated to false. Consequently, Max executed the MAXScript in the else section of the statement.

Collections and arrays

MAXScript has some very useful features to help you manipulate groups of objects. A group of objects is called a collection. You can think of a collection as a bag that holds a bunch of objects or variables. The things in the bag are in no particular order; they're just grouped together.

You can use collections to work with groups of a particular type of object. For example, the MAXScript

      a = $pokey*      a.wirecolor = red 

creates a collection that contains every object in your scene whose name starts with "Pokey" and makes every object in that collection turn red.

MAXScript has several built-in collections that you might find useful, such as cameras and lights, containing all the cameras and lights in your scene. So

      delete lights 

removes all the light objects from your scene (which may or may not be a good idea).

An array is a type of collection in which all the objects are in a fixed order, and you can access each member of the array by an index. For example

      a = #()     -- creates an empty array to use      a[1] = 5      a[2] = 10      a[5] = 12      a 

After the last line, Max prints out the current value for the array:

      #(5, 10, undefined, undefined, 12) 

Notice that Max makes the array big enough to hold however many elements we want to put in it, and that if we don't put anything in one of the positions, Max automatically puts in undefined, which simply means that array location has no value at all.

One last useful trick is that Max lets you use the as keyword to convert from a collection to an array:

      LightArray = (lights as array) 

Max takes the built-in collection of lights, converts it to an array, and names the array LightArray.

The members of an array or a collection don't all have to have the same data type, so it's completely valid to have an array with numbers, strings, and objects, like this:

      A = #(5,"Mr. Nutty",box radius:5) 
Note 

You can use the as MAXScript keyword to convert between data types. For example, (5 as string) converts the number 5 to the string "5," and (5 as float) converts the whole number 5 to the floating-point number 5.0.

Loops

A loop is a MAXScript construct that lets you override the normal flow of execution. Instead of processing each line in your script once and then quitting, Max can use loops to do something several times.

For example,

      j = 0      for i = 1 to 5 do      (        j = j + i      ) 

This MAXScript uses two variables-i and j-but you can use any variables you want in your loops. The script sets the variable j to 0 and then uses the variable i to count from 1 to 5. Max repeats the code between the parentheses five times, and each time the variable i is incremented by 1. Inside the loop, Max adds the current value of i to j. Can you figure out what the value of j is at the end of the script? If you guessed 15, you're right. To see why, look at the value of each variable as the script is running:

      When                  j     i      ----------------------------      First line            0     0      Start of loop         0     1      After first loop      1     1      Start of second loop  1     2      After second loop     3     2      Start of third loop   3     3      After third loop      6     3      Start of fourth loop  6     4      After fourth loop     10    4      Start of fifth loop   10    5      After fifth loop      15    5 

A loop is also useful for processing each member of an array or collection. The following MAXScript shows one way to turn every teapot in a scene blue:

      teapots = $teapot*              -- get the collection of teapots      for singleTeapot in teapots do      (        singleTeapot.wirecolor = blue      ) 

You can use a for loop to create a bunch of objects for you. Try this MAXScript:

      for I = 1 to 10 collect       (        sphere radius:15       ) 

The collect keyword tells Max to create a collection with the results of the MAXScript in the block of code inside the parentheses. The line

      sphere radius:15 

tells Max to create a sphere with radius of 15, so the entire script created 10 spheres and added them to your scene. Unfortunately, Max puts them all in the same spot, so let's move them around a bit so we can see them:

      i = -50      For s in spheres do      (       s.position = [i,i,i]       i = i + 10      ) 

Study this script to make sure that you understand what's going on. We use a for loop to process each sphere in our collection of spheres. For each one, we set its position to [i, i, i], and then we change the value of i so that the next sphere is at a different location.

Functions

The last feature of basic MAXScript that we look at is the function. Functions are small chunks of MAXScript that act like program building blocks. For example, suppose that you need to compute the average of a collection of numbers many times during a script you're writing. The MAXScript to do this might be:

      Total = 0      Count = 0      For n in numbers do      (       total = total + n       count = count + 1      )      average = total / (count as float) 

Given a collection of numbers called numbers, this MAXScript computes the average. Unfortunately, every time you need to compute the average, you have to type all that MAXScript again. Or you might be smart and just cut and paste it in each time you need it. Still, your script is quickly becoming large and ugly, and you always have to change the script to match the name of your collection you're averaging.

A function solves your problem. At the beginning of your script, you can define an average function like this:

      Function average numbers =      ( -- Function to average the numbers in a collection       local Total = 0       local Count = 0       For n in numbers do       (       total = total + n       count = count + 1       )       total / (count as float)      ) 

Now any time you need to average any collection of numbers in your script, you could just use this to take all the numbers in the collection called num and store their average in a variable called Ave:

      Ave = average num      -- assuming num is a collection 

Not only does this make your script much shorter if you need to average numbers often, but it makes it much more readable, too. It's very clear to the casual reader that you're going to average some numbers. Also, if you later realize that you wrote the average function incorrectly, you can just fix it at the top of the script. If you weren't using functions, you would have to go through your script and find every case where you averaged numbers and then fix the problem. (What a headache!)

Let's take another look at the function definition. The first line

      Function average numbers = 

tells Max that you're creating a new function called average. It also tells Max that to use this function, you have to pass in one piece of data, and that inside the function you refer to that data using a variable called numbers. It doesn't matter what the name of the actual variable was when the function was called; inside the function, you can simply refer to it as numbers.

Creating functions that use multiple pieces of data is also easy. For example,

      Function multEm a b c = (a * b * c) 

creates a function that multiplies three numbers together. To use this function to multiply three numbers and store the result in a variable called B, you would simply enter

      B = multEm 2 3 4 

The next two lines

      local Total = 0      local Count = 0 

create two variables and set them both to 0. The local keyword tells Max that the variable belongs to this function. No part of the script outside of the function can see this variable, and if there is a variable outside the function with the same name, changing the variable inside this function won't affect that variable outside the function. That way, you never have to worry about what other variables are in use when someone calls average; even if variables are in use that are named Total or Count, they won't be affected.

The last line

      total / (count as float) 

uses the Total and Count values to compute the average. How does that value get sent back to whoever called the function? Max evaluates all the MAXScript inside the function and returns the result. Because the last line is the last thing to be evaluated, Max uses the result of that calculation as the result of the entire function.

Tutorial: Creating a school of fish

Let's look at an example that puts into practice some of the things you've learned in this chapter. In this multipart tutorial, we use MAXScript to create a small school of fish that follows the dummy object around a path.

Part 1: Making the fish follow a path

In this part of the tutorial, we use MAXScript to move one of the fish along a path in the scene. To do this, follow these steps:

  1. Open the image from book Fish scene.max file from the Chap 49 directory on the DVD.

    This scene consists of two fish and a dummy object that follows a path. What we need to do is use MAXScript to create a small school of fish that follows the dummy object around the path.

  2. Press F11 to open the MAXScript Listener window. In the window, choose File image from book New Script to open the MAXScript Editor window, and type the following script:

          pathObj = $Dummy01      fishObj = $Fish1/FishBody      relPos = [0,-150,-50]   -- How close the fish is to the path      animate on      (       for t = 1 to 100 do at time t       (       fishObj.position = pathObj.position + relPos       )      )       

  3. Select the Camera01 viewport. Choose File image from book Evaluate All (or press Ctrl+E) to evaluate all the MAXScript in the Editor window, right-click the Camera01 viewport to activate it, and click the Play Animation button.

    The fish rigidly follows the dummy object's path. Figure 49.14 shows one frame of this animation.

    image from book
    Figure 49.14: First attempt at making the fish follow a path

Now let's explain the MAXScript entered in the previous tutorial. The first few lines create some variables that the rest of the script uses. pathObj tells the name of the object that the fish will follow, and fishObj is the name of the fish's body. (Notice that we can reference parts of the group hierarchy by using the object name, a forward slash, and then a child part.) Why bother creating a variable for the fish object? After we get this first fish working, we want to apply the same script to another fish. All we have to do is rename Fish1 as Fish2, re-execute the script, and we're finished!

The script also creates a variable called relPos, which we use to refer to the relative position of the fish with respect to the dummy object. If we have several fish in the scene, we don't want them all in the exact same spot, so this is an easy way to position each one.

The next block of MAXScript is new: We're using the animate on construct. This tells Max to generate keyframes for our animation. It's the same as if we had pressed Max's Animation button, run our script, and then shut Animation off. So any MAXScript inside the animate on parentheses creates animation keyframes. These parentheses define a section of the script we call a block.

Inside the animation block, we have a loop that counts from 1 to 100 (corresponding to each frame of our animation). On the end of the loop line, we have at time t, which tells Max that for each time through the loop, we want all the variables to have whatever values they'll have at that time. For example, if we want the fish to follow the dummy object, we have to know the position of the object at each point in time instead of just at the beginning; so each time through the loop, Max figures out for us where the dummy object will be.

Inside the loop, we set the fish's object to be that of the dummy object (at that point in time) and then adjust the fish's position by relPos.

Part 2: Adding body rotation and tail animation

Let's make that fish look a little more lifelike by animating its tail and having it rotate its body to actually follow the path. Also, we add a little unpredictability to its motion so that when we add other fish, they aren't exact copies of each other.

To improve the fish's animation, follow these steps:

  1. Type the revised version of the script (the new lines are in bold):

          pathObj = $Dummy01      fishObj = $Fish1/FishBody      fishTail = $Fish1/FishBody/FishTail      relPos = [0,-150,-50]  -- How close the fish is to the path      fishTail.bend.axis = 0 -- 0 is the x-axis      zadd = 4               -- vertical movement at each step      tailFlapOffset = (random 0 100)      tailFlapRate = 25 + (random 0 25)      animate on      (       for t = 0 to 100 do at time t       (        fishObj.position = pathObj.position + relPos        fishObj.position.z = relPos.z        relPos.z += zadd        -- let's say that there's a 10% chance that the fish will        -- change directions vertically        if ((random 1 100) > 90) then        (         zadd = -zadd        )        fishTail.bend.angle = 50 * sin (t * tailFlapRate +        tailFlapOffset)        oldRt = fishObj.rotation.z_rotation        newRt = (in coordsys pathObj pathObj.rotation.z_rotation)        if ((random 1 100) > 85) then        (         fishObj.rotation.z_rotation += (newRt - oldRt) *               (random 0.5 1.5)        )       )      ) 

  2. Save your script (File image from book Save), and then press Ctrl+E to evaluate the script again. This script is saved in the Chap 49 directory as image from book FishPath2.ms. Make the Camera01 viewport active, and click Play Animation. Figure 49.15 shows another frame of the animation. As you can see, the fish is heading in the right direction this time, and the tail is flapping wildly.

    image from book
    Figure 49.15: A tail-flapping fish that faces the right direction as it follows the path

Okay, let's look at what changed. First, we added a variable to refer to the fish's tail, so that it is easy to change when we add another fish. Also, we accessed the bend modifier of the tail and set its axis to 0, which corresponds to the X-axis. (You can try other values to see that it really does change the axis parameter in the rollout.)

Next, we created some more variables. We use zadd to tell Max how much to move the fish in the Z-direction at each step (we don't want our fish to always swim at the same level). tailFlapOffset and tailFlapRate are two variables used to control the tail flapping (I explain this when we get to the part of the script that uses them).

Inside the for loop, notice that we've overridden the fish's Z-position and replaced it with just the relative Z-position, so that each fish swims at its own depth and not the dummy object's depth. Then, at each step, we add zadd to the Z-position so that the fish changes depth slowly. We have to be careful, or our fish will continue to climb out of the scene or run into the ground, so at each step we also choose a random number between 1 and 100 with the function (random 1 100). If the random number that Max picks is greater than 90, we flip the sign of zadd so that the fish starts moving in the other direction. This is a fancy way of saying, "There's a 90 percent chance that the fish will continue moving in the same direction and a 10 percent chance that it will switch directions."

In the next part, we again access the tail's bend modifier, this time to set the bend angle. To get a nice back-and-forth motion for the tail, we use the sin function. In case you've forgotten all that math from when you were in school, a sine wave oscillates from 1 to 1 to 1 over and over again. By multiplying the function by 50, we get values that oscillate between 50 and 50 (pretty good values to use for our bend angle). We use tailFlapOffset to shift the sine wave so that the tail flapping of additional fish is out of synch slightly with this one (remember, we're trying to get at least a little realism here) and tailFlapRate to make each fish flap its tail at a slightly different speed.

The only thing left for us to do is to make the fish "follow" the path; that is, rotate its body so that it's facing the direction it's moving. The simplest way to do this is to use the following MAXScript (split into two lines to make it easier to read):

      newRt = (in coordsys pathObj pathObj.rotation.z_rotation)      fishObj.rotation.z_rotation = newRt 

The in coordsys construct tells Max to give us a value from the point of view of a particular coordinate system. Instead of pathObj, we could have asked for the Z-rotation in the world, local, screen, or parent coordinate system, too. In this case, we want to rotate the fish in the same coordinate system as the dummy object. To randomize the direction of the fish a little, we've made the rotation a little more complex:

      oldRt = fishObj.rotation.z_rotation      newRt = (in coordsys pathObj pathObj.rotation.z_rotation)      if ((random 1 100) > 85) then      (       fishObj.rotation.z_rotation += (newRt - oldRt) *                                            (random 0.5 1.5)      ) 

First, we save the old Z-rotation in oldRt, and then we put the new rotation in newRt. Again, we pick a random number to decide whether we'll do something; in this case we're saying, "There's an 85 percent chance we won't change directions at all." If our random number does fall in that other 15 percent, however, we adjust the fish's rotation a little. We take the difference between the new rotation and the old rotation and multiply it by a random number between 0.5 and 1.5, which means we adjust the rotation by anywhere from 50 percent to 150 percent of the difference between the two rotations. So any fish will basically follow the same path, but with a little variation here and there.

Note 

Max lets you use shorthand when adjusting the values of variables. Instead of saying a = a + b, you can just say a += b. Both have the same effect.

Part 3: Animating the second fish

This scene actually has two fish in it (the other one has been sitting patiently off to the side), so for the final part of this tutorial, we get both fish involved in the animation. To animate the second fish alongside the first one, follow these steps:

  1. At the top of the script, change these three lines (changes are in bold):

          pathObj = $Dummy01      fishObj = $Fish2/FishBody      fishTail = $Fish2/FishBody/FishTail      relPos = [50,75,0]   -- How close the fish is to the path 

  2. Choose File image from book Evaluate All (or press Ctrl+E) to run the script again, and then animate it. Figure 49.16 shows both fish swimming merrily.

    image from book
    Figure 49.16: Both fish swimming together

This script generates keyframes for the second fish because we changed the fishObj and fishTail variables to refer to the second fish. We've also moved the second fish's relative position so that the two don't run into each other.




3ds Max 9 Bible
3ds Max 9 Bible
ISBN: 0470100893
EAN: 2147483647
Year: 2007
Pages: 383

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net