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

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

 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,右侧返回1
public 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、向量夹角的计算

 
上图为向量的夹角公式,再来对照一下代码部分:

 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、垂直向量的取得

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

,即:

 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)”行为,先看下面这张图:

根据向量运算,可以先得到机车期望的理想速度(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):缓动动画)(http://www.cnblogs.com/yjmyzz/archive/2010/04/16/1713730.html)

 //到达(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前进路线上的)目标位置,然后再以寻找行为接近该位置,所以只要预定目标位置计算得合理,就算追捕者的速度要慢一点儿,也是完全有可能把目标给抓住的。

代码:

 //追捕(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)行为

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

如上图,先在物体运动的正前方,画一个指定半径的圈,然后让向量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实现,推荐大家对照阅读。

作者:菩提树下的杨过
出处:http://yjmyzz.cnblogs.com 
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
 

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

  1. “AS3.0高级动画编程”学习:第一章高级碰撞检测

    AdvancED ActionScript 3.0 Animation 是Keith Peters大师继"Make Things Move"之后的又一力作,网上已经有中文翻译版本了 ...

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

    在上一篇里,我们学习了“自主角色”的一些基本行为:寻找(seek).避开(flee).到达(arrive).追捕(pursue).躲避(evade).漫游(wander).这一篇将继续学习其它更复杂, ...

  3. “AS3.0高级动画编程”学习:第三章等角投影(上)

    什么是等角投影(isometric)? 原作者:菩提树下的杨过出处:http://yjmyzz.cnblogs.com 刚接触这个概念时,我也很茫然,百度+google了N天后,找到了一些文章: [转 ...

  4. “AS3.0高级动画编程”学习:第四章 寻路(AStar/A星/A*)算法 (下)

    在前一部分的最后,我们给出了一个寻路的示例,在大多数情况下,运行还算良好,但是有一个小问题,如下图: 很明显,障碍物已经把路堵死了,但是小球仍然穿过对角线跑了出来! 问题在哪里:我们先回顾一下ASta ...

  5. [书籍翻译] 《JavaScript并发编程》 第二章 JavaScript运行模型

    本文是我翻译<JavaScript Concurrency>书籍的第二章 JavaScript运行模型,该书主要以Promises.Generator.Web workers等技术来讲解J ...

  6. 3-8《Ruby元编程》第二章对象模型

    <Ruby元编程> 第二章 对象模型 类定义揭秘inside class definitions: class关键字更像一个作用域操作符,核心作用是可以在里面随时定义方法. [].meth ...

  7. Learning ROS for Robotics Programming - Second Edition(《ROS机器人编程学习-第二版》)

    Learning ROS for Robotics Programming - Second Edition <ROS机器人编程学习-第二版> ----Your one-stop guid ...

  8. oracle学习 第二章 限制性查询和数据的排序 ——03

    这里.我们接着上一小节2.6留下的问题:假设要查询的字符串中含有"_"或"%".又该如何处理呢? 開始今天的学习. 2.7  怎样使用转义(escape)操作符 ...

  9. C#高级编程 (第六版) 学习 第二章:C#基础

    第二章 基础 1,helloworld示例: helloworld.cs using System; using System.Collections.Generic; using System.Li ...

随机推荐

  1. 2018总结-->2019新目标

    2018完成的事情: ①考到了驾照: ②刷了很多题,春季找到了实习,赚到了去日本旅游的经费和2019毕业租房的预算,最后签了offer: ③去了西安.天津.山西,看到了不一样的人和事: ④发了小论文, ...

  2. Elisp 中变量赋值函数 set 与 setq 辨析

    在 Elisp 中,为变量赋值的函数有 set 与 setq,但是,两者存在很大的差异. 使用 set 赋值: 如果我们想为变量 flowers 赋值为一个 列表 '(rose violet dais ...

  3. Listen and Write 18th Feb 2019

    Weighted blanket has becomes very popular in many homes. they claim it can provide better sleep and ...

  4. 远程连接mysql8.0,Error No.2058 Plugin caching_sha2_password could not be loaded

    通过本地去连接远程的mysql时报错,原因时mysql8.0的加密方法变了. mysql8.0默认采用caching_sha2_password的加密方式 第三方客户端基本都不支持这种加密方式,只有自 ...

  5. springBoot和c3p0的整合

    首先创建c3p0的数据源类 package com.example.demo.config; import javax.sql.DataSource; import org.mybatis.sprin ...

  6. udt通信java

    udt协议是什么? 简单的是udp重发 经过上次的修正,重新测试,修复,测试各种环境,再次查找出源码错误,重新修正 修正内容在git中的修正说明中 同时针对之后的应用,对封装的代码也做了修改和重构 代 ...

  7. 三、CSS样式——文本

    CSS文本 概念:CSS文本属性可定义文本外观 通过文本属性,可以改变文本的颜色.字符间距.对齐文本.装饰文本.对文本缩进 属性 描述 color 文本颜色 direction 文本方向 line-h ...

  8. JavaScript数组方法--filter、find、findIndex

    继续数组方法,今天应该到filter了. filter:filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素. 使用: var words = ['spray', 'lim ...

  9. xml 转换成对象(采用反射机制对对对象属性赋值)

    /// <summary> /// 采用反射机制对对对象属性赋值 /// </summary> /// <param name="node">& ...

  10. SQL Server 2000 字段类型 Delphi 数据类型对照