当前位置: 首页 > 编程日记 > 正文

“AS3.0高级动画编程”学习:第二章转向行为(上)

因为这一章的内容基本上都是涉及向量的,先来一个2D向量类:Vector2D.as (再次强烈建议不熟悉向量运算的童鞋,先回去恶补一下高等数学-07章空间解释几何与向量代数.pdf)

package {import flash.display.Graphics;public class Vector2D {private var _x:Number;private var _y:Number;//构造函数public function Vector2D(x:Number=0,y:Number=0) {_x=x;_y=y;}//绘制向量(以便于显示)public function draw(graphics:Graphics,color:uint=0):void {graphics.lineStyle(0,color);graphics.moveTo(0,0);graphics.lineTo(_x,_y);}//克隆对象public function clone():Vector2D {return new Vector2D(x,y);}//位置归零public function zero():Vector2D {_x=0;_y=0;return this;}//是否在零位置public function isZero():Boolean {return _x==0&&_y==0;}//获得向量的角度public function get angle():Number {return Math.atan2(_y,_x);}//设置向量的模(即大小)public function set length(value:Number):void {var a:Number=angle;_x=Math.cos(a)*value;_y=Math.sin(a)*value;}//获取向量大小的平方public function get lengthSQ():Number {return _x*_x+_y*_y;}//获取向量的模(即大小)public function get length():Number {return Math.sqrt(lengthSQ);}//设置向量的角度public function set angle(value:Number):void {var len:Number=length;_x=Math.cos(value)*len;_y=Math.sin(value)*len;}	//截断向量(设置向量模最大值)public function truncate(max:Number):Vector2D {length=Math.min(max,length);return this;}//交换x,y坐标public function reverse():Vector2D {_x=- _x;_y=- _y;return this;}//定义二个向量的加法运算public function add(v2:Vector2D):Vector2D {return new Vector2D(_x+v2.x,_y+v2.y);}//定义二个向量的减法运算public function subtract(v2:Vector2D):Vector2D {return new Vector2D(_x-v2.x,_y-v2.y);}//向量模的乘法运算public function multiply(value:Number):Vector2D {return new Vector2D(_x*value,_y*value);}//向量模的除法运算public function divide(value:Number):Vector2D {return new Vector2D(_x/value,_y/value);}//判定二个向量(坐标)是否相等public function equals(v2:Vector2D):Boolean {return _x==v2.x&&_y==v2.y;}//设置x轴坐标public function set x(value:Number):void {_x=value;}//返回x轴坐标public function get x():Number {return _x;}//设置y轴坐标public function set y(value:Number):void {_y=value;}//返回y轴坐标public function get y():Number {return _y;}//单位化向量(即设置向量的模为1,不过这里用了一种更有效率的除法运算,从而避免了lengh=1带来的三角函数运算)public function normalize():Vector2D {if (length==0) {_x=1;return this;}//建议大家画一个基本的3,4,5勾股定理的直角三角形即可明白下面的代码var len:Number=length;_x/=len;_y/=len;return this;}		//判定向量是否为单位向量public function isNormalized():Boolean {return length==1.0;}//点乘(即向量的点积)public function dotProd(v2:Vector2D):Number {return _x*v2.x+_y*v2.y;}//叉乘(即向量的矢量积)public function crossProd(v2:Vector2D):Number {return _x*v2.y-_y*v2.x;}//返回二个向量之间的夹角public static function angleBetween(v1:Vector2D,v2:Vector2D):Number {if (! v1.isNormalized()) {v1=v1.clone().normalize();}if (! v2.isNormalized()) {v2=v2.clone().normalize();}return Math.acos(v1.dotProd(v2));//建议先回顾一下http://www.cnblogs.com/yjmyzz/archive/2010/06/06/1752674.html中提到的到夹角公式}//判定给定的向量是否在本向量的左侧或右侧,左侧返回-1,右侧返回1public function sign(v2:Vector2D):int {return perp.dotProd(v2)<0?-1:1;}//返回与本向量垂直的向量(即自身顺时针旋转90度,得到一个新向量)public function get perp():Vector2D {return new Vector2D(- y,x);//建议回顾一下"坐标旋转"}//返回二个矢量末端顶点之间的距离平方public function distSQ(v2:Vector2D):Number {var dx:Number=v2.x-x;var dy:Number=v2.y-y;return dx*dx+dy*dy;}//返回二个矢量末端顶点之间的距离public function dist(v2:Vector2D):Number {return Math.sqrt(distSQ(v2));}//toString方法public function toString():String {return "[Vector2D (x:"+_x+", y:"+_y+")]";}}
}

有几个地方稍加解释:

1、向量夹角的计算

gif.latex?%5Ccos%20(%5CTheta%20)=%5Cfrac%7B%5Cunderset%7Ba%7D%7B%5Crightarrow%7D%5Ccdot%20%5Cunderset%7Bb%7D%7B%5Crightarrow%7D%7D%7B%5Cleft%20%7C%20%5Cunderset%7Ba%7D%7B%5Crightarrow%7D%20%5Cright%20%7C%5Ctimes%5Cleft%20%7C%20%5Cunderset%7Bb%7D%7B%5Crightarrow%7D%20%5Cright%20%7C%20%7D
上图为向量的夹角公式,再来对照一下代码部分:

public static function angleBetween(v1:Vector2D,v2:Vector2D):Number {if (! v1.isNormalized()) {v1=v1.clone().normalize();}if (! v2.isNormalized()) {v2=v2.clone().normalize();}return Math.acos(v1.dotProd(v2));
}

首先对向量v1,v2做了单位化处理,使其变成(模为1的)单位向量,这样夹角公式中的|a|×|b|(即分母)自然也就是1,公式演变成cos(θ)=a.b(即夹角余弦 等于 向量a与b的点乘),然后再对其取反余弦Math.acos,最终得到夹角

2、垂直向量的取得

gif.latex?%5Cleft%5C%7B%5Cbegin%7Bmatrix%7D%20x_%7B1%7D%20=%20x_%7B0%7D%5Ccos(%5Calpha%20)%20-%20y_%7B0%7D%20%5Csin%20(%5Calpha%20)%5C%5C%20y_%7B1%7D%20=%20y_%7B0%7D%20%5Ccos%20(%5Calpha%20)+%20x_%7B0%7D%20%5Csin%20(%5Calpha%20)%5C%5C%20%5Cend%7Bmatrix%7D%5Cright.

上图是坐标(顺时针)旋转的标准公式,如果把α设置为90度,则

gif.latex?%5C%5Cx_%7B1%7D%20=%20-y_%7B0%7D%5C%5C%20y_%7B1%7D%20=%20x_%7B0%7D,即:

public function get perp():Vector2D {return new Vector2D(- y,x);
}

3、判定其它向量是在自身的左侧还是右侧

点击查看下一张

如上图,先取得A的垂直向量,然后计算其它向量跟垂直向量的点积(点乘的公式,在物理上的表现之一为 W = |F|*|S|Cos(θ) ),如果其它向量与该垂直向量的夹角小于90度,点乘的值必为正,反之为负,所以也就能判定左右了(注意:这里的左右是指人站在坐标原点,顺着向量A的方向来看的)

再来定义一个机车类Vehicle.as

package {import flash.display.Sprite;public class Vehicle extends Sprite {//边界行为:是屏幕环绕(wrap),还是反弹{bounce}protected var _edgeBehavior:String=WRAP;//质量protected var _mass:Number=1.0;//最大速度protected var _maxSpeed:Number=10;//坐标protected var _position:Vector2D;//速度protected var _velocity:Vector2D;//边界行为常量public static const WRAP:String="wrap";public static const BOUNCE:String="bounce";public function Vehicle() {_position=new Vector2D  ;_velocity=new Vector2D  ;draw();}protected function draw():void {graphics.clear();graphics.lineStyle(0);graphics.moveTo(10,0);graphics.lineTo(-10,5);graphics.lineTo(-10,-5);graphics.lineTo(10,0);}public function update():void {//设置最大速度_velocity.truncate(_maxSpeed);//根据速度更新坐标向量_position=_position.add(_velocity);			//处理边界行为if (_edgeBehavior==WRAP) {wrap();} else if (_edgeBehavior==BOUNCE) {bounce();}//更新x,y坐标值x=position.x;y=position.y;//处理旋转角度rotation=_velocity.angle*180/Math.PI;}//反弹private function bounce():void {if (stage!=null) {if (position.x>stage.stageWidth) {position.x=stage.stageWidth;velocity.x*=-1;} else if (position.x<0) {position.x=0;velocity.x*=-1;}if (position.y>stage.stageHeight) {position.y=stage.stageHeight;velocity.y*=-1;} else if (position.y<0) {position.y=0;velocity.y*=-1;}}}//屏幕环绕private function wrap():void {if (stage!=null) {if (position.x>stage.stageWidth) {position.x=0;}if (position.x<0) {position.x=stage.stageWidth;}if (position.y>stage.stageHeight) {position.y=0;}if (position.y<0) {position.y=stage.stageHeight;}}			}//下面的都是属性定义public function set edgeBehavior(value:String):void {_edgeBehavior=value;}public function get edgeBehavior():String {return _edgeBehavior;}public function set mass(value:Number):void {_mass=value;}public function get mass():Number {return _mass;}public function set maxSpeed(value:Number):void {_maxSpeed=value;}public function get maxSpeed():Number {return _maxSpeed;}public function set position(value:Vector2D):void {_position=value;x=_position.x;y=_position.y;}public function get position():Vector2D {return _position;}public function set velocity(value:Vector2D):void {_velocity=value;}public function get velocity():Vector2D {return _velocity;}override public function set x(value:Number):void {super.x=value;_position.x=x;}override public function set y(value:Number):void {super.y=value;_position.y=y;}}
}

没有什么新东西,都是以前学到的知识,测试一下上面这二个类:

package {	import flash.display.Sprite;import flash.display.StageAlign;import flash.display.StageScaleMode;import flash.events.Event;public class VehicleTest extends Sprite {private var _vehicle:Vehicle;public function VehicleTest() {stage.align=StageAlign.TOP_LEFT;stage.scaleMode=StageScaleMode.NO_SCALE;_vehicle=new Vehicle  ;addChild(_vehicle);_vehicle.position=new Vector2D(100,100);_vehicle.velocity.length=5;_vehicle.velocity.angle=Math.PI/4;//45度addEventListener(Event.ENTER_FRAME,onEnterFrame);}private function onEnterFrame(event:Event):void {_vehicle.update();}}
}

OK,现在可以进入正题了:(下面是从原书上直接抄过来的)

转向行为(steering behaviors)这一术语,指的是一系列使对象行动起来像似长有智商的算法。这些行为都归于人工智能或人工生命一类,是让对象呈现出拥有生命一般,对如何移动到目的地、捕捉或逃避其它对象、避开障碍物、寻求路径等做出因地适宜的决定。  

一、寻找行为(Seek)

简单点讲,就是角色本身试图移动(包括转向)到目标位置(这个位置可能是固定的,也可能是移动的)。

先定义一个从Vehicle继承的子类:具有转向能力的机车SteeredVehicle.as

package {import flash.display.Sprite;//(具有)转向(行为的)机车public class SteeredVehicle extends Vehicle {private var _maxForce:Number=1;//最大转向力private var _steeringForce:Vector2D;//转向速度public function SteeredVehicle() {_steeringForce = new Vector2D();super();}public function set maxForce(value:Number):void {_maxForce=value;}public function get maxForce():Number {return _maxForce;}override public function update():void {_steeringForce.truncate(_maxForce);//限制为最大转向速度,以避免出现突然的大转身_steeringForce=_steeringForce.divide(_mass);//惯性的体现_velocity=_velocity.add(_steeringForce);_steeringForce = new Vector2D();super.update();}}
}

代码不难理解:仅增加了最大转向力maxForce(主要是为了防止机车一瞬间就突然移动到目标位置,会引起视觉上的动画不连贯);另外对update做了重载处理,在更新机车x,y坐标及朝向(即rotation)之前,累加了转向速度并考虑到了物体的惯性。

再来考虑“寻找(seek)”行为,先看下面这张图:

5542838620100708162318077_640.jpg

根据向量运算,可以先得到机车期望的理想速度(desireVolocity)--注:如果用这个速度行驶,物体立马就能到达目标点。当然我们要体现物体是逐渐靠近目标点的,所以显然不可能用理想速度前行,而是要计算出转向速度force,最终再把转向速度force叠加到自身的速度_velocity上,这样机车就能不断向目标点移动了。

//寻找(Seek)行为
public function seek(target: Vector2D):void {var desiredVelocity:Vector2D=target.subtract(_position);desiredVelocity.normalize();desiredVelocity=desiredVelocity.multiply(_maxSpeed);//注:这里的_maxSpeed是从父类继承得来的var force:Vector2D=desiredVelocity.subtract(_velocity);_steeringForce=_steeringForce.add(force);
}

把这段代码加入到SteeredVehicle.as中就能让SteeredVehicle类具有seek行为,下面是测试代码:

package {import SteeredVehicle;import Vector2D;import flash.display.Sprite;import flash.display.StageAlign;import flash.display.StageScaleMode;import flash.events.Event;public class SeekTest extends Sprite {private var _vehicle:SteeredVehicle;public function SeekTest() {stage.align=StageAlign.TOP_LEFT;stage.scaleMode=StageScaleMode.NO_SCALE;_vehicle = new SteeredVehicle();addChild(_vehicle);addEventListener(Event.ENTER_FRAME, onEnterFrame);}private function onEnterFrame(event:Event):void {_vehicle.seek(new Vector2D(mouseX, mouseY));//以当前鼠标位置为目标点_vehicle.update();}}
}

二、避开(flee)行为

它跟寻找(seek)行为正好是相反的,可以通俗的理解为:“既然发现了目标,那么就调头逃跑吧”,所以代码上只要改一行即可

//避开(flee)行为
public function flee(target: Vector2D):void {var desiredVelocity:Vector2D=target.subtract(_position);desiredVelocity.normalize();desiredVelocity=desiredVelocity.multiply(_maxSpeed);var force:Vector2D=desiredVelocity.subtract(_velocity);_steeringForce=_steeringForce.subtract(force);//这是唯一与seek行为不同的地方,一句话解释:既然发现了目标,那就调头就跑吧!
}

同样,把上述代码加入到SteeredVehicle.as中就能让SteeredVehicle类具有flee行为,测试代码:

package {import SteeredVehicle;import Vector2D;import flash.display.Sprite;import flash.display.StageAlign;import flash.display.StageScaleMode;import flash.events.Event;public class FleeTest extends Sprite {private var _vehicle:SteeredVehicle;public function FleeTest() {stage.align=StageAlign.TOP_LEFT;stage.scaleMode=StageScaleMode.NO_SCALE;_vehicle = new SteeredVehicle();_vehicle.position = new Vector2D(stage.stageWidth/2,stage.stageHeight/2);_vehicle.edgeBehavior = Vehicle.BOUNCE;addChild(_vehicle);addEventListener(Event.ENTER_FRAME, onEnterFrame);}private function onEnterFrame(event:Event):void {_vehicle.flee(new Vector2D(mouseX, mouseY));//避开鼠标当前位置_vehicle.update();}}
}

seek行为与flee行为组合起来,可以完成类似“警察抓小偷”的效果

package {import SteeredVehicle;import Vector2D;import Vehicle;import flash.display.Sprite;import flash.display.StageAlign;import flash.display.StageScaleMode;import flash.events.Event;import flash.text.TextField;import flash.text.TextFormat;public class SeekFleeTest1 extends Sprite {private var _seeker:SteeredVehicle;//寻找者(可理解为:警察)private var _fleer:SteeredVehicle;//躲避者(事理解为:小偷)private var _seekerSpeedSlider:SimpleSlider ;//警察的最大速度控制滑块private var _txtSeekerMaxSpeed:TextField;private var _fleerSpeedSlider:SimpleSlider ;//小偷的最大速度控制滑块private var _txtFleerMaxSpeed:TextField;public function SeekFleeTest1() {stage.align=StageAlign.TOP_LEFT;stage.scaleMode=StageScaleMode.NO_SCALE;_seeker = new SteeredVehicle(0xff0000);_seeker.position=new Vector2D();_seeker.edgeBehavior=Vehicle.BOUNCE;addChild(_seeker);_seeker.maxSpeed = 5;_fleer = new SteeredVehicle(0x0000ff);_fleer.position=new Vector2D(stage.stageWidth*Math.random(),stage.stageHeight*Math.random());_fleer.edgeBehavior=Vehicle.BOUNCE;addChild(_fleer);addEventListener(Event.ENTER_FRAME, onEnterFrame);addSpeedControl();}//添加速度控制组件private function addSpeedControl():void{_seekerSpeedSlider = new SimpleSlider(5,25,10);_seekerSpeedSlider.rotation = 90;_seekerSpeedSlider.x = 150;_seekerSpeedSlider.y = 20;_seekerSpeedSlider.backColor = _seekerSpeedSlider.backBorderColor = _seekerSpeedSlider.handleColor = _seekerSpeedSlider.handleBorderColor =  0xff0000;addChild(_seekerSpeedSlider);_seekerSpeedSlider.addEventListener(Event.CHANGE,onSeekerSpeedChange); _txtSeekerMaxSpeed = new TextField();var _tfseeker:TextFormat = new TextFormat();_tfseeker.color = 0xff0000;_txtSeekerMaxSpeed.defaultTextFormat = _tfseeker;_txtSeekerMaxSpeed.text = "10";addChild(_txtSeekerMaxSpeed);_txtSeekerMaxSpeed.y = _seekerSpeedSlider.y -6;_txtSeekerMaxSpeed.x = _seekerSpeedSlider.x +3;_fleerSpeedSlider = new SimpleSlider(5,25,10);_fleerSpeedSlider.rotation = 90;_fleerSpeedSlider.x = 480;_fleerSpeedSlider.y = 20;_fleerSpeedSlider.backColor = _fleerSpeedSlider.backBorderColor = _fleerSpeedSlider.handleColor = _fleerSpeedSlider.handleBorderColor =  0x0000ff;addChild(_fleerSpeedSlider);_fleerSpeedSlider.addEventListener(Event.CHANGE,onFleerSpeedChange); _txtFleerMaxSpeed = new TextField();			var _tffleer:TextFormat = new TextFormat();_tffleer.color = 0x0000ff;			_txtFleerMaxSpeed.defaultTextFormat = _tffleer;_txtFleerMaxSpeed.text = "10";addChild(_txtFleerMaxSpeed);_txtFleerMaxSpeed.y = _fleerSpeedSlider.y -6;_txtFleerMaxSpeed.x = _fleerSpeedSlider.x +3;}function onSeekerSpeedChange(e:Event):void{_seeker.maxSpeed = _seekerSpeedSlider.value;_txtSeekerMaxSpeed.text = _seekerSpeedSlider.value.toString();}function onFleerSpeedChange(e:Event):void{_fleer.maxSpeed = _fleerSpeedSlider.value;_txtFleerMaxSpeed.text = _fleerSpeedSlider.value.toString();}private function onEnterFrame(event:Event):void {_seeker.seek(_fleer.position);//警察 抓 小偷_fleer.flee(_seeker.position);//小偷 躲 警察_seeker.update();_fleer.update();}		}
}

调整红色滑块和蓝色滑块,可改变seeker与fleer的最大速度。(注:代码中的SimpleSlider在Flash/Flex学习笔记(46):正向运动学中能找到) 如果愿意,您还可以加入碰撞检测,比如当“警察”抓住“小偷”时,显示一个提示:“小样,我抓住你了!”

如果加入更多的物体,比如A,B,C三个,让A追逐B同时躲避C,B追逐C同时躲避A,C追逐A同时躲避B,将是下面这副模样:

package {import flash.display.Sprite;import flash.display.StageAlign;import flash.display.StageScaleMode;import flash.events.Event;public class SeekFleeTest2 extends Sprite {private var _vehicleA:SteeredVehicle;private var _vehicleB:SteeredVehicle;private var _vehicleC:SteeredVehicle;public function SeekFleeTest2() {stage.align=StageAlign.TOP_LEFT;stage.scaleMode=StageScaleMode.NO_SCALE;_vehicleA=new SteeredVehicle(0xff0000)  ;_vehicleA.position=new Vector2D(stage.stageWidth*Math.random(),stage.stageHeight*Math.random());_vehicleA.edgeBehavior=Vehicle.BOUNCE;addChild(_vehicleA);_vehicleB=new SteeredVehicle(0x0000ff)  ;_vehicleB.position=new Vector2D(stage.stageWidth*Math.random(),stage.stageHeight*Math.random());_vehicleB.edgeBehavior=Vehicle.BOUNCE;addChild(_vehicleB);_vehicleC=new SteeredVehicle(0x00ff00)  ;_vehicleC.position=new Vector2D(stage.stageWidth*Math.random(),stage.stageHeight*Math.random());_vehicleC.edgeBehavior=Vehicle.BOUNCE;addChild(_vehicleC);addEventListener(Event.ENTER_FRAME,onEnterFrame);}private function onEnterFrame(event:Event):void {//A追求B,躲避C_vehicleA.seek(_vehicleB.position);_vehicleA.flee(_vehicleC.position);			//B追求C,躲避A_vehicleB.seek(_vehicleC.position);_vehicleB.flee(_vehicleA.position);//C追求A,躲避B_vehicleC.seek(_vehicleA.position);_vehicleC.flee(_vehicleB.position);_vehicleA.update();_vehicleB.update();_vehicleC.update();}}
}

Flash动画的边界,犹如人世间的一张网,将你我他都罩住,我们都在追寻一些东西,同时也在逃避一些东西,于是乎:爱我的人我不爱,我爱的人爱别人······ 现实如此,程序亦如此。

三、到达(arrive)行为

到达行为其实跟寻找行为很相似,区别在于:寻找行为发现目标后,不断移动靠近目标,但速度不减,所以会出现最终一直在目标附近二头来回跑,停不下来。而到达行为在靠近目标时会慢慢停下来,最终停在目标点。(这个咋这么熟悉?对了,这就是以前学习过来的缓动动画,详见Flash/Flex学习笔记(38):缓动动画)

//到达(arrive)行为
public function arrive(target: Vector2D):void {var desiredVelocity:Vector2D=target.subtract(_position);desiredVelocity.normalize();var dist:Number=_position.dist(target);if (dist>_arrivalThreshold) {desiredVelocity=desiredVelocity.multiply(_maxSpeed);} else {desiredVelocity=desiredVelocity.multiply(_maxSpeed*dist/_arrivalThreshold);}var force:Vector2D=desiredVelocity.subtract(_velocity);_steeringForce=_steeringForce.add(force);
}

当然这里的比例因子:_arrivalThreshold需要先定义,同时为了方便动态控制,还要对外以属性的形式暴露出来

private var _arrivalThreshold:Number=100;//到达行为的距离阈值(小于这个距离将减速)public function set arriveThreshold(value: Number):void {_arrivalThreshold=value;
}public function get arriveThreshold():Number {return _arrivalThreshold;
}

把上面这二段代码加入SteeredVehicle.as中,然后测试一把:

package {import flash.display.Sprite;import flash.display.StageAlign;import flash.display.StageScaleMode;import flash.events.Event;public class ArriveTest extends Sprite {private var _vehicle:SteeredVehicle;public function ArriveTest() {stage.align=StageAlign.TOP_LEFT;stage.scaleMode=StageScaleMode.NO_SCALE;_vehicle=new SteeredVehicle  ;addChild(_vehicle);addEventListener(Event.ENTER_FRAME,onEnterFrame);}private function onEnterFrame(event:Event):void {_vehicle.arrive(new Vector2D(mouseX,mouseY));_vehicle.update();}}
}

四、追捕(pursue)行为

追捕跟寻找很类似,不过区别在于:寻找(seek)是发现目标后,以预定的速度向目标靠拢,不管目标跑得多快还是多慢,所以如果目标比寻找者(seeker)要移动得快,seeker永远是追不上的;而追捕行为是要在目标前进的路上,提前把目标拦截到,也可以理解为先预定一个(target前进路线上的)目标位置,然后再以寻找行为接近该位置,所以只要预定目标位置计算得合理,就算追捕者的速度要慢一点儿,也是完全有可能把目标给抓住的。

554283862010070914301506_640.jpg

代码:

//追捕(pursue)行为
public function pursue(target:Vehicle):void {var lookAheadTime:Number=position.dist(target.position)/_maxSpeed;//假如目标不动,追捕者开足马力赶过去的话,计算需要多少时间var predictedTarget:Vector2D=target.position.add(target.velocity.multiply(lookAheadTime));seek(predictedTarget);
}

解释:假如目标不动的话,我们先计算二者之间的距离,然后以最大速度狂奔过去,大概需要lookAheadTime这么长时间,然后根据这个时间,得到预定的目标位置,再以该位置为目标,寻找(seek)过去。(当然这种算法并不精确,但是处理起来比较简单,重要的是:配合Enter_Frame事件后,它确实管用!)

测试代码:

package {import flash.display.Sprite;import flash.display.StageAlign;import flash.display.StageScaleMode;import flash.events.Event;import flash.events.MouseEvent;import flash.text.TextField;public class PursueTest extends Sprite {private var _seeker:SteeredVehicle;private var _pursuer:SteeredVehicle;private var _target:Vehicle;private var _isRun:Boolean = false;private var _text:TextField;public function PursueTest() {stage.align=StageAlign.TOP_LEFT;stage.scaleMode=StageScaleMode.NO_SCALE;_seeker = new SteeredVehicle(0x0000ff);				addChild(_seeker);_pursuer = new SteeredVehicle(0xff0000);			addChild(_pursuer);_target = new Vehicle(0x000000);			_target.velocity.length=15;//目标对象跑得快一点,这样才能看出区别addChild(_target);		_seeker.edgeBehavior = _target.edgeBehavior = _pursuer.edgeBehavior = Vehicle.BOUNCE;stage.addEventListener(MouseEvent.CLICK,stageClick);_text = new TextField();_text.text = "点击鼠标开始演示";_text.height = 20;_text.width = 100;			_text.x = stage.stageWidth/2 - _text.width/2;_text.y = stage.stageHeight/2 - _text.height/2;addChild(_text);}private function onEnterFrame(event:Event):void {_seeker.seek(_target.position);_seeker.update();_pursuer.pursue(_target);_pursuer.update();_target.update();}private function stageClick(e:MouseEvent):void{			if (!_isRun){_target.position=new Vector2D(stage.stageWidth/2,stage.stageHeight/2);addEventListener(Event.ENTER_FRAME, onEnterFrame);				_isRun = true;removeChild(_text);				}else{removeEventListener(Event.ENTER_FRAME, onEnterFrame);_isRun = false;_target.position = _seeker.position = _pursuer.position = new Vector2D(0,0);addChild(_text);_text.text = "点击鼠标重新开始";				}			}}
}

这里为了区别“追捕行为”与"寻找行为",我们同时加入了追捕者(_pursuer-红色)与寻找者(_seeker-蓝色),通过下面的演示可以看出,(红色)追捕者凭借算法上的优势,始终能更接近目标。

五、躲避(evade)行为

躲避跟追捕正好相反,可以理解为:如果我有可能挡在目标前进的路线上了,我就提前回避,让开这条道。(俗话:好狗不挡道)

//躲避(evade)行为
public function evade(target: Vehicle):void {var lookAheadTime:Number=position.dist(target.position)/_maxSpeed;var predictedTarget:Vector2D=target.position.add(target.velocity.multiply(lookAheadTime));flee(predictedTarget);//仅仅只是这里改变了而已
}

把前面学到的这些个行为放在一起乱测一通吧:

package {import flash.display.Sprite;import flash.display.StageAlign;import flash.display.StageScaleMode;import flash.events.Event;import flash.events.MouseEvent;import flash.text.TextField;public class PursueEvadeTest extends Sprite {private var _pursuer:SteeredVehicle;private var _evader:SteeredVehicle;private var _target:SteeredVehicle;private var _seeker:SteeredVehicle;private var _fleer:SteeredVehicle;private var _pursuer2:SteeredVehicle;private var _evader2:SteeredVehicle;private var _text:TextField;private var _isRun:Boolean = false;public function PursueEvadeTest() {stage.align=StageAlign.TOP_LEFT;stage.scaleMode=StageScaleMode.NO_SCALE;_pursuer=new SteeredVehicle(0xff0000);addChild(_pursuer);_evader=new SteeredVehicle(0x00ff00);			addChild(_evader);_target=new SteeredVehicle(0x000000);_target.velocity.length=15;			addChild(_target);_seeker=new SteeredVehicle(0xff00ff);addChild(_seeker);_fleer=new SteeredVehicle(0xffff00);			addChild(_fleer);_pursuer2 = new SteeredVehicle();addChild(_pursuer2);_evader2 = new SteeredVehicle();			addChild(_evader2);_evader2.edgeBehavior = _pursuer2.edgeBehavior = _target.edgeBehavior = _evader.edgeBehavior = _pursuer.edgeBehavior = _fleer.edgeBehavior = _seeker.edgeBehavior = Vehicle.BOUNCE;_text = new TextField();_text.text="点击鼠标开始演示";_text.height=20;_text.width=100;_text.x=stage.stageWidth/2-_text.width/2;_text.y=stage.stageHeight/2-_text.height/2;addChild(_text);stage.addEventListener(MouseEvent.CLICK,stageClick);}private function stageClick(e:MouseEvent):void {if (! _isRun) {_target.position=new Vector2D(stage.stageWidth/2,stage.stageHeight/2);_fleer.position=new Vector2D(400,300);_evader2.position=new Vector2D(400,200);_evader.position=new Vector2D(400,100);addEventListener(Event.ENTER_FRAME, onEnterFrame);_isRun=true;removeChild(_text);} else {_pursuer2.position =_evader2.position = _evader.position = _pursuer.position = _target.position=_seeker.position=_pursuer.position=	new Vector2D(0,0);removeEventListener(Event.ENTER_FRAME, onEnterFrame);_isRun=false;				addChild(_text);_text.text="点击鼠标重新开始";}}private function onEnterFrame(event:Event):void {_seeker.seek(_target.position);_fleer.flee(_target.position);_pursuer.pursue(_target);_evader.evade(_target);_pursuer2.pursue(_evader2);_evader2.evade(_pursuer2);_target.update();_seeker.update();_pursuer.update();_fleer.update();_evader.update();_pursuer2.update();_evader2.update();}}
}

对于这个示例,也许看不出”避开(flee)“与“躲避(evade)”的区别,反正都是不挡道嘛,没关系,下面会有机会看到区别的

六、漫游(wander)行为

顾名思义,就是要让物体在屏幕上漫不经心的闲逛。可能大家首先想到的是让速度每次随机改变一些(类似布朗运动),但很快您就会发现这样做的结果:物体象抽风一样在屏幕上乱动,一点都不连续,体现不出“漫不经心”闲逛的特征。所以我们需要一种更为平滑的处理算法:

2010070920560914.png

如上图,先在物体运动的正前方,画一个指定半径的圈,然后让向量offset每次随机旋转一个小小的角度,这样最终能得到转向力向量force=center+offset,最终把向量force叠加到物体的速度上即可.

private var _wanderAngle:Number=0;
private var _wanderDistance:Number=10;
private var _wanderRadius:Number=5;
private var _wanderRange:Number=1;//漫游
public function wander():void {var center:Vector2D=velocity.clone().normalize().multiply(_wanderDistance);var offset:Vector2D=new Vector2D(0);offset.length=_wanderRadius;offset.angle=_wanderAngle;_wanderAngle+=(Math.random()-0.5)*_wanderRange;var force:Vector2D=center.add(offset);_steeringForce=_steeringForce.add(force);
}public function set wanderDistance(value:Number):void {_wanderDistance=value;
}public function get wanderDistance():Number {return _wanderDistance;
}public function set wanderRadius(value:Number):void {_wanderRadius=value;
}public function get wanderRadius():Number {return _wanderRadius;
}public function set wanderRange(value:Number):void {_wanderRange=value;
}public function get wanderRange():Number {return _wanderRange;
}

虽然这次增加的代码看上去比较多,但是大部分是用于封装属性的,关键的代码并不难理解。好了,做下基本测试:

package {	import flash.display.Sprite;import flash.display.StageAlign;import flash.display.StageScaleMode;import flash.events.Event;public class WanderTest extends Sprite {private var _vehicle:SteeredVehicle;public function WanderTest() {stage.align=StageAlign.TOP_LEFT;stage.scaleMode=StageScaleMode.NO_SCALE;_vehicle = new SteeredVehicle();_vehicle.maxSpeed = 3;_vehicle.wanderDistance = 50;_vehicle.position=new Vector2D(200,200);//_vehicle.edgeBehavior = Vehicle.BOUNCE;addChild(_vehicle);addEventListener(Event.ENTER_FRAME, onEnterFrame);}private function onEnterFrame(event:Event):void {_vehicle.wander();_vehicle.update();}}
}

如果让漫游行为跟前面提到的行为组合,效果会更好一些:

package {import flash.display.Sprite;import flash.display.StageAlign;import flash.display.StageScaleMode;import flash.events.Event;import flash.events.MouseEvent;import flash.text.TextField;public class FleeEvadeWanderTest extends Sprite {private var _pursuer:SteeredVehicle;private var _evader:SteeredVehicle;private var _target:SteeredVehicle;private var _seeker:SteeredVehicle;private var _fleer:SteeredVehicle;		private var _text:TextField;private var _isRun:Boolean = false;public function FleeEvadeWanderTest() {stage.align=StageAlign.TOP_LEFT;stage.scaleMode=StageScaleMode.NO_SCALE;			_evader=new SteeredVehicle(0x00ff00);//躲避者(绿色)			addChild(_evader);_target=new SteeredVehicle(0x000000);//目标(黑色)			_target.velocity.length = 20;addChild(_target);		_fleer=new SteeredVehicle(0xffff00);//避开者(黄色)			addChild(_fleer);_target.edgeBehavior =  _evader.edgeBehavior =  _fleer.edgeBehavior = Vehicle.BOUNCE;_text = new TextField();_text.text="点击鼠标开始演示";_text.height=20;_text.width=100;_text.x=stage.stageWidth/2-_text.width/2;_text.y=stage.stageHeight/2-_text.height/2;addChild(_text);stage.addEventListener(MouseEvent.CLICK,stageClick);}private function stageClick(e:MouseEvent):void {if (! _isRun) {_target.position=new Vector2D(50,50);_evader.position = _fleer.position=new Vector2D(stage.stageWidth/2,stage.stageHeight/2);				addEventListener(Event.ENTER_FRAME, onEnterFrame);_isRun=true;removeChild(_text);} else {_evader.position = _target.position=_fleer.position=new Vector2D(0,0);removeEventListener(Event.ENTER_FRAME, onEnterFrame);_isRun=false;				addChild(_text);_text.text="点击鼠标重新开始";}}private function onEnterFrame(event:Event):void {_target.wander();			_fleer.flee(_target.position);			_evader.evade(_target);_target.update();			_fleer.update();_evader.update();}}
}

前面提到了flee(避开)与evade(躲避)很难看出区别,但在这个示例里,大概能看出一些细节上的些许不同:flee算法是以目标当前的位置为做基点避开的,而evade是以目标前进方向上未来某个时时间点的位置做为基点避开的,所以相对而言,(绿色的)evader更有前瞻性--即所谓的先知先觉,而(黄色的)fleer只是见知见觉,最终在视觉效果上,evader总是希望跟目标以反方向逃开(这样能躲得更远,更安全一点)。

注:博客园的nasa(微软MVP),对于本章内容也有相应的Sliverlight实现,推荐大家对照阅读。

转载于:https://www.cnblogs.com/yjmyzz/archive/2010/07/07/1773140.html

相关文章:

用Azure VM + Azure Database for MySQL搭建Web服务

仍然是一篇动手实验&#xff0c;实验演示如何在Azure的虚拟机内部署一个Web服务器&#xff0c;并且使用Azure Mysql PaaS作为本应用的数据库。此实验的目的一方面是为了演示Azure IaaS层和PaaS服务配合使用的常规操作&#xff0c;另一方面是为之后的文章打基础&#xff0c;后续…

C3P0_and_pro.properties配置文档代码

C3P0-config.xml配置文件 <c3p0-config> <!-- 默认配置&#xff0c;如果没有指定则使用这个配置 --> <default-config><property name"driverClass">com.mysql.jdbc.Driver</property><property name"jdbcUrl">jdbc:…

电视信号——行场同步

电视信号分NTSC制和PAL制两种制式, NTSC制每秒刷新60次, 而PAL制每秒刷新50次。 水平消隐&#xff1a;电子枪从左到右画出象素&#xff0c;它每次只能画一条扫描线&#xff0c;画下一条之前要先回到左边并做好画下一条扫描线的准备&#xff0c;这之间有一段时间叫做水平消隐&am…

QWidget一生,从创建到销毁事件流

版权声明&#xff1a;若无来源注明&#xff0c;Techie亮博客文章均为原创。 转载请以链接形式标明本文标题和地址&#xff1a;本文标题&#xff1a;QWidget一生&#xff0c;从创建到销毁事件流 本文地址&#xff1a;http://techieliang.com/2017/11/319/ 代码较多&#xff…

事物_软件分层

事务 事务是&#xff1a;在数据库指业务处理的”一个业务“对应数据库中的多个步骤的操作。例如银行转账。 面对的问题&#xff1a;程序接受请求后&#xff0c;会至少发送两条SQL语句&#xff0c;两条语句之间会有时间的间隔&#xff0c;如果间隔时间期间Mysql服务器发生意外&a…

雷林鹏分享:jQuery EasyUI 数据网格 - 创建属性网格

jQuery EasyUI 数据网格 - 创建属性网格 属性网格(property grid)带有一个内置的 expand(展开)/collapse(合并) 按钮&#xff0c;可以简单地为行分组。您可以简单地创建一个可编辑属性的分层(hierarchical)列表。 设置 HTML url"propertygrid_data.json" showGroup&q…

as3.0中如何阻止事件冒泡?

as3.0中的事件冒泡机制有时候会很烦人&#xff0c;比如一个Sprite(方便下文描述就命名为Container吧)把另一外Sprite(称为Child吧)做为子元素套进来以后&#xff0c;如果两个Sprite都注册了Mouse_Down事件&#xff0c;要想在Child上点击鼠标时系统只响应Child的Mouse_Down事件&…

紫色回归线:雅虎中国的运筹学

共同体并不意味着一个我们可以获得享受的世界&#xff0c;而是一个我们热切希望栖息、希望重新拥有的世界。—— 齐格蒙特.鲍曼紫色回归线&#xff1a;雅虎中国的运筹学紫色是比较中性的颜色&#xff0c;但紫色在生活中却也是温柔、神秘、甚至性感的代词。当雅虎中国将主色调重…

JavaScript_上

javaScript JavaScript,简称JS&#xff0c;是Web开发中不可缺少的脚本语言的&#xff0c;不需要编译就可以运行&#xff08;解释性语言&#xff09;。它“寄生”在HTML体内&#xff0c;随网络传输到客户端在浏览器中运行。js代码可以写到html的任何地方。一般写在 body 结束标签…

java类加载的表现形式

java中的类是动态加载的&#xff0c;我们先看一下我们常用的类加载方式&#xff0c;先有一个感性的认识&#xff0c;才能进一步 深入讨论,类加载无非就是下面三种方式。 class A{} class B{} class C{} public class Loader{ public static void main(String[] args) throws Ex…

.net core在vs开发环境下脱离iis运行

.net core相比之前.net的是一个可以跨平台&#xff0c;脱离iis运行的语言&#xff0c;并且项目启动的效率要比用iis启动快&#xff0c;可以说进一步提高了开发的效率。要想自己的项目core脱离iis&#xff0c;首先选择vs启动项目的载体&#xff1a; 如下图&#xff0c;不要选择I…

Sublime遇见中文乱码问题?

今天在写demo的时候&#xff0c;突然发现html页面上的中文在浏览器上显示乱码~&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 这时&#xff0c;我根据网上的提示安装了两个插件:converttoUtf-8&#xff0c;support Gbk ~~~然而&#xff0c;好像无济于事~~ 于是…

Dynamics AX 2009 升级PreSynchnoize时的无反应的解决

问题: 安装完升级补丁后在Data upgrade cockpit窗口点击Run或Train Run按钮后Update Job没有开始&#xff0c;始终显示为Ready状态。 #1 原因&#xff1a;数据库以前已经做过升级&#xff08;例如安装过AX 2009 SP1&#xff09;并且已经存在一个DataUpdate批处理组但是针对这个…

如何 搭建 RMAN 备份平台

一&#xff0e; RMAN 的一些理论知识RMAN Catalog 和 Nocatalog 的区别http://blog.csdn.net/tianlesoftware/archive/2010/06/02/5641763.aspxRMAN 系列&#xff08;一&#xff09;---- RMAN 体系结构概述http://blog.csdn.net/tianlesoftware/archive/2010/06/09/5659701.asp…

git更新代码报错,error: The following untracked working tree files would be overwritten by ch

git忽略大小写导致的&#xff0c; git config --add core.ignorecase true 转载于:https://www.cnblogs.com/newcbs/p/10732662.html

JavaScript_下_Dom

Dom对象 Dom对象&#xff1a;Document Object Model 文档对象模型。js是用来操作html的。 一个文档必须被加载到浏览器中&#xff0c;会按照HTML的层级结构转换成一个“家谱树”称为dom树。HTML文档里的所以的标签&#xff0c;属性&#xff0c;文本都会转换成dom树上的节点。 …

HDU 4300 Clairewd’s message

一道KMP的变式 本题仍是求最大前缀后缀&#xff0c;所以仍用KMP&#xff0c;但不同的是&#xff0c;本题有一个密码转换规则&#xff0c;不过好在题目中说了两段不重合&#xff0c;那么我们就可以在中间插入一个特殊符号*&#xff0c;保证求next数组时不会越过中线&#xff0c;…

GNS3模拟VPC注意几点

网上的GNS3入门到精通视频的确做得不错。现我写一下主要几点&#xff1a;1、创建MS lookback 适配器在添加硬件那里&#xff0c;并注意IP设置要在本地网卡同一子网&#xff0c;网关不用设置的。2、GNS3 的Dynamips目录的cygwin1.dll文件替代VPCS目录中文件。3、GNS3中的模拟PC先…

Windows Phone 7 Tip (5) -- App liftcycle

在新的trainning kit 中有一个例子解释的很清楚了&#xff1a; 1. Application_Launching&#xff1a;只有在新启动程序时触发 2. Application_Closing&#xff1a;只有在推出程序时触发--只有在程序mainpage时按硬后退键 3. Application_Activated&#xff1a;从home键或者其它…

npm i和npm install的区别

最近人用npm i来直接安装模块&#xff0c;但是有会报错&#xff0c;用npm install就不会报错&#xff0c;刚开始百思不得其解&#xff0c;它俩明明是同一个东西后来查npm的帮助指令发现还是没区别&#xff0c;npm i仅仅是npm install的简写&#xff1a; 实际使用的区别点主要如…

获取服务器路径的方式 【记录】

JSP页面获取服务器路径的方式 1.basePath方式 <% String path request.getContextPath(); String basePath request.getScheme()"://"request.getServerName()":"request.getServerPort()path"/"; %> 在url加入<%basePath%>如&…

系统安全设置部分项

1.设置合理的操作超时锁定&#xff08;10分钟以内&#xff09;&#xff0c;在恢复时需要重新鉴别。 开启连接超时 vim /etc/ssh/sshd_config ClientAliveInterval 120 //设置超时时间2分钟 ClientAliveCountMax 3 重启 /etc/init.d/sshd restart 2.设置TMOUT将自动在所设置…

简单的分级别写日志程序

/************************************************************************/ /* * 文件名称&#xff1a;write_log.cpp * 摘 要&#xff1a;此文件实现了普通WINDOWS程序中的日志功能 * 主要有以下特点&#xff1a; * 1. 根据日期创建日志文件目录&#xff0c;每天的日志分别…

达内——java变量

package xx;//为class文件分目录 import xx.xx//导入包中的类 public class 类名{ public static void main(String args[]){ } } 准备工作&#xff1a; jdk jre jvm gc idea pi 配置环境变量 变量名支持字母&#xff0c;数字&#xff0c;_和$&#xff0c;但是数字不能开头&…

JSON Web Tokens测试工具

JSON Web Tokens官方提供测试工具https://jwt.io某些静态资料需要链接google、twitter服务器&#xff0c;被墙无法访问。现在提供可以方法测试工具http://hingtai.cn:8000/转载于:https://www.cnblogs.com/birdstudio/p/7985617.html

oracle 分页写法

select * from (select ROWNUM RN ,TT.* from ( select * from YQ_FEED_BACK_MESSAGE WHERE MODEL_TYPE 3 AND FEED_BACK_TYPE4 order by FEED_BACK_DATE DESC)TT where rownum<5 )where RN >0

给GRUB添加新的项目

安装了win10&#xff0c;然后又安装了manjaro&#xff0c;最后又安装了Ubuntu。开机默认就是进入的Ubuntu的grub&#xff0c;然而我比较喜欢manjaro的grub主题。在bios中设置manjaro的引导为默认引导&#xff0c;但是此时manjaro的grub没有Ubuntu&#xff0c;进入到manjaro系统…

Netapp存储基础之WAFL, NVRAM, RAID, SnapShot

此章节是重点之重点。先讲基本概念。WAFL的全称是Write Anywhere File Layout. 从类似于其它UNIX的文件系统比如Berkeley Fast File System (FFS) 和 TransArc Episode file system. 它的核心理念是"文件任意地方写"。先说普通的文件系统&#xff0c;是由inode和data…

读取本地照片 以流的形式进行显示

获取到前端传来的文件名称&#xff0c;到相应的文件中去读取&#xff0c;通过流的形式写到响应体中。 /*** 显示图片 * getFeedBackPicture.do?picName* return*/RequestMapping(value"/viewPhoto/{photopath}")public void getFeedBackPicture(HttpServletRespons…

如何轻松搞定机构资格准入?

银联的“资格入网”虽涉及环节庞杂&#xff0c;但杂而有序&#xff0c;初次接触银联的机构极有可能不知道先从哪一步骤开始&#xff0c;很多用户甚至不知道该怎么填写材料&#xff0c;所以我整理了一份详细的入网流程&#xff0c;以供大家参考&#xff0c;希望能帮助到大家~ 入…