[Silverlight动画]转向行为 - 群落

2018-01-16 12:00:22 浏览数 (1)

说到群落,很难不引用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();
            }
        }
    }

0 人点赞