伪3D Flash菜单傻瓜教程

2006-11-04 16:30 | Army

考虑到这个包含AS的Flash教程可能对于部分不懂编程的人来说难以理解,所以牵涉到一些程序基础的问题,我会尽量用蓝色字体先介绍一些概念问题。当然,稍微有点基础的人就可以跳过,甚至根本不必看我的方法——直接就能自己写。好了,废话不多说,这个教程还是面向广大的入门用户的。但愿不要有人抱怨这个教程实在是太长了……

在即将开始之前,我首先假定读者懂得一些Flash的基本操作,理解补间动画的原理等,一些过于简单的东西就不多费口舌了。这个菜单的源文件如下(mx2004以上版本):

http://ff9.ffsky.cn/flash_teach\3dmenu/exp.fla

先Ctrl+F9建立一个图形元件,我用的是一个圆角矩形,以左上角顶点对齐,取名square。如果你乐意,完全可以自己创建出复杂绚丽的图案效果:




再建立一个MC(影片剪辑),取名square_movie,把刚才的square拖进去,居中对齐。新建个图层,再用文字工具在图层中心建个文本框,它的大小自己可根据需要调整,我是52*18的。建完后在下方的属性框中记得把它调成动态文本,再把变量取个名字intro。图中蓝色框框就是动态文本框,红色的地方是标出变量名。



好了,我来介绍一下变量的概念吧,这1+1=2的问题,sigh……
AS中定义变量是var这个关键字,不过我们可以把它省略掉,也可不指明类型。我想这也是为什么AS是脚本语言而非真正的语言的原因……
假如定义一个intro_exp的变量:
var intro_exp = "Army";
再用Flash的trace函数(可以理解为一种打印给你看的方法)输出这个变量:
trace(intro);
这样屏幕上就会显示Army的字样。上边Army两侧的英文字符双引号是说:Army是个字符串,输出时要输出全部字符串!
如果这样定义:
var intro_exp = 3;
现在没有""号了,这就说明它是个整数3。好了,不再费口舌了,我相信大多数人认为这是在浪费时间。


别忘了把这个square_movie标上链接符,方法是在库中右键单击,然后选链接,在里面给它起个变量名chs,如图:



现在要开始进入正题了,建好的square_movie只是在库中,没有放到舞台上,这怎么能看见呢?显然,那个起的名字chs就起作用了,有了它我们就可以随时随地的把它调用出来。这好比你有了个名字,别人就可以随时叫你出来一样。
在新建的Flash文件中第一帧(最好是专门用来放AS的空白帧)上写上如下代码:

//五个按钮的旋转
d = Math.PI * 2 / 5;
for(i = 1; i <= 5; i++) {
_root.attachMovie("chs","chs" + i,i);
eval("chs" + i).rot = i * d;
eval("chs" + i)._alpha = 0;
eval("chs" + i).num = i;
}


先来说明下d = Math.PI * 2 / 5;这句的意思。
Math.PI是AS内部提供的特殊变量,就是数学中的弧度值π(180度),Math.PI * 2自然就是乘以2后得360度一个圆周了!后面的/ 5是除以5,为什么除以5呢?因为我有5个子菜单,你需要几个就除以几,不过注意数值过大或过小可能导致看起来很别扭。自然整句的意思就是:把360度的弧度平均分成5等分,然后把这一等分的弧度值赋给变量d。
下面是个for循环,可能有人对for语句看起来感觉很恐怖,我还是来白痴地说明下吧……

写个小例子就可以使你明白这玩意儿了:
for(i = 1; i <= 5; i++) {
trace(i);//输出i的值。
}
意思是,把for这里花括号{}中的内容连续执行一定次数,多少次则是for后面()里规定的。上面(i = 1; i <= 5; i++)规定的是,从i = 1开始执行,每执行一次i++自增1,到i <= 5即小于等于5为止。所以这个的打印结果就是:12345。OK,简单?


