因为这一章的内容基本上都是涉及向量的,先来一个2D向量类:Vector2D.as (再次强烈建议不熟悉向量运算的童鞋,先回去恶补一下高等数学-07章空间解释几何与向量代数.pdf)
原作者:菩提树下的杨过
出处:http://yjmyzz.cnblogs.com
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package { 2 import flash.display.Graphics; 3 4 public class Vector2D { 5 private var _x:Number; 6 private var _y:Number; 7 8 //构造函数 9 public function Vector2D(x:Number=0,y:Number=0) { 10 _x=x; 11 _y=y; 12 } 13 14 //绘制向量(以便于显示) 15 public function draw(graphics:Graphics,color:uint=0):void { 16 graphics.lineStyle(0,color); 17 graphics.moveTo(0,0); 18 graphics.lineTo(_x,_y); 19 } 20 21 //克隆对象 22 public function clone():Vector2D { 23 return new Vector2D(x,y); 24 } 25 26 //位置归零 27 public function zero():Vector2D { 28 _x=0; 29 _y=0; 30 return this; 31 } 32 33 //是否在零位置 34 public function isZero():Boolean { 35 return _x==0&&_y==0; 36 } 37 38 //获得向量的角度 39 public function get angle():Number { 40 return Math.atan2(_y,_x); 41 } 42 43 //设置向量的模(即大小) 44 public function set length(value:Number):void { 45 var a:Number=angle; 46 _x=Math.cos(a)*value; 47 _y=Math.sin(a)*value; 48 } 49 50 //获取向量大小的平方 51 public function get lengthSQ():Number { 52 return _x*_x+_y*_y; 53 } 54 55 //获取向量的模(即大小) 56 public function get length():Number { 57 return Math.sqrt(lengthSQ); 58 } 59 60 //设置向量的角度 61 public function set angle(value:Number):void { 62 var len:Number=length; 63 _x=Math.cos(value)*len; 64 _y=Math.sin(value)*len; 65 } 66 67 68 //截断向量(设置向量模最大值) 69 public function truncate(max:Number):Vector2D { 70 length=Math.min(max,length); 71 return this; 72 } 73 74 //交换x,y坐标 75 public function reverse():Vector2D { 76 _x=- _x; 77 _y=- _y; 78 return this; 79 } 80 81 82 //定义二个向量的加法运算 83 public function add(v2:Vector2D):Vector2D { 84 return new Vector2D(_x+v2.x,_y+v2.y); 85 } 86 87 //定义二个向量的减法运算 88 public function subtract(v2:Vector2D):Vector2D { 89 return new Vector2D(_x-v2.x,_y-v2.y); 90 } 91 92 //向量模的乘法运算 93 public function multiply(value:Number):Vector2D { 94 return new Vector2D(_x*value,_y*value); 95 } 96 97 //向量模的除法运算 98 public function divide(value:Number):Vector2D { 99 return new Vector2D(_x/value,_y/value); 100 } 101 102 //判定二个向量(坐标)是否相等 103 public function equals(v2:Vector2D):Boolean { 104 return _x==v2.x&&_y==v2.y; 105 } 106 107 //设置x轴坐标 108 public function set x(value:Number):void { 109 _x=value; 110 } 111 112 //返回x轴坐标 113 public function get x():Number { 114 return _x; 115 } 116 117 //设置y轴坐标 118 public function set y(value:Number):void { 119 _y=value; 120 } 121 122 //返回y轴坐标 123 public function get y():Number { 124 return _y; 125 } 126 127 128 //单位化向量(即设置向量的模为1,不过这里用了一种更有效率的除法运算,从而避免了lengh=1带来的三角函数运算) 129 public function normalize():Vector2D { 130 if (length==0) { 131 _x=1; 132 return this; 133 } 134 //建议大家画一个基本的3,4,5勾股定理的直角三角形即可明白下面的代码 135 var len:Number=length; 136 _x/=len; 137 _y/=len; 138 return this; 139 } 140 141 //判定向量是否为单位向量 142 public function isNormalized():Boolean { 143 return length==1.0; 144 } 145 146 //点乘(即向量的点积) 147 public function dotProd(v2:Vector2D):Number { 148 return _x*v2.x+_y*v2.y; 149 } 150 151 //叉乘(即向量的矢量积) 152 public function crossProd(v2:Vector2D):Number { 153 return _x*v2.y-_y*v2.x; 154 } 155 156 //返回二个向量之间的夹角 157 public static function angleBetween(v1:Vector2D,v2:Vector2D):Number { 158 if (! v1.isNormalized()) { 159 v1=v1.clone().normalize(); 160 } 161 if (! v2.isNormalized()) { 162 v2=v2.clone().normalize(); 163 } 164 return Math.acos(v1.dotProd(v2));//建议先回顾一下http://www.cnblogs.com/yjmyzz/archive/2010/06/06/1752674.html中提到的到夹角公式 165 } 166 167 //判定给定的向量是否在本向量的左侧或右侧,左侧返回-1,右侧返回1 168 public function sign(v2:Vector2D):int { 169 return perp.dotProd(v2)<0?-1:1; 170 } 171 172 //返回与本向量垂直的向量(即自身顺时针旋转90度,得到一个新向量) 173 public function get perp():Vector2D { 174 return new Vector2D(- y,x);//建议回顾一下"坐标旋转" 175 } 176 177 178 //返回二个矢量末端顶点之间的距离平方 179 public function distSQ(v2:Vector2D):Number { 180 var dx:Number=v2.x-x; 181 var dy:Number=v2.y-y; 182 return dx*dx+dy*dy; 183 } 184 185 //返回二个矢量末端顶点之间的距离 186 public function dist(v2:Vector2D):Number { 187 return Math.sqrt(distSQ(v2)); 188 } 189 190 //toString方法 191 public function toString():String { 192 return "[Vector2D (x:"+_x+", y:"+_y+")]"; 193 } 194 } 195 }
有几个地方稍加解释:
1、向量夹角的计算
1 public static function angleBetween(v1:Vector2D,v2:Vector2D):Number { 2 if (! v1.isNormalized()) { 3 v1=v1.clone().normalize(); 4 } 5 if (! v2.isNormalized()) { 6 v2=v2.clone().normalize(); 7 } 8 return Math.acos(v1.dotProd(v2)); 9 }
首先对向量v1,v2做了单位化处理,使其变成(模为1的)单位向量,这样夹角公式中的|a|×|b|(即分母)自然也就是1,公式演变成cos(θ)=a.b(即夹角余弦 等于 向量a与b的点乘),然后再对其取反余弦Math.acos,最终得到夹角
2、垂直向量的取得
上图是坐标(顺时针)旋转的标准公式,如果把α设置为90度,则
1 public function get perp():Vector2D { 2 return new Vector2D(- y,x); 3 }
3、判定其它向量是在自身的左侧还是右侧
如上图,先取得A的垂直向量,然后计算其它向量跟垂直向量的点积(点乘的公式,在物理上的表现之一为 W = |F|*|S|Cos(θ) ),如果其它向量与该垂直向量的夹角小于90度,点乘的值必为正,反之为负,所以也就能判定左右了(注意:这里的左右是指人站在坐标原点,顺着向量A的方向来看的)
再来定义一个机车类Vehicle.as
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package { 2 import flash.display.Sprite; 3 4 public class Vehicle extends Sprite { 5 //边界行为:是屏幕环绕(wrap),还是反弹{bounce} 6 protected var _edgeBehavior:String=WRAP; 7 //质量 8 protected var _mass:Number=1.0; 9 //最大速度 10 protected var _maxSpeed:Number=10; 11 //坐标 12 protected var _position:Vector2D; 13 //速度 14 protected var _velocity:Vector2D; 15 16 //边界行为常量 17 public static const WRAP:String="wrap"; 18 public static const BOUNCE:String="bounce"; 19 20 public function Vehicle() { 21 _position=new Vector2D ; 22 _velocity=new Vector2D ; 23 draw(); 24 } 25 26 27 protected function draw():void { 28 graphics.clear(); 29 graphics.lineStyle(0); 30 graphics.moveTo(10,0); 31 graphics.lineTo(-10,5); 32 graphics.lineTo(-10,-5); 33 graphics.lineTo(10,0); 34 } 35 36 37 public function update():void { 38 39 //设置最大速度 40 _velocity.truncate(_maxSpeed); 41 42 //根据速度更新坐标向量 43 _position=_position.add(_velocity); 44 45 //处理边界行为 46 if (_edgeBehavior==WRAP) { 47 wrap(); 48 } else if (_edgeBehavior==BOUNCE) { 49 bounce(); 50 } 51 52 //更新x,y坐标值 53 x=position.x; 54 y=position.y; 55 56 //处理旋转角度 57 rotation=_velocity.angle*180/Math.PI; 58 } 59 60 //反弹 61 private function bounce():void { 62 if (stage!=null) { 63 if (position.x>stage.stageWidth) { 64 position.x=stage.stageWidth; 65 velocity.x*=-1; 66 } else if (position.x<0) { 67 position.x=0; 68 velocity.x*=-1; 69 } 70 if (position.y>stage.stageHeight) { 71 position.y=stage.stageHeight; 72 velocity.y*=-1; 73 } else if (position.y<0) { 74 position.y=0; 75 velocity.y*=-1; 76 } 77 } 78 } 79 80 //屏幕环绕 81 private function wrap():void { 82 if (stage!=null) { 83 if (position.x>stage.stageWidth) { 84 position.x=0; 85 } 86 if (position.x<0) { 87 position.x=stage.stageWidth; 88 } 89 if (position.y>stage.stageHeight) { 90 position.y=0; 91 } 92 if (position.y<0) { 93 position.y=stage.stageHeight; 94 } 95 } 96 } 97 98 //下面的都是属性定义 99 100 101 public function set edgeBehavior(value:String):void { 102 _edgeBehavior=value; 103 } 104 105 public function get edgeBehavior():String { 106 return _edgeBehavior; 107 } 108 109 110 public function set mass(value:Number):void { 111 _mass=value; 112 } 113 114 public function get mass():Number { 115 return _mass; 116 } 117 118 public function set maxSpeed(value:Number):void { 119 _maxSpeed=value; 120 } 121 122 public function get maxSpeed():Number { 123 return _maxSpeed; 124 } 125 126 public function set position(value:Vector2D):void { 127 _position=value; 128 x=_position.x; 129 y=_position.y; 130 } 131 132 public function get position():Vector2D { 133 return _position; 134 } 135 136 public function set velocity(value:Vector2D):void { 137 _velocity=value; 138 } 139 140 public function get velocity():Vector2D { 141 return _velocity; 142 } 143 144 override public function set x(value:Number):void { 145 super.x=value; 146 _position.x=x; 147 } 148 149 override public function set y(value:Number):void { 150 super.y=value; 151 _position.y=y; 152 } 153 } 154 }
没有什么新东西,都是以前学到的知识,测试一下上面这二个类:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package { 2 import flash.display.Sprite; 3 import flash.display.StageAlign; 4 import flash.display.StageScaleMode; 5 import flash.events.Event; 6 7 public class VehicleTest extends Sprite { 8 private var _vehicle:Vehicle; 9 public function VehicleTest() { 10 stage.align=StageAlign.TOP_LEFT; 11 stage.scaleMode=StageScaleMode.NO_SCALE; 12 _vehicle=new Vehicle ; 13 addChild(_vehicle); 14 _vehicle.position=new Vector2D(100,100); 15 _vehicle.velocity.length=5; 16 _vehicle.velocity.angle=Math.PI/4;//45度 17 addEventListener(Event.ENTER_FRAME,onEnterFrame); 18 } 19 private function onEnterFrame(event:Event):void { 20 _vehicle.update(); 21 } 22 } 23 }
OK,现在可以进入正题了:(下面是从原书上直接抄过来的)
转向行为(steering behaviors)这一术语,指的是一系列使对象行动起来像似长有智商的算法。这些行为都归于人工智能或人工生命一类,是让对象呈现出拥有生命一般,对如何移动到目的地、捕捉或逃避其它对象、避开障碍物、寻求路径等做出因地适宜的决定。
一、寻找行为(Seek)
简单点讲,就是角色本身试图移动(包括转向)到目标位置(这个位置可能是固定的,也可能是移动的)。
先定义一个从Vehicle继承的子类:具有转向能力的机车SteeredVehicle.as
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package { 2 import flash.display.Sprite; 3 4 //(具有)转向(行为的)机车 5 public class SteeredVehicle extends Vehicle { 6 private var _maxForce:Number=1;//最大转向力 7 private var _steeringForce:Vector2D;//转向速度 8 9 public function SteeredVehicle() { 10 _steeringForce = new Vector2D(); 11 super(); 12 } 13 public function set maxForce(value:Number):void { 14 _maxForce=value; 15 } 16 public function get maxForce():Number { 17 return _maxForce; 18 } 19 20 override public function update():void { 21 _steeringForce.truncate(_maxForce);//限制为最大转向速度,以避免出现突然的大转身 22 _steeringForce=_steeringForce.divide(_mass);//惯性的体现 23 _velocity=_velocity.add(_steeringForce); 24 _steeringForce = new Vector2D(); 25 super.update(); 26 } 27 } 28 }
代码不难理解:仅增加了最大转向力maxForce(主要是为了防止机车一瞬间就突然移动到目标位置,会引起视觉上的动画不连贯);另外对update做了重载处理,在更新机车x,y坐标及朝向(即rotation)之前,累加了转向速度并考虑到了物体的惯性。
再来考虑“寻找(seek)”行为,先看下面这张图:
根据向量运算,可以先得到机车期望的理想速度(desireVolocity)--注:如果用这个速度行驶,物体立马就能到达目标点。当然我们要体现物体是逐渐靠近目标点的,所以显然不可能用理想速度前行,而是要计算出转向速度force,最终再把转向速度force叠加到自身的速度_velocity上,这样机车就能不断向目标点移动了。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //寻找(Seek)行为 2 public function seek(target: Vector2D):void { 3 var desiredVelocity:Vector2D=target.subtract(_position); 4 desiredVelocity.normalize(); 5 desiredVelocity=desiredVelocity.multiply(_maxSpeed);//注:这里的_maxSpeed是从父类继承得来的 6 var force:Vector2D=desiredVelocity.subtract(_velocity); 7 _steeringForce=_steeringForce.add(force); 8 }
把这段代码加入到SteeredVehicle.as中就能让SteeredVehicle类具有seek行为,下面是测试代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package { 2 import SteeredVehicle; 3 import Vector2D; 4 import flash.display.Sprite; 5 import flash.display.StageAlign; 6 import flash.display.StageScaleMode; 7 import flash.events.Event; 8 9 public class SeekTest extends Sprite { 10 11 private var _vehicle:SteeredVehicle; 12 13 public function SeekTest() { 14 stage.align=StageAlign.TOP_LEFT; 15 stage.scaleMode=StageScaleMode.NO_SCALE; 16 _vehicle = new SteeredVehicle(); 17 addChild(_vehicle); 18 addEventListener(Event.ENTER_FRAME, onEnterFrame); 19 } 20 21 private function onEnterFrame(event:Event):void { 22 _vehicle.seek(new Vector2D(mouseX, mouseY));//以当前鼠标位置为目标点 23 _vehicle.update(); 24 } 25 } 26 }
二、避开(flee)行为
它跟寻找(seek)行为正好是相反的,可以通俗的理解为:“既然发现了目标,那么就调头逃跑吧”,所以代码上只要改一行即可
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //避开(flee)行为 2 public function flee(target: Vector2D):void { 3 var desiredVelocity:Vector2D=target.subtract(_position); 4 desiredVelocity.normalize(); 5 desiredVelocity=desiredVelocity.multiply(_maxSpeed); 6 var force:Vector2D=desiredVelocity.subtract(_velocity); 7 _steeringForce=_steeringForce.subtract(force);//这是唯一与seek行为不同的地方,一句话解释:既然发现了目标,那就调头就跑吧! 8 }
同样,把上述代码加入到SteeredVehicle.as中就能让SteeredVehicle类具有flee行为,测试代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package { 2 import SteeredVehicle; 3 import Vector2D; 4 import flash.display.Sprite; 5 import flash.display.StageAlign; 6 import flash.display.StageScaleMode; 7 import flash.events.Event; 8 9 public class FleeTest extends Sprite { 10 11 private var _vehicle:SteeredVehicle; 12 13 public function FleeTest() { 14 stage.align=StageAlign.TOP_LEFT; 15 stage.scaleMode=StageScaleMode.NO_SCALE; 16 _vehicle = new SteeredVehicle(); 17 _vehicle.position = new Vector2D(stage.stageWidth/2,stage.stageHeight/2); 18 _vehicle.edgeBehavior = Vehicle.BOUNCE; 19 addChild(_vehicle); 20 addEventListener(Event.ENTER_FRAME, onEnterFrame); 21 } 22 23 private function onEnterFrame(event:Event):void { 24 _vehicle.flee(new Vector2D(mouseX, mouseY));//避开鼠标当前位置 25 _vehicle.update(); 26 } 27 } 28 }
seek行为与flee行为组合起来,可以完成类似“警察抓小偷”的效果
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package { 2 import SteeredVehicle; 3 import Vector2D; 4 import Vehicle; 5 import flash.display.Sprite; 6 import flash.display.StageAlign; 7 import flash.display.StageScaleMode; 8 import flash.events.Event; 9 import flash.text.TextField; 10 import flash.text.TextFormat; 11 12 public class SeekFleeTest1 extends Sprite { 13 private var _seeker:SteeredVehicle;//寻找者(可理解为:警察) 14 private var _fleer:SteeredVehicle;//躲避者(事理解为:小偷) 15 private var _seekerSpeedSlider:SimpleSlider ;//警察的最大速度控制滑块 16 private var _txtSeekerMaxSpeed:TextField; 17 private var _fleerSpeedSlider:SimpleSlider ;//小偷的最大速度控制滑块 18 private var _txtFleerMaxSpeed:TextField; 19 20 public function SeekFleeTest1() { 21 stage.align=StageAlign.TOP_LEFT; 22 stage.scaleMode=StageScaleMode.NO_SCALE; 23 24 _seeker = new SteeredVehicle(0xff0000); 25 _seeker.position=new Vector2D(); 26 _seeker.edgeBehavior=Vehicle.BOUNCE; 27 addChild(_seeker); 28 _seeker.maxSpeed = 5; 29 30 _fleer = new SteeredVehicle(0x0000ff); 31 _fleer.position=new Vector2D(stage.stageWidth*Math.random(),stage.stageHeight*Math.random()); 32 _fleer.edgeBehavior=Vehicle.BOUNCE; 33 addChild(_fleer); 34 addEventListener(Event.ENTER_FRAME, onEnterFrame); 35 36 37 addSpeedControl(); 38 } 39 40 //添加速度控制组件 41 private function addSpeedControl():void{ 42 _seekerSpeedSlider = new SimpleSlider(5,25,10); 43 _seekerSpeedSlider.rotation = 90; 44 _seekerSpeedSlider.x = 150; 45 _seekerSpeedSlider.y = 20; 46 _seekerSpeedSlider.backColor = _seekerSpeedSlider.backBorderColor = _seekerSpeedSlider.handleColor = _seekerSpeedSlider.handleBorderColor = 0xff0000; 47 addChild(_seekerSpeedSlider); 48 _seekerSpeedSlider.addEventListener(Event.CHANGE,onSeekerSpeedChange); 49 _txtSeekerMaxSpeed = new TextField(); 50 var _tfseeker:TextFormat = new TextFormat(); 51 _tfseeker.color = 0xff0000; 52 _txtSeekerMaxSpeed.defaultTextFormat = _tfseeker; 53 _txtSeekerMaxSpeed.text = "10"; 54 addChild(_txtSeekerMaxSpeed); 55 _txtSeekerMaxSpeed.y = _seekerSpeedSlider.y -6; 56 _txtSeekerMaxSpeed.x = _seekerSpeedSlider.x +3; 57 58 59 60 _fleerSpeedSlider = new SimpleSlider(5,25,10); 61 _fleerSpeedSlider.rotation = 90; 62 _fleerSpeedSlider.x = 480; 63 _fleerSpeedSlider.y = 20; 64 _fleerSpeedSlider.backColor = _fleerSpeedSlider.backBorderColor = _fleerSpeedSlider.handleColor = _fleerSpeedSlider.handleBorderColor = 0x0000ff; 65 addChild(_fleerSpeedSlider); 66 _fleerSpeedSlider.addEventListener(Event.CHANGE,onFleerSpeedChange); 67 _txtFleerMaxSpeed = new TextField(); 68 var _tffleer:TextFormat = new TextFormat(); 69 _tffleer.color = 0x0000ff; 70 _txtFleerMaxSpeed.defaultTextFormat = _tffleer; 71 _txtFleerMaxSpeed.text = "10"; 72 addChild(_txtFleerMaxSpeed); 73 _txtFleerMaxSpeed.y = _fleerSpeedSlider.y -6; 74 _txtFleerMaxSpeed.x = _fleerSpeedSlider.x +3; 75 76 } 77 78 function onSeekerSpeedChange(e:Event):void{ 79 _seeker.maxSpeed = _seekerSpeedSlider.value; 80 _txtSeekerMaxSpeed.text = _seekerSpeedSlider.value.toString(); 81 } 82 83 function onFleerSpeedChange(e:Event):void{ 84 _fleer.maxSpeed = _fleerSpeedSlider.value; 85 _txtFleerMaxSpeed.text = _fleerSpeedSlider.value.toString(); 86 } 87 88 private function onEnterFrame(event:Event):void { 89 _seeker.seek(_fleer.position);//警察 抓 小偷 90 _fleer.flee(_seeker.position);//小偷 躲 警察 91 _seeker.update(); 92 _fleer.update(); 93 } 94 } 95 }
调整红色滑块和蓝色滑块,可改变seeker与fleer的最大速度。(注:代码中的SimpleSlider在Flash/Flex学习笔记(46):正向运动学中能找到) 如果愿意,您还可以加入碰撞检测,比如当“警察”抓住“小偷”时,显示一个提示:“小样,我抓住你了!”
如果加入更多的物体,比如A,B,C三个,让A追逐B同时躲避C,B追逐C同时躲避A,C追逐A同时躲避B,将是下面这副模样:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package { 2 3 import flash.display.Sprite; 4 import flash.display.StageAlign; 5 import flash.display.StageScaleMode; 6 import flash.events.Event; 7 8 public class SeekFleeTest2 extends Sprite { 9 10 private var _vehicleA:SteeredVehicle; 11 private var _vehicleB:SteeredVehicle; 12 private var _vehicleC:SteeredVehicle; 13 14 public function SeekFleeTest2() { 15 16 stage.align=StageAlign.TOP_LEFT; 17 stage.scaleMode=StageScaleMode.NO_SCALE; 18 _vehicleA=new SteeredVehicle(0xff0000) ; 19 _vehicleA.position=new Vector2D(stage.stageWidth*Math.random(),stage.stageHeight*Math.random()); 20 _vehicleA.edgeBehavior=Vehicle.BOUNCE; 21 addChild(_vehicleA); 22 23 _vehicleB=new SteeredVehicle(0x0000ff) ; 24 _vehicleB.position=new Vector2D(stage.stageWidth*Math.random(),stage.stageHeight*Math.random()); 25 _vehicleB.edgeBehavior=Vehicle.BOUNCE; 26 addChild(_vehicleB); 27 28 _vehicleC=new SteeredVehicle(0x00ff00) ; 29 _vehicleC.position=new Vector2D(stage.stageWidth*Math.random(),stage.stageHeight*Math.random()); 30 _vehicleC.edgeBehavior=Vehicle.BOUNCE; 31 addChild(_vehicleC); 32 33 addEventListener(Event.ENTER_FRAME,onEnterFrame); 34 } 35 36 private function onEnterFrame(event:Event):void { 37 38 //A追求B,躲避C 39 _vehicleA.seek(_vehicleB.position); 40 _vehicleA.flee(_vehicleC.position); 41 42 //B追求C,躲避A 43 _vehicleB.seek(_vehicleC.position); 44 _vehicleB.flee(_vehicleA.position); 45 46 //C追求A,躲避B 47 _vehicleC.seek(_vehicleA.position); 48 _vehicleC.flee(_vehicleB.position); 49 50 _vehicleA.update(); 51 _vehicleB.update(); 52 _vehicleC.update(); 53 } 54 } 55 }
Flash动画的边界,犹如人世间的一张网,将你我他都罩住,我们都在追寻一些东西,同时也在逃避一些东西,于是乎:爱我的人我不爱,我爱的人爱别人······ 现实如此,程序亦如此。
三、到达(arrive)行为
到达行为其实跟寻找行为很相似,区别在于:寻找行为发现目标后,不断移动靠近目标,但速度不减,所以会出现最终一直在目标附近二头来回跑,停不下来。而到达行为在靠近目标时会慢慢停下来,最终停在目标点。(这个咋这么熟悉?对了,这就是以前学习过来的缓动动画,详见Flash/Flex学习笔记(38):缓动动画)(http://www.cnblogs.com/yjmyzz/archive/2010/04/16/1713730.html)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //到达(arrive)行为 2 public function arrive(target: Vector2D):void { 3 var desiredVelocity:Vector2D=target.subtract(_position); 4 desiredVelocity.normalize(); 5 var dist:Number=_position.dist(target); 6 if (dist>_arrivalThreshold) { 7 desiredVelocity=desiredVelocity.multiply(_maxSpeed); 8 } else { 9 desiredVelocity=desiredVelocity.multiply(_maxSpeed*dist/_arrivalThreshold); 10 } 11 var force:Vector2D=desiredVelocity.subtract(_velocity); 12 _steeringForce=_steeringForce.add(force); 13 }
当然这里的比例因子:_arrivalThreshold需要先定义,同时为了方便动态控制,还要对外以属性的形式暴露出来
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 private var _arrivalThreshold:Number=100;//到达行为的距离阈值(小于这个距离将减速) 2 3 public function set arriveThreshold(value: Number):void { 4 _arrivalThreshold=value; 5 } 6 7 public function get arriveThreshold():Number { 8 return _arrivalThreshold; 9 }
把上面这二段代码加入SteeredVehicle.as中,然后测试一把:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package { 2 3 import flash.display.Sprite; 4 import flash.display.StageAlign; 5 import flash.display.StageScaleMode; 6 import flash.events.Event; 7 public class ArriveTest extends Sprite { 8 private var _vehicle:SteeredVehicle; 9 public function ArriveTest() { 10 stage.align=StageAlign.TOP_LEFT; 11 stage.scaleMode=StageScaleMode.NO_SCALE; 12 _vehicle=new SteeredVehicle ; 13 addChild(_vehicle); 14 addEventListener(Event.ENTER_FRAME,onEnterFrame); 15 } 16 private function onEnterFrame(event:Event):void { 17 _vehicle.arrive(new Vector2D(mouseX,mouseY)); 18 _vehicle.update(); 19 } 20 } 21 }
四、追捕(pursue)行为
追捕跟寻找很类似,不过区别在于:寻找(seek)是发现目标后,以预定的速度向目标靠拢,不管目标跑得多快还是多慢,所以如果目标比寻找者(seeker)要移动得快,seeker永远是追不上的;而追捕行为是要在目标前进的路上,提前把目标拦截到,也可以理解为先预定一个(target前进路线上的)目标位置,然后再以寻找行为接近该位置,所以只要预定目标位置计算得合理,就算追捕者的速度要慢一点儿,也是完全有可能把目标给抓住的。
代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //追捕(pursue)行为 2 public function pursue(target:Vehicle):void { 3 var lookAheadTime:Number=position.dist(target.position)/_maxSpeed;//假如目标不动,追捕者开足马力赶过去的话,计算需要多少时间 4 var predictedTarget:Vector2D=target.position.add(target.velocity.multiply(lookAheadTime)); 5 seek(predictedTarget); 6 }
解释:假如目标不动的话,我们先计算二者之间的距离,然后以最大速度狂奔过去,大概需要lookAheadTime这么长时间,然后根据这个时间,得到预定的目标位置,再以该位置为目标,寻找(seek)过去。(当然这种算法并不精确,但是处理起来比较简单,重要的是:配合Enter_Frame事件后,它确实管用!)
测试代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package { 2 import flash.display.Sprite; 3 import flash.display.StageAlign; 4 import flash.display.StageScaleMode; 5 import flash.events.Event; 6 import flash.events.MouseEvent; 7 import flash.text.TextField; 8 9 public class PursueTest extends Sprite { 10 private var _seeker:SteeredVehicle; 11 private var _pursuer:SteeredVehicle; 12 private var _target:Vehicle; 13 private var _isRun:Boolean = false; 14 private var _text:TextField; 15 16 public function PursueTest() { 17 stage.align=StageAlign.TOP_LEFT; 18 stage.scaleMode=StageScaleMode.NO_SCALE; 19 20 _seeker = new SteeredVehicle(0x0000ff); 21 addChild(_seeker); 22 23 _pursuer = new SteeredVehicle(0xff0000); 24 addChild(_pursuer); 25 26 _target = new Vehicle(0x000000); 27 _target.velocity.length=15;//目标对象跑得快一点,这样才能看出区别 28 29 addChild(_target); 30 31 _seeker.edgeBehavior = _target.edgeBehavior = _pursuer.edgeBehavior = Vehicle.BOUNCE; 32 33 stage.addEventListener(MouseEvent.CLICK,stageClick); 34 35 _text = new TextField(); 36 _text.text = "点击鼠标开始演示"; 37 _text.height = 20; 38 _text.width = 100; 39 _text.x = stage.stageWidth/2 - _text.width/2; 40 _text.y = stage.stageHeight/2 - _text.height/2; 41 addChild(_text); 42 43 } 44 private function onEnterFrame(event:Event):void { 45 _seeker.seek(_target.position); 46 _seeker.update(); 47 _pursuer.pursue(_target); 48 _pursuer.update(); 49 _target.update(); 50 } 51 52 private function stageClick(e:MouseEvent):void{ 53 if (!_isRun){ 54 _target.position=new Vector2D(stage.stageWidth/2,stage.stageHeight/2); 55 addEventListener(Event.ENTER_FRAME, onEnterFrame); 56 _isRun = true; 57 removeChild(_text); 58 } 59 else{ 60 removeEventListener(Event.ENTER_FRAME, onEnterFrame); 61 _isRun = false; 62 _target.position = _seeker.position = _pursuer.position = new Vector2D(0,0); 63 addChild(_text); 64 _text.text = "点击鼠标重新开始"; 65 } 66 } 67 } 68 }
这里为了区别“追捕行为”与"寻找行为",我们同时加入了追捕者(_pursuer-红色)与寻找者(_seeker-蓝色),通过下面的演示可以看出,(红色)追捕者凭借算法上的优势,始终能更接近目标。
五、躲避(evade)行为
躲避跟追捕正好相反,可以理解为:如果我有可能挡在目标前进的路线上了,我就提前回避,让开这条道。(俗话:好狗不挡道)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //躲避(evade)行为 2 public function evade(target: Vehicle):void { 3 var lookAheadTime:Number=position.dist(target.position)/_maxSpeed; 4 var predictedTarget:Vector2D=target.position.add(target.velocity.multiply(lookAheadTime)); 5 flee(predictedTarget);//仅仅只是这里改变了而已 6 }
把前面学到的这些个行为放在一起乱测一通吧:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package { 2 3 import flash.display.Sprite; 4 import flash.display.StageAlign; 5 import flash.display.StageScaleMode; 6 import flash.events.Event; 7 import flash.events.MouseEvent; 8 import flash.text.TextField; 9 10 public class PursueEvadeTest extends Sprite { 11 12 private var _pursuer:SteeredVehicle; 13 private var _evader:SteeredVehicle; 14 private var _target:SteeredVehicle; 15 private var _seeker:SteeredVehicle; 16 private var _fleer:SteeredVehicle; 17 private var _pursuer2:SteeredVehicle; 18 private var _evader2:SteeredVehicle; 19 private var _text:TextField; 20 private var _isRun:Boolean = false; 21 22 public function PursueEvadeTest() { 23 stage.align=StageAlign.TOP_LEFT; 24 stage.scaleMode=StageScaleMode.NO_SCALE; 25 26 _pursuer=new SteeredVehicle(0xff0000); 27 addChild(_pursuer); 28 29 _evader=new SteeredVehicle(0x00ff00); 30 addChild(_evader); 31 32 _target=new SteeredVehicle(0x000000); 33 _target.velocity.length=15; 34 addChild(_target); 35 36 _seeker=new SteeredVehicle(0xff00ff); 37 addChild(_seeker); 38 39 _fleer=new SteeredVehicle(0xffff00); 40 addChild(_fleer); 41 42 43 _pursuer2 = new SteeredVehicle(); 44 addChild(_pursuer2); 45 46 _evader2 = new SteeredVehicle(); 47 addChild(_evader2); 48 49 _evader2.edgeBehavior = _pursuer2.edgeBehavior = _target.edgeBehavior = _evader.edgeBehavior = _pursuer.edgeBehavior = _fleer.edgeBehavior = _seeker.edgeBehavior = Vehicle.BOUNCE 50 ; 51 _text = new TextField(); 52 _text.text="点击鼠标开始演示"; 53 _text.height=20; 54 _text.width=100; 55 _text.x=stage.stageWidth/2-_text.width/2; 56 _text.y=stage.stageHeight/2-_text.height/2; 57 addChild(_text); 58 stage.addEventListener(MouseEvent.CLICK,stageClick); 59 } 60 61 62 private function stageClick(e:MouseEvent):void { 63 if (! _isRun) { 64 _target.position=new Vector2D(stage.stageWidth/2,stage.stageHeight/2); 65 _fleer.position=new Vector2D(400,300); 66 _evader2.position=new Vector2D(400,200); 67 _evader.position=new Vector2D(400,100); 68 addEventListener(Event.ENTER_FRAME, onEnterFrame); 69 _isRun=true; 70 removeChild(_text); 71 } else { 72 _pursuer2.position =_evader2.position = _evader.position = _pursuer.position = _target.position=_seeker.position=_pursuer.position= new Vector2D(0,0); 73 removeEventListener(Event.ENTER_FRAME, onEnterFrame); 74 _isRun=false; 75 addChild(_text); 76 _text.text="点击鼠标重新开始"; 77 } 78 } 79 80 81 private function onEnterFrame(event:Event):void { 82 _seeker.seek(_target.position); 83 _fleer.flee(_target.position); 84 _pursuer.pursue(_target); 85 _evader.evade(_target); 86 87 _pursuer2.pursue(_evader2); 88 _evader2.evade(_pursuer2); 89 90 _target.update(); 91 _seeker.update(); 92 _pursuer.update(); 93 _fleer.update(); 94 _evader.update(); 95 96 _pursuer2.update(); 97 _evader2.update(); 98 } 99 } 100 }
对于这个示例,也许看不出”避开(flee)“与“躲避(evade)”的区别,反正都是不挡道嘛,没关系,下面会有机会看到区别的
六、漫游(wander)行为
顾名思义,就是要让物体在屏幕上漫不经心的闲逛。可能大家首先想到的是让速度每次随机改变一些(类似布朗运动),但很快您就会发现这样做的结果:物体象抽风一样在屏幕上乱动,一点都不连续,体现不出“漫不经心”闲逛的特征。所以我们需要一种更为平滑的处理算法:
如上图,先在物体运动的正前方,画一个指定半径的圈,然后让向量offset每次随机旋转一个小小的角度,这样最终能得到转向力向量force=center+offset,最终把向量force叠加到物体的速度上即可.
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 private var _wanderAngle:Number=0; 2 private var _wanderDistance:Number=10; 3 private var _wanderRadius:Number=5; 4 private var _wanderRange:Number=1; 5 6 //漫游 7 public function wander():void { 8 var center:Vector2D=velocity.clone().normalize().multiply(_wanderDistance); 9 var offset:Vector2D=new Vector2D(0); 10 offset.length=_wanderRadius; 11 offset.angle=_wanderAngle; 12 _wanderAngle+=(Math.random()-0.5)*_wanderRange; 13 var force:Vector2D=center.add(offset); 14 _steeringForce=_steeringForce.add(force); 15 } 16 17 public function set wanderDistance(value:Number):void { 18 _wanderDistance=value; 19 } 20 21 public function get wanderDistance():Number { 22 return _wanderDistance; 23 } 24 25 public function set wanderRadius(value:Number):void { 26 _wanderRadius=value; 27 } 28 29 public function get wanderRadius():Number { 30 return _wanderRadius; 31 } 32 33 public function set wanderRange(value:Number):void { 34 _wanderRange=value; 35 } 36 37 public function get wanderRange():Number { 38 return _wanderRange; 39 }
虽然这次增加的代码看上去比较多,但是大部分是用于封装属性的,关键的代码并不难理解。好了,做下基本测试:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package { 2 import flash.display.Sprite; 3 import flash.display.StageAlign; 4 import flash.display.StageScaleMode; 5 import flash.events.Event; 6 public class WanderTest extends Sprite { 7 private var _vehicle:SteeredVehicle; 8 public function WanderTest() { 9 stage.align=StageAlign.TOP_LEFT; 10 stage.scaleMode=StageScaleMode.NO_SCALE; 11 _vehicle = new SteeredVehicle(); 12 _vehicle.maxSpeed = 3; 13 _vehicle.wanderDistance = 50; 14 _vehicle.position=new Vector2D(200,200); 15 //_vehicle.edgeBehavior = Vehicle.BOUNCE; 16 addChild(_vehicle); 17 addEventListener(Event.ENTER_FRAME, onEnterFrame); 18 } 19 private function onEnterFrame(event:Event):void { 20 _vehicle.wander(); 21 _vehicle.update(); 22 } 23 } 24 }
如果让漫游行为跟前面提到的行为组合,效果会更好一些:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package { 2 3 import flash.display.Sprite; 4 import flash.display.StageAlign; 5 import flash.display.StageScaleMode; 6 import flash.events.Event; 7 import flash.events.MouseEvent; 8 import flash.text.TextField; 9 10 public class FleeEvadeWanderTest extends Sprite { 11 12 private var _pursuer:SteeredVehicle; 13 private var _evader:SteeredVehicle; 14 private var _target:SteeredVehicle; 15 private var _seeker:SteeredVehicle; 16 private var _fleer:SteeredVehicle; 17 private var _text:TextField; 18 private var _isRun:Boolean = false; 19 20 public function FleeEvadeWanderTest() { 21 stage.align=StageAlign.TOP_LEFT; 22 stage.scaleMode=StageScaleMode.NO_SCALE; 23 24 _evader=new SteeredVehicle(0x00ff00);//躲避者(绿色) 25 addChild(_evader); 26 27 _target=new SteeredVehicle(0x000000);//目标(黑色) 28 _target.velocity.length = 20; 29 addChild(_target); 30 31 _fleer=new SteeredVehicle(0xffff00);//避开者(黄色) 32 addChild(_fleer); 33 34 _target.edgeBehavior = _evader.edgeBehavior = _fleer.edgeBehavior = Vehicle.BOUNCE; 35 36 _text = new TextField(); 37 _text.text="点击鼠标开始演示"; 38 _text.height=20; 39 _text.width=100; 40 _text.x=stage.stageWidth/2-_text.width/2; 41 _text.y=stage.stageHeight/2-_text.height/2; 42 addChild(_text); 43 stage.addEventListener(MouseEvent.CLICK,stageClick); 44 } 45 46 47 private function stageClick(e:MouseEvent):void { 48 if (! _isRun) { 49 _target.position=new Vector2D(50,50); 50 _evader.position = _fleer.position=new Vector2D(stage.stageWidth/2,stage.stageHeight/2); 51 addEventListener(Event.ENTER_FRAME, onEnterFrame); 52 _isRun=true; 53 removeChild(_text); 54 } else { 55 _evader.position = _target.position=_fleer.position=new Vector2D(0,0); 56 removeEventListener(Event.ENTER_FRAME, onEnterFrame); 57 _isRun=false; 58 addChild(_text); 59 _text.text="点击鼠标重新开始"; 60 } 61 } 62 63 64 private function onEnterFrame(event:Event):void { 65 _target.wander(); 66 _fleer.flee(_target.position); 67 _evader.evade(_target); 68 _target.update(); 69 _fleer.update(); 70 _evader.update(); 71 72 73 } 74 } 75 }
前面提到了flee(避开)与evade(躲避)很难看出区别,但在这个示例里,大概能看出一些细节上的些许不同:flee算法是以目标当前的位置为做基点避开的,而evade是以目标前进方向上未来某个时时间点的位置做为基点避开的,所以相对而言,(绿色的)evader更有前瞻性--即所谓的先知先觉,而(黄色的)fleer只是见知见觉,最终在视觉效果上,evader总是希望跟目标以反方向逃开(这样能躲得更远,更安全一点)。
注:博客园的nasa(微软MVP),对于本章内容也有相应的Sliverlight实现,推荐大家对照阅读。
出处:http://yjmyzz.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。