当前位置:首页>软件教程>maya教程>教程内容

mel语初解之一-基础和界面篇(5)

来源: 作者:admin 学习:20369人次

本文主要是针对初学Maya的人,如果你已经学了一个月以上的Maya,那你就可以学习这篇mel教程了。

我来写一点mel扫盲教程,主要是针对初学Maya的人,如果你已经学了一个月以上的Maya,那你就可以学习这篇mel教程了。不一定要等到Maya掌握的非常熟练才去学mel,相反,如果你在初学Maya的时候就对mel有所了解,那对于将来更好的了解和掌握Maya会起到事半功倍的效果。

当然首先要提到的是mel的基本概念,如下:
mel是Maya Embedded Language(Maya内置语言)的缩写,是一种强大的命令和脚本语言,让你直接控制Maya的特征、进程和工作流程。

其次要提到的是应该抱怎样的态度:
有许多人认为mel比较难,我的看法是并不比游戏攻关难,在网上经常看到有人为攻一关花掉一个月时间,学mel如果能达到这个境界一定会很了不起。学mel的确可能会枯躁一些,因为找不到像CS那么多人给你去杀。
mel是个可大可小的东西,如果你不是搞程序开发的,应该尽可能的让mel给你节省时间,而不要在学习和编写mel程序上花太多时间,应尽量编写简单且实用的程序。我的教程也是针对这一点写的,如果你已经做好了准备,就和我一起进入mel的世界吧。

今天要讲的是命令组合。
打开Maya,在命令行(Command Line)输入"cone"并按回车,视图中创建了一个圆锥。如果你没有看到命令行,用Display->UI Elements->Command Line来显示它。你也许会问用命令行创建圆锥跟用菜单创建有什么差别。我来告诉你,只是形式上的差别,本质上菜单和命令行都是在执行mel命令,可以说你在Maya中的一举一动都是在执行mel命令。

现在打开mel编辑器(Script Editor),mel编辑器分两部分,上面是历史窗,下面是编辑窗。历史窗显示你刚刚执行的命令和命令的结果或错误信息。编辑窗可以输入命令,而且可以输入多行命令,但每个MEL 命令之后必须以分号结束。选择mel编辑器的菜单Edit->Clear History,把历史窗清洁一下。

在编辑窗输入"cone -ax 0 1 0 -r 1;"并按Ctrl+Enter或数字键那边的Enter,视图中创建了正立的一个圆锥。再看历史窗,多了两行,第一行是刚刚执行过的命令,第二行是命令的返回值(Return Result)。我明天会着重讲返回值,今天先来解释一下标志(Flag)。刚才那个"-ax"和"-r"就是"cone"命令的标志,一般一个标志分长短两种写法,比如"-r"和"-radius"就是同一个标志,写哪个都行。长标志看起来比较清楚,而短标志写起来比较方便。因此,对于你比较熟悉的标志,尽量用短标志,不熟悉的就用长标志。改一下"cone"命令"-r"后面的数值,比如改成"cone -ax 0 1 0 -r 3"再执行,发现创建出的圆锥半径变大了,由此你可知道-r/-radius是控制圆锥半径的。

当然"cone"命令还有很多标志,要深入了解这个命令,最好的办法是看帮助文档,Maya为此专门提供了一个mel命令(就是help命令)来快速察看帮助。执行"help -doc cone",就可以打开cone命令的帮助文档。要想看到所有mel命令的帮助,选择Maya菜单Help->MEL Command Reference... Maya的帮助文档中一共收录了将近一千条mel命令,听起来很恐怖吧。不过没关系,要想在恶劣的环境下自在的生存,首先要学会对困难视而不见。看帮助时按分类(Categories)去看,只要翻翻你感兴趣的几个分类就行了,如果你都不感兴趣,就都不要看了,等用到了再去翻也不迟。

现在摆在你面前的一个严峻的问题就是你怎样从这近一千条mel命令找到你想要的那条命令。别怕,你虽然不熟悉mel,但你还不至于不熟悉Maya的菜单吧。选择Create->NURBS Primitives->Cone来创建一个圆锥,这时你会发现mel历史窗中多了一行命令,其中"cone -p 0 0 0 -ax 0 1 0 -ssw 0 -esw 360 -r 1 -hr 2 -d 3 -ut 0 -tol 0.01 -s 8 -nsp 1 -ch 1;"就是一个完整的创建圆锥的命令。在命令行执行一下这句命令,会发现可以得到跟菜单相同的结果。有了这种方法,就不用为你掌握的mel命令太少而犯愁了。

你也许已经意识到得到每一步操作的命令意味着什么了,就是可以把这些命令写在一起,然后一下子执行,就像Office里的宏和PhotoShop里的Actions一样。好,那就开始吧。

现在Maya虽然已经支持默认灯光渲染了,不过我们建模时经常还是会打一盏灯来观察一下效果。具体过程是先创建灯光,再把它移动到一个合适的位置,然后旋转一下角度,然后还要调整亮度。如果你经常要打灯光观察模型,你一定希望点一个按钮来完成这一切,我们的实例1就来解决这个问题吧。
------------------------------------------------------
实例1:摆放灯光
首先清洁一下mel历史窗准备记录命令。
选择Create->Lights->Directional Light创建一盏直射灯,把直射灯往左后方移动一下,再旋转一下使它的方向指向视图中心,Ctrl+a打开属性编辑器,把灯光的属性(Intensity)改为1.2。
这时再看看mel历史窗,多了类似如下几行:
defaultDirectionalLight(1, 1,1,1, "0", 0,0,0);
move -r -5.393374 -0.124913 3.360977 ;
rotate -r -os -33.086209 0 0 ;
rotate -r -os 0 -24.179269 0 ;
setAttr "directionalLightShape1.intensity" 1.2;

把这几行字都选中,用鼠标左键拖放到Shelf工具条上,会发现Shelf上多了一个mel图标。新建一个文件,按一下这个mel图标,你会发现你成功了。

一般用户编写的常用的mel程序都是放在Shelf里的,因此你需要对Shelf了解一下。鼠标左键在Shelf左下角的三角形那里按下,在下拉菜单选择Shelf Editor...打开Shelf编辑器。
关于Shelf编辑器的我就不多讲了,你自己瞎按一会儿也会弄明白的。我要说的是既然你已经开始学mel了,要那么多乱七八糟的Shelf也没什么用,你可以只留一个General,其它的都删掉,然后新建一个Shelf,名为myTools。注意我所说的这些操作都是在Shelf编辑器中进行的,不要跑到"你的文档\maya\5.0\prefs\shelves"那里去删Shelf,否则再启动Maya时那些Shelf会死而复生的。更改Shelf图标也在Shelf编辑器中,你可以把那个难看的mel图标换成你喜欢的美女。看看好莱坞人用的Maya工作界面,都是自己定制的Shelf,我们别的地方不如人家,这一点还是可以跟人家很像的。

现在来解释一下刚才的几句命令。凭你对英文的理解,其实也能猜到这几句的意思吧。

"defaultDirectionalLight"就是创建一盏直射灯。
"move"就是移动,"-r/-relative"是经常用到的,意思是相对于物体当前的位置。
例如"move -r 1 0 0;"是说把物体沿X轴移动一个格子,而"move 1 0 0;"是说把物体的"translateX"属性设为1,"translateY"和"translateY"属性设为0。
"rotate"用了两次,就是旋转了两次,看一下右边通道框里的旋转数值,你可以把这两句并作一句,就是"rotate -35.534708 -20.070585 13.771703;"。"-os"是指物体坐标轴,相对"-ws"于世界坐标轴而言的。要想让物体沿世界坐标轴旋转,双击旋转工具图标打开工具设置窗,把Rotate Mode由Local改为Global即可。
"setAttr"是设置属性,这可是一个关键性的命令,需要重视起来。

对于属性你一定不会陌生的,它是设动画的关键。属性可以在属性编辑器(Attribute Editor)和通道框(Channel Box)中修改,也可以用mel的"setAttr"直接修改。物体属性的写法是物体名+"."+属性名(object.attribute),属性名同mel标志一样有长短两种写法。选择通道的菜单Channels->Channel Names->...可以切换长短名的显示,平常看到的那个"Nice"名只是为了好看,在mel和表达式中都不中用。
了解属性对于我以后要讲到的表达式有决定性的意义,表达式就是通过对物体的属性与时间之间、属性与属性之间建立某种运算关系来控制动画的。

根据前面的说法,"move 1 0 0 nurbsCone1;"同样可以写成:
setAttr "nurbsCone1.translateX" 1;
setAttr "nurbsCone1.translateY" 0;
setAttr "nurbsCone1.translateZ" 0;
或:
setAttr "nurbsCone1.tx" 1;
setAttr "nurbsCone1.ty" 0;
setAttr "nurbsCone1.tz" 0;

最后让我们来编一个比较实用的mel命令组合吧。
------------------------------------------------------
多边形人头建模时,一般是先建一半脸,建好后再镜像,再圆滑一下。
你建一半脸的时候,很难想像出镜像圆滑后整张脸的样子吧。你现在学会了mel命令组合,可以实现点一个按钮来完成镜像和圆滑了,这样你可以在建模的过程中预览一下完成的效果。这样有用的程序后面还要继续完善下去,先给它起个形象的名字叫myFullMoon(月圆),版本订为1.0。
------------------------------------------------------

实例2:myFullMoon1.0
打开你的半边脸文件,为了在操作过程中减少不必要的麻烦,我们先来把模型规范化一下:
如果你的模型是右半脸,把它改成左半脸。要保证你能在前视图看到模型的正面脸,侧视图看到侧面脸,顶视图看到头顶。把模型放到视图中心线的左边。选中模型,选择Modify->Center Pivot,选择Modify->Freeze Transformations,选择Display->Component Display->Backfaces不显示背面,如图,记住你的模型宽度是几个格子。别忘了把mel历史窗打扫干净。
规范好了,就存一下盘。

选中模型,现在可以开始记录命令了。选择菜单Edit Polygons->Extrude Face。mel历史窗记录了四行命令,你要知道的是只有一行是对你有用的。想知道为什么?你把每行命令单独试试就知道了,只有
polyExtrudeFacet -ch 1 -kft 0 -pvx -3.926295877 -pvy -1.118658066 -pvz 4.649739981 -tx 0 -ty 0 -tz 0 -rx 0 -ry 0 -rz 0 -sx 1 -sy 1 -sz 1 -ran 0 -divisions 1 -off 0 -ltz 0 -ws 0 -ltx 0 -lty 0 -lrx 0 -lry 0 -lrz 0 -lsx 1 -lsy 1 -lsz 1 -ldx 1 -ldy 0 -ldz 0 -w 0 -gx 0 -gy -1 -gz 0 -att 0 -mx 0 -my 0 -mz 0 polySurface1.f[0:682];
这句可以挤出面。

现在要在通道框里改几个属性,Keep Face Tog属性改为on,Scale X为-1,Translate X为模型宽度的负值(我的是-7.5)。
历史窗多了三行有用的命令:
setAttr "polyExtrudeFace1.keepFacesTogether" on;
setAttr "polyExtrudeFace1.translateX" -7.5;
setAttr "polyExtrudeFace1.scaleX" -1;

