65.9K
CodeProject 正在变化。 阅读更多。
Home

MetaAgent,一个转向行为模板库

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (18投票s)

2003年5月13日

5分钟阅读

viewsIcon

115454

用于创建具有(有趣)类生命行为的自主代理的库。

引言

本文介绍 MetaAgent,一个用于创建转向行为的 C++ 库。

metaagent logo

关于行为的一些历史

1986 年,Craig Reynolds 正在编写 boids,一个用于动画动物运动(如鸟群和鱼群)的计算机模型。他于 [Craig Reynolds, 87] 发表了一篇关于它的技术论文。他的方法因其简单性而令人惊叹,因为该模型基于 3 个简单规则:

  • 分离图分离:一个 boid 应该避开它的邻居。为此,您只需将 boid 远离其最近邻居的中心即可。
  • 分离图对齐:一个 boid 倾向于将其速度与邻居对齐。
  • 分离图内聚:一个 boid 倾向于靠近它的邻居

这 3 个规则产生的力通过求和(带权重)合并在一起并应用于 boid。Craig Reynolds 的 boids 已经并且仍然在他的个人网页[^]上飞翔。

此后,Craig Reynolds 又发布了一篇重要论文 [Craig Reynolds, 99],描述了许多行为,以“赋予”自主角色“生命”:目标追踪、避障、漫游等...

MetaAgentOpenSteer

MetaAgent 并非唯一围绕转向行为的项目。事实上,它是另一个项目 OpenSteer(由 Craig Reynolds 发起)的小妹妹。

OpenSteer Logo

为什么是另一个库?

首先,玩自主角色很有趣,如果您计划学习 C++,这是一个很棒的项目。这基本上就是 MetaAgent 的开始方式:一个用于测试泛型编程和元编程的“游乐场”。

构建另一个库的真正原因是 OpenSteer 主要是一个 C 函数包装到一些 C++ 类中的集合(好吧,我夸张了……)。MetaAgent 计划(并有望成功)利用 C++ 和泛型编程的全部力量来创建行为。

MetaAgent 指南

以下是该项目尝试遵循的一些指南

  1. 将所有类分解为正交的策略(我稍后会谈到它)
  2. 使用信号和槽进行渲染,
  3. 尽可能多地使用 STL 和 Boost
在本文中,我将重点关注策略概念。信号和槽留待下一次 :)。

策略类设计

我在 Andrei Alexandrescu 的著名书籍《Modern C++ design》中遇到了策略类设计,参见 [Alexandrescu, 2001]。基本思想是通过组合小类(称为策略,每个策略只负责一个行为或结构方面)来组装具有复杂行为的类。该过程的关键点是策略分解的选择。

Andrei Alexandrescu 用整整一章的篇幅介绍了策略设计,我将尝试在下面的代理行为创建中进行说明。

自主代理如何工作?

代理基本上是一个身体(动力学),它根据其大脑(行为)移动。它可以分解为几个部分

  • 身体,实现动力学
  • 大脑,由一个行为组成

使用策略构建代理

策略分解

“就好像 [some_host_class] 充当了一个小型代码生成引擎,你配置了它生成代码的方式。”Andrei Alexandrescu。

让我们从构建代理的动态模型开始。这个主体必须能够移动并对转向力(将由行为给定)做出反应。

如前所述,我们希望使用策略。因此,我们希望将该模型分解为正交策略。让我们看看以下事实

  • 无论动态模型类型如何,您总是可以检索质心状态。
  • 动态模型不需要知道代理“大脑”中发生了什么,它只需要最终的转向
Splitting agent in policies

动态模型和行为可以看作是策略

template< 
   typename ModelPolicy,
   typename BehaviorPolicy
>
class agent : public ModelPolicy, public BehaviorPolicy

如您所见,`agent` 继承自 `ModelPolicy` 和 `BehaviorPolicy`,因此它继承了它们的所有方法!`agent` 被称为宿主类,因为它是由策略构建的。

确定接口

