轻轻松松学习JavaScript
JavaScript 的历史虽短,但却发展迅速。最初NetScape开发了LiveScript语言,使得它的Navigator和Web 服务器产品有了基本的脚本编写功能。当 Navigator 2.0中加入了Java小程序后,NetScape才把LiveScript变成了JavaScript,至此JavaScript诞生。
本文将以一个有趣的小程序为开端,介绍JavaScript的基本语法与功能。此趣味程序为小球碰撞仿真程序,它可仿真弹性碰撞和动能有损的不完全恢复碰撞。你若想了解此例程中的球间碰撞处理程序,需熟悉矢量的坐标旋转和一些基本的物理学知识。如果你对此一窍不通,也没什么关系,因为对于小球之间的碰撞程序仅为几个函数,你只管调用就是了,这并不影响我们对JavaScript 的学习。之所以要写这样一个带点旁门左道的趣味例程,只是希望你对JavaScript多增加一分兴趣。
废话就不多说,现在开始吧。
第一步:材料的准备
制作两张小球图片,红球 、蓝球 各一张。图片格式为 GIF。你可先用3D软件制作出立体球图片,然后再将其转为GIF格式,这样程序动画会显得更加逼真。需注意的是图片背景一定要透明,不然就会让人看出破绽。最后还需一张背景图,图片格式同上。
第二步:编写主页骨架
1. 在NetObject ScriptBuilder 中用New 命令生成一个HTML文件的骨架。顺便在新生成的HTML文件中,将TITLE标志之间的文字改为"小球碰撞仿真演示程序"。
2. 加入背景图案和小球图片资源。
将BODY标志改为,此语句中的background 属性指定window.gif 为背景图案。
为加载小球图片资源,在标志与间添加以下两句
这两句HTML脚本的含义完全相同,标志IMG用于加入图片资源,其中SRC属性指明加入的图片文件名。ID属性指出图片的对象名称,这是个重要属性,因为以后在JavaScript 编程中要使用它。如第一句中,指明BallBlue为图片资源的对象名称(你可以把它想象成变量名)。STYLE属性指明图片在浏览器中的风格,其中"position" 开关设为"absolute",这样图片在窗口中的定位就可使用绝对坐标模式,而非文本行模式。
HTML 部分的编写到此完成。接下来该用JavaScript 让图片动起来了。
第三步:编写JavaScript 程序
1.仿真程序使用窗口对象的SetTimeout()方法循环调用Move_Step()函数,达到移动图片的目的,而Move_Step()根据速度矢量来计算移动一步的长度和方向。碰撞处理分为两部分,第一是球对墙壁的碰撞处理,其处理函数为Ball_Crash_Wall(),第二是球体之间的碰撞处理,其处理函数为Ball_Crash_Ball()。还需设置一些碰撞标志变量,记录当前碰撞状态,以避免仿真误差带来的错误,也许现在你还不明白为什么误差会导致错误,不要紧,后面会慢慢告诉你。
2.图片对象使用属性 style.pixelLeft、style.pixelTop 指定图片在窗口上的位
置,这两个属性是图片运动的关键。不过在计算图片移动位置时并不用它,而是用数组变量BallCoord 记录图片当前坐标位置,因为属性 style.pixelLeft、style.pixelTop 是整型变量,而在计算碰撞中有小数生成,这样如果直接用 style.pixelLeft、style.pixelTop 记录位置,除了有误差,最重要的是在窗口边缘发生某些类型的球间碰撞时,会产生小球被撞出屏幕的错误。而数组变量可为浮点型,用于存放小数,从而避免了错误的发生。
(一)变量定义与程序的初始化
JavaScript 语句可放在HTML文件的主体(BODY区)中或文件头(HEAD区)中,区别是HEAD 区一般摆放预制好的函数和全局变量,以供BODY区的语句调用,而BODY区一般放入程序初始语句。JavsScript 的变量定义比较自由,但仍需满足先定义后使用的基本规则。
1.在HEAD区中定义全局变量:
Window_Top = 22 //窗口顶端位置。
Window_Left = 22 //窗口左端位置。
Window_Bottom = 294 //窗口底端位置。
Window_Right = 590 //窗口右端位置。
Setp_Proportion = 3.00 //速度放大比率。
BallCoord = new Array(new Array(2),new Array(2)) //定义一个二维数组,用于记录两球的当前坐标。
BallDiametre = new Array(66,92) //定义一维数组,存放两球的直径,其值分别为66和92。
BallMoveVector = new Array(new Array(2,1),new Array(1,-2))//定义一个二维数组,用于记录两球的速度向量,第二维分别存放X与Y轴的速度。
CrashingWall = new Array(new Array(false,false,false,false),new Array(false,false,false,false))//数组 CrashingWall 用于避免墙壁碰撞的二次计算,值为 true ,表明当前处于碰撞状态。初值为 false。
2.在BODY区中完成程序的初始工作。其内容如下:
这里你已经看见了一个JavaScript 程序的框架。标志SCRIPT 告诉浏览器,下面的内容为脚本程序,用LANGUAGE="JavaScript" 指明程序语言为JavaScript。不管是在BODY区,还是HEAD区中,任何JavaScript程序必须放在 标志之间。程序中用 new Array() 定义了一个数组Balls,以便以后存放图片对象。接下来的语句Balls[0] = BallBlue 将图片对象BallBlue 赋给Balls[0],如果你细心,你会发现BallBlue正是加载图片资源语句中指定的对象名。语句Balls[0].style.pixelTop = BallCoord[0][0] = 100,同时将值100赋给数组变量BallCoord[0][0]和图片对象的style.pixelTop属性。从而坐标数组变量BallCoord[0][0]得到了初值并且指定了球1的初始顶端位置。其余语句的含义雷同。最后一句调用函数Move_Step()将小球移动一步。
(二) 单步移动函数Move_Step()讲解
在HTML文件的HEAD区中编写Move_Step() 函数其内容如下:
function Move_Step() {
Ball_Crash_Ball() //调用球间碰状处理函数
Balls_Crash_Wall(0) //调用蓝球碰墙处理函数
Balls_Crash_Wall(1) //调用红球碰墙处理函数
//计算移动步长
BallCoord[0][0] += BallMoveVector[0][0] * Setp_Proportion
BallCoord[0][1] += BallMoveVector[0][1] * Setp_Proportion
BallCoord[1][0] += BallMoveVector[1][0] * Setp_Proportion
BallCoord[1][1] += BallMoveVector[1][1] * Setp_Proportion
//移动一步
Balls[0].style.pixelTop = BallCoord[0][0]
Balls[0].style.pixelLeft = BallCoord[0][1]
Balls[1].style.pixelTop = BallCoord[1][0]
Balls[1].style.pixelLeft = BallCoord[1][1]
window.setTimeout("Move_Step()",100) //100毫秒后再次调用Move_Step()函数
}
该函数的大部分含义,你可看其中的注释,这里只对几点加以说明。
1在语句BallCoord[0][0] += BallMoveVector[0][0] * Setp_Proportion中, BallMoveVector数组存放着速度向量。Setp_Proportion 变量存放步长放大比率,BallMoveVector[0][0] 乘以Setp_Proportion 得到移动一步的长度。最后该语句加上BallCoord[0][0]原值后再赋给BallCoord[0][0]本身,得到移动位置。
2.语句window.setTimeout("Move_Step()",100) 表示100毫秒后再次调用Move_Step()函数。现在有了setTimeout()方法,程序就不断的循环起来了。
Ball_Crash_Ball()是小球之间的碰撞处理函数, 你只管调用就是了,如果你不使用它,程序一样能运行,只是少了处理球间碰撞的功能。(在Ball_Crash_Ball()函数中调用了函数CrashEnd_Speed() 与atan360(x,y),以及使用了全局变量CrashingBalls,因而要正确使用它,必须将以上所说的函数与变量都拷入你的程序中)
(三) 在HEAD区中编写墙壁碰撞处理函数Balls_Crash_Wall()
一个简化的函数如下:
function Balls_Crash_Wall(i) {
if( BallCoord[i][0] <= Window_Top)
BallMoveVector[i][0] = - BallMoveVector[i][0]
if ( BallCoord[i][1] <= Window_Left)
BallMoveVector[i][1] = - BallMoveVector[i][1]
if ( BallCoord[i][0] + BallDiametre[i] >= Window_Bottom)
BallMoveVector[i][0] = - BallMoveVector[i][0]
if ( BallCoord[i][1] + BallDiametre[i] >= Window_Right)
BallMoveVector[i][1] = - BallMoveVector[i][1]
}
函数首先判断是否球已经碰到了墙壁,如是就将相应方向上的速度方向反向,而大小不变。但这个函数有可能发生错误(错误发生的原因有些复杂,这里只梢作提示。由于小球移动的步长往往大于1个点,而仿真又是离散进行的,从而在有些情况下会发生错误)。需用数组变量CrashingWall 来标识碰撞状态,使得小球与墙壁处于碰撞状态时不能再发生第二次小球与墙壁的碰撞,相应的语句也要更改,如第一句改为
if( BallCoord[i][0] <= Window_Top)
{ if (!CrashingWall[i][0]) { BallMoveVector[i][0] = - BallMoveVector[i][0] }
CrashingWall[i][0] = true
} else CrashingWall[i][0] = false
语句中CrahingWall数组的类型为逻辑型,记录球与墙壁是否正在发生碰撞。当为碰撞状态时,就不必再将球的运动方向反向。
到此程序的编写基本完成,其它详细内容见光盘的原程序及说明。现在在NetObject ScriptBuilder 中选择菜单Preview 项,运行程序。
附注:
1. 运行本例程需先安装IE4.0以上浏览器 或Netscape Navigator 4.0以上浏览器
2. 球间碰撞处理提示:当两球发生碰撞时,计算两球圆心连线与X轴的夹角。在依据此夹角对坐标进行旋转。将原速度矢量映射到旋转后的坐标中。从而将球间的任意碰撞转变为正对心碰撞。用冲量定理可推出碰后的速度计算公式。然后再将坐标旋转成原始坐标。可得碰撞后的速度矢量。看了上面的文字是不是有些烦人,其实并非如此,程序中只用了11行语句就完成了全部计算。