现在你可以看出来模型面的法线反了,好,把它正过来。按F8回到物体选择模式,选择Edit Polygons->Normals->Reverse。
这回你该从这几行命令中看出有用的命令了: polyNormal -normalMode 0 -ch 1 polySurface1.f[0:1449];

下一步是Polygons->Smooth,找到这句命令: polySmooth -mth 0 -dv 1 -c 1 -kb 1 -ksb 1 -khe 0 -kt 1 -sl 1 -dpe 1 -ps 0.1 -ro 1 -ch 1 polySurface1.f[0:1449];

最后是取消模型的选择: select -cl ;

好了,操作结束。来整理一下有用的命令吧,结果如下:
polyExtrudeFacet -ch 1 -kft 0 -pvx -3.926295877 -pvy -1.118658066 -pvz 4.649739981 -tx 0 -ty 0 -tz 0 -rx 0 -ry 0 -rz 0 -sx 1 -sy 1 -sz 1 -ran 0 -divisions 1 -off 0 -ltz 0 -ws 0 -ltx 0 -lty 0 -lrx 0 -lry 0 -lrz 0 -lsx 1 -lsy 1 -lsz 1 -ldx 1 -ldy 0 -ldz 0 -w 0 -gx 0 -gy -1 -gz 0 -att 0 -mx 0 -my 0 -mz 0 polySurface1.f[0:682];
setAttr "polyExtrudeFace1.keepFacesTogether" on;
setAttr "polyExtrudeFace1.translateX" -7.5;
setAttr "polyExtrudeFace1.scaleX" -1;
polyNormal -normalMode 0 -ch 1 polySurface1.f[0:1449];
polySmooth -mth 0 -dv 1 -c 1 -kb 1 -ksb 1 -khe 0 -kt 1 -sl 1 -dpe 1 -ps 0.1 -ro 1 -ch 1 polySurface1.f[0:1449];
select -cl ;

把这几行命令都选中,用鼠标左键拖放到Shelf工具条上,Shelf上出现mel图标。重新载入模型文件,按一下这个mel图标,你又一次成功了。

对Maya熟悉一点的人会觉得那三个setAttr用的很愚蠢,因为完全可以在polyExtrudeFacet命令的标志中解决问题。其实我们的第一步只是要求做到,第二步才是要求做好。以下是简化过的结果:
polyExtrudeFacet -ch 1 -kft 1 -pvx -3.926295877 -pvy -1.118658066 -pvz 4.649739981 -translateX -7.5 -ty 0 -tz 0 -rx 0 -ry 0 -rz 0 -scaleX -1 -sy 1 -sz 1 -ran 0 -divisions 1 -off 0 -ltz 0 -ws 0 -ltx 0 -lty 0 -lrx 0 -lry 0 -lrz 0 -lsx 1 -lsy 1 -lsz 1 -ldx 1 -ldy 0 -ldz 0 -w 0 -gx 0 -gy -1 -gz 0 -att 0 -mx 0 -my 0 -mz 0 polySurface1.f[0:682];
polyNormal -normalMode 0 -ch 1 polySurface1.f[0:1449];
polySmooth -mth 0 -dv 1 -c 1 -kb 1 -ksb 1 -khe 0 -kt 1 -sl 1 -dpe 1 -ps 0.1 -ro 1 -ch 1 polySurface1.f[0:1449];
select -cl ;

关于使用mel命令组合的方法,最后要提到一点,如果你执行命令时发觉mel历史窗没有变化,可以在mel编辑器勾选Script->Echo All Commands。一般我们不需要显示那么多命令,因为很多对我们没用,所以不选Echo All Commands。

通过myFullMoon1.0我们可以把一半脸变成整张脸,但如果你发现这张脸不够美观想回到半脸状态修改怎么办?我告诉你,按z键撤消上一步操作。这应该不是你不知道的,但你可能因旋转相机观察而已经操作了很多步无法撤回了,还存了盘。

如果你没删构造历史,那你就是幸运的。介绍一个命令--"delete",就是你按键盘delete键所执行的命令。执行"delete polySurface1",瞧,把你那个整张脸模型都删去了吧。如果你真的都删去了,赶快撤消这一步,因为这不是你我想做的事。我们来编一个恢复成半张脸的程序myHalfMoon。

实例3:myHalfMoon
你一定猜到我这回要用函数了,对,把下面的几句代码执行一下:
global proc myHalfMoon()
{
delete polySmoothFace1;
delete polyNormal1;
delete polyExtrudeFace1;
}

执行完了,好像不起作用,因为你还不知道它的作用。你不知道你在不知不觉中已经定义了一个mel命令,命令的名称就是"myHalfMoon"。试试吧,选中模型,在命令行输入"myHalfMoon",按回车,命令就起作用了。现在如果说myHalfMoon就是个mel函数,你应该觉得比天书容易理解一点了吧。
------------------------------------------------------
[注]
关于Maya中的函数概念问题,按以前的说法,说myHalfMoon是函数是不严谨的。
"A function is a procedure with a return value. " [Maya 4.5 Help docs]
函数是带有返回值的程序。

这样myHalfMoon只能应称之为程序(procedure),带有返回值的程序才叫函数(Functions)。不过这样反而带来麻烦,我很高兴看到Maya5.0已经改变了这种说法。

"User defined functions are called procedures. " [Maya 5.0 Help docs] 用户定义的函数称之为程序(procedure)。以前的定义不见了,言下之意函数的定义跟返回值没什么关系了。

这下子可能给你带来了更多的疑问,我先回答几个吧。

问:Maya中的mel命令都是通过mel函数编出来的吗?
答:不是。有900多个Maya内置命令,这些命令是寄生在Maya模块文件(WinNT版的模块文件就是dll文件或mll文件),是已经编译好的汇编指令组合。内置命令命令一般都带有标志(Flag),而mel函数命令没有标志。mel函数命令通常被称为“脚本命令”(Script Command),内置命令则直接称“命令”(Command)。

问:怎样才能知道一个命令是不是脚本命令?
答:用whatIs命令。
例如:"whatIs myHalfMoon;",结果中出现"Mel procedure"(mel程序)字样,说明是脚本命令。

问:返回值是个什么东东?
答:以后会讲到的。

相信你一定掌握了把myFullMoon也变成函数的本领了吧,如果还没掌握我可要怀疑你的智商了。不过呢,反正我的教程也是白痴级的,我就把它说白了吧:
------------------------------------------------------
实例4:myFullMoon2.0
把Shelf上那个myFullMoon图标用鼠标中键拖放到mel编辑窗,把mel代码洒到编辑窗里。然后把这些代码写到"global proc myFullMoon(){ }"的大括号里,如下:
global proc myFullMoon(){polyExtrudeFacet -ch 1 -kft 1 -pvx -3.926295877 -pvy -1.118658066 -pvz 4.649739981 -translateX -7.5 -ty 0 -tz 0 -rx 0 -ry 0 -rz 0 -scaleX -1 -sy 1 -sz 1 -ran 0 -divisions 1 -off 0 -ltz 0 -ws 0 -ltx 0 -lty 0 -lrx 0 -lry 0 -lrz 0 -lsx 1 -lsy 1 -lsz 1 -ldx 1 -ldy 0 -ldz 0 -w 0 -gx 0 -gy -1 -gz 0 -att 0 -mx 0 -my 0 -mz 0 polySurface1.f[0:682];
polyNormal -normalMode 0 -ch 1 polySurface1.f[0:1449];
polySmooth -mth 0 -dv 1 -c 1 -kb 1 -ksb 1 -khe 0 -kt 1 -sl 1 -dpe 1 -ps 0.1 -ro 1 -ch 1 polySurface1.f[0:1449];
select -cl ;}

我不得不在一开始就强调格式问题,你的代码虽然没错,但代码不只是给机器用的,还要给人看。以下是改过格式的代码:

global proc myFullMoon(){
polyExtrudeFacet -ch 1 -kft 1
-pvx -3.926295877 -pvy -1.118658066 -pvz 4.649739981
-translateX -7.5 -ty 0 -tz 0
-rx 0 -ry 0 -rz 0
-scaleX -1 -sy 1 -sz 1
-ran 0 -divisions 1 -off 0 -ltz 0 -ws 0
-ltx 0 -lty 0
-lrx 0 -lry 0 -lrz 0
-lsx 1 -lsy 1 -lsz 1
-ldx 1 -ldy 0 -ldz 0
-w 0
-gx 0 -gy -1 -gz 0
-att 0
-mx 0 -my 0 -mz 0
polySurface1.f[0:682];

polyNormal -normalMode 0 -ch 1
polySurface1.f[0:1449];

polySmooth -mth 0 -dv 1 -c 1
-kb 1 -ksb 1 -khe 0 -kt 1
-sl 1 -dpe 1 -ps 0.1 -ro 1 -ch 1
polySurface1.f[0:1449];
select -cl ;
}

格式其实也没有固定的写法,总之,尽量写得能让人看清楚就行了。把这些代码粘贴到记事本上,然后存成一个mel文件,注意文件名必须是"myFullMoon.mel"(脚本命令名 + ".mel"),然后把这个文件放到"我的文档\e:\My Documents\maya\4.5(5.0)\scripts"目录中。这样当你每次启动Maya时,"myFullMoon"命令就能像Maya的其它命令一样工作了。

把Shelf上以前的mel图标删掉(就是把mel图标用鼠标中键拖进Shelf最右边的小垃圾桶里)。把"myFullMoon"这几个字拖到Shelf上重新建立图标。图标上加几个字(mFM),如图。以后升级"myFullMoon"的时候图标命令依旧,只要改"myFullMoon.mel"文件就行了。

"myFullMoon"现在存在一个很大的缺陷,就是只能对那个半边脸(polySurface1)起作用,换个物体就不行了,要是程序里那个"polySurface1"能随意改成其它多边形物体就好了。
呵,要想好好,就要使用变量(variable)。

----------------------------------------
Maya的变量名称必须以一个美元符号($)开头,这是为了和物体名区别开。这很容易接受,如果可以不写"$",Maya会以为你的"polySurface1"也是变量。不过忘写美元符号是视金钱如粪土的mel初学者常犯的错误,我盼望大家少犯这种错误,不要辜负了Alia$Wavefront的一片苦心。

变量名称中不能包含有空格或其它的特殊字符,不能用中文。变量名称是大小写敏感(Case Sensitive)的,不能有大小写错误。mel命令和mel函数也是大小写敏感的,同样不能有大小写错误。

下面把有关变量的一些知识详细介绍一下:
------------------------------------------------------
下面列出了五种数据类型:
类型 含义 例子
int 整数 10 ,-5 ,0
float 小数 392.6 ,7.0 和-2.667
string 一个或多个字母 “What's up, chief?"
Vector 三个浮点数 《3 ,7.7 ,9.1 》
matrix 多个浮点数组 <<1.1, 2, 3; 6.7, 5, 4.9>>

