每一个Steering行为会产生一个力,他们会作用在速度向量上,这个合力的方向和大小会驱动AI个体实现一些行为(如Seek,Flee,Wander等),大致的计算方式如下:
steering = seek(); // this can be any behavior steering = truncate (steering, max_force) steering = steering / mass velocity = truncate (velocity + steering , max_speed) position = position + velocity
因为Steering力是向量,向量能和别的向量叠加。真正神奇的事情是它能和任意数量的向量叠加。
steering = nothing(); // the null vector, meaning "zero force magnitude" steering = steering + seek(); steering = steering + flee(); (...) steering = truncate (steering, max_force) steering = steering / mass velocity = truncate (velocity + steering , max_speed) position = position + velocity
整合后的Steering力最终会形成一个代表了所有分力的合力。在上面的代码中,最终的Steering力会使游戏角色在Seek某些东西的同时躲避另一些东西。
看一下下面的合力的例子:

这种将单个行为整合在一起的方式可以产生非常复杂的运动行为。想象一下如果是通过编码来实现会有多困难,这些计算可能需要计算距离,区域,路径,图结构等,如果你关心的事情还是动态的,那基本上每一帧都要去计算这些东西。
而Steering行为中,所有的力都是动态的,它们本身就是在游戏的每一帧计算的,因此它们能够顺应环境的变化。
Movement Manager 移动管理
为了同时能够方便的使用多个Steering行为,一个用于管理的Manager很有必要。目的是能够创建一个黑盒,使得所有游戏中的个体都能够具备Steering的能力。
Manager会持有游戏个体的一个引用,这个游戏个体姑且称为Host, Manager会提供给这个个体一堆Steering方法,比如Seek 和 Flee。每一次方法被调用,Manager会更新其内部的属性产生一个Steering力。
在Manager处理完所有的调用后,Manager会将Steering力赋给Host作为其速度向量。

