[Silverlight动画]转向行为 - 群落
说到群落,很难不引用Craig Reynolds和他的"boilds"模拟系统。Reynolds很牛的将一个看似非常恐怖的复杂过程,拆成了几个比较简单的行为。
想想鸟群,它含有三个主要角色:
首先,鸟们都保持在同一个区域。如果有只鸟离队伍远了,就该马上归队。这叫凝聚。如图
其次,尽管鸟们都在一起飞,但是要避免不会互相碰到。为此,它们各自都有一个空间来预防其它鸟太接近。这叫分离。
最后,鸟们飞行在同一个方向。当然各自的角度不一定相同,但是大方向是差不多的。这叫队列。
这三个行为凝聚、分离和队列组成了复杂的群落行为。
当考虑鸟群时,就以整个群落是一条心去想象,或者认为每个鸟都充分认识群中的其它鸟。我不想为此去争论什么,但我要说,当开始理解这三个行为,何以促成群落 行为时,你会发现,每个鸟根本不需要知道多少东西,也不需要什么民主集中一条心来指挥群落。实际上,每个鸟就只需要看看临近的几只伙伴。如果靠太近就离远 点,如果方向差太多就转过来点,最终以此形成了传说中的群落行为。
尽管群落行为技术上被拆成了三个子行为,然而它们几乎总是捆绑出现的。一般不太会只对角色使用其中一两个行为,所以就把这仨放于同一个函数中好了。这样效率也高,避免要做三次循环。
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函数,这个简单的不想说了。
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; }
测试:
<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>
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(); } } }