在上面的类型中,除matrix(矩阵)类型外,都可以构成Array(数组)。一个数组是某种数据类型的多个数据的序列。例如,一个包含三个元素的数组就是一个整数,接着一个整数,再接着一个整数。用户可以把矩阵考虑为一个包含多个浮点数组的数组,或是一个二维浮点数组。
------------------------------------------------------
定义变量和为变量赋值
定义变量就是指明变量属于何种数据类型。为变量赋值就是给一个定义的变量以指定的数值。
下面的例子表明了如何使用一个步骤来定义和为变量赋值:
int $temp = 3 ;
float $Temp =222.222;
string $tEmp = “Heya kid.”;
vector $teMp = <<1,2.7,3.2>>;
matrix $temP[2][3]=<<4.5,1,0.2;-13,9911,0.007>>;

当用户精确的定义矩阵时,用户必须指明矩阵的行列数。下面的例子表明如何为整数、浮点、字符串和矢量数组赋值和定义它们。例子:数组的定义和赋值
int $Temp[5]={100, 1000, -70, 2, 9822};
float $TeMp[4]={ 43.3, -10.7, 0, 82.5};
string $TemP[3]={“Lord”,”Flies”,”cool brown fox2.”};
vector $tEmp[2]={<<0,0,0>>,<<0.01,-2,16>>};

如果一个变量被定义,但没有为其赋值,则Maya 为其分配默认的数值0 ,而对字符串变量,Maya 为其分配两个空引号。
------------------------------------------------------
Reserved words (保留字)
保留字是一些在MEL 中带有固定含义的单词。这些保留字可用于指定变量类型、逻辑控制,或描述一个数值。下面就是MEL 中的保留字。break case continue default do
else false float for global
if in int matrix no
off on proc return string
switch true vector while yes

Data type keywords (数据类型关键字)
int float vetor string matrix
Boolean constant keywords(布尔关键字)
yes no on off true false
Flow control keywords(流程控制关键字)
if else for while do in
break continue default switch case

其它的关键字
global return source catch alias proc
就像变量名称,保留字是大小写敏感的。因此,int 是整数类型,而Int 就不是。实际上,alias 、sourcecatch 也是保留字。因为它们使用起来,更像命令,它们没有包含在上面的表中。

关于变量的数据类型(Data Types),还得从int讲起。
int
int是整数(integer)的意思。例如:
int $i = 3;

有一种布尔类型的数据,在mel中也用int表示。布尔数据只有两个值,"是"或"否",mel用"1"或"0"来表示。因此,下面几句代码的作用是相同的。
int $bool = 1;
int $bool = yes;
int $bool = true;
int $bool = on;

与之对应的下面几句代码的作用也是相同的。
int $bool = 0;
int $bool = no;
int $bool = false;
int $bool = off;

float
float是小数,也就是浮点数的意思。例如:
float $f = 3.3333;

浮点运算是3d运算的重点,你一定也注意到了Maya中的大部分物体属性都是浮点数值。从表示颜色这一点就可以明显地看出来。比如说红色,在网页中或平面软件中的表示为FF0000(255, 0, 0)(整数),可在3d软件中,红色就成了(1.0000, 0.0000, 0.0000)(浮点数)。

下面该讲到字符串(string)了,今天的重点就是文字游戏。

"print"命令的用法:
"print"命令可以输出任何变量的值。如:
string $s = "你好世界";
print $s;
=======================
结果为:你好世界

string
string是字符串的意思,变量$a是个字符串,它的值为"你好世界"。
字符串的值要用引号引起来。有些情况下你不打引号Maya也会猜出是字符串,要我把这些情况一一列举可是件很麻烦的事,反正你只须知道是字符串打上引号肯定没错误,而如果你不打引号让Maya猜谜的话,Maya一旦猜不出就会有错误提示信息,你再根据此信息来修正。

转义符(escape character)
引号里面的字如果也有引号,就把"变为\"。和c语言一样,\为mel的转义符(escape character)。例如:
string $s = "你\"好\"世界";
print $s;
=======================
结果为:你"好"世界

除了引号以外,还有一些符号有时也要相应改变。比如把\变为\\,把tab空格变为\t,把换行符变为\n或\r\n等。

数组(array)
一旦你对字符串有了一定的印象,我就可以讲几个字符处理的mel命令了。
不过在这之前,还必须要提一提数组(array)的概念。
一群变量放在一起可以组成一个数组,例如:
int $ia[5]={100, 1000, -70, 2, 9822};

你也可以先弄一个空数组,然后往里填数据。
注意,数组是从零开始的!!!
例如:
string $sa[];
$sa[0] = "你好";
$sa[1] = "世";
$sa[2] = "界";
print $sa;
=======================
结果为:你好

下面来讲几个字符处理的mel命令,当然这几个命令的出场顺序要根据我们的实际需要来安排。这几个命令分别为:
substring
tokenize
size
clear
match
substitute

会了这几个命令,对于字符处理差不多就够用了。

在讲解之前,先把下面一行代码在命令行执行一下:
string $obj = "pSphere1.translateX";
=======================
结果为:pSphere1.translateX

"substring"命令的用法:
现在得到一个字符串$obj和它的值,可是这个值不是我们想要的。我们想要的只是这个物体的名称("pSphere1"),而不包括它的属性(".translateX")。怎样从$obj中提取物体的名称呢?方法很多,第一种方法是先指定你要提取的是"pSphere1.translateX"中的第几个字符到第几个字符。数数看,连猴子也能数出来,"pSphere1"是"pSphere1.translateX"中的第1个字符到第8个字符。好了,现在用"substring"命令:

substring $obj 1 8;
=======================
结果为:pSphere1

获取命令的返回值
这个结果是"substring"命令的返回值,这一点我在第一课里曾提到过。要想把命令值的返回存到一个变量里,以便以后使用,有两种方法:

// 第一种是用"`"的方法,比较常用。
string $objName = `substring $obj 1 8`;
=======================
结果为:pSphere1

// 第二种是用eval的方法,"eval"对于执行字符串中的命令很有用。
string $objName = eval("substring $obj 1 8");
=======================
结果为:pSphere1

对于不需要标志的命令,还有第三种方法:
// 这种方法符合学过c语言的人的习惯,用于一些脚本命令很方便。
string $objName = substring($obj, 1, 8);
=======================
结果为:pSphere1

这三种方法的结果是一样的,具体用哪种方法根据自己的习惯来决定。

"tokenize"命令的用法:
下面接着来讲把"pSphere1"从"pSphere1.translateX"中提取出来的第二种方法,这种方法不用数数,是一种很实用的方法。方法是从一个字符(".")的位置把字符串截成两段,把这两段存到一个字符串数组中。具体方法如下:
string $buffer[];
tokenize "pSphere1.translateX" "." $buffer;
string $objName = $buffer[0];
=======================
结果为:pSphere1

因为数组是从0开始的,$buffer[0]是"pSphere1.translateX"的第一段,它的值是"pSphere1",$buffer[1]是第二段,它的值是"translateX"。

"tokenize"是一个很有用的命令,我需要再举几个例子把它的用法讲明白。
"tokenize"的返回值是把字符串分成的段数,例如用"/"可以把"1/2/3/4/5"分成5份,"tokenize"的返回值就是5。如下:
string $buffer[];
int $numTokens = `tokenize "1/2/3/4/5" "/" $buffer`;
=======================
结果为:5

如果不指定分割字符,"tokenize"会根据一个默认的空格字符来分割。例如:
string $buffer[];
tokenize "How are you?" $buffer;
print $buffer;
=======================
结果为:How
are
you?
也就是: $buffer[0]=="How"; $buffer[1]=="are"; $buffer[2]=="you?";

"size"命令的用法:
"tokenize"的返回值可以表示字符串被分成的段数,通过衡量这个字符串的大小也可以得知字符串被分成的段数,这就要用到"size"命令:

string $buffer[];
tokenize "1/2/3/4/5" "/" $buffer;
int $numTokens = `size $buffer`;
=======================
结果为:5

"size"命令可以求出一个数组是由多少个元素组成,也可以求出一个字符串是由多少个字符组成。刚才说道猴子能数出"pSphere1"是由8个符组成,现在你不用数也会知道这一点,你知道我的意思当然不是指你就是猴子:
int $size = `size "pSphere1"`;
=======================
结果为:8

要注意的是一个中文字占用两个字节,"size"为2!
int $size = `size "中文"`;
=======================
结果为:4

"clear"命令的用法:
对于一些"size"很大的数组,会占用很多内存,你不用时要用"clear"把它清空。清空后它的"size"为0。
int $buffer[5]={1, 2, 3, 4, 5};
clear $buffer;
=======================
结果为:0

"match"命令的用法:
"match"是字符串中的查找功能,返回值是一个字符串。如果找到了,就返回要找的字符串,没找到,就返回""。例如:
match "this" "this is a test";
=======================
结果为:this

match "that" "this is a test";
=======================
结果为:

"match"命令可以使用通配符,以下是使用规则:
. 代表任何一个单独的字符
* 代表0个或多个字符
+ 代表1个或多个字符
^ 代表一行中第一个字符的位置
$ 代表一行中最后一个字符的位置
\ 转义符(escape character). 把它写在特殊的字符前面(如 '*')
[...] 代表指定范围内的任意一个字符
(...) 用于把部分通配符表述组织在一起

例:
match "a*b*" "abbcc";
=======================
结果为:abb

match "a*b*" "bbccc";
=======================
结果为:bb

match "a+b+" "abbcc";
=======================
结果为:abb

match "^the" "the red fox";
=======================
结果为:the

match "fox$" "the red fox";
=======================
结果为:fox

match "[0-9]+" "sceneRender019.iff";
=======================
结果为:019

match "(abc)+" "123abcabc456";
=======================
结果为:abcabc

match("\\^.", "ab^c");
=======================
结果为:^c

"substitute"命令的用法:
"substitute"是字符串中的替换功能,返回值是一个字符串,返回替换后的结果。例如:
string $text = "ok?";
$text = `substitute "?" $text "!"`;
=======================
结果为:ok!

也可以通过此方法把不想要的字符去掉。例如:
string $text = "ok?";
$text = `substitute "?" $text ""`;
=======================
结果为:ok

下面是"substitute"使用通配符的方法:
string $test = "Hello ->there<-";
string $regularExpr = "->.*<-";
string $s1 = `substitute $regularExpr $test "Mel"`;
=======================
结果为:Hello Mel

字符串的联合(unite)
最后讲一下字符串的联合(unite)。
字符串可以做加法,但不能做减法:
string $s1 = "你好";
string $s2 = "世界";
string $text = $s1 + $s2;
=======================
结果为:你好世界

字符串的加法跟数字的加法不同:
int $i = 1 + 2 + 3;
=======================
结果为:6

string $s = "1" + "2" + "3";
=======================
结果为:123

+=
以下两种写法的结果是相同的:
string $s = "你好";
$s = $s + "世界";
=======================
结果为:你好世界

string $s = "你好";
$s += "世界";
=======================
结果为:你好世界