现在继续看上面的程序,_root.attachMovie("chs","chs" + i,i);,这里用到了_root.attachMovie方法,意思是把库中的影片剪辑添加到舞台上来!至于如何添加,后面的("chs","chs" + i,i);规定了内容:把库中名字叫做chs的MC添加上来、取新名字为"chs" + i,深度级别是i。
"chs" + i可能新手乍看很糊涂,它的意思是把chs这个字符串与整数i相连起来,并最终作为一个字符串!所以5次循环之后,添加到舞台上的5个MC则各自有了自己的新名字:chs1、chs2、chs3、chs4、chs5。这样,5个长得一模一样的MC有了不同的名字,就容易使用它们了。
最后的深度级别很容易理解,数值越高层次越高,就像是图层越靠上一样。

eval("chs" + i).rot = i * d;这句,eval("chs" + i)是AS内部函数,把()内的变量转换成可以访问的MC变量名。有人可能很糊涂:用eval干什么,直接"chs" + i不就行了?请注意:"chs" + i是一个变量,但它不是一个MC的名字!名字为a的MC和变量a有着本质的区别,eval的作用说白点就是把变量的内容转换成MC的名字!于是这句的意思就是:为刚才for循环添加的舞台上的5个MC各自定义它本身的一个rot变量。整句中的.符号就是区分变量作用域的。

好了,我相信也许会有新手对作用域和那个.起疑问了,下面是介绍:
Flash中的最根部的就是舞台,_root就是这个舞台的名字,这下你们理解了前面出现的那个_root的意思了吧。在舞台上放一个MC(名字就叫MC吧),则MC属于这个舞台,再在这个MC中放一个新的MC(管它叫X吧),则X属于MC,当然,X也属于舞台。如果在舞台上的帧上定一一个变量a = 3;则只能在舞台上直接访问,如果在MC或X中访问,得加上一个路径访问:_root.a,直接写a是错误的。如果在MC中定义b = 4;则在MC的帧上可以直接使用b,而在舞台和X上都得这样访问:_root.MC.b。明白了吗?.的作用之一就是区分它们之间的包含层级关系,这些只是介绍绝对访问,至于相对访问,还是自己去查help手册吧,它和绝对访问一样容易。


回到eval("chs" + i).rot = i * d;,相信都能理解了吧,为添加到舞台上的5个MC定义了不同的各自名为rot的变量,chs1.rot就是1 * d,chs2.rot就是2 * d …… chs5.rot就是5 * d。你问d是什么?看开头,那个5等分的圆周弧度……
再来,eval("chs" + i)._alpha = 0;这句,_alpha(以下划线_开头的一般是指MC的属性)是透明度,这样5个MC全部被设置成为0透明度,也就是看不见。这个是为了避免刚被附加到舞台上时出现的位置问题,你可以再完成全部后去掉这句看看有什么情况,注意要眼尖点才行。
eval("chs" + i).num = i;,和那个rot一样,为5个MC分别定义了num变量,值是从1到5。

mymove = function() {
for(i = 1;i <= 5;i++) {
var tem = eval("chs" + i).rot += 0.02;
var tt = 40 * Math.cos(tem - Math.PI / 2) + 60;
eval("chs" + i)._x = 220 + 110 * Math.cos(tem - 0.2);
eval("chs" + i)._y = 220 + 50 * Math.sin(tem);
eval("chs" + i)._xscale = eval("chs" + i)._yscale = 30 * Math.sin(tem) + 80;
eval("chs" + i).swapDepths(tt);
eval("chs" + i)._alpha = tt;
}
}
choose = setInterval(mymove,20);


mymove = function() {}定义了一个匿名函数,你也可以叫它方法。简单点讲,定义函数(函数的内容则是紧接着的配对花括号{})就是将一连串的程序代码合并到一起并起个名字,调用时用那个名字就执行了所有的一串程序。

举个例子来说:
mymove = function() {
var a = 3;
var b = "Army";
trace(a);
trace(b);
}
这样,就可以用mymove这个名字来调用定义的那个匿名函数了。执行操作过后,结果是:3Army。显然,定义的a和b即输出都得到了执行。至于不是匿名的普通函数,则是这样定义:
function print_x(x) {
trace(x);
}
发现有什么不同了吗?前面那个定义的名字没了,function后面跟了一个具体的实实在在的名字:print_x。再后边(x)是行参,如果没有行参,也得写上(),比如:print_x()。加上行参是为了让你理解一些概念,此时我们这样调用这个函数:print_x(3),则会打印3;如这样用print_x(5),则打印5。明白了吗?实际调用时的(3)和(5)叫做实参。


