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

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

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

package {
	import flash.display.Graphics;

	public class Vector2D {
		private var _x:Number;
		private var _y:Number;
		
		//构造函数
		public function Vector2D(x:Number=0,y:Number=0) {
			_x=x;
			_y=y;
		}

		//绘制向量(以便于显示)
		public function draw(graphics:Graphics,color:uint=0):void {
			graphics.lineStyle(0,color);
			graphics.moveTo(0,0);
			graphics.lineTo(_x,_y);
		}

		//克隆对象
		public function clone():Vector2D {
			return new Vector2D(x,y);
		}
		
		//位置归零
		public function zero():Vector2D {
			_x=0;
			_y=0;
			return this;
		}

		//是否在零位置
		public function isZero():Boolean {
			return _x==0&&_y==0;
		}

		//获得向量的角度
		public function get angle():Number {
			return Math.atan2(_y,_x);
		}
		
		//设置向量的模(即大小)
		public function set length(value:Number):void {
			var a:Number=angle;
			_x=Math.cos(a)*value;
			_y=Math.sin(a)*value;
		}
		
		//获取向量大小的平方
		public function get lengthSQ():Number {
			return _x*_x+_y*_y;
		}

		//获取向量的模(即大小)
		public function get length():Number {
			return Math.sqrt(lengthSQ);
		}
		
		//设置向量的角度
		public function set angle(value:Number):void {
			var len:Number=length;
			_x=Math.cos(value)*len;
			_y=Math.sin(value)*len;
		}	

		
		//截断向量(设置向量模最大值)
		public function truncate(max:Number):Vector2D {
			length=Math.min(max,length);
			return this;
		}

		//交换x,y坐标
		public function reverse():Vector2D {
			_x=- _x;
			_y=- _y;
			return this;
		}
		
		
		//定义二个向量的加法运算
		public function add(v2:Vector2D):Vector2D {
			return new Vector2D(_x+v2.x,_y+v2.y);
		}

		//定义二个向量的减法运算
		public function subtract(v2:Vector2D):Vector2D {
			return new Vector2D(_x-v2.x,_y-v2.y);
		}

		//向量模的乘法运算
		public function multiply(value:Number):Vector2D {
			return new Vector2D(_x*value,_y*value);
		}

		//向量模的除法运算
		public function divide(value:Number):Vector2D {
			return new Vector2D(_x/value,_y/value);
		}

		//判定二个向量(坐标)是否相等
		public function equals(v2:Vector2D):Boolean {
			return _x==v2.x&&_y==v2.y;
		}

		//设置x轴坐标
		public function set x(value:Number):void {
			_x=value;
		}

		//返回x轴坐标
		public function get x():Number {
			return _x;
		}
		
		//设置y轴坐标
		public function set y(value:Number):void {
			_y=value;
		}

		//返回y轴坐标
		public function get y():Number {
			return _y;
		}


		//单位化向量(即设置向量的模为1,不过这里用了一种更有效率的除法运算,从而避免了lengh=1带来的三角函数运算)
		public function normalize():Vector2D {
			if (length==0) {
				_x=1;
				return this;
			}
			//建议大家画一个基本的3,4,5勾股定理的直角三角形即可明白下面的代码
			var len:Number=length;
			_x/=len;
			_y/=len;
			return this;
		}		

		//判定向量是否为单位向量
		public function isNormalized():Boolean {
			return length==1.0;
		}

		//点乘(即向量的点积)
		public function dotProd(v2:Vector2D):Number {
			return _x*v2.x+_y*v2.y;
		}

		//叉乘(即向量的矢量积)
		public function crossProd(v2:Vector2D):Number {
			return _x*v2.y-_y*v2.x;
		}
		
		//返回二个向量之间的夹角
		public static function angleBetween(v1:Vector2D,v2:Vector2D):Number {
			if (! v1.isNormalized()) {
				v1=v1.clone().normalize();
			}
			if (! v2.isNormalized()) {
				v2=v2.clone().normalize();
			}
			return Math.acos(v1.dotProd(v2));//建议先回顾一下http://www.cnblogs.com/yjmyzz/archive/2010/06/06/1752674.html中提到的到夹角公式
		}

		//判定给定的向量是否在本向量的左侧或右侧,左侧返回-1,右侧返回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):缓动动画)

//到达(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实现,推荐大家对照阅读。

 

目录
相关文章
|
2月前
|
关系型数据库 MySQL PHP
PHP编程:从基础到高级的旅程
PHP,一种流行的服务器端脚本语言,因其在Web开发中的广泛应用而受到许多开发者的青睐。本文将从PHP的基本概念和语法入手,逐步深入到面向对象编程、数据库操作以及框架使用等高级主题。无论你是PHP新手还是有一定经验的开发者,这篇文章都将为你提供有价值的参考和学习路径。
38 2
|
3月前
|
C# Windows 监控
WPF应用跨界成长秘籍:深度揭秘如何与Windows服务完美交互,扩展功能无界限!
【8月更文挑战第31天】WPF(Windows Presentation Foundation)是 .NET 框架下的图形界面技术,具有丰富的界面设计和灵活的客户端功能。在某些场景下,WPF 应用需与 Windows 服务交互以实现后台任务处理、系统监控等功能。本文探讨了两者交互的方法,并通过示例代码展示了如何扩展 WPF 应用的功能。首先介绍了 Windows 服务的基础知识,然后阐述了创建 Windows 服务、设计通信接口及 WPF 客户端调用服务的具体步骤。通过合理的交互设计,WPF 应用可获得更强的后台处理能力和系统级操作权限,提升应用的整体性能。
98 0
|
3月前
|
自然语言处理 前端开发 JavaScript
前端进阶必读:JS闭包深度解析,掌握这一特性,你的代码将焕然一新!
【8月更文挑战第23天】闭包是JavaScript的一项高级功能,让函数能够访问和操作外部函数作用域中的变量。本文深入解析闭包概念、组成及应用场景。闭包由函数及其词法环境构成,通过在一个函数内定义另一个函数来创建。它有助于封装私有变量、维持状态和动态生成函数。然而,不当使用闭包可能导致内存泄漏或性能问题。掌握闭包对于实现模块化代码和成为优秀前端开发者至关重要。
40 0
|
设计模式 分布式计算 算法
一些令人惊叹的编程方式:
一些令人惊叹的编程方式:
111 1
|
安全
游戏开发零基础入门教程(12):从想法到设计的过程
一个游戏通常开始于一个想法,这个想法可以是千奇百怪的,可以是五花八门的,甚至可以是可笑的。不论如何有一个想法,是一个游戏的开端。当你有了一个想法了以后,如果你希望它最终能够变成一个真正的游戏,那么你就必须要继续往前走,反复的思考,雕琢你的粗糙的想法,将它细化,形成一份具体的可行的游戏设计方案。
122 0
|
XML 移动开发 前端开发
这16种原生函数和属性的区别,你真的知道吗? 精心收集,高级前端必备知识,快快打包带走
原生内置了很多API, 作用类似,却也有差千差万别,了解其区别,掌握前端基础,是修炼上层,成为前端高级工程师的必备知识,让我们一起来分类归纳,一起成长吧。
199 0
这16种原生函数和属性的区别,你真的知道吗? 精心收集,高级前端必备知识,快快打包带走
|
自然语言处理 搜索推荐
革命性的基于知识编程语言Wolfram发布第一个演示
我们曾在去年年底时介绍过 Stephen Wolfram 这位传奇人物雄心勃勃的新计划,一种将颠覆编程甚至颠覆世界的全新计算模式—Wolfram 语言。2 个月过去之后,这门语言推出了第一个演示视频。虽然视频只有 10 几分钟,但看到那简洁明了的代码以及丰富绚丽的结果之后,你一定会感到极大的震撼。
199 0
革命性的基于知识编程语言Wolfram发布第一个演示
|
人工智能
带你读《少儿人工智能趣味入门动画与游戏编程一本通》之二:角色的基础:“运动”“外观”“声音”模块
Scratch是图形化的编程语言,它具有学习环境趣味性强、操作简单且直观等特点,很好适合6-12岁的孩子学习。本书是立足于Scratch 3.0版本的少儿编程入门书,能让孩子轻松愉快地掌握编程技能,锻炼和提高思维能力和创造力,为迎接人工智能时代的到来做好准备。本书以对Scratch中积木块的分类讲解作为主线,并将编程的核心思想融入大量精心设计的案例,让孩子在实际动手操作中更直观、更深刻地理解不同积木块的运用。本书对积木块的功能和用法解释详尽,语言通俗易懂,能够减少孩子对编程的畏惧心理,没有编程基础的家长也能陪伴孩子一起阅读,在融洽的亲子互动氛围中,自信、愉快地完成学习。
下一篇
无影云桌面