添加注释
为了增加mel程序的可读性,你可以在你的mel代码中添加注释。注释可起到解释、提示或描述脚本的作用,程序执行时会跳过这些注释,执行注释以外的代码。mel的注释跟c语言是一样的。
--------------------------------------------
单行注释
如果要创建单行的注释,输入两个反斜线(//),然后输入注释:
int $locator = 7; //Default locator number is lucky.
--------------------------------------------
多行注释
如果要创建多行的注释,输入一个反斜线加一个星号(/*)。在注释的结束,输入一个星号加反斜线(*/)。
用户可以在一行或多行的某部分中使用这种注释。
/*This is an example of a
variable-line comment.*/
--------------------------------------------
注意,在表达式中不能使用多行注释技术,表达式中只能使用单行注释(//)。

上一课讲到了获取命令的返回值,有了这种方法,就可以继续我们的"myFullMoon"了。我们可以把选中的物体名称存入一个变量,再根据物体名称对物体进行处理。
--------------------------------------------
获取选中物体的名称
"ls"命令的用法:
ls -sl的意思就是获得选择物体的名称,要想对物体进行操作这是必然的一步。
我来具体演示一下方法,以便大家更好的掌握。

实例5:选择物体清单
新建一个场景,创建一盏直射灯、一个多边形球,一个多边形方块,一个Nurbs平面,一个Nurbs圆柱,一个细分方块。如图。
选中所有物体,然后执行"ls -sl"命令:
ls -sl;
=======================
结果为:pSphere1 pCube1 nurbsCylinder1 subdivCube1 directionalLight1 nurbsPlane1

下面编个程序以清单的形式输出物体名称:
global proc printObjNameSheet()
{
string $objects[] = `ls -sl`;
int $num = `size $objects`;

string $objNameSheet;
for($i=0; $i<$num; $i++)
{
$objNameSheet += $i+1;
$objNameSheet += ")";
$objNameSheet += $objects[ $i ];
$objNameSheet += "\n";
}

string $output = "\n*******************\n";
$output += "一共选择了";
$output += $num; // 注意$num不能写成"$num"
$output += "个物体\n";
$output += "它们分别是:\n";
$output += $objNameSheet;

print $output;
}
printObjNameSheet;
--------------------------------------------
把上面的代码拉到Shelf上,随便选择几个物体,然后执行这些代码,就会看到mel历史窗中输出的物体名称清单。

由于上一课已经讲过了字符处理的方法,相信你上面的大部分代码都看得懂。不过,"for($i=0; $i<$num; $i++)"这种语法以前没讲过,现在来具体讲讲。
$i++
$i++的意思就是$i=$i+1或$i+=1。

for语句
for语句是一个典型的循环语句。我下面具体分析一下。

string $objects[] = `ls -sl`;
int $num = `size $objects`;

如图,已知我们选中了三个物体,物体名分别存入了$objects数组中。
跟据我们所知的,用代数的方法把所知的值代入变量中,就有了以下结果:
$num == 3;
$objects[0] == nurbsPlane1;
$objects[1] == directionalLight1;
$objects[2] == pSphere1;

--------------------------------------------
string $objNameSheet;
for($i=0; $i<$num; $i++)
{
$objNameSheet += $i+1;
$objNameSheet += ")";
$objNameSheet += $objects&i;;
$objNameSheet += "\n";
}


继续用代数的方法:
//当$i=0时:
$objNameSheet += 0+1;
//这时:$objNameSheet == "1"
$objNameSheet += ")";
//这时:$objNameSheet == "1)"
$objNameSheet += $objects[0];
//这时:$objNameSheet == "1)nurbsPlane1"
$objNameSheet += "\n";
//这时:$objNameSheet == "1)nurbsPlane1\n"

//当$i=1时:
$objNameSheet += 1+1;
//这时:$objNameSheet == "1)nurbsPlane1\n2"
$objNameSheet += ")";
//这时:$objNameSheet == "1)nurbsPlane1\n2)"
$objNameSheet += $objects[1];
//这时:$objNameSheet == "1)nurbsPlane1\n2)directionalLight1"
$objNameSheet += "\n";
//这时:$objNameSheet == "1)nurbsPlane1\n2)directionalLight1\n"

//当$i=2时:
$objNameSheet += 2+1;
//这时:$objNameSheet == "1)nurbsPlane1\n2)directionalLight1\n3"
$objNameSheet += ")";
//这时:$objNameSheet == "1)nurbsPlane1\n2)directionalLight1\n3)"
$objNameSheet += $objects[2];
//这时:$objNameSheet == "1)nurbsPlane1\n2)directionalLight1\n3)pSphere1"
$objNameSheet += "\n";
//这时:$objNameSheet == "1)nurbsPlane1\n2)directionalLight1\n3)pSphere1\n"

//当$i=3时:
// $i = $num 结束循环


--------------------------------------------
因为mel不能设断点进行调试分析,我只好用这种笨办法来分析了,但愿你能看懂。

注意for语句分号的位置,初学者常会在这里出错,不要把分号写在那个小括号的后面。大括号后面也不要写分号。

关于获取选中物体的名称,再举一个例子。例如,你想把所有选中的物体沿x轴移动5:最简单的方法当然是: move -r -x 5;
不过,为了演示ls -sl的用法,请看下面的代码:
string $objects[] = `ls -sl`; // 获取选择物体名称
int $num = `size $objects`; // 获得物体的数量
for($i=0; $i<$num; $i++)
{
select -r $object;
move -r -x 5;
}

in
还有一种方法可以达到同样的效果:
string $objects[] = `ls -sl`; // 获取选择物体名称
for($object in $objects)
{
select -r $object;
move -r -x 5;
}

"filterExpand"命令的用法:
不过"ls -sl"是获得所有选择物体的名称,如果你只想对选择物体中的多边形物体进行操作,最好的方法是不用"ls",而用"filterExpand"。
// -sm 12是多边形物体, -sm 10是Nurbs表面,其它的请
// 参看filterExpand命令的帮助文档(help -doc filterExpand;)。
string $polygons[] = `filterExpand -sm 12`;
for($polygon in $polygons)
{
select -r $polygon;
move -r -x 5;
}

--------------------------------------------
[注]
用"ls"命令也有办法达到与"filterExpand"类似的效果,为了减轻你们的学习复担,我就不多讲了。用"filterExpand"更简便一些。

选中刚才那个场景的全部物体,下面的代码分类输出选择物体的名称:

global proc printObjNameSheet()
{
int $num;
string $polygons[] = `filterExpand -sm 12`;
string $nurbs[] = `filterExpand -sm 10`;
string $polyNNurbs[] = `filterExpand -sm 10 -sm 12`;

string $output = "\n*******************\n";
$output += "一共选择了";
$num = `size $polygons`;
$output += $num + "个多边形物体\n它们是:\n";

print $output;
print $polygons;

$output = "\n一共选择了";
$num = `size $nurbs`;
$output += $num + "个Nurbs物体\n它们是:\n";

print $output;
print $nurbs;

$output = "\n选择的物体中Nurbs物体和多边形物体共有";
$num = `size $polyNNurbs`;
$output += $num + "个\n它们是:\n";

print $output;
print $polyNNurbs;
}
printObjNameSheet;

如何判断选择的是面,还是点,还是边呢?如下:

// 多边形点
string $vertices[] = `filterExpand -ex 1 -sm 31`;
// 多边形边
string $edges[] = `filterExpand -ex 1 -sm 32`;
// 多边形面
string $faces[] = `filterExpand -ex 1 -sm 34`;

// 如果框选很多,要想获取其中的nurbs和polygon物体,-sm可以出现不止一次。
string $polygons[] = `filterExpand -sm 10 -sm 12`;

选择一些多边形物体的面,下面的代码输出面的名称:
global proc printObjNameSheet()
{
int $num;
string $faces[] = `filterExpand -sm 34`;

string $output = "\n*******************\n";
$output += "一共选择了";
$num = `size $faces`;
$output += $num + "个面\n它们是:\n";

print $output;
print $faces;
}
printObjNameSheet;

"filterExpand -ex/expand"的用法
关于filterExpand -ex/expand的用法,如果懒得看help,看下面的吧:
Object Type / Mask
Handle 0
Nurbs Curves 9
Nurbs Surfaces 10
Nurbs Curves On Surface 11
Polygon 12
Locator XYZ 22
Orientation Locator 23
Locator UV 24
Control Vertices (CVs) 28
Edit Points 30
Polygon Vertices 31
Polygon Edges 32
Polygon Face 34
Polygon UVs 35
Subdivision Mesh Points 36
Subdivision Mesh Edges 37
Subdivision Mesh Faces 38
Curve Parameter Points 39
Curve Knot 40
Surface Parameter Points 41
Surface Knot 42
Surface Range 43
Trim Surface Edge 44
Surface Isoparms 45
Lattice Points 46
Particles 47
Scale Pivots 49
Rotate Pivots 50
Select Handles 51
Subdivision Surface 68
Polygon Vertex Face 70
NURBS Surface Face 72
Subdivision Mesh UVs 73

前面的准备就绪,现在来继续myFullMoon的新版。

实例6:myFullMoon3.0
前面提到过升级"myFullMoon"的时候图标命令依旧,只要改"myFullMoon.mel"文件就行了。
现在打开"myFullMoon.mel"文件,用下面的代码替换掉原来的代码:

// myFullMoon3.0
global proc myFullMoon()
{
string $polygons[] = `filterExpand -sm 12`;
// 如果选择了多个多边形物体,只对第一个进行操作
string $polygon = $polygons[0];

select -r $polygon;
ConvertSelectionToFaces; // 转换为选择面模式

polyExtrudeFacet -ch 1 -kft 1
-pvx -3.926295877 -pvy -1.118658066 -pvz 4.649739981
-translateX -7.5 -ty 0 -tz 0
-rx 0 -ry 0 -rz 0
-scaleX -1 -sy 1 -sz 1
-ran 0 -divisions 1 -off 0 -ltz 0 -ws 0
-ltx 0 -lty 0
-lrx 0 -lry 0 -lrz 0
-lsx 1 -lsy 1 -lsz 1
-ldx 1 -ldy 0 -ldz 0
-w 0
-gx 0 -gy -1 -gz 0
-att 0
-mx 0 -my 0 -mz 0;

polyNormal -normalMode 0 -ch 1;

polySmooth -mth 0 -dv 1 -c 1
-kb 1 -ksb 1 -khe 0 -kt 1
-sl 1 -dpe 1 -ps 0.1 -ro 1 -ch 1;
select -cl ;
}

现在这些代码还不会生效,直到你关闭再重新打开Maya。要想让mel文件里的代码立即生效,需要Source Script。有三种方法可以Source Script:
1. 选择mel编辑器菜单File->Source Script...,打开"myFullMoon.mel"文件。
2. 把"myFullMoon.mel"文件直接拖放到Maya视图窗口中。
3. 使用"source"命令。

"source"命令的用法:
通过"source"命令可以执行mel文件中的代码。例如:
source "c:/temp/myFullMoon.mel";
或:
source "c:\\temp\\myFullMoon.mel";

如果你的mel文件是放在Maya的Script目录(我的文档\e:\My Documents\maya\4.5(5.0)\scripts),"source"命令可以不指定路径:
source "myFullMoon.mel";

Source了"myFullMoon.mel"之后,就可以试试了。
选中以前那个半边脸模型,给它改个名字,再按"myFullMoon"命令的Shelf图标,可以看到新代码起作用了。

当然现在又来了新的问题,还记得以前用的模型宽度是7.5吧,每个模型的宽度是不同的,模型宽度也应该是个变量。要想获得模型宽度,需要用到边界框(Bounding Box),获取边界框是以后要做的事情,我把它放到建模部分讲。

前面讲了很多东西,你可能还要慢慢消化。不过如果你真的能把前面讲的都理解了,相信你的mel已经入门了。从下一课开始,我来讲用mel编写界面的方法。

Maya帮助文档中把编写界面的mel命令称为ELF命令。
"Most ELF commands create and modify UI elements. " [Maya Help docs]
大部分ELF命令创建和修改UI元素。

"ELF","UI"这么多缩写真的很让人感到厌烦。
"ELF" - Extended Layer Framework(扩展层框架)
这个词汇有点多余,我会尽可能少提到它。

"UI" - User Interface(用户界面)
用户界面的含义相信大多数人都知道,用户界面又称人机界面,实现用户与计算机之间得通信,以控制计算机或进行用户和计算机之间得数据传送得系统部件。具体的说,我们平常使用的窗口、菜单、按钮等都是用户界面的元素。
"界面"
我们平常所说的界面都是指“用户界面”,因为程序员是少数派,用户才是上帝,所以用户们从来不怕直接说界面会被别人误解为“应用程序设计界面”。我后面也把用户界面简称为界面,希望不要给我纠正。

[注]
"API" - Application Programming Interface
有几种不同译法:应用程序界面,应用程序设计界面,应用程序接口。这是一种专门给程序员使用的界面或接口,例如Maya API。

现在来编写一个窗口。用mel编写一个窗口就像创建一个球或者方块那么简单。

"window"命令 (创建窗口)
在命令行输入"window"(打字的候要小心,不要让视窗变成寡妇window-widow)然后回车,一个窗口就创建好了。不要因为没看到有什么变化而认为我在欺骗你,"window"命令创建的窗口在默认情况下是不可见的,换句话说,"window"命令的"-vis/-visible"的默认参数值为0(或false),也就是说直接输入"window"命令和输入"window -visible false"是等效的。

"showWindow"命令 (显示窗口)
要想创建可以看得到的窗口,有两种方法。一种方法是在创建窗口时用"window -visible true",另一种方法是创建窗口后执行"showWindow"命令。这时我会推荐你用第二种方法,"showWindow"的好处是既显示窗口,又把这个窗口推到所有窗口的最前面。

好,现在就在命令行输入"showWindow"并回车,一个窗口便显示了出来。

唯一的命名
因为你可以创建很多个窗口,因此如果想完全控制每个窗口,就必须给每个窗口指定一个唯一的命名。执行下面两句命令:
-----------------------------------------
window -t "我的测试窗" myTestWin;
showWindow myTestWin;
-----------------------------------------
这里"myTestWin"就是窗口的名字。
"-t/-title"的用途想必不用我多作解释了吧?

当一个窗口创建时,会另人想到另一个问题,就是怎么把它销毁(删除)。点窗口右上角标题栏上的那个"×"可以关闭窗口,然而关闭窗口却蕴含着两层意思。在大多数情况下,关闭窗口可以销毁窗口,有少数情况,关闭窗口以后窗口只是处于不可见状态,窗口并没被销毁,你随时可以用"showWindow"命令来显示它。用"-ret/-retain"标志创建的窗口就属于这种少数情况。

"deleteUI"命令 (销毁界面元素)
要想让彻底销毁窗口,使用"deleteUI"命令。"deleteUI"可以销毁任何界面元素,包括布局、控件和菜单。在命令行输入"deleteUI myTestWin"并回车,"myTestWin"就被销毁了。

为方便以后对于这个窗口代码的调用,我们来编写一个函数。
-----------------------------------------
global proc myTest()
{
if(`window -ex myTestWin`) // 如果myTestWin窗口已经存在了
deleteUI myTestWin; // 就把myTestWin销毁

window -t "我的测试窗" myTestWin;
showWindow myTestWin;
}
myTest;

"if"语句
这里用到了if语句,我觉得if语句是最容易理解的语句,把"if"直译成"如果"就可以理解。if后面那个小括号里的代码表示的只能是一个布尔值,是(1, true)或否(0, false)。当表示否时,可以在前面加一个叹号,"!"可以直译成"不"。例如:
if(!`window -ex myTestWin`) // 表示如果myTestWin窗口不存在
也就是:
if(`window -ex myTestWin` == false) // `window -ex myTestWin`的返回值为0

现在只是编写了一个空的窗口,要是想往窗口中添加一些按钮或者文本框什么的怎么办呢?

控件,布局
按钮或者文本框这些东东统称控件(control),mel摆放控件的特色是使用布局(layout)。在不同的布局中,控件摆放的方法不同,比如竖布局(columnLayout)中的控件都是一个挨一个竖着摆放的,而横布局(rowLayout)中的控件都是横着排列的。

"window"命令之后必须有一个布局,这个布局作为窗口的主布局,其它控件或布局都放置在这个主布局之中。

层级关系
由于布局之中既可以放布局又可以放控件,因此布局与控件,布局与布局之间构成了一种层级关系(父子关系)。例如,布局columnLayout2在布局columnLayout1之中,columnLayout1称为columnLayout2的父布局(Parents),columnLayout2称为columnLayout1的子布局(Children)。

"setParent"命令 (指定当前布局)
默认的,刚刚创建的布局为当前布局,新建的布局或控件都建在当前布局中。要想使以前创建的布局成为当前布局,使用setParent命令:"setParent 布局名"。要想使上一级布局(父布局)成为当前布局,使用"setParent .."。

"columnLayout"命令 (竖布局)
对于不在乎界面美观的人来说,学会一个竖布局差不多就足够了。
竖布局中的控件都是一个挨一个竖着摆放的,请看下面的代码:
-------------------------------------------------------------
global proc myTest()
{
if(`window -ex myTestWin`)
deleteUI myTestWin;

window -t "我的测试窗" myTestWin;
columnLayout -adj 1; // 主布局
text -l "圆锥名称";
// 和控制窗口一样,要想控制控件,也要给控件一个唯一的命名。
// 这里文本框(textField)控件的名字为"myConeNameFld"
textField -tx "cone" myConeNameFld;
button -l "创建圆锥" -c "cone";
button -l "删除" -c "delete";

showWindow myTestWin;
}
myTest;
-------------------------------------------------------------
执行这段代码,不要关掉生成的窗口。
"text"命令 (静态文本)
"textField"命令 (文本框)
"button"命令 (按钮)

这里用到三个关于界面的mel命令:"text","textField","button"。
根据生成的窗口你就可以猜到这三个命令的意思。
"text"为静态文本,"textField"为文本框,"button"为按钮。
"-l/-label"可能是界面命令中最明显、最容易理解的标志了,意思是界面元素的标签。
"-tx/-text"是文本框中的文字。
"-c/-command"是要执行的mel命令,注意这个命令是一个字符串,一般要用引号引起来。
"-adj"决定是否自动调整布局内控件的宽度。

"-q/-query"(查询)和"-e/-edit"(编辑)标志
这是两个非常特殊又非常有用的标志,所有的界面命令都用到这两个标志。
"-q/-query" 表示查询的意思,你可以通过它获得某一标志的数值。
"-e/-edit" 表示编辑的意思,你可以通过它修改某一标志的数值。

下面举例说明它们的用法:
在刚才创建的窗口的文本框中随便输入几个字,在命令行输入"textField -q -tx myConeNameFld"并回车,可以在输出窗口中看到文本框中的字。
在命令行输入"textField -e -tx "change" myConeNameFld"并回车,文本框中的字就变成了"change"。前面说过"deleteUI"可以销毁任何界面元素,在命令行输入"deleteUI myConeNameFld"并回车,文本框消失了。

获取本框中的文字
编写按钮命令

要想创建的圆锥根据本框中的文字来命名,使用下面的代码:
-----------------------------------------
// 先编写一个函数(createCone),然后在按钮命令中调用这个函数。
global proc createCone()
{
string $name = `textField -q -tx myConeNameFld`; // 获取文本框中的文字
cone -name $name; // 用文本框中的文字为名称创建圆锥
}

global proc myTest()
{
if(`window -ex myTestWin`)
deleteUI myTestWin;

window -t "竖布局(columnLayout)测试窗" myTestWin;
columnLayout -adj 1;
text -l "圆锥名称";
textField -tx "cone" myConeNameFld;
button -l "创建圆锥" -c "createCone"; // 按钮命令中调用createCone函数
button -l "删除" -c "delete";

showWindow myTestWin;
}
myTest;
-----------------------------------------
改一下文本框中的字,按"创建圆锥"按钮,这时创建的圆锥是按照文本框中的文字命名的。注意,对物体命名时不能使用标点符号,不能使用大多数的中文字,具体原因在后面讲到如何用mel判断物体名称是否合法时再解释。

下面讲几个常用布局:

"rowColumnLayout"命令 (横竖布局)
控件横竖排列的布局:
-----------------------------------------
global proc myTest()
{
if(`window -ex myTestWin`) deleteUI myTestWin;

window -t "横竖布局(rowColumnLayout)测试窗" myTestWin;
rowColumnLayout -nc 3 -cw 1 60 -cw 2 80 -cw 3 100; // 横竖布局
button; button; textField;
button; textField; button;
button;

showWindow myTestWin;
}
myTest;
-----------------------------------------
"-nc/numberOfColumns"指定一共有几列。
"-cw/columnWidth"指定某一列的宽度。
"-cw 1 60"第一列的宽度为60像素。
"-cw 2 80"第二列的宽度为80像素。以此类推。

"scrollLayout"命令 (滚动布局)
滚动布局中只能放布局,不能放控件。
滚动布局可以给它的子布局加上滚动条。
-----------------------------------------
global proc myTest()
{
if(`window -ex myTestWin`) deleteUI myTestWin;

window -t "滚动布局(scrollLayout)测试窗" myTestWin;
scrollLayout;
columnLayout;
button;button;button;button;button;
button;button;button;button;button;

showWindow myTestWin;
}
myTest;

"frameLayout"命令 (边框布局)
边框布局中只能放布局,不能放控件。
下面的代码提供几种不同风格的边框:
-----------------------------------------
global proc myTest()
{
if(`window -ex myTestWin`) deleteUI myTestWin;

window -t "边框布局(frameLayout)测试窗" myTestWin;
columnLayout -adjustableColumn true;
// 第一种:可折叠边框
// 你熟悉的Attribute Editor和Tool Settings等窗口就是用的这种边框
frameLayout -label "Collapsable" -collapsable on -borderStyle "etchedIn"; // 可折叠
columnLayout;
text -l "-cl/collapse 0 -折叠";
text -l "-cl/collapse 1 -展开";
setParent ..;
setParent ..;
// 其它几种标签在边框线的位置不同,边框线的风格不同,如图。
// 第二种:
frameLayout -label "Buttons" -labelAlign "top" -borderStyle "in";
columnLayout;
button; button;
setParent ..;
setParent ..;
// 第三种:
frameLayout -label "Scroll Bars" -labelAlign "center" -borderStyle "out";
columnLayout;
intSlider; intSlider;
setParent ..;
setParent ..;
// 第四种:
frameLayout -label "Fields" -labelAlign "center" -borderStyle "etchedIn";
columnLayout;
intField; intField;
setParent ..;
setParent ..;
// 第五种:
frameLayout -label "Check Boxes" -labelAlign "bottom" -borderStyle "etchedOut";
columnLayout;
checkBox; checkBox;
setParent ..;
setParent ..;

showWindow myTestWin;
}
myTest;
-----------------------------------------
"-cll/collapsable"边框是否可折叠。
"-cl/collapse"如果边框可折叠,现在是否折叠。
"-bs/borderStyle"边框线的风格,可用值为:"in", "out", "etchedIn", 或 "etchedOut"。
"labelAlign"边框标签的对齐方式,可用值为:are "top", "center", 或 "bottom"。

"paneLayout"命令 (窗格布局)
窗格布局一个很明显的特点就是有分隔线分隔窗格面板,你可以移动分隔线来调整窗格面板的大小。Maya的四视图就是用了这个布局的,还有mel编辑器也是。
-----------------------------------------
global proc myTest()
{
if(`window -ex myTestWin`) deleteUI myTestWin;

window -t "窗格布局(paneLayout)测试窗" myTestWin;
paneLayout -configuration "quad";
button;
textScrollList -append "one" -append "two" -append "three";
scrollField;
scrollLayout;
columnLayout;
button; button; button;
showWindow myTestWin;
}
myTest;
-----------------------------------------
"-cn/configuration"pane的组合方式,可用值为:"single", "horizontal2", "vertical2", "horizontal3", "vertical3", "top3", "left3", "bottom3", "right3", "horizontal4", "vertical4", "top4", "left4", "bottom4", "right4", "quad"。

"tabLayout"命令 (标签布局)
标签布局中只能放布局,不能放控件。标签布局中可以放多个并列的子布局,当鼠标点到某一标签时,就显示与此标签对应的子布局。对于控件很多的面板,标签布局无疑是最好的选择。
-----------------------------------------
global proc myTest()
{
if(`window -ex myTestWin`) deleteUI myTestWin;

window -t "标签布局(tabLayout)测试窗" myTestWin;
string $tabs = `tabLayout`;
string $child1 = `columnLayout -adj 1`; // 标签1
button; button; button;
setParent ..;

string $child2 = `rowColumnLayout -numberOfColumns 2`; // "标签2"
button; button; button;
setParent ..;

tabLayout -edit
-tabLabel $child1 "标签1" -tabLabel $child2 "标签2"
$tabs;

showWindow myTestWin;
}
myTest;
-----------------------------------------
string $tabs = `tabLayout`;
这句的意思获取tabLayout命令的返回值,存到变量$tabs里。
前面说过界面元素要有一个唯一的命名,如果你没有给它命名,Maya会自动给它一个命名。界面命令的返回值就是界面元素的名称,因此用获取命令返回值的方法就可以获得界面元素的名称。(string $变量 = `界面命令`;)

string $child1 = `columnLayout -adj 1`;
这句也是同样的道理。

"formLayout"命令 (表单布局)
表单布局是最强大、最复杂、最具有可塑性的布局。这种布局可以随意把控件放到任何你想要的地方,也可以根据窗口的缩放即时缩放指定的控件。如果你很注重界面舒适美观的话,它就是你必然的选择。
-----------------------------------------
global proc myTest()
{
if(`window -ex myTestWin`) deleteUI myTestWin;

window -t "表单布局(formLayout)测试窗" myTestWin;

// 创建表单布局和布局中的控件
string $form = `formLayout`;
string $b1 = `button -label "按钮1"`;
string $b2 = `button -label "按钮2"`;
string $b3 = `button -label "按钮3"`;

// 分别描述每个按钮的上下左右位置
formLayout -edit
// 按钮1
-attachForm $b1 "top" 5 // 顶部距离表单5个像素
-attachForm $b1 "left" 5 // 左边距离表单5个像素
-attachForm $b1 "bottom" 5 // 底部距离表单5个像素
-attachPosition $b1 "right" 0 75 // 右边距离表单的75%
// 按钮2
-attachForm $b2 "top" 5 // 顶部距离表单5个像素
-attachControl $b2 "left" 5 $b1 // 左边距离"按钮1"5个像素
-attachNone $b2 "bottom" // 底部为默认值
-attachForm $b2 "right" 5 // 右边距离表单5个像素
// 按钮3
-attachControl $b3 "top" 5 $b2 // 顶部距离"按钮2"5个像素
-attachControl $b3 "left" 5 $b1 // 左边距离"按钮1"5个像素
-attachNone $b3 "bottom" // 底部为默认值
-attachForm $b3 "right" 5 // 右边距离表单5个像素
$form;

showWindow myTestWin;
}
myTest;
-----------------------------------------
当你拉大或缩小这个窗口时,"按钮1"的宽度总是占整个窗口的75%。

编写表单布局一般分为两步:
1.创建表单布局和布局中的控件。
2.分别描述每个按钮的上下左右位置。
"-af/attachForm" 距离表单多少个像素。
"-ac/attachControl" 距离控件多少个像素。
"-ap/attachPosition" 距离表单的几(-numberOfDivisions)分之几多少个像素。
"-nd/numberOfDivisions" 把表单分成多少份,好用于"-ap/attachPosition"。默认值为100。

关于表单布局,再举两个例子:
-----------------------------------------
// 这是一种常用的按钮摆放方法
global proc myTest()
{
if(`window -ex myTestWin`) deleteUI myTestWin;

window -t "表单布局1(formLayout)测试窗" myTestWin;

string $form = `formLayout -numberOfDivisions 3`; // 分3份
// "-horizontalScrollBarThickness 0"无横向滚动条
string $scroll = `scrollLayout -horizontalScrollBarThickness 0`;
columnLayout;
button; button; button;button; button;
button; button; button;button; button;
setParent ..;
setParent ..;

string $sep = `separator`;
string $b1 = `button -label "按钮1"`;
string $b2 = `button -label "按钮2"`;
string $b3 = `button -label "按钮3"`;

formLayout -edit
// 滚动布局
-attachForm $scroll "top" 4 // 顶部距离表单4个像素
-attachForm $scroll "left" 4 // 左边距离表单4个像素
-attachControl $scroll "bottom" 4 $sep // 底部距离"分隔线"4个像素
-attachForm $scroll "right" 4 // 右边距离表单4个像素
// 分隔线
-attachNone $sep "top" // 顶部为默认值
-attachForm $sep "left" 2 // 左边距离表单2个像素
-attachControl $sep "bottom" 4 $b1 // 底部距离"按钮1"4个像素
-attachForm $sep "right" 2 // 右边距离表单2个像素
// 按钮1
-attachNone $b1 "top" // 顶部为默认值
-attachForm $b1 "left" 4 // 左边距离表单4个像素
-attachForm $b1 "bottom" 4 // 底部距离表单4个像素
-attachPosition $b1 "right" 2 1 // 右边距离表单的1/3处2个像素
// 按钮2
-attachNone $b2 "top" // 顶部为默认值
-attachControl $b2 "left" 4 $b1 // 左边距离"按钮1"表单4个像素
-attachForm $b2 "bottom" 4 // 底部距离表单4个像素
-attachPosition $b2 "right" 2 2 // 右边距离表单的2/3处2个像素
// 按钮3
-attachNone $b3 "top" // 顶部为默认值
-attachControl $b3 "left" 4 $b2 // 左边距离"按钮2"4个像素
-attachForm $b3 "bottom" 4 // 底部距离表单4个像素
-attachForm $b3 "right" 4 // 右边距离表单4个像素
$form;

showWindow myTestWin;
}
myTest;

-----------------------------------------
// 随意摆放控件的位置
global proc myTest()
{
if(`window -ex myTestWin`) deleteUI myTestWin;

window -t "表单布局2(formLayout)测试窗" myTestWin;

string $form = `formLayout`;
string $b1 = `button -l "按钮1"`;
string $b2 = `button -l "按钮2"`;
string $b3 = `button -l "按钮3"`;

formLayout -e
-af $b1 "top" 1
-af $b1 "left" 5
-af $b2 "top" 19
-af $b2 "left" 132
-af $b3 "top" 46
-af $b3 "left" 40
$form;

showWindow myTestWin;
}
myTest;

"rowLayout"命令 (横布局)
横布局与横竖布局有些相似,只不过控件只能横着摆而已。
-----------------------------------------
window;
rowLayout -numberOfColumns 3
-columnWidth3 80 75 150
-adjustableColumn 2
-columnAlign 1 "right"
-columnAttach 1 "both" 0
-columnAttach 2 "both" 0
-columnAttach 3 "both" 0;
text;
intField;
intSlider;
showWindow;
-----------------------------------------

"gridLayout"命令 (格子布局)
格子布局与横竖布局有些相似,受格子的影响,每个的控件大小相同。
-----------------------------------------
window;
gridLayout -numberOfColumns 2 -cellWidthHeight 50 50;
button; button; button; button;
button; button; button;
showWindow;
-----------------------------------------

"menuBarLayout"命令 (菜单条布局)
用于一个窗口放多个菜单条的情况。
-----------------------------------------
string $window = `window`;
columnLayout -adjustableColumn true;
// Create first menu bar layout.
string $menuBarLayout = `menuBarLayout`;
menu -label "File";
menuItem -label "New";
menuItem -label "Open";
menuItem -label "Close";
menu -label "Help" -helpMenu true;
menuItem -label "About...";
columnLayout;
button -label "Add Menu"
-command ("menu -parent " + $menuBarLayout + "; menuItem");
setParent ..;
setParent ..;

separator -height 10 -style "none";

// Create a second menu bar layout.
//
menuBarLayout;
menu -label "Edit";
menuItem -label "Cut";
menuItem -label "Copy";
menuItem -label "Paste";
menu -label "View";
menuItem -label "Fonts...";
menuItem -label "Colors...";
columnLayout;
text -label "Add some controls here.";
setParent ..;
setParent ..;

showWindow $window;
-----------------------------------------

"shelfLayout"命令 (工具架布局)
放置shelf按钮的布局,支持鼠标中键拖放代码。
-----------------------------------------
window;
tabLayout;
shelfLayout Anim;
setParent ..;
shelfLayout Render;
setParent ..;
shelfLayout Misc;
setParent ..;
showWindow;
-----------------------------------------

"shelfTabLayout"命令 (工具架标签布局)
-----------------------------------------
可以使工具架上多一个垃圾桶图标,以便删除不用的shelf按钮。
window;
shelfTabLayout
-image "smallTrash.xpm"
-imageVisible true mainShelfTab;
shelfLayout Dynamics;
setParent ..;
shelfLayout Rendering;
setParent ..;
shelfLayout Animation;
setParent ..;
showWindow;

共有的标志(一):
如果你读过mel命令的帮助文档,你会发现所有的界面命令都具有一些共同的标志。比如,"-q"、"-e"就是所有界面命令都具有的标志。我把这些标志介绍一下,这样无论任何界面(布局或控件)命令,只要用到这些标志时,你们就都知道怎么去用了。

"-q/-query"(查询)
表示查询的意思,你可以通过它获得某一标志的数值。(前面讲过了)

"-e/-edit"(编辑)
表示编辑的意思,你可以通过它修改某一标志的数值。(前面讲过了)

"-ex/exists"(是否存在)
通过"-ex"可以得知一个界面元素是否存在。
例如前面讲过的"if(`window -ex myTestWin`)"。

"-dt/defineTemplate" (定义模板)
"-ut/useTemplate" (使用模板)
一般我们没必要定义模板,不过有几个Maya已经定义好的模板有时会用到,我以后再讲。

"-p/parent" (指定父级界面元素)
指定父级界面元素(布局或菜单),平常我们用"setParent"命令来解决这个问题,所以"-p"很少用到。

"-en/enable" (是否激活)
我们经常看到一些菜单或按钮上的字是灰色的,表示它们现在不能用。
使用"-en"就可以让你的界面元素上的字变灰,使它们不能用。
例如"button -e -en off myButton1"就是让按钮myButton1变得不能用。
而"button -e -en on myButton1"就是让按钮myButton1恢复活力。

"-w/width" (宽度)
界面元素的宽度为多少像素。
你会发现使用这个标志时经常不起作用。
比如如果在布局命令中指定了里面控件的宽度,再给布局中这些控件指定宽度就不起作用了。
窗口的宽度只在第一次创建时有用,以后创建会使用预置中的尺寸。预置中的尺寸就是窗口关闭时的大小,这个尺寸记录在预置文件(windowPrefs.mel)中,可以通过"windowPref"命令去除窗口的预置尺寸,方法是"windowPref -remove 窗口名;"

"-h/height" (高度)
界面元素的高度为多少像素。
用法同"-w/width",例如"button -w 32 -h 32;"。

"-vis/visible" (是否可见)
指定界面元素是否可见。
前面讲过"window -visible off"的问题。其它界面元素也可以通过"-visible off"暂时隐藏,想要显示出来,就用"-visible on"。

"-io/isObscured" (是否看不到)
查询界面元素是否不可见。你肯定会问"-io"和"-vis"的区别。"-io"只能用于查询(-q)模式,般查询结果正好与"-vis"的查询结果相反。不过"-io"的不可见包括窗口最小化、父级界面元素隐藏等造成的不可见因素。

"-m/manage" (可控制)
指定界面元素是否可见。跟"-vis"没什么区别。

"-ann/annotation" (注释)
给界面元素注释。注释可以在帮助栏显示,也可以让鼠标在界面元素上停一会,在弹出式的淡黄底色注释条看到。

"-bgc/backgroundColor" (底色)
要想编写彩色窗口就要用到这个标志。

还有一些共有标志留到以后讲。

布局到此已经全讲完了,接下来讲控件。
前面提到过一些控件:
"text"命令 (静态文本)
"textField"命令 (文本框)
"button"命令 (按钮)
"separator"命令 (分隔线)

这些控件虽然已经讲过了,但由于十分重要,现在在强调一下,一定要熟练掌握。

另外,先随便讲几个:
"picture"命令 (图片)
静态图片。
---------------------------------------------------
picture -w 80 -h 60 -image "sphere.xpm" -tile on;
---------------------------------------------------
"-i/image"指定图片。
"-tl/tile"指定图片是否重复排叠显示。

"iconTextButton"命令 (图标文本按钮)
既有字又有图标的按钮,你可以只显示字或图标。
---------------------------------------------------
string $window = `window`;
columnLayout -adj 1;
iconTextButton -style "textOnly"
-image1 "sphere.xpm" -label "sphere";
iconTextButton -style "iconOnly"
-image1 "spotlight.xpm" -label "spotlight";
iconTextButton -style "iconAndTextHorizontal"
-image1 "cone.xpm" -label "cone";
iconTextButton -style "iconAndTextVertical"
-image1 "cube.xpm" -label "cube";
showWindow $window;

"symbolButton"命令 (符号按钮)
有图片的按钮。
---------------------------------------------------
symbolButton -image "cone.xpm" -c "cone";
---------------------------------------------------

"intSlider"命令 (整数型滑动条)
---------------------------------------------------
intSlider -min -100 -max 100 -value 0;
---------------------------------------------------
"-min"最小值
"-max"最大值
"-value"当前值

一直这么讲下去难免有些枯燥乏味,还是来点实战轻松一下吧。
下面介绍一下赌牌游戏myFourUp的编写方法。
------------------------------
实例7:赌牌游戏myFourUp
myFourUp程序很像一个扑克机,它的原版是Stephen D. Gilbert的Visual C++ 6编程蓝皮书中的一个实例(FourUp),我认为这个程序用于Mel也同样很能说明问题,就擅自把它移植了过来,不过我在界面和算法部分都作了较大改动。

说到打赌,myFourUp程序与拉斯韦加斯和亚特兰大城中赌场工作人员特别喜爱的扑克游戏相似。但myFourUp更简单,因此你可以集中精力学习程序设计和界面编写方法。
myFourUp并非技巧游戏。当游戏开始时,给玩者1000美元。每一轮(需花一定赌注,比如2美元)玩者接到4张牌。牌值无关紧要-我们关心的只是花色。如果玩者得到两对(例如,两张红心和两张方片),将得4美元。如果玩者得到三个单张,将得到6美元。如果发牌者摆出四张同一花色,将得8美元。

当然,游戏并非使用真钱。如果你想编写程序以便能拨入玩者的银行帐户来取出赢利(或弥补亏空),那是你自己的事。

在我们编写代码之前,让我们先花一点时间来计划一下程序的外观以及它将如何工作。最好是用纸和笔画出该布局,毕竟修订一个计划比改变一个完成的程序要容易。

现在可以根据草图来编写代码,不过编写代码之前最好先讲一下什么是全局变量。
全局变量(Global Variable)
我们以前用到的变量都是局部变量。局部变量只能在函数里面用,当函数运行时局部变量被创建,函数运行结束后,局部变量就被删除了。

如果用户需要在一个函数中创建的变量,其它的函数也可用,则用户可以通过全局变量来实现。如果你创建了一个全局变量,你可以通过函数,也可以通过命令行不断修改它的值。全局变量将一直保留在内存中,直到你退出Maya时才会被删除。

在变量声明前加上"global"表示为全局变量。(在命令行声明全局变量可以不写"global")
例如:
global float $counter;

跟椐Maya帮助文档的说法应当尽量少用全局变量,否则很容易会因为粗心而出错。比如你声明了一个全局变量$counter,而Maya中却碰巧已经有了一个全局变量$counter,此时你对$counter变量值所作的任何修改都将影响以前就存在的全局变量$counter,这将会产生错误。
为避免碰巧同名变量的存在,请用较为独特的名称来命名全局变量。Maya中的全局变量都是以小写字母"g"开头(例如$gMainWindow),你可以用除"g"以外的你喜欢的字母开头,本教程中用到的全局变量一般都以"mg"开头。

下面开始我们的程序。程序虽然简单,但这个是一个完善的游戏程序,对于某些初学者来说可能有些难度,如果你实在看不懂,就索性照抄一遍,借此体验一个编程的感觉。

在myFourUp程序中,用一个全局变量$mgMoneyRemaining来记录你拥有的总钱数,当你赢钱时总钱数会增加,反之则会减少。
----------------------------------
//global proc myFourUp(){以下的代码写在括号中}

// 初始化总钱数为1000美元
global int $mgMoneyRemaining;
$mgMoneyRemaining = 1000;

界面代码:
首先创建窗口:
----------------------------------
if(`window -ex myFourUpWin`) deleteUI myFourUpWin;
window -t "Four Up"
-widthHeight 293 440
-sizeable false
myFourUpWin;
----------------------------------
"-wh/widthHeight"指定窗口的长度和宽度分别为多少像素。也可用"-w"和"-h"分别来指定。
"-s/sizeable"指定窗口是否可以拉大缩小(变化尺寸)。

为了能够随意摆放控件,用一个表单布局作为窗口的主布局。
----------------------------------
string $form = `formLayout -backgroundColor 0.353 0.353 0.259`;
----------------------------------
创建布局$form中的元素:
----------------------------------
// 标题图片
string $pTitle_f = `picture -image "title_f.bmp" fourUpTitle`;
// 边框图片
string $pFrame_l = `picture -image "frame_l.bmp"`;
string $pFrame_r = `picture -image "frame_r.bmp"`;
string $pFrame_b = `picture -image "frame_b.bmp"`;

// 位图按钮(图标文本按钮)
string $bBtn_1 = `iconTextButton -style "iconOnly"
-backgroundColor 0.353 0.353 0.259
-w 106 -h 41 -image1 "btn_deal.bmp"
-c "dealCards"`;
string $bBtn_2 = `iconTextButton -style "iconOnly"
-backgroundColor 0.353 0.353 0.259
-w 106 -h 41 -image1 "btn_Exit.bmp"
-c "deleteUI myFourUpWin;"`;

// 横布局
// 包括一个静态图片,一个文本框和一个符号按钮
string $row = `rowLayout -numberOfColumns 3
-backgroundColor 0.353 0.353 0.259
-columnWidth3 82 60 80`;
picture -image "deposit.bmp";
textField -ed false -tx "$2" placeBetField;
symbolButton -image "max.bmp" -c "placeBetMax";
setParent ..;

// 整数型滑动条
string $slider = `intSlider
-backgroundColor 0.568 0.588 0.463
-min 2 -max 100 -value 2 -step 20
-dragCommand "placeBet" placeBetSlider`;

// 两个边框布局
string $frame_1 = `frameLayout -backgroundColor 0.863 0.729 0.435
-label "你还剩:$1000 "
-labelAlign "bottom" -borderStyle "etchedIn"
-h 65 MoneyRemainingFrame`;
// @@@A 创建布局$frame_1中元素的代码写在这里:

setParent ..;

string $frame_2 = `frameLayout -backgroundColor 0.863 0.729 0.435
-label "赢钱规则:"
-labelAlign "bottom" -borderStyle "etchedIn"
-h 110`;
// @@@B 创建布局$frame_2中元素的代码写在这里:

setParent ..;
----------------------------------

指定布局$form中每个界面元素的位置,
注意每张图片所放置的位置。
----------------------------------
formLayout -e
// frame_l.bmp直接放在布局$form的左上角
-af $pFrame_l "top" 0
-af $pFrame_l "left" 0

// title_f.bmp
-af $pTitle_f "top" 0 // 上边紧靠布局的上边
-ac $pTitle_f "left" 0 $pFrame_l // 左边紧靠frame_l.bmp

// frame_r.bmp
-af $pFrame_r "top" 0 // 上边紧靠布局的上边
-ac $pFrame_r "left" 0 $pTitle_f // 左边紧靠title_f.bmp

// frame_b.bmp
-af $pFrame_b "bottom" 0 // 底边紧靠布局的底边
-ac $pFrame_b "left" 0 $pFrame_l // 左边紧靠frame_l.bmp

-ac $frame_1 "top" 5 $pTitle_f
-ac $frame_1 "left" 5 $pFrame_l
-ac $frame_1 "right" 2 $pFrame_r

-ac $frame_2 "top" 12 $frame_1
-ac $frame_2 "left" 5 $pFrame_l
-ac $frame_2 "right" 2 $pFrame_r

-ac $row "top" 12 $frame_2
-ac $row "left" 5 $pFrame_l

-ac $slider "top" 8 $row
-ac $slider "left" 5 $pFrame_l
-ac $slider "right" 2 $pFrame_r

// btn_deal.bmp
-ac $bBtn_1 "bottom" 0 $pFrame_b // 底边紧靠frame_b.bmp
-ac $bBtn_1 "left" 0 $pFrame_l // 左边紧靠frame_l.bmp

// btn_exit.bmp
-ac $bBtn_2 "bottom" 0 $pFrame_b // 底边紧靠frame_b.bmp
-ac $bBtn_2 "right" 0 $pFrame_r // 右边紧靠frame_r.bmp
$form;
----------------------------------
显示窗口:
----------------------------------
showWindow myFourUpWin;
----------------------------------

界面和主要部分编好了,下面让我们来填补两个边框布局。

在前面代码的"@@@A..."这句注释之后填写如下代码:
----------------------------------
// 布局$form_1中的布局分两层
string $form_1 = `formLayout`;
// 先写的为第一层,第一层放置底纹图片"back_1.bmp"
// "-tl"图片重叠摆放
string $pBack_1 = `picture -tl 1 -image "back_1.bmp"`;

// 放置四种花色图片
// spade - 黑桃,club - 梅花,diamond - 方片,heart - 红心。
string $card_s = `picture -image "card_spade.bmp" cardImg_0`;
string $card_c = `picture -image "card_club.bmp" cardImg_1`;
string $card_d = `picture -image "card_diamond.bmp" cardImg_2`;
string $card_h = `picture -image "card_heart.bmp" cardImg_3`;

// 指定位置
formLayout -e
-af $pBack_1 "top" 0
-af $pBack_1 "bottom" 0
-af $pBack_1 "left" 0
-af $pBack_1 "right" 0

-af $card_s "top" 6
-ap $card_s "left" 8 0

-af $card_c "top" 6
-ap $card_c "left" 8 25

-af $card_d "top" 6
-ap $card_d "left" 8 50

-af $card_h "top" 6
-ap $card_h "left" 8 75
$form_1;

setParent ..;

布局$form_2编写的方法类似布局$form_1,我就不做解释了。
在"@@@B..."这句注释之后填写如下代码:
----------------------------------
string $form_2 = `formLayout`;
string $pBack_2 = `picture -tl 1 -image "back_1.bmp"`;
string $pT1 = `text -l " 两对同花 $4"
-backgroundColor 0.568 0.588 0.463 placeBetText1`;
string $pT2 = `text -l " 三个同花 $6"
-backgroundColor 0.568 0.588 0.463 placeBetText2`;
string $pT3 = `text -l " 全部同花 $8"
-backgroundColor 0.568 0.588 0.463 placeBetText3`;

formLayout -e
-af $pBack_2 "top" 0
-af $pBack_2 "bottom" 0
-af $pBack_2 "left" 0
-af $pBack_2 "right" 0

-ap $pT1 "top" 10 0
-af $pT1 "left" 15
-af $pT1 "right" 15

-ap $pT2 "top" 10 30
-af $pT2 "left" 15
-af $pT2 "right" 15

-ap $pT3 "top" 10 60
-af $pT3 "left" 15
-af $pT3 "right" 15
$form_2;

// 因为$form_2之后不再创建界面元素了,所以可以不写"setParent ..;"

界面部分编完了,现在来编写代码让这个游戏运转起来。
在前面的代码中,有三处需要调用自定义的命令,分别是:

1)位图按钮(btn_deal.bmp)。
string $bBtn_1 = `iconTextButton ... -c "dealCards"`;

2)"-dragCommand"拉动滑动条时执行的命令。
string $slider = `intSlider ... -dragCommand "placeBet" placeBetSlider`;