回到上面那个程序,依然是个for循环。var tem = eval("chs" + i).rot += 0.02;这句我是合起来写的,分开就是eval("chs" + i).rot += 0.02;var tem = eval("chs" + i).rot;第一句意思是每个MC的rot变量自增0.02,然后第二句把自增后的值赋给tem变量。0.02这个数值可以自己改,它决定了5个MC旋转的速度。为什么呢?因为这个方法是每隔一定时间执行一次的,这样每个MC的弧度值都会一直不断改变,改变的多少就是那个0.02决定了,如果你写0.05,这样每次移动得多,当然显得快了。
var tt = 40 * Math.cos(tem - Math.PI / 2) + 60;这句,与平面直角坐标系有关,如果你觉得烦,就不用多考虑,直接使用吧,脑子中有这个数学模型的人可以继续深入。tt是定义了这个MC的深度与透明度,40 * Math.cos(tem - Math.PI / 2) + 60;,可以大概算出来它的范围吧,[20,100]。
eval("chs" + i)._x = 220 + 110 * Math.cos(tem - 0.2);和eval("chs" + i)._y = 220 + 50 * Math.sin(tem);两句,_x是MC的横坐标,_y是它的纵坐标。想想tem变量是不断在改变的,220 + 110 * Math.cos(tem - 0.2)和220 + 50 * Math.sin(tem)的运算是用三角函数不断使MC的弧度在一个椭圆线上改变。有人问为什么?很显然,220和220是圆心,也就是直角坐标系的圆点,110和50则是半径(数学牛人可能认为我在说谎了,椭圆中只有长轴短轴,不应该说半径,我这样只是便于一般人理解而已)。第一句中的tem减去0.2是为了让5个MC旋转起来有些倾斜的感觉,想一想这是为什么?这些数值都是可改的,你自己可以试试。
eval("chs" + i)._xscale = eval("chs" + i)._yscale = 30 * Math.sin(tem) + 80;设定了每个MC不同时刻的大小。_xscale是指它的横向缩放百分比,_yscale是纵向的。这样,根据不同时刻的弧度值,用三角函数就可推算出离近时放大一些,离远时缩小一点。
eval("chs" + i).swapDepths(tt);和eval("chs" + i)._alpha = tt;两句,第一句是将影片的剪辑的深度与tt交换,这样,tt的值是一直在变的,所以当MC移动得较近时就可以通过三角函数计算得到比较高的层级,移动远时就低,如此近的层级高于远的,效果就是近的在上面,远的在下面,伪3D效果也就出来了。那个第二句是设定透明度的,近的透明度高,远的透明度低,更增加了3D感。
好了,这个函数介绍完了,记得一开始说的每隔一段时间执行一次这个函数吗?最后一句choose = setInterval(mymove,20);就是这个,setInterval是AS内部函数,功能是每隔一段时间执行一个函数。两个行参中,20是20毫秒,执行的函数名是mymove。前面定义的choose则是将这个setInterval函数赋给这个变量。这里有点嵌套的概念,mymove本身就是个函数,setInterval是执行它的函数,choose则是这个函数赋值的变量。


5个MC的旋转已经讲解完,接下来是制作鼠标疑上去的效果了。

//按钮介绍的显示与隐藏
/*1*/
chs1.intro = "me";
chs2.intro = "article";
chs3.intro = "web";
chs4.intro = "code";
chs5.intro = "words";
/*2*/
function disintro(n)
{
/*3*/
mcx = eval("chs" + n)._x;
mcy = eval("chs" + n)._y;
mcw = eval("chs" + n)._width;
mch = eval("chs" + n)._height;
/*4*/
textintro._x = eval("chs" + n)._x + eval("chs" + n)._width * 0.5;
textintro._y = eval("chs" + n)._y + eval("chs" + n)._height * 0.5;
/*5*/
if(mcx >= 220 && mcy >= 227) {
textintro.gotoAndStop(2);
}
else if(mcx < 220 && mcy >= 227) {
textintro.gotoAndStop(3);
textintro._x -= mcw;
}
else if(mcx < 220 && mcy < 227) {
textintro.gotoAndStop(4);
textintro._x -= mcw;
textintro._y -= mch;
}
else if(mcx >= 220 && mcy < 227) {
textintro.gotoAndStop(5);
textintro._y -= mch;
}
/*6*/
switch(n) {
case 1:
textintro.textintro = "个人资料";
break;
case 2:
textintro.textintro = "收藏文字";
break;
case 3:
textintro.textintro = "页面作品";
break;
case 4:
textintro.textintro = "源码作品";
break;
case 5:
textintro.textintro = "给我留言";
break;
default:
break;
}
/*7*/
dis_fade_controll = setInterval(dis_fade,10);
}
/*8*/
function fadintro() {
textintro.gotoAndStop(1);
clearInterval(dis_fade_controll);
}
/*9*/
function dis_fade() {
textintro._alpha += 5;
if(textintro._alpha >= 80)
textintro._alpha = 80;
}


