APP下载

基于JS的人类行为模拟及逼近算法的研究实现

2020-06-02吕丙东

关键词:眼珠调用眼球

高 云,吕丙东

(山西大同大学计算机与网络工程学院,山西大同 037009)

在互联网高速发展的今天,网络编程已经成为程序设计的最主要的需求。目前主流的页面设计语言是Java 语言,使用JSP 可以开发出功能强大的网站,但是JSP需要在服务器端执行,浏览器和服务器之间频繁的交互会大大降低页面响应的效率[1]。因此在不需要和服务器响应的情况下,应尽量避免使用JSP进行开发,前端程序设计语言JS就是最理想的选择了[1-2]。

JS 仅在页面前端进行响应,由浏览器进行解析,功能强大并且执行效率高[3],所以选用JS 来进行页面的开发。

1 研究综述

用户友好是页面设计必不可少的要素,模拟人类行为,尤其是思考即人工智能也是目前程序设计的重要思考方向[4]。从模拟人类动作的角度出发,使用JS编写页面程序模拟一双盯着鼠标的眼睛,页面加载时,将鼠标眼睛定位在页面的左上角固定位置,当鼠标在页面上移动时,鼠标眼睛会追随着鼠标进行移动,并且会调整眼球的方向,就像人类的眼睛一样,随着鼠标位置的变化眼球转动。当鼠标最终静止不动时,鼠标眼睛停留在鼠标上,两只眼球向内集中在鼠标箭头位置,类似于人类的对睛眼。

当鼠标移动时,鼠标眼睛追随鼠标移动的过程是一个逐渐逼近的过程,也就是说,鼠标眼睛和鼠标箭头之间的距离越大,移动速度越快,越接近鼠标位置时,移动速度越慢。通过这种方式能够模拟出鼠标眼睛移动的动态渐进过程,而不是将鼠标眼睛随着鼠标位置的变换进行跳跃式移动。眼球的转动也是逐渐进行的[4]。

其次,在鼠标眼睛移动的过程中,需要先计算出水平移动的距离(X轴方向)与垂直移动的距离(Y轴方向),然后分别根据计算结果进行水平和垂直的位移,由于水平和垂直的位移距离是不一样的,如果在两个方向上以同样的速度进行位移,有可能会先完成某一方向的位移之后还在继续进行另一方向的位移,为了避免这种情况,我们将鼠标眼睛在X轴方向和Y轴方向上成比例进行位移,这样,就保证了两个方向上的位移同时完成。

可以看出,整个程序实现的关键在于设置鼠标眼睛(图像元素)随着鼠标位置移动的坐标以及调节X轴方向和Y轴方向位移的比例。

2 页面初始化定位分析

在页面加载时,需要在页面上放置眼睛,并将其固定在页面的左上角位置,这部分功能由ECMAScript代码来完成。由于眼睛中的眼球部分是会随着鼠标的位置而转动方向的,即眼球会在眼睛的区域内移动而不是固定在眼睛区域的某个位置保持不动,所以必须将眼睛和眼球分别用2个不同的图片对象进行表示,然后叠加放置在一起表示一个完整的眼睛。如图1所示,使用图像“b.png”表示眼睛,图像“f.png”表示眼球。这里选择使用扩展名为png的图像,原因是图像的大小仅为几个KB,这就使的打开图像并不会成为拖累页面加载速度的因素。

图1 眼睛示意图

表示眼睛的图像(b.png)中眼睛是一对的,而表示眼球的图像(f.png)中眼球是单个的,这是因为眼睛移动时是作为一个整体一起随着鼠标移动的,而眼球随着鼠标一起移动的同时,还会跟随鼠标的方向分别转动,所以放置眼球时要将两个眼球单独放置以便它们分别进行不同的位移。

初始化鼠标眼睛时需要完成2个动作,第一是适当的按照预定位置和一定格式放置各个图像对象,使之组成图1 中所示的完整的眼睛,第二是给这些图像后期的动作设置明确的指向(包含所要完成动作的事件句柄)。

2.1 初始化对象位置

由于页面最初加载时即需要初始化鼠标眼睛,所以在window 对象的onload 事件中调用函数来完成初始化功能。即:

window.onload=setHandlers(200,100);

使用函数setHandlers(200,100) 在坐标(200,100)处放置鼠标眼睛,放置时,将鼠标眼睛分为三层对象,分别表示眼睛,左眼球和右眼球。

使用div元素来表示这三层对象,采用绝对定位放置对象。将眼睛放置在坐标(200,100)处,眼睛半径为20,左眼球放置于坐标(210,210)处(即左眼睛中央),右眼球放置于坐标(250,210)处(即右眼睛中央),左右眼球半径均为10。将眼睛初始化为如图2的样式,即眼球是在眼睛的正中央,如图2所示。

图2 眼睛初始化

程序中需要用到一些全局变量来在各个函数中共享鼠标眼睛和鼠标的坐标,在放置鼠标眼睛后,将这些全局变量赋值以便后面移动时使用。

