|
用Flash编写微型教学软件──《矢量分解》制作要点 |
紫琅中学 吴建国 |
| Macromedia公司的Flash是制作平面矢量动画的最流行的工具,目前主要的应用领域仍然是网页中的动画制作。可以相信,作为一款优秀的制作矢量动画的工具,在数学和自然科学课程的教学软件制作领域,Flash应该也会获得越来越广、越来越深的应用。作者做这样的判断是基于下述几点考虑:Flash提供的矢量绘图工具非常适合于科学和几何上的应用;Falsh 的播放文件数据量小,便于网上传送;目前最常用的网页浏览器中都已内嵌了Flash播放器;Flash入门比较容易;Flash 5的编程能力已经较为强大,例如可以控制立体声在不同声道的分配,可以从数据库获取数据,Flash MX的编程功能又有所增强;Macromedia公司为保持这个产品领先的竞争力,会不断地发展它的编程能力和其它能力;就国内的情况而言,短短三年的时间内,中学教师中用Flash制作课件的作者增加了不少,水平也有了明显的提高。本文作者也做了一些探索,这里介绍《矢量分解》的功能和制作过程。 打开《矢量分解》播放文件后,屏幕上呈现一个如图所示的平行四边形。原矢量的大小和方向,可以通过拖动位于原矢量末端的小图标来改变;分矢量的方向可以通过拖动位于分矢量上的小图标来改变。教师可以利用这个软件讲授力的分解、位移的分解、速度的分解、加速度的分解所遵循的定则,研究和例示涉及矢量分解的各种各样的问题。下面介绍用Flash 5制作《矢量分解》的过程,尤其是ActionScript代码的编写。(使用Flash 6或Flash MX,做法可以完全相同。) 一.创建符号 新建一个电影剪辑(Movie Clip),名称为“vector”,画一条线段,长度为100像素,水平,左端位于原点,右端配一个箭头,这个电影剪辑用以表示矢量。 新建一个电影剪辑,名称为“line”,画一条细线或虚线,长度为100像素,水平,左端位于原点,这个电影剪辑用以表示平行于分矢量的辅助线。 新建一个名称为“noAnswer”的“电影剪辑”:文字为“无解”。新建一个名称为“infinity”的电影剪辑,文字为“有无数的解”。 新建一个按扭(Button),其图形几何中心位于原点。新建一个名称为“aControl”的电影剪辑,其中引入上述按扭(将来在按扭上附上适当的ActionScript代码:指向按扭按住左键(press)之时,该电影剪辑开始移动,松开左键(release)之时,该电影剪辑停止移动)。再新建三个这样的内含按扭的电影剪辑,名称分别为“bControl”,“cControl”,“mainControl”。这四个可以跟随鼠标移动的电影剪辑用以确定第一分矢量的方向,第二分矢量的方向,原矢量末端的位置,平行四边形的位置。上述按扭,其第一帧上的图形(比如圆),尺寸应该很小,甚至为零,其第二帧至第四帧上的图形尺寸应该大一些。 新建一个名称为“empty”的电影剪辑,内部为空白。新建一个名称为“obsever”的电影剪辑,两帧(第二帧为普通帧),引入“empty”(将来在这个“empty”实例上捆绑比较长的一段ActionScript代码)。 新建一个名称为“main”的电影剪辑,内部暂时为空白。 二.组装 打开场景1(scene1),引入“main”,实例名与符号名相同。 打开符号库。 打开“main”。向“main”内引入一些符号:三个“vector”,实例名为“a”,“b”,“c”,它们的“中心”(左端)均位于原点,“vector”的这些实例用以表示第一个分矢量,第二个分矢量,原矢量;两个“line”,实例名为“u”,“v”,用以表示分矢量“a”,“b”的两条对边;“aControl”,“bControl”,“cControl”,“mainControl”各一个,实例名与符号名相同;“noAnswer”,“infinity”各一个,实例名与符号名相同;一个“obsever”。 三.编写ActionScript代码 “mainControl”内部的按扭实例,捆绑以下ActionScript代码: on (press) { _root.main.startDrag(true); } on (release) { stopDrag (); } 这段代码的功能是,鼠标指针指向“mainControl”按住左键,可以拖动“main”,松开左键,停止拖动。圆括号内的参数true表示拖动的对象“main”的中心位于鼠标指针末端。 “aControl”内部的按扭实例,捆绑以下ActionScript代码: on (press) { _root.main.aControl.startDrag(true); } on (release) { stopDrag (); } 这段代码的功能是,鼠标指向“aControl”时,按住左键,可以拖动“aControl”,松开左键,停止拖动。“bControl”和“cControl”内部的按扭实例捆绑类似的ActionScript代码。 “obsever”在这个微型软件中的作用,相当于乐队的指挥在整个乐队的表演中的作用。它内部的ActionScript代码负责指定有向线段c的末端随“cControl”的移动而移动,“a”的方向随“aControl”的移动而改变,“b”的方向随“bControl”的移动而改变,等等。 “obsever”内部捆绑在“empty”上的ActionScript代码,必须反复执行,为此把代码的主体部分放在以下代码的花括号内。 onClipEvent (enterFrame) { } “empty”这个电影剪辑每次进入下一帧时,都会执行这个花括号内的代码。 代码的主体部分如下,双斜杠后面的文字是注释。 cx = _root.main.cControl._x; cy = _root.main.cControl._y; cr = Math.sqrt(cx*cx+cy*cy); //变量cx和cy记录“cControl”的位置信息,变量cr记录“cControl”离原点的距离。 cRad = Math.atan2(cy, cx); cDegree = cRad*180/Math.PI; //cRad记录连结“cControl”与原点的线段的角度(跟x轴正方向的夹角),以弧度为单位;cDegree也是记录连结“cControl”与原点的线段的角度,以度为单位。 _root.main.c._xscale = cr; //设置原矢量“c”的长度等于cr。 _root.main.c._rotation = cDegree; //设置原矢量“c”的角度等于cDegree。 aRad = Math.atan2(_root.main.aControl._y, _root.main.aControl._x); aDegree = aRad*180/Math.PI; //aRad记录连结“aControl”与原点的线段的角度。第一分矢量“a”的角度应该等于aDegree。 bRad = Math.atan2(_root.main.bControl._y, _root.main.bControl._x); bDegree = bRad*180/Math.PI; acDegree = Math.abs(aDegree-cDegree); //acDegree记录“a”,“c”之间的夹角,取绝对值。 bcDegree = Math.abs(bDegree-cDegree); abDegree = Math.abs(aDegree-bDegree); acDegreeMin = Math.min(acDegree, 360-acDegree); //acDegreeMin记录“a”,“c”之间的夹角,取[0,180]范围内的数值。 bcDegreeMin = Math.min(bcDegree, 360-bcDegree); abDegreeMin = Math.min(abDegree, 360-abDegree); //以下把“noAnswer”等电影剪辑的“可见性”设置为“false”(不可见)。 _root.noAnswer._visible = false; _root.infinity._visible = false; _root.main.a._visible = false; _root.main.b._visible = false; _root.main.u._visible = false; _root.main.v._visible = false; //以下挑出两种无解情况,让读者看得见“noAnswer”。 if (acDegreeMin+bcDegreeMin>abDegreeMin+0.1) { _root.noAnswer._visible = true;} else if (Math.abs(aDegree-bDegree) == 180 && Math.abs(aDegree-cDegree)*Math.abs(bDegree-cDegree) != 0) { _root.noAnswer._visible = true;} //以下挑出两种有无数解的情况,让读者看得见“infinity”。 else if (Math.abs(aDegree-bDegree) == 180 && Math.abs(aDegree-cDegree)*Math.abs(bDegree-cDegree) == 0) { _root.infinity._visible = true;} else if (Math.abs(aDegree-cDegree) == 0 && Math.abs(bDegree-cDegree) == 0) { _root.infinity._visible = true;} //以下比较长的一段代码,针对普通情况。 else { _root.main.a._visible = true; _root.main.b._visible = true; _root.main.u._visible = true; _root.main.v._visible = true; //设置“a”,“b”,“u”,“v”可见。 ar = cr*Math.sin(bRad-cRad)/Math.sin(Math.PI-bRad+aRad); //利用正弦定理计算第一分矢量a的长度应取的数值ar。 _root.main.a._rotation = aDegree; //让“a”的角度等于aDegree。 _root.main.a._xscale = ar; //让“a”的长度等于ar。 _root.main.a._yscale = 100; //让“a”的粗细保持原来的数值。 br = cr*Math.sin(aRad-cRad)/Math.sin(Math.PI-aRad+bRad); _root.main.b._rotation = bDegree; _root.main.b._xscale = br; _root.main.b._yscale = 100; _root.main.u._rotation = aDegree; _root.main.u._xscale = ar; _root.main.v._rotation = bDegree; _root.main.v._xscale = br; //下面的ax,ay记录分矢量“a”的末端的位置信息,它们确定“v”的位置;bx,by记录分矢量“b”的末端的位置信息,它们确定“u”的位置。 ax = ar*Math.cos(aRad); ay = ar*Math.sin(aRad); bx = br*Math.cos(bRad); by = br*Math.sin(bRad); _root.main.u._x = bx; _root.main.u._y = by; _root.main.v._x = ax; _root.main.v._y = ay;} //以下针对两个矢量接近重合的情况.让一个矢量的粗细减为原来的50%。 if (acDegreeMin<3) { _root.main.a._yscale = 50;} if (bcDegreeMin<3) { _root.main.b._yscale = 50;} 四.润饰 可以通过有关面板确定各有向线段的颜色,用不同颜色的有向线段表示不同的矢量。可以通过有关面板确定“main”中各实例的参数,使得初始画面符合作者的期望;要达到这个目标,也可以在场景1第一帧放置适当的ActionScript代码,把“obsever”中引用的变量的初值做适当的设置。可以做一个电影剪辑,画上辐射状的一系列线段,作为调整和观察角度的基准,这个电影剪辑的可见性可以让读者选择。可以在“main”中放置几个“输入文本框”(input textbox),以便读者指定数据以及向读者显示数据。像前面叙述的那样用一个包含线段和箭头的电影剪辑表示一个矢量是方便的,但如果箭头画得长一些,当放大率比较大时,箭头会过分大,比较难看,如果箭头画得短一些,当放大率比较小时,箭头会过分小。为了解决这个问题,可以用一个包含线段的电影剪辑和一个包含箭头的电影剪辑共同表示一个矢量,两个电影剪辑的放大倍数(_xscale或_yscale)可以独立控制。 |