MAX脚本翻译教学
要想使MAXScript 成为高效工作流程的一部分,我们得养成一些新习惯。在我们开始正式研究MAXScript 之前,我们先看看关于脚本编写的思路和步骤。本节内容就包含了许多这方面的信息和提示。
从通假代码开始
(通假代码的概念就像我们学习一句英语时要先看的那行译文,它用来写成我们自己能够理解的意图),通假代码并不属于任何一种程序语言,而是作为一套编写脚本的内在含义和指导思想。事实上通假代码往往比实际代码本身更加重要;因为我们如果很清楚编写脚本的原理和目的,那么剩下的工作仅仅是把这些翻译成MAXScript可以理解的语法而已。就像绘画,如果总体内容与轮廓线都已经勾勒出来,我们就只需要丰富它的细节了。(我把下文中所使用的通假代码都以红色斜体表示)
使用MAXScript Reference帮助手册
我不能说推荐各位使用MAXScript手册。但是每当我编写脚本时,我差不多时时刻刻都离不开它。这个手册其实就像一本外语词典,我得用它把我说出的通假代码翻译成MAXScript能够理解的标准语。
而实际上,那个帮助手册对于我们关于使用原理与脚本结构方面的问题帮助甚微,尽管在第7个版本中增加了许多出色的"how-to" 示例,然而那并不够……
此外,帮助手册还有个用途是我们可以从它上面看到哪些功能是有可能实现的而哪些不能……并不是3dsmax中的一切都可以实现脚本化,比如Loft——手册中关于放样建模那个章节写着“Loft compound objects are not constructible by MAXScript”(放样复合物体无法通过MAXScript来创建),看到了它,我们就不必绞尽脑汁做徒劳的思考和无谓的尝试了……
窃:)
当我们想不出怎么完成某个操作时,找找其他高手写好的功能类似的脚本并将其篡改为我们想要的……这对于编写脚本或程序是很正常的事,并且它可以节省我们重新思考组合大量代码的时间。偷窥他人的程序结构有时也能教我们学到某些已知问题的全新解法或者不同的脚本运行流程。同时,如果我们的脚本需要标注自己的版权,那么即使其中关键部分只有一小段别人的代码甚至哪怕是一两个函数,出于我们的大国之风、君子之度,也应该把人家的名字放在作者信息附近比较好:) 据我所知,干这一行的通常不会介意我们的移花接木,当然也不一定,你可以发邮件询问原作者的意思……说不定他还会热心地给你帮助和解答呢。推荐一个我喜欢的淘脚本的好地方: www.scriptspot.com
使用Macro Recorder 宏记录器,但不要依赖
在MAXScript 菜单中我们会可以找到Macro Recorder,它的功能是把我们的鼠标和键盘操作转化为脚本形式记录下来。比如我们想知道如何写出一个建立球体的脚本,可以先打开Macro Recorder,再用命令面板的建立球体按钮完成操作,之后就可以看到Macro Recorder所记录的类似这样一段脚本:
Sphere radius:6.57503 smooth:on segs:32 chop:0 slice:off sliceFrom:0 sliceTo:0 mapCoords:on recenter:off pos:[-2.50576,-0.672316,0] isSelected:on
(↑单行,很长——要想看到完整版,有必要按F11打开MAXScript Listener)
如果我们要在脚本中建立球体,那这个就是完成该任务的范例(先不必汗,宏记录下来的都是超级完整标准版,其实可以写成短小精悍型的)。要想使用这种记录后的脚本,一个简单的办法是把它们选中之后,用鼠标拖到屏幕上方的工具按钮——就出现了一个新按钮,每次按它的时候,都会给你在同样位置建立一个同样大小和参数的球……当然名称和颜色还是会有区分的。
提示:有些操作是Macro Recorder无法记录的,并且它也不总是会记录完全正确的脚本。有些操作虽然是可以写成脚本,但并不出现在Macro Recorder的记录中;还有的时候它会给你一些错误的信息,并且像上面的例子一样,它生成的代码总是包含大量不必要的默认参数。因此非必要时刻就不要依赖它。
为有价值的脚本进行通用化处理
在我们的工作中,经常有为了某种特定需要而开发专用软件的情况。假如一个动画师想使10个物体沿一个特殊物体的表面运动。对于编写脚本来说,我们可以写出只能专门控制这10个物体的运动脚本,这种方法会显得比较快因为我不必制作一个操作界面,并可以对脚本所控制的10个物体进行特定的省时省事的操作。这种做法的另一个含义就是——用完就扔,因为它只能解决这一个问题,而下次另一个任务中要使用类似的功能时,我还得重新写过……因此,我们有必要学习适时地为脚本进行通用化。
当我不能确定手头的脚本有没有必要再次使用时,我采取的办法是在其中加入许多注释说明,并将它保存到我脚本目录中一个名为“草稿”的目录。如果下次用得着,我就可以把它拿出来方便地修改和通用化,避免大规模重复性劳动的灾难。
为编写长脚本准备一个优秀的文字编辑软件
尽管我们可以用max内置的脚本编辑器(MAXScript菜单->New Script),然而这个编辑器有个先天不足:一旦你的脚本错误或者任何其他原因导致了max的崩溃……那它也就完了,写在里面的脚本会丢失,也可能带走了之前的部分工作成果。因此我更喜欢在一个独立的文字编辑软件中编写我的脚本,比如Windows系统的记事本或者我最爱的TextPad(www.textpad.com)。它有很多很酷的长处:它的排版更合理,语法高亮显示功能比max内置的更快更好,还可以同时在多个文挡之间进行搜索和替换,还有它可以同时打开多个文件同时查看与编辑……等等。
提示:然而内置的MAXScript 编辑器有一个功能是无可替代的,那就是在使用Visual MAXScript(VMXS) 工具创建界面时,VMXS只会把它生成的代码写进MAXScript 编辑器的窗口里面。在那之后我才可以把它们拷贝/粘贴到我的Textpad里面。
本书附带的DVD中包含了一个评估版的TextPad和它使用语法高亮功能所需的MaxScript.syn文件。注意它是一个商业软件,过期请注册。(虽然没有DVD,但我从电驴中直接搜索到了它的软件本身和注册文件,搜索关键词TaxtPad)
为MAXScript 工作设置TextPad
-
运行安装程序
-
将MaxScript.syn 复制到Program Files\TextPad 4\system目录下.如果DVD不在手边,可以去 www.textpad.com/add-ons/synh2m.html下载这个小文件。
-
打开TextPad,在菜单中选择Config/New Document Class.
-
在添加文档类型的向导中,输入MaxScript作为类型名,并点击Next.
-
输入 *.ms, *.mcr 为该类型的成员,Next.
-
勾选"Enable syntax highlighting"复选框,并从下拉菜单中选择MaxScript,点击Next。
-
在摘要信息中确认你的选择,并点击Finish完成配置。
玩转max的更多乐趣
——MAXScript中的十个(左右)常用词
MAXScript可以代替我们执行大量常规操作任务。只要你掌握了几个简单的命令,就可以把千万次的鼠标操作瞬间自动完成,甚至用一种全新的方式解决许多难题。写作本章的目的并非是作为脚本编写向导,而是为你提供几个工具来帮助你实现任务自动化,无须深入研究技术手册或者搜索网站。
我们与MAXScript进行交流的工具叫做“Listener”(可以从菜单MAXScript-> MAXScript Listener打开),它是一个输入窗口,给我们用来输入命令并立即执行,而MAXScript也在其中显示反馈信息。我们输入的命令行显示为黑色,而MAXScript的反馈显示为蓝色(表示输出值)和红色(表示错误信息)
让我们从这些词汇开始
$
show
move
rotate(eulerAngles x y z)
scale
in coordsys
for
where
classOf
random
#() 和 []
at
time
(知道为什么标题说是“10个左右”了吧?多出了两个不是词的,呵呵,顺便说一下本文中提到的全部符号都要用半角输入,也就是没开中文输入法的状态,比如句号不是圆圈而是点)
那现在,我们开始吧!!
美圆符号 $
$ 的意思表示我们当前所选择的物体。就目前来说,它在我们只选择一个物体的时候最好用,因为选择多个不同物体时的操作略有区别。我们用$来做什么呢?嗯,MAXScript的一个最简单的常用功能就是查看和修改物体们的属性。要查看查看所选物体的属性,用这样格式的命令“$.属性”,比如 $.radius 和 $.height 分别表示查看半径和高度。(radius和height这样的常用单词我们应该很熟悉了,在MAXScript中的参数全部都是基于英语的,我们所用的中文版只不过是界面汉化而已,因此如果对英文单词完全陌生的话在MAXScript里仍然是寸步难行,我想这也是官方中文版的MAXScript没有进行翻译的原因所在吧)——和3dsmax中的总体规则一样,属性也有它自己的层次结构,也就是说某个属性里面也会包含它自己的属性,层次之间用句点分隔,比如输入 $.material.diffusemap.coords.U_Tiling 就会查询到当前所选物体的漫反射贴图的U方向重复次数。 (那行代码可以翻译成这样的中文: $.材质.漫反射图.坐标.U重复 因此我们可以看出它的语序其实和汉语相同,从主到次,从大范围说到小目标……不知道开发MAXScript的人怎么想的,反正我觉得挺习惯:)
如果像上面那样只输入属性名称,那Listener只会告诉你该属性的数值。而如果想要修改它为一个新值,则使用 $.属性=数值 的格式,如$.length = 12.5。 而要修改为相对值,则可以用 += , -= , *= 和 /= 来表示在当前值之上进行加减乘除,比如 $.radius /= 6 表示将当前物体的半径属性除以6(如果它有radius属性的话)。
提示:对于Listener中的命令,输入后最好用小键盘的回车,因为小回车会命令MAXScript立即执行该行代码,而大回车在某些情况下会等待输入更多条件语句。
在等号后面使用的数值并不一定得是简单的数字。我们可以使用其他的属性值、数学表达式,或者在其他属性之上进行数学运算。
$还有另一个类似的用法:在它后面接物体的名称,比如输入 $Box01,就等于告诉MAXScript说我们要对名为Box01的物体进行操作,用这样的格式来套用上面所说的属性修改方法同样适用(比如 $box01.height)。此外,也可以和在Windows系统中一样使用通配符*和?,比如输入 $box*, 就等于指定了名称中以box开头的所有物体。(我们将在后续部分深入讨论对于多物体操作的知识。)
提示:如果物体名称中存在空格,那么在MAXScript中提到它们的时候就要在名称两端加上单引号,例如 $'my box'
“Show” 命令
我们怎样才能知道一个物体所具有的属性名称呢?从上面的例子可以看出不同的物体具有的属性名称可能远远超出了我们的记忆能力,这就是show命令的用武之地了。show可以在MAXScript Listener中显示一个物体的属性名称列表。例如选择一个box之后输入 show $,就可以看到Listener返回了这样的信息:
.height : float
.length : float
.lengthsegs : integer
.width :
float
.widthsegs : integer
.mapCoords : boolean
.heightsegs :
integer
false
冒号左边的单词是属性名,冒号右边的词指出该属性所需的数值类型(末尾的那个false是MAXScript在打嗝,我们就当没听见好了)。如果我们试图为某个属性赋予一个错误类型的值,比如 $Sphere01.radius = true,那MAXScript就会抱怨出一行的红字的错误信息。
提示:常用数值类型的名称含义
-
integer:整数
-
float:浮点数(即允许带小数)
-
string:字符串,数值外要带有双括号,例如 "hello world"
-
boolean:真\假 值,也用来表示打开和关闭,可以使用 true,false,on,off
-
point2:二维坐标,两个数字之间用逗号分隔,外加方括号,例如 [10,20]
-
point3:三维坐标,三个数字之间用逗号分割,外加方括号,例如 [10,22,133]
-
color:颜色RGB数值,在圆括号以 color开头,加三组数值分别表示RGB值,以空格分隔,例如纯白色为 (color 255 255 255)
必须指出的是并非物体的全部属性都会按show的命令显示出来,事实上为了使“搜索结果”清晰明了,所有物体以及特定类型的物体所共有的属性如 name, material,和renderable等等,都不会在这里显示出来。同时,物体被附加的修改器、材质等等的属性也不会显示。如果需要一份绝对完整详尽的属性列表,可以去MAXScript参考手册中的"General Node Properties"章节(埋藏很深,最好使用帮助系统的“索引”功能)此外还有Properties, Methods, Operators, and Literals 条目记载着关于物体类型的信息(瞧见了吧,我前面说离不开参考手册可不是开玩笑的).
当一个物体的属性中还有次级属性的时候,我们也可以通过show命令来查看次级属性的清单.例如要查看一个box的材质,就可以输入 show $Box01.material ,如果要进一步查看它的凹凸贴图属性列表,根据上个命令所返回的属性列表可以找到凹凸贴图的属性名为bumpMap,因此输入 $Box01.material.bumpmap ,以此类推。
用“双词模式”进行工作
让我们看看现在我们都可以做些什么了,每次只需使用 MAXScript中的两个词,就能做到许多以前做不到的事:
- 在场景中寻找并修改任意物体的任意属性,即使它被隐藏、冻结或者深藏在群组之中。
- 迅速设置任何物体的任何参数,包括使用表达式将一个物体的设置复制到另一个物体上。
- 通过在物体名中使用通配符( * 和 ? ),一次性设置一堆物体的某种属性。
两个词的作用就不错,是吧?现在根据你所掌握的词汇,看看能否分辨出下列单行脚本的作用:
$Box01.height = $Box02.height
$Box01.height = $Box01.length = $Box01.width = 10
$Sphere01.radius = $Box01.height/2
$Box*.height = 10
$Box*.isHidden = false
$Omni*.multiplier /= 2
$Box*.material = $Sphere01.material
$Spot*.on = false
用简体中文翻译过来是这样的:
将Box01的高度设置为与Box02相同
把Box01设置为边长为10的正方体
将Sphere01的半径设置为Box01高度的一半(换句话说,让它们一样高)
把场景中所有名称以Box开头的物体的高度值设置为10
取消所有名称以Box开头的物体的隐藏状态
把名称以Omni开头的所有灯光的亮度设置为原来的一半
把Sphere01的材质赋予所有名称以Box开头的物体
把名称以Spot开头的灯光关闭(这个例子的脚本不会正常运行,原因稍后详细解释,这里只是为了使你了解MAXScript可以自动操作这类任务)
"move," "rotate," "scale," 和 "in coordsys"
现在让我们再认识四个MAXScript所用的动词。对于前三个move、rotate 和scale ,都是我们已经非常熟悉的了(移动、旋转、缩放),我们只需稍微解释它们的用法就可以;而对于Coordsys我想你也可以猜个大概了(坐标系)。
对于 move 命令,我们要在赋予它的XYZ坐标值,这个坐标值需要三个数值,它们之间用逗号分隔,外面加上方括号(MAXScript把这种类型的数值叫做point3),如:
move $Box01 [10,0,0]
这一行的意思是将Box01沿X轴向移动10个单位。注意这是一个相对位移。如果要移动到指定的绝对坐标,则实际上是在修改该物体的位置属性 position ,按照前面谈到修改属性的方法:
$Box01.position = [10,0,0]
这样就将这个Box放在世界坐标的10,0,0位置上了。
下面说说rotate——旋转。旋转的数值要显得稍微复杂一点点,因为它要我们再顺便多学一个单词。为了避免使用极度恐怖的传统真3D旋转数学算法,MAXScript为我们准备了eulerAngles旋转值输入法;即X旋转和Y旋转再加上个Z旋转……和名称类似的那个 Euler XYZ旋转控制器一样,这种旋转算法存在某些灵活性和精确方面的问题,但它在易用性上的长处使它值得使用。
rotate $Box01 (eulerAngles 90 90 0)
以上脚本表示将物体沿X和Y轴各旋转90度。(注意这里也是相对旋转)
接下来就可以谈谈缩放了。scale像move一样需要赋予的是一个point3数值,需要注意的是100%的缩放值是用1来表示的,因此假如做一个50%的均匀缩放,则脚本如下:
scale $Box01 [.5,.5,.5]
(其中的.5就是0.5,在大多数电脑数字输入中,如果小数点前为0都可以省略,直接写点接小数)
现在物体的各种运动方式的做法我们都知道了,默认情况下它们都使用世界坐标系。如果想要使用局部坐标、上层物体坐标等其他坐标系统,就需要在我们的运动命令前面冠以 in coordSys 某坐标系,例如将前面第一个移动物体的例子改为:
in coordSys local move $Box01 [10,0,0]
这样就是把Box01在局部坐标Local上移动了10个单位了。
用"for"循环实现自动化
是我们了解 MAXScript中最重要的自动化工具的时候了——"for"循环,用法格式如下:
for 某些变量 in 被操作物体清单 do 要做的事情
换句话说,在被操作物体清单 所列出的每个物体上进行一项操作(或者是一系列的操作流程。要做的事情 可以是许多命令的结合)。这就是我们开头提到的“自动化操作任务”的实际用法。
for i in selection do (i.wirecolor = color 0 128 0)
在这个例子中,某些变量 被命名为i(变量名是随意的,只不过在循环计数中使用i是电脑程序编写的习惯而已),而被操作物体清单 在这里使用了关键字selection,你大概已经猜到它的意思是"当前所选物体集合",而所谓要做的事情是设置物体的属性为一个指定数值,在这里所做的是把物体的“网格色”(即默认无材质状态下的显示颜色)设置为绿色,如果把它翻译成简体中文就是这个样子:
for (每个物体) in (选择集) do (设置网格色为绿色)
这样看起来就很简单明了了吧?
需要了解的是,在上述循环里的每一次操作中,i 都是替操作对象物体“占位”用的,当循环进行中自动操作到每一个物体时,i 就把位置让给该物体名称 ,相当于用$或者$物体名 来替换。
上面的例子中使用了selection 来指定操作目标,而实际应用中有许多其他的情况,包括MAXScript预置关键字(objects, geometry, lights, cameras, helpers, shapes, systems, sceneMaterials, meditMaterials, 和spacewarps),带通配符的名称如($Box*),数字以及专门定义的物体列表称为arrays(梢后介绍)。现在我们有了for 这一利器,就可以开始真正进行自动化任务了:
for i in $*Omni* do (i.multiplier = i.multiplier*0.5)
上面的脚本会将名称中带有Omni字样的所有灯光亮度减半。
for i in selection do i.material = $Box01.material
这一行则是将所选择的所有物体赋予和Box01相同的材质
我们也可以在do 的后面将多个命令放在一起使用,只要在它们外面加上括号,命令之间用分号隔开就可以了:
for i in geometry do (i.motionblur = #image; i.motionBlurOn = ture)
这相当与将场景中全部的几何体打开运动模糊并将其模式设置为image方式。
提示:在使用MAXScript的 Macro Recorder即宏记录功能所得到的脚本中,它用来指代当前所选的物体的是$,常见的一种错误会出现于此,因为$ 是代表当前所选的单个物体,可是当选择多个物体时就应当使用selection 关键字,因此我们有必要把所有值得重复使用的脚本里面的$ 给清理掉。通常使用的方法就是把这段来自宏记录的脚本命令放到一个 for i in selection 循环中去(也就是do 之后的括号里),并且用i 来替换其中的所有 $ 。(这个注释提到的问题只有在使用宏记录遇到错误时才容易理解,现在可以不用深究,但它是i 的典型应用)
“where”与“classOf”
现在我们来尝试一个有问题的for 循环。写一个单行脚本来关闭场景中所有的灯光:
for i in lights do i.on = false
如果,在我们的场景中有任何一盏带有目标点的灯光物体,就会看到Listener中返回了类似这样的错误信息:
-- Error occurred in i loop
-- Frame:
-- i: $Target:Spot01.Target @ [-11.212518,-15.219044,0.000000]
-- Unknown property: "on" in $Target:Spot01.Target @ [-11.212518, -15.219044,0.000000]
这些信息是什么意思?让我们一行一行地翻译过来:
脚本中的 for 循环所使用的变量 i 发生问题。
如果这是一个动画中产生的问题,则其动画帧数为:
当问题产生时,i 在方括号所指坐标中为 Spot01.Target 进行操作。
问题本身是——在该物体中不存在可供设置的"on"属性。
换句话说,我们的问题是由于使用了表示灯光的关键字 lights ,这个概念中把灯光的目标点物体都包含在了操作对象以内,因此MAXScript抱怨说那家伙没有开关可以设置,并且因此停工(如果场景中有多盏灯光你就会发现有些灯光没有被关闭)。要想避免这种情况,我们就得在操作对象的集合中排除灯光的目标点,这也就是介绍本节 where 与classOf 的必要性所在了。
在一个for循环中,where 为我们提供了一个过滤开关,它可以在循环运行中的每一次操作之前对物体属性是否“合格”进行判断。如果判断为“是”,则进行指定的操作,否则就将该对象忽略掉。它可以简写为:
for i in selection where (false) do 操作
false即“假”,因此忽略操作该物体,而当下列情况时:
for i in selection where (true) do 操作
则对目标执行操作。
在实际使用中,我们要将where后面加上一个用于得出true/false 真/假结果的表达式,或者说取得Boolean类型的数值。在表达式中我们使用一些运算符来进行真假判断。关于两个数值的比较有许多种方法。见下列表格:
表格1 比较运算符
|
|||
运算符 |
含义 | 示例 | 输出值 |
== |
全等 |
1 == 2 |
false |
1 == 1 |
true |
||
!= |
不等 |
1 != 2 |
true |
1 != 1 |
false |
||
> |
大于 |
1 > 2 |
false |
2 > 1 |
true |
||
< |
小于 |
1 < 2 |
true |
2 < 1 |
false |
||
>= |
大于等于 |
1 >= 2 |
false |
1 >= 1 |
true |
||
2 >= 1 |
true |
||
<= |
小于等于 |
1 <= 2 |
true |
1 <= 1 |
true |
||
2 <= 1 |
false |
表格2 布尔(真)运算符
|
|||
运算符 |
真判断条件 |
示例 |
示例输出 |
and |
双方俱为“真” |
false and false |
false |
true and false |
false |
||
true and true |
true |
||
or |
其中一方为“真” |
false or false |
false |
(右边带大写的TRue表示强调) |
true or false |
TRue |
|
true or true |
true |
||
not |
对条件进行反判断(是非颠倒) |
not true |
false |
not false |
TRue |
我们来做几个试验:
for i in $Box* where (i.height > 10) do i.height = 10
↑找出名称以Box开头并且高度大于10的所有物体,将其高度设置为10
for i in $Box* where ((i.height > 9) and (i.height < 11)) do
i.height = 10
↑找出名称以Box开头并且高度在9和11之间的物体,将它们的高度设置为10
for i in geometry where (i.material == undefined) do i.isHidden =
false
↑将所有未赋予材质的几何体取消隐藏状态
for i in shapes where (i.baseobject.renderable == ture) do i.name =
"RS_" + i.name
↑将所有可渲染样条曲线的名称前加上“RS_”前缀(以便于按名称选择)
(上述例子有必要实际操作一下,并且可以尝试自己修改运算符和判断条件,结果很有趣的)
提示:上面最后一个例子提到,样条曲线Spline物体的 renderable 属性要使用 object.baseobject.renderable 进行访问和修改(还有必要提示一下实际应用时第一个object要换成物体名称或者代号i什么的),同时它也是所有物体的共有属性。同样常用的一个类似属性是灯光物体的 castShadows 属性。
好了,现在回顾一下我们本节开始的脚本错误问题。我们得从(同属于灯的)众多的操作对象中区分它们的类别,因此在where 功能句里面要使用一个关于判断类别的关键词:classOf
max中的每一个物体都从属与某一Class类别(想象一下人们的星座或者生肖),每一类别的成员都有一些共同的参数并按特定的方式行动。box 们属于一种类别 ,sphere们也是,它们都与 editable mesh类别有着显著的区别。现在再请出我们可爱的Box01来表演一下:
classOf $Box01
MAXScript返回值:
Box
和show的功能类似,它可以作为一种参考工具来用,只是前者用来查询属性名,后者用来查询类别名而已。而它与show的最大不同在于它是可以用于表达式中做真假判断运算的:
classOf $Box01 == Box
MAXScript返回true。要想把这个判断句用于where句法,只需为它加上圆括号:
for i in geometry where (classOf i == Box) do i.height *=2
↑将所有Box类别的物体高度翻倍,无论它们的名字叫什么。
在使用classOf 修理我们的灯光脚本之前,还有一件事得注意。如果,我们的box被加上了一个修改器,比如Bend弯曲,那么:
classOf $Box01
返回值就成了 Editable_mesh而不是Box了。因为默认情况下所查询到的是物体当前的状态,而不是它的原始类别(除非我们将修改器关闭)。如果需要忽略修改结果查询物体的原始类别,这样做:
classOf $Box01.baseobject
此时(相当于查询物体所具有的baseobject属性)MAXScript就会着眼于修改堆栈的最底端的物体状态返回所需的值了。
灯光的关闭方法正解
有了where 和 classOf,现在是时候解决我们前面的关灯脚本问题了。当时我们所写的脚本是:
for i in lights do i.on = false
它的操作结果是遇到没有on属性的目标点物体而失败中止。要纠正这一错误,我们要做的是把指定操作对象的命令改为忽略目标点物体的操作。这项技术可是批处理灯光和摄象机的必修课!
- 重置(Reset) 3ds max
- 在场景中随意建立几盏灯光,至少包含一个带有目标点的灯(当然最好也弄个茶壶什么的照照……这样开的关的看得更明显一点)
- 在视图中随意选择一个灯光目标点物体,在Listener中输入 ClassOf $ 回车,可以看到max返回的类别名为 Targetobject。
- 这样我们就知道了它的类别名,让我们通过判断运算确认一下:
classOf $ == Targetobject - 看到max返回了true,现在我们就可以确定在where后面填写什么来过滤目标点物体了。
- 在Listener中输入:
for i in lights where (classOf i != targetObject) do i.on = false
回车,可以看到全部的灯光关闭成功,MAXScript毫无怨言了。
这样一来,for 循环脚本在我们场景管理中的真正实力就完全显现出来了。根据已经掌握的技术,我们可以将修改成千上万指定物体属性的任务瞬间完成。下面我来介绍一下我的工作中高度频繁使用的简单操作语句:
for i in cameras where (classOf i != targetObject) do i.mpassenabled = ture
↑同时打开所有摄象机的多过程处理效果
for i in geometry where (i.material == $Box01.material) do i.isHidden = false
↑把与Box01材质相同的所有物体取消隐藏,这一脚本有效解决了材质编辑器的按材质选择功能无法操作隐藏物体的问题。
for i in geometry where i.material == undefined do print i.name
↑把所有尚未赋予材质的物体名显示出来
for i in lights where (classOf i != targetobject) do i.multiplier *= 1.05
↑把场景中全部灯光提亮5%(1.05=105%)
for i in lights where (classOf i == targetSpot) do (i.showCone = i.showNearAtten = i.showFarAtten = off)
↑关闭所有目标聚光灯的光锥与照明范围显示
for i in shapes where (i.baseobject.renderable == true) do (i.isHidden = false;selectMore i)
↑将所有可渲染样条曲线取消隐藏(即使它们被附加了修改器)
毫无疑问,有了这些脚本,我们手头的麻烦事要少得多了。而要想写出更加个性化的实用脚本,根据自己的需要从使用show命令开始吧。
“random“ 随机数
要制作出高品质的3D图象,我们通常要避免那种趋向于完美规律化的计算机风格。一个十分规则、整齐得理想化的场景会破坏图象的真实感,为避免这种情况,我们有很多有效的技术都来制造某些将场景“搞乱”的因素。可是靠手动操作去安排一切总是不够随机化(当然费力是更实在的)。如果你使用过粒子系统,你一定知道为它们添加随机性更是产生可靠效果的关键因素。而在MAXScript命令中的 random 就是一个“制造混乱”的神奇的工具(当然它是需要小心控制的)。用法如下:
random 数值1 数值2
↑其中的两个数值应当是同一类型,它们用于指定所生成随机数值的下限和上限。
现在来用我们已有的菜鸟级MAXScript词汇,来为一个场景添加一点真实度吧。
使用"for" 和"random"为场景添加无序性
1. 打开附带的文件cafe_start.max(在这里下载) 这是一个包含一些桌椅的场景,排列得非常整齐而死板。
提示:这个场景的一个视图被设置为Listener了。要想在你自己的场景中这样玩,右键点击视图标签(也就是Top或者Left什么的)并在菜单中选择Views ->Extended -> MAXScript Listener。
2. 渲染摄象机视图,再去Render菜单下打开max内置的 RAM Player ,使用左边属于Channel A 的茶壶按钮将渲染结果载入。我们接下来操作的效果比较细微,我们可以通过这种方法更精确地对比前后的变化。
for i in $*Table* do rotate i (eulerAngles 0 0 (random -180 180))
↑把所有名字里包含关键字table的物体,随便沿Z轴旋转一个角度。
下面我们来分析一下该句中的最后一部分。MAXScript将每一行代码都视为一个表达式。这意味着每行代码的结尾都会运算出一个数值,同时也表明行内任何一个部分都可以是独立的表达式,只要它可以正确地运算得出某种数值,就可以像直接将一个简单数值放在那里一样使用。
而当MAXScript得到一个random时,它就会简单地将一个数值结果放进代码中对应的位置上。圆括号的作用在于指出数值间的运算顺序,就如同2+3*2=8而(2+3)*2=10一样。
现在让我们把每组桌椅稍微移动一下。为了使椅子们保持与对应桌子之间的相对关系,应该将它们一起移动。这可以直接通过脚本实现,只是要用上更多的词汇与好长的一堆代码,然而我们目前的练习是旨在节省时间而且简单易行的,因此可以多点几下鼠标来代替大规模输入代码的麻烦。
4.用我们熟悉的选择与连接工具,将每组椅子连接到它们对应的桌子上。
5.连接好之后,在Listener中输入:
for i in $*Table* do move i [(random 5 5),(random 5 5),0]
这样桌子们的排列就不那么惊人地整齐了。在上一行代码中,我们使用了random表达式来指定了随机的X和Y坐标(因为我们不需要桌子离开地面,所以Z坐标保持为0。)
有一个值得探讨的问题是我们为何不直接使用这样一个简单些的方法:
move $*Table* [(random 5 5),(random 5 5),0]
如果使用上面这段代码,那么random表达式实际上只为每个X和Y运算了一次,因此给予每个桌子的移动坐标完全相同,而像前面那样在for循环中使用random,则会在每次运算中生成一次随机数值,因此桌子们各自移动的幅度才会有所区别。不过当然有些时候我们也可能需要后一种方式,所以只需记住它们的不同,根据实际需要选择应用就可以了。(说实话,我觉得那个地砖效果对画面干扰太严重,实际上用第一种方法移动的时候也会让人产生所有桌子同步移动的感觉,因为它们都同时朝相似的方向移动了难以看出差别的一点点,但实际上的确是不同的,不信的话可以把5改成50什么的再回车:)
接下来轮到椅子们了。摆弄它们的位置需要我们多一点思考过程。大体来讲,它们在离桌子远近距离上的差别应当比其他方向的位移更明显,而现在的情况是每四个椅子围绕着一个桌子,这时要想实现上述效果,我们就需要在每个椅子的局部坐标上来进行move了。
6.前边我们谈到过,把in coordSys local 放到一个物体运动命令前面就可以使其在局部坐标而不是默认的世界坐标上运动,因此我们输入下面这样的代码:
for i in $*chair* do in coordSys local move i [(random -6 3), (random -1 1),0]
这样就成功地实现了我们所需的随机排列效果。椅子局部上的X轴就在它们各自朝向桌子的方向上,因此我们将它的随机程度设置得较高,而在Y轴方向上,一个较小的侧向偏移也就可以了,Z轴和前面一样,保持为0。
7.关于摆弄椅子们的最后一件事和我们最开始对桌子的操作一样,已经心中有数了吧:
for i in $*chair* do rotate i (eulerAngles 0 0 (random -5 5))
8.好了,渲染视图,并将结果装载到RAM Player的B通道里面。
这个场景的最终效果可以在DVD上找到,cafe_01.max。如果你想进一步改善这一场景,可以用类似的方法移动桌子,或者旋转和缩放一下桌子上的花等等。(没找到,应该是要自己动手栽吧?)
现在我们是不是有如同方便地使用瑞士军刀似的快感呢?这才只是用了MAXScript宏大词汇库中的几个呢!
#() 和 [] 与 数列的用法
数列Array就是一个系列的数据组合。数列的表示方法是把许多数据放进一个圆括号里,数据之间用逗号分隔,括号的前面加上#号,例如:
myArray = #($Box01,$Box02,$Box03)
上面代码的作用就是定义一个名叫myArray的数列,该数列的内容即为三个box的集合。而当我们需要在其他地方使用这些box时,就可以用这一个数列来代替它们。此外当我们需要单独指定一个数列中的某个对象时,就在数列名后面加上一个方括号并在其中夹进对象的序号,比如在定义了刚才的序列之后输入:
myArray[2]
我们就得到了类似下面的返回值:
$Box:Box02 @ [6.8000152,13.702519,0.000000]
↑即数列myArray中的第二个对象的名称及其所在位置
还有,输入:
myArray[2].height
就相当于访问Box02的height 参数。
在MAXScript中,很多内置的数据结构被组织为数列的形式,并可以在一个for循环的运算中通过指定序号来访问它们。通常我们并不对数列本身进行操作,而是通过for循环在一系列的序号之中进行操作,例如:
for i in 1 to 3 do myArray[i].height = i*10
↑在这行代码中,我们分别在数列序号和目标的高度设置中使用了两个i,因为在每次运算中i的数值增加1,于是Box01的高度被设置为10,而Box02则设置为20,Box03即为30。换句话说——我们用简单的两行代码写出了一个楼梯!
在MAXScript内置的 meditMaterials数列中,我们可以使用这种技术以random功能为一组物体赋予随机的材质。假设有个场景中需要12个人举起不同的牌子。我们就可以在材质编辑器中的前12个格子中放入不同的牌子材质,选择这些牌子,输入下面的代码:
for i in selection do i.material = meditMaterials[(random 1 12)]
由于(random 1 12)的运算结果是一个数字, 那么在循环中每次运算都会产生一个随机的数字作为被指定的序号,也就是把材质编辑器中那前12个材质随机分配给所选择的每个牌子了。(然而原文中并没有提到different一词,也就是实际上通常会发生几个牌子共用同一材质,而有的材质没有被使用的情况,另外原文中所用的物体是planes,我难以通过上下文确定它究竟指的是飞机还是平面,但只要用法正确就可以了,物体本身倒是无关紧要的)
同时,一个物体的修改器堆栈也是以数列形式来访问的,modifiers[1]表示堆栈最顶部的修改器。当你不清楚一个数列到底有多长时,可以用数列里的count属性来查询到里面数据的数量,比如下列用法:
for i in 1 to $.modifiers.count do $.modifiers[i].Enabled = true
↑将所选物体的所有修改器设置为开启状态。
当我们处理多个物体的修改堆栈时,就有必要将代码分成多行来写(实际上可以一行写完,但过长的单行代码会很难于阅读和除错)。要想输入多行脚本,我们就得打开MAXScript Editor (MAXScript菜单->New Script)而不是Listener了。输入后需要执行该代码时,则使用窗口菜单中的 File -> Evaluate All命令。 多行代码一般写做这样的格式:
for i in geometry where (i.modifiers.count > 0) do ( for j in i.modifiers where ((classOf j == meshsmooth) or (classOf j == TurboSmooth)) do ( j.useRenderIterations = true j.renderIterations = 3 j.iterations = 1 ) )
↑(看起来挺复杂是吧?如果把它们写成紧密的单行不是更可怕吗)用独立的圆括号和回车断行是为了将代码结构从视觉上分隔开,以便清晰易读。最上面的一个for 循环正是我们一直使用的for i in selection do 操作.而这里面的操作本身又是一个for循环,它使用了字母j而不是i作为变量名,因为i已经被前一个循环中使用了,因此我们必须换一个名称以避免产生冲突。
上面这段脚本写成通假代码是这样的意思:
for 所有 geometry 带有至少 1 个修改器 do
(
for 此物体的每个是meshsmooth 或
turbosmooth的修改器 do
(
打开该modifier的渲染插值参数
设置 render
iterations为3
设置 view iterations为1
)
)
变量j在循环中为前面i所指定的每个物体的修改器代入数值j。之后它对该修改器是否MeshSmooth或者TurboSmooth进行检查判断,如果它是,就进行do后面指定的操作内容:勾选Render Iterations 的复选框,设置渲染插值为3,视图插值为1。
注意脚本后面的结束圆括号是两个,为了保证脚本的正确运行,所有圆括号都要对称使用,而上例中将括号缩进的方法就可以帮助我们更清晰地看出圆括号是否已经正确关闭了。
提示:在MAXScript Editor中,按Ctrl+B 可以选择光标附近(方或圆)括号里所有的字符。而在TextPad中按Ctrl+M则会把光标移动到最近的括号。这是一种修正未关闭括号问题的有力工具。
用"at time" 制作动画
好极了!在我们开头提到的必备词汇表中,这里是最后一个!使用at time ,我们就可以用前面学过的修改静态参数一样的方法来设置动画了。它的用法是:
at time 帧数
↑之后接任何一段MAXScript脚本。如果你的AutoKey自动关键帧按钮已经打开,这行代码就会在所指定的帧上设置一个关键帧。
提示:使用at time 代码相当于用鼠标将时间滑块拖动到指定帧并执行操作。因此如果界面中的Auto Key按钮并没有开启,就不会生成对应的关键帧。
在下面的例子中,我们将前面学过的脚本语言之上施展at time的法术,将20个物体飞速地移动起来。当我们要做的动画无需十分严谨的时候(例如作为背景的物体或一次性大规模的物体操作),用MAXScript来做动画就能够节省大量的时间。
用"at time"附魔:闹鬼咖啡屋
1. 打开附带DVD中的 cafe_haunted_start.max。这和前面用过的是同一个咖啡屋,只不过稍微有点变化,出鬼了!
提示:如果你渲染场景,所看到的光晕是在render effect渲染效果里面设置的。
2.下面的脚本会使咖啡屋里的桌椅诡异地漂浮悬空,但现在还不是设置动画的时候。从菜单里选择MAXScript->Open Script,打开DVD上的 Haunted_furniture.ms,或者把下列代码拷贝到MAXScript Editor窗口中:
for i in $*table* do ( for j in 1 to 3 do ( at time ((j*30) + random -10 10) i.position.z = (random 5 30) at time ((j*30) + random -10 10) rotate i (eulerangles (random -2 2) (random -2 2) (random -30 30)) ) )
for i in $*chair* do ( for j in 1 to 3 do ( at time ((j*30) + random -10 10) in coordsys local move i [(random -2 2), (random -2 2), (random -6 6)] at time ((j*30) + random -10 10) rotate i (eulerangles (random -2 2) (random -2 2) (random -5 5)) ) )
3.打开 Auto Key 自动关键帧按钮(事实上可以用脚本实现,但目前先手动好了)
4.执行上述脚本,即在MAXScript Editor的菜单中选择File->Evaluate All或者按Ctrl+E。
5.关闭Auto Key,再播放动画就可以看到效果了。如果不是很满意,可以Undo两次并重新运行脚本以获得一个不同的随机效果。
事实上,这个脚本只是我们前面的移动桌椅练习的延伸(又嵌入了for循环和随机数功能),不同的只是(增加了z轴运动)拓展到了三维空间与时间的变化。刚才的脚本用通假代码可以这样理解:
for 所有名称中包含"table"的物体 ( at time 大约30,60,90帧 设置关键帧:Z轴位置为5到30的随机值 at time 大约30,60,90帧 设置关键帧:X,Y轴旋转2度以内,Z轴旋转30度以内 ) for 所有名称中包含"chaire"的物体 ( at time 大约30,60,90帧 设置关键帧:在局部坐标XY轴上移动最多2单位,Z轴运动最多6单位 at time 大约30,60,90帧 设置关键帧:在局部坐标XY轴上旋转最多2度,Z轴旋转最多5度
显然,对于具体数值的选择可以依个人审美观点而不同。上面的只是脚本程序结构的演示,可以非常灵活地修改。
还要记住的是,与粒子动画和修改器驱动动画所不同的是,这种动画方式可以作为手动动画的基础操作——如果我们对上面四个桌子运动中的三个满意而不喜欢最后一个的动作,可以直接进行手动修改。尽管如此,我们如果没有这一脚本的话需要耗费的手工操作要多得多,因此即使脚本操作的结果不十分完美,我们仍然节省了许多手工操作。
仅仅使用MAXScript中的几个词,我们已经可以批量处理大量物体、设置场景随机化、甚至生成动画!即使我们不再深入研究更多脚本知识,现有的几手已经等于掌握许多灵活而强大的工具了。