mouseX,mouseY表示鼠标的当前位置,页面最初载入时,鼠标位置未知,所以需要初始化鼠标位置,而鼠标位置会引起鼠标眼睛的移动,为了在页面最初加载时不移动鼠标眼睛,所以将鼠标位置初始化为眼睛正中。realX和realY表示眼睛的当前目标位置,由于图像的位置是用其矩形区域的左上角坐标表示的,所以这里使用pixelLeft和pixelTop,即取眼睛对象样式的左端位置和顶端位置表示眼睛对象的坐标。各个变量含义如图3所示。

图3 各全局变量含义

2.2 完成对象的第一次移动

当需要的对象和全局变量都设置完成后,就可以着手设计鼠标眼睛后期的动作。由于鼠标眼睛完成的动作是随着鼠标位置的改变(鼠标移动)不停的趋近于鼠标,也就是说,后期鼠标的移动会触发的鼠标事件为onmousemove,因此在这里我们为document 对象的onmousemove 事件句柄分配值,使鼠标移动时会调用相应的函数[2],为了使程序结构更加紧凑,设计更加合理,我们使用了函数的闭包来完成这一功能[3],即将该函数体的实现整合在函数setHandlers()的内部。

鼠标移动时,获取事件发生位置的坐标值x和y,将其作为在document。body 中x轴和y轴方向上的偏移量,得到鼠标当前位置的坐标。需要注意的是,这里虽然定义了匿名函数,将鼠标移动时调用的代码写入函数中分配给onmousemove 事件句柄,但是却并没有立即调用函数,只有在页面加载后移动鼠标,触发了事件后代码才会生效,这样就保证了在整个页面运行过程中,移动鼠标都会执行这段代码[5]。

在该匿名函数中仅实现了鼠标移动时获取鼠标移动的位置坐标,并没有进一步规定鼠标眼睛完成的动作。我们将鼠标眼睛移动的动作写在函数moveEyes()中,紧接着上面的代码调用它,而不是将函数moveEyes()放在document.onmousemove 事件中调用。这样设计的目的有两个,第一,在页面初始化时,鼠标眼睛的样式是如图2 所示那样,我们希望在整个页面运行过程中鼠标眼睛的样式前后保持一致,即为图1 中所示的样式,所以在三层对象设置完后就需要移动鼠标眼睛让其改变初始样式。第二,由于鼠标眼睛追随鼠标位移是一个逐渐逼近的过程,如果将函数moveEyes()放在document.onmousemove 事件中调用,那么鼠标眼睛就像是粘在了鼠标上,随着鼠标移动寸步不离,达不到程序设计预期的效果。

3 模拟人类眼睛转动及渐进逼近的分析实现

鼠标眼睛移动的全部功能放在函数moveEyes()中实现,根据前面的分析,为了达到程序预期的效果,编写函数moveEyes()必须同时考虑以下三个问题。

(1)如何将组成鼠标眼睛的三层对象全部移动并且始终保持整体的视觉效果;

(2)如何实现鼠标眼睛移动时的渐次逼近;

(3)如何实现鼠标眼睛移动时在X轴和Y轴方向上的成比例移动。

由于组成鼠标眼睛的对象有三层,所以在移动时要考虑全部三层的位移情况,已经描述过,我们使用全局变量realX和realY表示眼睛的当前目标位置,而左右眼球在移动的过程中要始终保持在眼睛的范围内,其目标位置坐标由眼睛坐标和鼠标位置坐标计算得出,使用dxLeft 表示左眼球目标位置的x坐标,dxRight 表示右眼球目标位置的x坐标,二者在y轴上进行同步位移,使用dy表示左右眼球在目标位置上的y坐标。接下来的工作就是如何按照程序设计要求获取上述的这些坐标值。

3.1 眼睛逼近

鼠标眼睛在向鼠标移动的过程是一个逐渐逼近的过程,也就是说,在进行移动过程时,是由远到近,由快到慢的过程,而不是从前一个位置跳跃到目标位置。所以我们将整个位移的长度分别在x轴方向和y轴方向上分为若干个小的区间,鼠标眼睛分别在这些小区间上做渐次移动,每一次调用函数moveEyes()完成一次小区间上的移动,为此,我们需要不停的调用函数moveEyes()保证完成整个位移。使用定时器每隔规定的时间间隔就调用一次moveEyes()完成一次小区间位移是个好的选择,window 对象的setTimeout()方法可以完成这一要求,虽然该方法完成的是只调用一次代码,但调用的代码是函数自身也会构成循环调用。

调用时间的选取也是应该考虑的问题,时间太短会加重页面响应的负担,时间太长会使眼球转动显得呆滞,观察人类眼球转动的速度,设定调用时间为100。

tID=setTimeout('moveEyes()',100);

在完整位移上划分的若干小区间并不是等距的,由于移动是由快到慢的过程,即小区间的长度是越接近目标位置距离越小。我们将每个小区间的长度规定为当前需要位移长度的十分之一,当距离目标位置足够近时,小区间的长度是趋于0的。