与经典接口(纯虚方法的集合)不同,策略接口是松散定义的。只需在宿主类中使用策略方法,无需任何事先声明,如果它们未在策略类中定义,编译器将报错。因此,我们只需编写一个方法,让代理思考行动

template< 
   typename ModelPolicy,
   typename BehaviorPolicy
>
class agent : public ModelPolicy, public BehaviorPolicy
{
public:
    void think_and_act()
    {

第一步,思考并计算转向力。这将是 BehaviorPolicy 的工作。

        // vec is some 2D vector
        vec steering_force = think( get_acceleration(), get_velocity(), get_position() );

第二步,将计算出的转向力施加到模型上并积分方程

        act( steering_force );  // move according to the steering force -> ModelPolicy
    };
};

太棒了,我们刚刚定义了 ModelPolicyBehaviorPolicy 的接口。

实现 ModelPolicy

实现策略的类称为策略类。最简单的动态模型是点质量模型,通过显式欧拉方案积分

class point_mass_model
{
public:
    void act( vec steering )
    {
        m_acceleration = m_steering / m_mass;
        m_velocity += m_acceleration;
        m_position += m_velocity;
    }
protected:
    vec m_acceleration;
    vec m_velocity;
    vec m_position;
};

备注:有人可能会争辩说,积分应该与模型分离,这完全正确。但为了清晰起见,我在这个例子中将它们合并在一起。

现在,`point_mass_model` 类几乎可以使用了。我们只需要为状态添加一些 getter(`get_acceleration` 等),因为 `BehaviorPolicy` 需要它们

class point_mass_model
{
...
   vec const & get_accelartion() const { return m_acceleration;};
...
};

实现 BehaviorPolicy

行为策略类只需要实现 think 方法。以下行为使代理

  • 圆形移动(通过取垂直于速度的方向)
    // this class makes the agent go round
    struct circle_move_behavior
    {
        // this is the interface to implement
        vec think( 
            vec const& acceleration,
            vec const& veloctiy,
            vec const& position ) const    
        {
             return -perpendicular( velocity );
        };
    };
    
  • 追踪目标(通过将速度指向目标)
    struct seek_behavior
    {
        // the target
        vec m_target;
    
        // this is the interface to implement
        vec think( 
            vec const& acceleration,
            vec const& veloctiy,
            vec const& position ) const    
        {
             return m_target - position;
        };
    
    };
    

备注:当然,转向规范存在问题,但为了清晰起见,此处不予讨论。

在宿主类中合并策略

策略的魔力就在这里。通过合并不同的策略,我们创建了完全不同的代理

// agent will go round
agent< point_mass_model, circle_move_behavior > circle_mover;
// this agent will track a target
agent< point_mass_model, seek_behavior > seeker;

更好的是,您可以通过以下方式简单地更改目标

seeker.m_target = new_target;

因为 seeker 继承自 seek_behavior 并且 m_target 是一个公共属性。

小结

使用策略,创建具有不同动力学和行为的代理就像更改一些模板参数一样简单。这是 MetaAgent 背后的主要思想。

Merging dragon model with sheep model

想参与吗?

以上是对使用策略构建行为可能性的非常粗略的描述。例如,寻求目标可以分解为

  1. 预测目标碰撞点:PredictoryPolicy
  2. 跟踪预测的碰撞点:TrackerPolicy

然后,您可以灵活地组合各种预测器和跟踪器。

如果您感兴趣,可以访问 MetaAgent WikiWikiWeb[^] 学习/贡献项目。

以下是 MetaAgent 演示应用程序的一些快照

wanderer moving randomly on the screen
漫游行为。
 
Seeking: still target,
arrival acceleration modification, moving target
寻求行为变体。

历史

05-20-2003已修复图像链接到新站点
05-12-2003 初次发表

参考

[MetaAgent] http://metaagent.sourceforge.net[^]
[Craig Reynolds, 87] http://www.red3d.com/cwr/papers/1987/boids.html[^]
[Craig Reynolds, 99] http://www.red3d.com/cwr/papers/1999/gdc99steer.html[^]
[OpenSteer] http://opensteer.sourceforge.net[^]
© . All rights reserved.