但愿你不要被这段程序吓着,看上去它很长,但其实很简单,比前面的那个三角函数要容易多了。
我在一些行之前标了行号,为了便于讲解,它放在说明/**/中,会被解释器忽略,实际使用时可以删掉。
首先/*1*/段,还记得库中square_movie影片剪辑里的那个动态文本吗,chs1.intro = "me";等5句就是将它们赋值。这里你如果有OOP的经验的话就会非常容易理解——库中的MC是一个class类,舞台上添加的5个MC就是其生成的对象,intro动态文本则是类的一个属性。有人说,Flash其实就是一个OOP软件,这点在这里可以非常贴切地证明。
接着/*2*/段,介绍相应的AS之前需要先建立一个元件,我为之取名text_line,形状如图:



然后作一个遮照效果的text_line_movie,就是一条竖线作mask从左边移动到右边,形成最终效果中那个移动的红点(线)的:



最后新建一个text_intro影片剪辑,它共有5帧,头一帧空白并写上如下AS:
this.stop();this._alpha = 0;
用来让它刚被载入时就停止在第一帧。后面四帧则是把text_line和text_line_movie放进去,不过每次都以原点水平翻转或者垂直翻转一下,这样做是为了让你鼠标移上去后它在四个象限上的显示方向都是正确的——第一象限是朝右上,第二朝右下,第三左下,第四左上。另外还要在处于4个象限上的红色方框内各自放入一个动态文本框,名字都取为textintro。



把做好的这个MC拖到舞台上来吧,由于是第一帧空白,所以你只能看到一个标志其坐标的小圆点。别忘了把它命名为和库中一样的名字——text_intro。
OK,继续来说/*2*/段吧。首先定义了一个disintro(n)函数,n是传入的参数,至于在哪传入后面将介绍,你可以首先理解为鼠标放到那5个不同的MC上,获取的n值从1到5不等。
/*3*/段的4句话,获取了鼠标所指的那个MC的x、y坐标值及高宽度,然后赋给了定义的4个变量(注意我省略了var关键字)。
/*4*/段的2句话是关键,将text_intro的坐标值设定为鼠标所指的MC的坐标值各加上其高宽的一半。为什么呢?因为square_movie当初创建时是以左上角为顶点的,加上一半后text_intro的坐标是否就是它的正中心了?我想聪明的读者可能已经发现我这样写其实浪费笔墨,直接将mcx + mcw * 0.5赋给它不就行了?
理解了上面之后,/*5*/段的就很清楚了:根据鼠标当前坐标判断是在第几象限,然后text_intro跳转到相应的帧,以实现4个象限不同方向的效果。text_intro目前是在所指MC的坐标中心,不同象限只要x或y坐标再加或减MC的坐标值一半即可。新手大概不理解if条件是什么意思吧,我还是来充当一下那个傻瓜一样的引路人,其它的人可以跳过并继续~

