布尔囧囧

导航

 

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

 

原作者:菩提树下的杨过
出处:http://yjmyzz.cnblogs.com 

 

  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 }
View Code

有几个地方稍加解释:

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

  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 }
View Code

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

 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 }
View Code

 

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

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

一、寻找行为(Seek)

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

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

 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 }
View Code

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

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

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

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 }
View Code

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

 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 }
View Code

 

 

 

二、避开(flee)行为

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

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 }
View Code

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

 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 }
View Code

 

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

 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 }
View Code

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

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

 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 }
View Code

 

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

 

三、到达(arrive)行为

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

 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 }
View Code

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

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 }
View Code

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

 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 }
View Code

 

 

 

四、追捕(pursue)行为

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

代码:

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 }
View Code

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

测试代码:

 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 }
View Code

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

 

五、躲避(evade)行为

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

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 }
View Code

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

  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 }
View Code

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

六、漫游(wander)行为

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

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

 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 }
View Code

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

 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 }
View Code

 

 

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

 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 }
View Code

 

 

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

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

 

作者:菩提树下的杨过
出处:http://yjmyzz.cnblogs.com 
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
 
posted on 2015-08-14 20:00  布尔囧囧  阅读(270)  评论(0编辑  收藏  举报