Movement manager: plugin architecture.插件架构
对象模版化
Manager有一堆方法,每个代表了一个Steering行为,每个行为都需要外部的一些信息来使自己工作,比如Seek行为需要外部提供一个目标点,追踪行为需要目标更多的一些信息,如当前位置和速度。空间中的位置也可以用向量表示,这在大多数游戏引擎中非常常见。
在追踪行为中的目标实际上可以是任何东西。为了让Manager更通用,Manager所接收的target必须遵循一定的规则,提供一些查询接口。
PS:some principles of object-oriented programming,
假定IBoid描述了游戏中可以使用Steering行为的所有个体。只要实现这个接口,就能够被Manager接收。
假设接口IBoid描述了一个实体可以被移动管理控制,只要实现这个接口,任何游戏中的类可以使用转向行为。接口实现如下:
public interface IBoid { function getVelocity() :Vector3D; function getMaxVelocity() :Number; function getPosition() :Vector3D; function getMass() :Number; }
现在Manager可以以一种通用的方式和个体进行交互了。Manager由两个基本属性和一堆方法构成。
Movement Manager Structure移动管理器结构
现在Manager可以以一种通用的方式和个体进行交互了。Manager由两个基本属性和一堆方法构成。
public class SteeringManager { public var steering :Vector3D; public var host :IBoid; // The constructor public function SteeringManager(host :IBoid) { this.host = host; this.steering = new Vector3D(0, 0); } // The public API (one method for each behavior) public function seek(target :Vector3D, slowingRadius :Number = 20) :void {} public function flee(target :Vector3D) :void {} public function wander() :void {} public function evade(target :IBoid) :void {} public function pursuit(target :IBoid) :void {} // The update method. // Should be called after all behaviors have been invoked public function update() :void {} // Reset the internal steering force. public function reset() :void {} // The internal API private function doSeek(target :Vector3D, slowingRadius :Number = 0) :Vector3D {} private function doFlee(target :Vector3D) :Vector3D {} private function doWander() :Vector3D {} private function doEvade(target :IBoid) :Vector3D {} private function doPursuit(target :IBoid) :Vector3D {} }
Manager在实例化的时候必须接收一个对Host的引用,Manager会在之后改变Host的速度向量。
每个行为被表示为两个方法,一个Public的一个Private的。拿Seek来作为例子:
public function seek(target :Vector3D, slowingRadius :Number = 20) :void {} private function doSeek(target :Vector3D, slowingRadius :Number = 0) :Vector3D {}
Public的Seek方法用来告诉Manager应用这种行为。这个方法没有返回值,它的参数和行为本身有关。在之后的Private方法里会返回一个经过Steering计算的向量值,这个向量值会存放在Manager的属性中。
// The publish method. // Receives a target to seek and a slowingRadius (used to perform arrive). public function seek(target :Vector3D, slowingRadius :Number = 20) :void { steering.incrementBy(doSeek(target, slowingRadius)); // incrementBy=> steering = steering + xx; } // The real implementation of seek (with arrival code included) private function doSeek(target :Vector3D, slowingRadius :Number = 0) :Vector3D { var force :Vector3D; var distance :Number; desired = target.subtract(host.getPosition()); distance = desired.length; desired.normalize(); if (distance <= slowingRadius) { desired.scaleBy(host.getMaxVelocity() * distance/slowingRadius); } else { desired.scaleBy(host.getMaxVelocity()); } force = desired.subtract(host.getVelocity()); return force; }
别的行为也是类似的实现方式。
public function pursuit(target :IBoid) :void { steering.incrementBy(doPursuit(target)); } private function doPursuit(target :IBoid) :Vector3D { distance = target.getPosition().subtract(host.getPosition()); var updatesNeeded :Number = distance.length / host.getMaxVelocity(); var tv :Vector3D = target.getVelocity().clone(); tv.scaleBy(updatesNeeded); targetFuturePosition = target.getPosition().clone().add(tv); return doSeek(targetFuturePosition); }
之前所说的所有行为都可以添加到 behavior() 到 doBehavior();
Applying and Updating Steering Forces 应用和更新转向力
每次行为方法被调用,返回的向量会被加到Manager的属性steering中。这个属性就代表了所有行为的结果。
在所有行为被调用之后,Manager会把steering的值赋给host的velocity。
然后u执行update()方法;
public function update():void { var velocity :Vector3D = host.getVelocity(); var position :Vector3D = host.getPosition(); truncate(steering, MAX_FORCE); steering.scaleBy(1 / host.getMass()); velocity.incrementBy(steering); truncate(velocity, host.getMaxVelocity()); position.incrementBy(velocity); }
上述的方法必须在执行完所有行为后,再施加给对象。
使用
让我们假设一个类叫做Prey使用steering behavior,结构如下。
public class Prey { public var position :Vector3D; public var velocity :Vector3D; public var mass :Number; public function Prey(posX :Number, posY :Number, totalMass :Number) { position = new Vector3D(posX, posY); velocity = new Vector3D(-1, -2); mass = totalMass; x = position.x; y = position.y; } public function update():void { velocity.normalize(); velocity.scaleBy(MAX_VELOCITY); velocity.scaleBy(1 / mass); truncate(velocity, MAX_VELOCITY); position = position.add(velocity); x = position.x; y = position.y; } }
使用这个结构,实例可以使用欧拉积分移动,类似第一个例子。要让其使用manager,需要实现IBoid接口。
public class Prey implements IBoid { public var position :Vector3D; public var velocity :Vector3D; public var mass :Number; public var steering :SteeringManager; public function Prey(posX :Number, posY :Number, totalMass :Number) { position = new Vector3D(posX, posY); velocity = new Vector3D(-1, -2); mass = totalMass; steering = new SteeringManager(this); x = position.x; y = position.y; } public function update():void { velocity.normalize(); velocity.scaleBy(MAX_VELOCITY); velocity.scaleBy(1 / mass); truncate(velocity, MAX_VELOCITY); position = position.add(velocity); x = position.x; y = position.y; } // Below are the methods the interface IBoid requires. public function getVelocity() :Vector3D { return velocity; } public function getMaxVelocity() :Number { return 3; } public function getPosition() :Vector3D { return position; } public function getMass() :Number { return mass; } }
update()
方法需要修改为如下:
public function update():void { // Make the prey wander around... steering.wander(); // Update the manager so it will change the prey velocity vector. // The manager will perform the Euler intergration as well, changing // the "position" vector. steering.update(); // After the manager has updated its internal structures, all we must // do is update our position according to the "position" vector. x = position.x; y = position.y; }
可以使用所有的行为。只要在manager调用update之前。
下面的代码显示另外一个版本的更新方法。不过这次回增加一个躲避角色的行为。
public function update():void { var destination :Vector3D = getDestination(); // the place to seek var hunter :IBoid = getHunter(); // get the entity who is hunting us // Seek the destination and evade the hunter (at the same time!) steering.seek(destination); steering.evade(hunter); // Update the manager so it will change the prey velocity vector. // The manager will perform the Euler intergration as well, changing // the "position" vector. steering.update(); // After the manager has updated its internal structures, all we must // do is update our position according to the "position" vector. x = position.x; y = position.y; }
hunter狩猎者会追逐prey猎物,如果靠近的话,它会追逐到没体力为止。然后开始漫无目的游荡,直到恢复体力。
Here is the Hunter’s update()
method:
public function update():void { if (resting && stamina++ >= MAX_STAMINA) { resting = false; } if (prey != null && !resting) { steering.pursuit(prey); stamina -= 2; if (stamina <= 0) { prey = null; resting = true; } } else { steering.wander(); prey = getClosestPrey(position); } steering.update(); x = position.x; y = position.y; }
public function update():void { var distance :Number = Vector3D.distance(position, Game.mouse); hunter = getHunterWithinRange(position); if (hunter != null) { steering.evade(hunter); } if (distance <= 300 && hunter == null) { steering.seek(Game.mouse, 30); } else if(hunter == null){ steering.wander(); } steering.update(); x = position.x; y = position.y; }
猎物会无限游荡。如果猎人太近就躲避。如果没有猎人,鼠标离的很近,就会追踪鼠标。
代码如下:
public function update():void { var distance :Number = Vector3D.distance(position, Game.mouse); hunter = getHunterWithinRange(position); if (hunter != null) { steering.evade(hunter); } if (distance <= 300 && hunter == null) { steering.seek(Game.mouse, 30); } else if(hunter == null){ steering.wander(); } steering.update(); x = position.x; y = position.y; }
例子会之后给出unity的。
在实现这个代码后,再继续后续教程。