说到群落,很难不引用Craig Reynolds和他的"boilds"模拟系统。Reynolds很牛的将一个看似非常恐怖的复杂过程,拆成了几个比较简单的行为。
想想鸟群,它含有三个主要角色:
首先,鸟们都保持在同一个区域。如果有只鸟离队伍远了,就该马上归队。这叫凝聚。如图
其次,尽管鸟们都在一起飞,但是要避免不会互相碰到。为此,它们各自都有一个空间来预防其它鸟太接近。这叫分离。
最后,鸟们飞行在同一个方向。当然各自的角度不一定相同,但是大方向是差不多的。这叫队列。
这三个行为凝聚、分离和队列组成了复杂的群落行为。
当考虑鸟群时,就以整个群落是一条心去想象,或者认为每个鸟都充分认识群中的其它鸟。我不想为此去争论什么,但我要说,当开始理解这三个行为,何以促成群落 行为时,你会发现,每个鸟根本不需要知道多少东西,也不需要什么民主集中一条心来指挥群落。实际上,每个鸟就只需要看看临近的几只伙伴。如果靠太近就离远 点,如果方向差太多就转过来点,最终以此形成了传说中的群落行为。
尽管群落行为技术上被拆成了三个子行为,然而它们几乎总是捆绑出现的。一般不太会只对角色使用其中一两个行为,所以就把这仨放于同一个函数中好了。这样效率也高,避免要做三次循环。
代码语言:javascript复制 public void flock(List<Vehicle> vehicles) {
Vector2D averageVelocity = _velocity.clone();
Vector2D averagePosition = new Vector2D(0, 0);
int inSightCount = 0;
for (int i = 0; i < vehicles.Count; i )
{
Vehicle vehicle = vehicles[i];
if (vehicle!=this&&inSight(vehicle))
{
averageVelocity = averageVelocity.add(vehicle.velocity);
averagePosition = averagePosition.add(vehicle.position);
if (tooClose(vehicle))
{
flee(vehicle.position);
inSightCount ;
}
}
}
if (inSightCount>0)
{
averageVelocity = averageVelocity.divide(inSightCount);
averagePosition = averagePosition.divide(inSightCount);
seek(averagePosition);
_steeringForce.add(averageVelocity.subtract(_velocity));
}
}
首先,传递一个持有机车的数组。通过遍历这个数组找出进入视野的其它机车。把进入视野的机车的速度和位置都加起来,然后统计次数,最后以此求得平均值。如果机车靠太近,用避开函数离开之,以此实现分离。唯一要注意的地方就是处理过程中对自身的忽略。
当走完整个数组,算出平均速度和位置后,寻找平均位置,叠加平均转向力即完成任务。
似乎没啥了不起,不过有几个函数我们还没介绍呢,视野中(inSight)和太接近(tooClose):
inSight 函数判定一个机车是否能看到另一个机车。为此,先要检测两者间距离是否在视野范围内,如果不是就返回false。接着用向量的数学运算判断机车的前后关 系,这里采用的实现方式比较死板,只认前方的机车,在后面就当作看不见。这个做做例子够用了,如果要作改进,可以先考虑做一个可变化的视野范围。窄的视野 范围意味着角色只能沿着视野方向,注意不到两边,宽的视野意味着角色可以看到边上的一些东西。不同的视野范围,会导致不同的群落模式。
再来是tooClose函数,这个简单的不想说了。
代码语言:javascript复制 private double _inSightDist = 200;
private double _tooCloseDist = 60;
private bool tooClose(Vehicle vehicle)
{
return _postion.dist(vehicle.position) < _tooCloseDist;
}
private bool inSight(Vehicle vehicle)
{
if (_postion.dist(vehicle.position)>_inSightDist)
{
return false;
}
Vector2D heading = _velocity.clone().normalize();
Vector2D difference = vehicle.position.subtract(_postion);
double dotProd = difference.dotProd(heading);
if (dotProd<0)
{
return false;
}
return true;
}
测试:
代码语言:javascript复制<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Steer" xmlns:ed="http://schemas.microsoft.com/expression/2010/drawing" x:Class="Steer.FlockTest"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
</Grid>
</UserControl>
代码语言:javascript复制 public partial class FlockTest : UserControl
{
private List<Vehicle> _vehicles;
private int _numVehicles = 30;
public FlockTest()
{
InitializeComponent();
Loaded = new RoutedEventHandler(FlockTest_Loaded);
}
void FlockTest_Loaded(object sender, RoutedEventArgs e)
{
_vehicles = new List<Vehicle>();
for (int i = 0; i < _numVehicles; i )
{
SteeredVehicle vehicle = new SteeredVehicle();
RegularPolygon rp = new RegularPolygon();
rp.Width = 15;
rp.Height = 15;
rp.Fill = new SolidColorBrush(Colors.Blue);
rp.PointCount = 3;
TransformGroup tf = new TransformGroup();
CompositeTransform ct = new CompositeTransform();
tf.Children.Add(ct);
rp.RenderTransform = tf;
ct.Rotation = 90;
vehicle.Children.Add(rp);
vehicle.Width = 15;
vehicle.Height = 15;
vehicle.HorizontalAlignment = HorizontalAlignment.Left;
vehicle.VerticalAlignment = VerticalAlignment.Top;
LayoutRoot.Children.Add(vehicle);
_vehicles.Add(vehicle);
}
CompositionTarget.Rendering = new EventHandler(CompositionTarget_Rendering);
}
void CompositionTarget_Rendering(object sender, EventArgs e)
{
foreach (SteeredVehicle vehicle in _vehicles)
{
vehicle.flock(_vehicles);
vehicle.update();
}
}
}