首先确定每次位移眼睛的小区间长度,使用鼠标当前位置和取得的眼睛上一次停留的位置之间分别在x轴方向和y轴方向上的距离,再乘以0.1,就得到这次位移的小区间长度。即在X和Y轴上的位移量分别为(mouseX-realX-40) * 0.1 和(mouseY-realY-20)*0.1。

眼睛对象在x轴方向和y轴方向上的距离计算图示如图4所示。

图4 x轴y轴方向距离计算图示

将计算所得的当前距离*0.1,即得到本次位移的长度,加上上一次眼睛停留的位置坐标,得到眼睛的目标位置坐标。

需要注意的是,这里并没有获取鼠标眼睛实际的实时坐标,而只是选择当前能够得到的最近一次的坐标作为计算时的当前坐标,这样做并不是没有任何偏差,但是相对于获取实际的实时坐标付出的代价来说,这些偏差是可以忽略不计的,并且也是可以在后期加以控制的[3]。

3.2 眼球转动

相较于眼睛对象来说,眼球对象坐标的确定就要复杂多了,眼球不光要产生向鼠标位置逼近的动作,同时为了产生人类眼珠模拟的效果,眼珠还要在眼睛中有转动的动作,即眼珠首先要随着鼠标转动到鼠标的方向,再沿着鼠标方向和眼睛同时进行逼近,并且保证眼珠不能移动到眼睛之外。为了保证眼珠始终在眼睛之内,将眼珠的定位首先设置为眼睛正中,再加上一个不超出眼睛范围偏移量。

左眼珠在眼睛正中的位置为(realX+10,realY+10),右眼珠在眼睛正中的位置为(realX+50,realY+10),偏移量在X和Y轴方向都不能超过10,但是左右眼珠的偏移量是不同的,也就是说人类眼睛观看偏向某一方的物体的时候,两只眼球的角度是不同,即它们在X和Y轴上的位移是不同的,为了实现这个角度α,采用其当前位置与鼠标位置构成的直角三角形的斜边长度与两条直角边的比例乘以10 作为偏移量,如左眼球构成的直角三角形如图5所示。

图5 直角三角形示意图

眼球位移必须区分左右来单独计算。这里我们先来确定左眼球的坐标,由于不考虑左眼球的实际位置,计算就要简单的多,将眼球的当前位置始终认为是处于眼睛中央,而目标位置则总是认为向内集中,其位移计算如图6所示。

图6 左眼球位移示意图

那么其在x轴方向上和y轴方向上的距离分别为:

dy=mouseY-realY-20;

dxLeft=mouseX-realX-20;

计算斜边长度即左眼球与鼠标的直线距离为:

r=Math.sqrt(dxLeft*dxLeft+dy*dy)

由此确定左眼球的下一个位置为(dxLeft*10/r+realX+10,dy*10/r+realY+10)。

需要注意的是,如果眼睛已经移动到距离鼠标位置很近的位置时,眼睛位置和鼠标位置之间距离缩小,其位移距离也相应缩小,但是眼球和鼠标位置所构成的直角三角形的某一个锐角会变得很小[3],导致直角边和斜边比接近与1,眼球在眼睛中的偏移量就会接近于10,导致眼球在眼睛中的跳动,为了解决这个问题,采用将r的值始终保持在不小于眼睛的半径20,即r=Math。max(Math sqrt(dxLeft*dxLeft+dy*dy),20),保证在极为接近目标时,眼球在眼睛中呈缓慢转动状态。

理解了左眼球位移的计算方法,右眼球也就不难理解了,由于两眼球在y轴上的位移是相同的,只需要单独计算出右眼球x轴上的位移即可,不难看出,右眼球x轴上位移为mouseX-realX-60。

在页面存在的整个过程中,函数moveeyes()实际上是始终在不停的被调用的,假如鼠标眼睛已经成功的移到了目标位置,函数moveeyes()仍旧在不停的执行,只不过执行的是位移为0的移动。直到退出页面时,调用了window 对象的clearTimeout(tID)函数为止,该定时器才会被释放。

window.onunload=function() {if (tID) clearTimeout(tID);}。

4 算法运行分析

该算法运行时,可在页面加载时将眼睛定位与页面(200,100)的位置,并将眼珠向内集中。在鼠标移动的过程中,由远及近,速度又快到慢的接近鼠标位置,实现了逼近鼠标的功能,在眼睛逼近过程中眼珠始终保持着向着鼠标位置方向的转动。完整的实现了人类行为模拟及逼近算法的实现。

猜你喜欢

眼珠调用眼球
抓人眼球
抓人眼球
眼珠为何不怕冷
系统虚拟化环境下客户机系统调用信息捕获与分析①
“上帝”视角回顾2017年:8张照片震撼眼球
眼球经济(yǎn qiú jīng jì)[目玉経済]
基于属性数据的系统调用过滤方法
黑眼圈 红眼珠
利用RFC技术实现SAP系统接口通信
青蛙