3)横布局中的符号按钮(max.bmp)。
symbolButton ... -c "placeBetMax";

// 按下"Max"(max.bmp)按钮执行的命令
global proc placeBetMax()
{
// 把滑动条拨到最右边
intSlider -e -v 100 placeBetSlider;
// 文本框中填入相应的值$100
textField -e -tx "$100" placeBetField;

// 修改赢钱规则
text -e -l (" 两对同花 $200") placeBetText1;
text -e -l (" 三个同花 $300") placeBetText2;
text -e -l (" 全部同花 $400") placeBetText3;
}

// 拉动滑动条时执行的命令
global proc placeBet()
{
// 获得滑动条上的数值
int $deposit = `intSlider -q -v placeBetSlider`;
// 文本框中填入滑动条的数值
string $text = "$" + $deposit;
textField -e -tx $text placeBetField;

// 修改赢钱规则
int $m1 = $deposit * 2;
int $m2 = $deposit * 3;
int $m3 = $deposit * 4;

text -e -l (" 两对同花 $" + $m1) placeBetText1;
text -e -l (" 三个同花 $" + $m2) placeBetText2;
text -e -l (" 全部同花 $" + $m3) placeBetText3;
}

现在来对付关键部分,相对比较复杂。
我们可以先来考虑一下,当玩家点击了"deal"按钮时,我们希望发生什么?
1)随机选择4张牌作为一付牌。
2)更新每张牌的图像以指示该付牌的花色。
3)统计每种花色牌的张数。
4)统计同种花色的牌一共有几对。
5)从总钱数扣去赌注。
6)计算输赢,如果赢钱加入总钱数中。
7)赢钱后加亮赢钱规则字样的底色。
8)修改标题图片,如果是赢钱显示"Congratulations",否则显示"Misfortune"。
8)显示剩余金额。
----------------------------------
// 位图按钮(btn_deal.bmp)命令
global proc dealCards()
{
global int $mgMoneyRemaining;

// 分别用四个变量记录每种花色牌的张数
int $num_spade = 0;
int $num_club = 0;
int $num_diamond = 0;
int $num_heart = 0;
// 用$pairs记录同种花色的牌一共有几对
int $pairs = 0;

for($i=0; $i<4; $i++)
{
// 随机选择4张牌中的一张
int $num = `rand 4`;
if($num == 0) {
// 更新花色牌的图片
picture -e -image "card_spade.bmp" ("cardImg_"+$i);
// 统计每种花色牌的张数
$num_spade++; // $num_spade = $num_spade + 1
}
if($num == 1) {
picture -e -image "card_club.bmp" ("cardImg_"+$i);
$num_club++;
}
if($num == 2) {
picture -e -image "card_diamond.bmp" ("cardImg_"+$i);
$num_diamond++;
}
if($num == 3) {
picture -e -image "card_heart.bmp" ("cardImg_"+$i);
$num_heart++;
}
}

// 统计同种花色的牌一共有几对
if($num_spade == 2)
$pairs++;
if($num_club == 2)
$pairs++;
if($num_diamond == 2)
$pairs++;
if($num_heart == 2)
$pairs++;

//----------------------------------------------
// 从滑动条上得到赌注数额
$deposit = `intSlider -q -v placeBetSlider`;
int $m1 = $deposit * 2;
int $m2 = $deposit * 3;
int $m3 = $deposit * 4;

// 先用总钱数减去赌注
$mgMoneyRemaining -= $deposit;

// 调整文字,为的是保证文本框底色的刷新
text -e -l "" -backgroundColor 0.568 0.588 0.463 placeBetText1;
text -e -l "" -backgroundColor 0.568 0.588 0.463 placeBetText2;
text -e -l "" -backgroundColor 0.568 0.588 0.463 placeBetText3;
// 修改标题图片为"Misfortune"
picture -e -image "title_m.bmp" fourUpTitle;

// 两对同花
if($pairs == 2) {
// 赢的钱加入总钱数中
$mgMoneyRemaining += $m1;
// 加亮文本底色,变为亮黄色
text -e -backgroundColor 1 1 0.463 placeBetText1;
// 修改标题图片为"Congratulations"
picture -e -image "title_c.bmp" fourUpTitle;
}

// 三个同花
if( $num_spade == 3 || $num_club == 3 ||
$num_diamond == 3 || $num_heart == 3 ) {
$mgMoneyRemaining += $m2;
text -e -backgroundColor 1 1 0.463 placeBetText2;
picture -e -image "title_c.bmp" fourUpTitle;
}

// 全部同花
if( $num_spade == 4 || $num_club == 4 ||
$num_diamond == 4 || $num_heart == 4 ) {
$mgMoneyRemaining += $m3;
text -e -backgroundColor 1 1 0.463 placeBetText3;
picture -e -image "title_c.bmp" fourUpTitle;
}

// 刷新
text -e -l (" 两对同花 $" + $m1) placeBetText1;
text -e -l (" 三个同花 $" + $m2) placeBetText2;
text -e -l (" 全部同花 $" + $m3) placeBetText3;

// 显示剩余金额
frameLayout -e -label ("你还剩:$" + $mgMoneyRemaining) MoneyRemainingFrame;
}

