Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
3ds Max 6 Bible (2004).pdf
Скачиваний:
55
Добавлен:
17.08.2013
Размер:
50.02 Mб
Скачать

Chapter 48 Automating with MAXScript 1127

10.Now it’s time to try out your first MAXScript effort. Add a sphere to your scene. Make sure that it’s selected before moving to the next step.

11.In the top pane of the MAXScript Listener window, select all the text (an easy way to do so is by pressing Ctrl+A), and then hold down the Shift key and press the Enter key to tell Max to execute the MAXScript.

In Figure 48-7, you can see the script and the sphere that has been squashed and has changed color.

Figure 48-7: Running the new squash-and-turn-purple script

Setting MAXScript Preferences

The Preference Settings dialog box, opened with the Customize Preference Settings menu command, includes a panel of MaxScript settings, shown in Figure 48-8. Using these settings, you can set which scripts load automatically, the default settings for the Macro Recorder, and even what font is displayed in the Script Editor window.

1128 Part XII MAXScript and Plug-Ins

Figure 48-8: The MAXScript panel includes options for controlling MAXScript.

Max includes two directories in its default installation that can be used to automatically load scripts when Max starts. These directories are scripts and scripts\startup, but you can change these directories using the Configure Paths dialog box. The Load Startup Scripts option causes any scripts in these directories to be loaded automatically when Max starts.

Types of Scripts

All scripts are not created equal, and Max categorizes different scripts based on how they work. For more information, the MAXScript online help provides exhaustive information on their various options.

The main thing to consider when deciding what type of script to create is the user interface. Ask yourself what the most logical user interface would be for the type of tool you’re creating, and this gives you a hint as to which type of script is well suited for the task.

Macro scripts

Macro scripts are scripts created with the Macro Recorder. Any script that is associated with a toolbar button is considered a Macro script. Max organizes Macro scripts by their category, which you can change by editing the script file. To call a Macro script from another script, you can use the macros command. For example,

macros.run “objects” “sphere”

runs the “sphere” script in the “objects” category.

Chapter 48 Automating with MAXScript 1129

Macro scripts are generally scripts that require no other user input; you just click a button, and the script works its magic.

Scripted utilities

A scripted utility is a MAXScript that has its own custom rollout in the Utilities panel, like the SphereArray example. This type of script is particularly useful when your script has parameters that the user needs to enter, such as the radius in the SphereArray script. Scripted utilities are easy to build using the Visual MAXScript Editor.

Scripted right-click menus

When you right-click an object in your scene, Max opens a pop-up menu of options for you to choose from, much like a quadmenu. Scripted right-click menus let you append your own menu items to the right-click menu. If you create a script that modifies some property of an object, making the script available through the right-click menu makes it easily accessible.

Scripted mouse tools

You can use scripted mouse tools to create scripts that handle mouse input in the viewports. These scripts listen for commands from the mouse, such as clicking the mouse buttons and clicking and dragging the cursor. For example, you would use this type of MAXScript if you were making a new primitive object type so that users could create the new objects just like they would a sphere or a box.

Scripted plug-ins

Scripted plug-ins are by far the most complex type of MAXScript available. They mirror the functionality of non-MAXScript plug-ins (which are written in other programming languages such as C++). You can create scripted plug-ins that make new geometry, create new shapes, control lights, act as modifiers, control texture maps and materials, and even produce special rendering effects.

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-

Much of the discussion that follows will sound familiar if you’ve already read the chapter on

Reference

expressions found in Chapter 32, “Using the Expression Controller.” 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

1130 Part XII MAXScript and Plug-Ins

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

 

quote 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 classof 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

Chapter 48 Automating with MAXScript 1131

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 Teapot.max file from the Chap 48 directory on the CD-ROM. This file has a simple teapot object.

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

3.Select the teapot object and 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 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 48-9 shows the commands, their results in the MAXScript Listener window, and the resulting teapot object.

1132 Part XII MAXScript and Plug-Ins

Figure 48-9: 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 after 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.

Chapter 48 Automating with MAXScript 1133

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-

These same expressions can be used within the Expression controller. You can find details on

Reference

this controller in Chapter 32, “Using the Expression Controller.”

 

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 48-10 (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

1134 Part XII MAXScript and Plug-Ins

Figure 48-10: 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) (a == b) and (b == 2)

(a > b) or (a < b) (a != b) and (b == 3)

--true because a IS greater than b

--false because both expressions are

--not true

--true because at least one IS true

--false because b is NOT equal to 3

Chapter 48 Automating with MAXScript 1135

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.

1136 Part XII MAXScript and Plug-Ins

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.

Chapter 48 Automating with MAXScript 1137

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:

1138 Part XII MAXScript and Plug-Ins

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 will be 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

Chapter 48 Automating with MAXScript 1139

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:

1140 Part XII MAXScript and Plug-Ins

1.Open the Fish scene.max file from the Chap 48 directory on the CD-ROM.

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 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

)

)

4.Select the Camera01 viewport. Choose File 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 48-11 shows one frame of this animation.

Figure 48-11: First attempt at making the fish follow a path

Chapter 48 Automating with MAXScript 1141

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 key frames 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 key frames. 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 lines that are new 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

1142 Part XII MAXScript and Plug-Ins

--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 Save), and then press Ctrl+E to evaluate the script again. This script is saved in the Chap 48 directory as FishPath2.ms. Make the Camera01 viewport active, and click Play Animation. Figure 48-12 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.

Figure 48-12: A tail-flapping fish that faces the right direction as it follows the path

Chapter 48 Automating with MAXScript 1143

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. In order 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

1144 Part XII MAXScript and Plug-Ins

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 (what changed is 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 Evaluate All, or press Ctrl+E to run the script again, and then animate it. Figure 48-13 shows both fish swimming merrily.

This script generates key frames 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.

Figure 48-13: Both fish swimming together