条件语句有if和switch,两者功能相同,只是适应场地不同,举个例子来说:
var age = 21;
var sex = "male";
if(age <= 21 && sex == "male") {
trace("我是正太米");
}
else if(age > 21 && sex == "male") {
trace("我是叔叔米");
}
else if(age <= 21 && sex != "male") {
trace("我是loli米");
}
else {
trace("我是大姨妈米");
}
每个if或者else if后面的括号是判断语句,如果其为真,则执行后面{}的语句。最后一个else的意思是上面所有的判断都不符合,执行我吧。很显然,这个例子的输出结果是“我是正太米”。&&是并且操作符,只有当两个判断都为真时最终结果才为真。
如果改写成switch,则应该这样写:
var level = 40;
switch(level) {
case 10:
trace("10级洋葱剑士");
break;
case 30:
trace("30级魔界幻士");
break;
case 40:
trace("40级贤者");
break;
case 99:
trace("99级眼怪");
break;
default:
trace("我是大鸟,等级对我来说是浮云!");
break;
}
这个程序里switch对()里进行匹配判断,找到和level相同的case语句,然后执行。break的意思是执行完毕后跳出swtich,否则它会继续向下搜索匹配的东西,直到最后。末尾的default和if语句中的else功能一样。这个无疑输出“40级贤者”。


懂得了switch之后,/*6*/段的东西也很好理解了,根据鼠标指向的MC的不同获取不同的n行参后,让textintro变量文本得到不同的值,从而实现了指向不同的MC显示不同的文字说明效果。注意我写的textintro.textintro可能有些语意混淆,通常这不是一个好的编程风格,头一个textintro是指处在舞台上的textintro影片剪辑,后一个textintro是指影片剪辑里的那个名叫textintro的动态文本。这段话很拗口吧,你可以把这个作为一个反面教材,来警惕自己以后不要出现类似的情况。
/*7*/段使用了AS内部函数,每隔10毫秒执行dis_fade函数。
/*8*/段定义了fadintro函数,它的功能是清除dis_fade函数,并让textintro影片剪辑重新回到第一帧空白帧。
/*9*/段定义了dis_fade函数,它使textintro的透明度不断自增5,直到80。那个if语句判断如果大于80的话,还是会重新被设置为80的。

别以为这样就结束了,还有最后一部呢,选择库中开始做好的square_movie影片剪辑,在上面加入如下AS:

/*1*/
this.onRollOver = function() {
clearInterval(_root.choose);
this._xscale = this._yscale *= 1.1;
_root.disintro(num);
_root.over_btn.gotoAndPlay(2);
}
/*2*/
this.onRollOut = function() {
_root.choose = setInterval(_root.mymove,20);
this._xscale = this._yscale /= 1.1;
_root.fadintro();
}
/*3*/
this.onRelease = function() {
clearInterval(_root.choose);
_root.fadintro();
_root.goto(num);//点击显示不同内容
}


/*1*/段定义了一个匿名函数,this.onRollOver意思是每当鼠标移到这个MC的上面时如何如何。函数内部首先清理一个choose函数,choose将在后面定义。this._xscale = this._yscale *= 1.1;的意思是这个MC的宽高各增大到1.1倍,从而实现鼠标移上去后放大效果。后面的_root.disintro(num);很眼熟吗?这里就是上面disintro传入的实参,你问num是多少,往上翻,看看一开始是否就为5个MC各自初始化了自己的num变量值。最后一句是一个声音文件,和主程序无太大关联,你可以忽略它。
/*2*/段的this.onRollOut是指鼠标移开。鼠标移上去时5个MC围绕椭圆的旋转要停止,这就是choose的用途:清楚mymove函数。鼠标移开后要继续旋转,所以mymove要继续每隔20毫秒执行,这就是第一句的意思。第二句和上面相反,移开后MC的宽高要缩小1.1倍。最后一句调用fadintro函数。
/*3*/段this.onRelease是指鼠标点击释放后执行的函数。点击后要跳到不同的地方去,所以先清理choose,然后执行fadintro。最后那个goto函数我没写,自己想想也就明白了:根据不同的num值用if或switch判断,然后根时间轴跳到别的位置去。这样就实现了点不同的MC,跳转到不同的内容效果。

好了,还有什么不明白的吗,回帖提问吧。我是个错字大王,里面可能有错字或者程序写错的地方,请指出。如果你看了这篇教程后觉得至少理解了一些东西,那说明我的辛苦努力还是有点收获的……
最后要感谢来福同学的一针见血,若不是他说我的2.0版主页的背景图被用烂的话,这个2.5版也就不会出了,当然也就不会有此教程。

by Army.