全部代码编写完毕,现在你可以体验赌牌的乐趣了。

我把全部范例代码和图片放到附件中,供你们参考。

1515133-myFourUp.rar (56.52k)

"if"语句 (如果)
关于"if"语句以前讲的不够详细,现在做一点简要补充。小括号里的双竖线"||"表示"或"的意思,而"&&"表示"并且"的意思。

"if"后面的小括号里的代码表示的只能是一个布尔值,1或0。
因此要表示相等要写双等号。

"双等号的用法"
----------------------------------
int $test = 4;
int $bool = $test == 5; // 如果$test等于5,$bool = 1(true)
----------------------------------
这里因为$test = 4,不等于5,所以$bool = 0(false)。

"rand"命令 (随机)
前面用到"rand"命令,"rand"是随机的意思,就是从指定的范围内随便取出一个数值。mel里的"rand"同C语言的"rand"有一个很明显的差别,mel是浮点数的随机,C语言是整数的随机。

我在myFourUp程序中写的"int $num = `rand 4`;" 实际上是一种浮点数转整数的方法,还有一种写法更容易看明白一些:
float $floatNum = `rand 4`;
int $num = (int)$floatNum;

学习 · 提示

  • 一定要打开PS,跟着教程做一遍,做完的图到这交作业:提交作业
  • 建议练习时,大家自己找素材,尽量不要用教程提供的素材。
  • 教程有看不懂的地方,可以到论坛发帖提问:新手求助
  • 加官方微信,随时随地,想学就能学:ps_bbs,或扫右侧二维码!
  • 关注我们学更多,每天都有新教程:新浪微博 抖音视频 微信小程序
- 发评论 | 交作业 -
最新评论
42017-01-13 04:08
42017-01-13 03:52
挺牛逼的
2016-03-14 07:30
超赞的文章
陈军波2015-12-14 12:59
竟然没人评论

关注大神微博加入>>

网友求助,请回答!