☰

教程 8:Supervisor(30 分钟)

[Supervisor(https://cyberbotics.com/doc/reference/supervisor)(监督者)负责监管一个世界(world),能够对它进行信息的设置或获取。 本教程将教你:

这些只是 [Supervisor(https://cyberbotics.com/doc/reference/supervisor) 所能完成的部分任务,完整概述请参阅其文档。 但请注意,即使启用了 supervisor 选项,也无法直接访问安装在其他机器人上的设备所记录的测量值。

设置环境并添加 Supervisor

动手实践 #1:目标是创建环境并添加一个 Supervisor。通过 File / New / New Project Directory... 菜单项创建一个新项目目录,并按照以下说明操作:

  1. 将项目目录命名为 my_supervisor,而不是建议的 my_project。
  2. 将世界文件命名为 my_supervisor.wbt,而不是建议的 empty.wbt。
  3. 勾选所有的复选框,包括默认未勾选的 "Add a rectangle arena"。
  4. 为了获得更大的空间,通过修改 floorSize 字段把场地大小设置为 10x10 米。
  5. 向场景中添加一个 BB-8 机器人,点击 Add 按钮 并导航到:PROTO nodes (Webots projects) / robots / sphero / bb8。
  6. 出于本教程的目的,点击 controller 字段,然后点击 Select 按钮,并从列表中选择 <none>,以移除 BB-8 的默认控制器。
  7. 向场景中添加一个简单的 [Robot(https://cyberbotics.com/doc/reference/robot) 节点,它将成为我们的 Supervisor。 点击 Add 按钮时,可以在 base nodes 类别中找到 [Robot(https://cyberbotics.com/doc/reference/robot) 节点。 为了更好地跟踪它,将其 name 字段改为 supervisor。
  8. 尽管名称已更改,该节点目前仍然只是一个 [Robot(https://cyberbotics.com/doc/reference/robot),要将此机器人变为 [Supervisor(https://cyberbotics.com/doc/reference/supervisor),需要把它的 supervisor 字段设置为 "TRUE"。
  9. 与普通机器人一样,supervisor 的行为也是由控制器定义的。 使用 File / New / New Robot Controller... 菜单项添加一个控制器,选择你喜欢的编程语言。 本教程选择 Python,但也会提供所有其他选项的代码。 将控制器命名为 supervisor_controller 并点击 finish(对于 C++ 和 Java,请将其命名为 SupervisorController)。
  10. 再次展开 [Robot(https://cyberbotics.com/doc/reference/robot) 节点,按下 controller 字段并点击 Select 按钮,以便将你刚刚创建的控制器赋予 supervisor。
  11. 保存世界。

如果你遵循了这些步骤,你的环境应该如下所示:

tutorial_ball.png

最终生成的环境。

使用 Supervisor 移动物体

到目前为止,我们的 [Supervisor(https://cyberbotics.com/doc/reference/supervisor) 相当无聊,因为赋予它的控制器什么也不做。

在本节中,我们将把 supervisor 编程为把 BB-8 机器人移动到另一个位置。 需要指出的是,为了实现这一点我们实际上是在"作弊"——我们不是指示 BB-8 移动到新位置,而是把它直接传送到那里。 换句话说,这种移动将无视所有物理规则,但这也正是 [Supervisor(https://cyberbotics.com/doc/reference/supervisor) 的强大之处,因为它可以随意打破规则。

你可能已经注意到,我们从 File / New / New Robot Controller... 菜单项创建的默认控制器是为经典机器人配置的,而不是为 supervisor 配置的。 因此,要获得 supervisor 的能力,需要对控制器做一些细微的改动。 首先,根据你所选择的语言,用下面的代码替换默认控制器的内容并保存。

这些改动包括:

注意: 重要的是要记住,supervisor 只不过是一个拥有特殊能力的机器人,这意味着机器人能做的一切,supervisor 也能做。 也就是说,如果你有了 supervisor 实例,就不需要 Robot 实例。 例如,决定控制器运行节奏的无限循环(即:while robot.step(TIME_STEP) != -1)不需要更改,因为 supervisor 同样可以做到。

以下是根据你所选编程语言需要遵循的说明。

将控制器内容替换为以下内容。 保存并使用 Build 按钮编译。

#include <webots/robot.h>
#include <webots/supervisor.h>
#include <stdio.h>

int main(int argc, char **argv) {
  wb_robot_init();

  const int time_step = (int)wb_robot_get_basic_time_step();

  // [CODE PLACEHOLDER 1]

  int i = 0;
  while (wb_robot_step(time_step) != -1) {
    // [CODE PLACEHOLDER 2]

    i++;
  }

  wb_robot_cleanup();

  return 0;
}

将控制器内容替换为以下内容。 保存并使用 Build 按钮编译。

#include <webots/Supervisor.hpp>

// All the webots classes are defined in the "webots" namespace
using namespace webots;

int main(int argc, char **argv) {

  Supervisor *robot = new Supervisor(); // create Supervisor instance

  int timeStep = (int)robot->getBasicTimeStep();

  // [CODE PLACEHOLDER 1]

  int i = 0;
  while (robot->step(timeStep) != -1) {
    // [CODE PLACEHOLDER 2]

    i++;
  }

  delete robot;

  return 0;
}

将控制器内容替换为以下内容并保存。

from controller import Supervisor

robot = Supervisor()  # create Supervisor instance

timestep = int(robot.getBasicTimeStep())

# [CODE PLACEHOLDER 1]

i = 0
while robot.step(timestep) != -1:
  # [CODE PLACEHOLDER 2]

  i += 1

将控制器内容替换为以下内容。 保存并使用 Build 按钮编译。

import com.cyberbotics.webots.controller.Supervisor;
import com.cyberbotics.webots.controller.Node;
import com.cyberbotics.webots.controller.Field;

public class SupervisorController {

  public static void main(String[] args) {
    Supervisor robot = new Supervisor(); // create Supervisor instance

    int timeStep = (int) robot.getBasicTimeStep();

    // [CODE PLACEHOLDER 1]

    int i = 0;
    while (robot.step(timeStep) != -1) {
      // [CODE PLACEHOLDER 2]

      i++;
    }
  }
}

将控制器内容替换为以下内容并保存。

function supervisor_controller

TIME_STEP = wb_robot_get_basic_time_step();

% [CODE PLACEHOLDER 1]

i = 0;
while wb_robot_step(TIME_STEP) ~= -1
  % [CODE PLACEHOLDER 2]

  i = i + 1;
end

将 BB-8 移动到新位置相当简单,如果你想在没有 supervisor 帮助的情况下做到这一点,只需把它的 translation 字段更改为期望的值,例如 0 0 2.5。 [Supervisor(https://cyberbotics.com/doc/reference/supervisor) 的做法与此几乎相同。

动手实践 #2:使用 Supervisor 移动 BB-8。 原则上世界可能非常复杂,因此需要一种方法在其他物体之中唯一地识别我们的 BB-8。 为此,我们可以使用在[教程 2(tutorial-2-modification-of-the-environment.html) 中探讨过的 DEF 机制。 在场景树中点击 BB-8 节点,给它一个 DEF 名称 "BB-8",然后保存世界。

在幕后,每个节点都通过节点引用唯一标识,拥有这个引用,我们就可以修改它。 要检索该引用,可以使用 supervisor 函数 [wb_supervisor_node_get_from_def(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_node_get_from_def)。 在 CODE PLACEHOLDER 1 中,检索 BB-8 的节点引用。

WbNodeRef bb8_node = wb_supervisor_node_get_from_def("BB-8");

现在我们能够访问该节点了,接下来需要访问它的 translation 字段,具体来说,我们需要该字段的引用,就像刚才对节点所做的那样。 为此,可以使用 [wb_supervisor_node_get_field(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_node_get_field) 函数。

WbFieldRef translation_field = wb_supervisor_node_get_field(bb8_node, "translation");

最后,既然 translation 字段的引用已经可用,剩下的就是把它设置为不同的值。 同样,supervisor API 拥有完成此操作所需的所有工具。 translation 字段的类型为 SFVec3,这仅仅意味着它是一个三维向量。 该字段的值可以使用 [wb_supervisor_field_set_sf_vec3f(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_field_set_sf_vec3f) 函数来设置。

if (i == 0) {
  const double new_value[3] = {2.5, 0, 0};
  wb_supervisor_field_set_sf_vec3f(translation_field, new_value);
}

这就是全部了。 如果你保存、构建并运行模拟,你将看到 BB-8 瞬间被传送到一个新位置。

动手实践 #2:使用 Supervisor 移动 BB-8。 原则上世界可能非常复杂,因此需要一种方法在其他物体之中唯一地识别我们的 BB-8。 为此,我们可以使用在[教程 2(tutorial-2-modification-of-the-environment.html) 中探讨过的 DEF 机制。 在场景树中点击 BB-8 节点,给它一个 DEF 名称 "BB-8",然后保存世界。

在幕后,每个节点都通过节点引用唯一标识,拥有这个引用,我们就可以修改它。 要检索该引用,可以使用 supervisor 方法 [getFromDef(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_node_get_from_def)。 在 CODE PLACEHOLDER 1 中,检索 BB-8 的节点引用。

Node *bb8Node = robot->getFromDef("BB-8");

现在我们能够访问该节点了,接下来需要访问它的 translation 字段,具体来说,我们需要该字段的引用,就像刚才对节点所做的那样。 为此,可以使用 [getField(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_node_get_field) 方法。

Field *translationField = bb8Node->getField("translation");

最后,既然 translation 字段的引用已经可用,剩下的就是把它设置为不同的值。 同样,supervisor API 拥有完成此操作所需的所有工具。 translation 字段的类型为 SFVec3,这仅仅意味着它是一个三维向量。 该字段的值可以使用 [setSFVec3f(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_field_set_sf_vec3f) 方法来设置。 在 CODE PLACEHOLDER 2 中,添加以下内容:

if (i == 0) {
  const double newValue[3] = {2.5, 0, 0};
  translationField->setSFVec3f(newValue);
}

这就是全部了。 如果你保存、构建并运行模拟,你将看到 BB-8 瞬间被传送到一个新位置。

动手实践 #2:使用 Supervisor 移动 BB-8。 原则上世界可能非常复杂,因此需要一种方法在其他物体之中唯一地识别我们的 BB-8。 为此,我们可以使用在[教程 2(tutorial-2-modification-of-the-environment.html) 中探讨过的 DEF 机制。 在场景树中点击 BB-8 节点,给它一个 DEF 名称 "BB-8",然后保存世界。

在幕后,每个节点都通过节点引用唯一标识,拥有这个引用,我们就可以修改它。 要检索该引用,可以使用 supervisor 方法 [getFromDef(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_node_get_from_def)。 在 CODE PLACEHOLDER 1 中,检索 BB-8 的节点引用。

bb8_node = robot.getFromDef('BB-8')

现在我们能够访问该节点了,接下来需要访问它的 translation 字段,具体来说,我们需要该字段的引用,就像刚才对节点所做的那样。 为此,可以使用 [getField(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_node_get_field) 方法。

translation_field = bb8_node.getField('translation')

最后,既然 translation 字段的引用已经可用,剩下的就是把它设置为不同的值。 同样,supervisor API 拥有完成此操作所需的所有工具。 translation 字段的类型为 SFVec3,这仅仅意味着它是一个三维向量。 该字段的值可以使用 [setSFVec3f(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_field_set_sf_vec3f) 方法来设置。

if i == 0:
  new_value = [2.5, 0, 0]
  translation_field.setSFVec3f(new_value)

这就是全部了。 如果你保存并运行模拟,你将看到 BB-8 瞬间被传送到一个新位置。

动手实践 #2:使用 Supervisor 移动 BB-8。 原则上世界可能非常复杂,因此需要一种方法在其他物体之中唯一地识别我们的 BB-8。 为此,我们可以使用在[教程 2(tutorial-2-modification-of-the-environment.html) 中探讨过的 DEF 机制。 在场景树中点击 BB-8 节点,给它一个 DEF 名称 "BB-8",然后保存世界。

在幕后,每个节点都通过节点引用唯一标识,拥有这个引用,我们就可以修改它。 要检索该引用,可以使用 supervisor 方法 [getFromDef(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_node_get_from_def)。 在 CODE PLACEHOLDER 1 中,检索 BB-8 的节点引用。

Node bb8Node = robot.getFromDef("BB-8");

现在我们能够访问该节点了,接下来需要访问它的 translation 字段,具体来说,我们需要该字段的引用,就像刚才对节点所做的那样。 为此,可以使用 [getField(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_node_get_field) 方法。

Field translationField = bb8Node.getField("translation");

最后,既然 translation 字段的引用已经可用,剩下的就是把它设置为不同的值。 同样,supervisor API 拥有完成此操作所需的所有工具。 translation 字段的类型为 SFVec3,这仅仅意味着它是一个三维向量。 该字段的值可以使用 [setSFVec3f(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_field_set_sf_vec3f) 方法来设置。

if (i == 0) {
  double newValue[] = {2.5, 0, 0};
  translationField.setSFVec3f(newValue);
}

这就是全部了。 如果你保存、构建并运行模拟,你将看到 BB-8 瞬间被传送到一个新位置。

动手实践 #2:使用 Supervisor 移动 BB-8。 原则上世界可能非常复杂,因此需要一种方法在其他物体之中唯一地识别我们的 BB-8。 为此,我们可以使用在[教程 2(tutorial-2-modification-of-the-environment.html) 中探讨过的 DEF 机制。 在场景树中点击 BB-8 节点,给它一个 DEF 名称 "BB-8",然后保存世界。

在幕后,每个节点都通过节点引用唯一标识,拥有这个引用,我们就可以修改它。 要检索该引用,可以使用 supervisor 方法 [wb_supervisor_node_get_from_def(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_node_get_from_def)。 在 CODE PLACEHOLDER 1 中,检索 BB-8 的节点引用。

bb8_node = wb_supervisor_node_get_from_def('BB-8');

现在我们能够访问该节点了,接下来需要访问它的 translation 字段,具体来说,我们需要该字段的引用,就像刚才对节点所做的那样。 为此,可以使用 [wb_supervisor_node_get_field(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_node_get_field) 函数。

translation_field = wb_supervisor_node_get_field(bb8_node, 'translation');

最后,既然 translation 字段的引用已经可用,剩下的就是把它设置为不同的值。 同样,supervisor API 拥有完成此操作所需的所有工具。 translation 字段的类型为 SFVec3,这仅仅意味着它是一个三维向量。 该字段的值可以使用 [wb_supervisor_field_set_sf_vec3f(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_field_set_sf_vec3f) 函数来设置。

if (i == 0)
  new_value = [2.5, 0, 0]
  wb_supervisor_field_set_sf_vec3f(translation_field, new_value);
end

这就是全部了。 如果你保存并运行模拟,你将看到 BB-8 瞬间被传送到一个新位置。

这是一个简单的例子,但无论你希望修改哪个字段,原理都是相同的。 例如,你可以增大物体的尺寸、改变它的颜色、改变光照条件,或者在物体越界时重置它的位置,可能性是无穷的。 这只是一个获取节点引用的问题,从中可以按名称获得字段引用,然后使用适合你要修改类型的函数设置它的值。

生成与移除节点

Supervisor 还可以用于填充环境,从而动态地搭建场景。 本节聚焦于如何添加和移除节点,具体来说,我们将从这个世界中移除 BB-8,并用另一个机器人替换它,即 Nao。

动手实践 #3:移除和添加节点。 在上一节中,我们已经看到了如何检索对象的节点引用。 可以使用 [wb_supervisor_node_remove(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_node_remove) 函数从场景树中移除一个节点。 if 条件并非必需,它只是在移除之前增加了 10 步的延迟,使效果更明显。 在 CODE PLACEHOLDER 2 中添加以下代码。

if (i == 10)
  wb_supervisor_node_remove(bb8_node);

10 个时间步之后,BB-8 将从场景中被移除。 现在,让我们在 20 个时间步之后添加 Nao 机器人。 为了添加一个节点,我们必须知道希望在场景树的哪个位置生成它。 它应该被添加到场景树的顶层吗?还是应该作为另一个节点的字段插入? 这些问题将决定节点如何插入以及需要使用哪个 supervisor 函数,但其中不变的因素是,我们需要这个位置的引用。 在这种情况下,Nao 机器人将被添加到场景树的最后位置,也就是 BB-8 之前出现的位置。 首先,我们需要向 Webots 声明 Nao,作为我们创建的世界的可导入 PROTO。 要继续,请点击位于场景树顶部的 IMPORTABLE EXTERNPROTO 按钮。 位于场景树下方的字段编辑器应该显示一个 IMPORTABLE EXTERNPROTO 面板,其中有一个名为 "Insert new" 的按钮。 点击此按钮,并从 PROTO nodes (Webots Projects) 部分选择 Nao 机器人。 按下 "Insert" 按钮。 如果你把鼠标悬停在 "Insert new" 按钮下方出现的 Nao 项目上,你将在工具提示中看到它的下载 URL。 保存世界文件,以便存储此信息。

尽管并不明显,场景树实际上是一个 [Group(https://cyberbotics.com/doc/reference/group) 节点,场景树中的每个对象——如 WorldInfo、Viewpoint、TexturedBackground 等等——都不过是定义为它子节点的节点。 我们将这个包含一切的 [Group(https://cyberbotics.com/doc/reference/group) 节点称为 root 节点。 为了插入 Nao 机器人,我们所需要的引用实际上是 root 节点的 children 字段的引用。 在 CODE PLACEHOLDER 1 标记的位置,以下代码可以获取该引用。

WbNodeRef root_node = wb_supervisor_node_get_root();
WbFieldRef children_field = wb_supervisor_node_get_field(root_node, "children");

要生成一个节点,你应该使用 supervisor 函数 [wb_supervisor_field_import_[mf/sf]_node_from_string(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_field_import_mf_node_from_string)。 这些函数名称中的 "mf_node" 和 "sf_node" 部分指定了对象被插入到其中的节点的类型。 "mf_node" 代表 multi-field node(多字段节点),而 "sf_node" 代表 single-field node(单字段节点)。

如前所述,Nao 应该被添加到 root 节点的 children 字段中,你可能已经猜到,这个 children 字段是多字段类型。

让我们在 20 个时间步之后从字符串添加它,在 CODE PLACEHOLDER 2 中添加以下代码片段:

if (i == 20)
  wb_supervisor_field_import_mf_node_from_string(children_field, -1, "Nao { }");

"-1" 指定了我们希望插入节点的位置,在本例中,是把它插入到最后一个位置。 "Nao { }" 是一个描述我们希望生成什么的字符串。 描述对象的方式是使用 VRML97 格式,这也是世界文件中使用的格式。 20 个时间步之后,Nao 机器人将在场景中央生成。

假设我们希望 Nao 生成在 BB-8 原来的位置,我们当然可以按照动手实践 2 的流程把它移动过去,但那并不聪明。 实际上,我们可以直接在字符串中指定 translation 字段! 将字符串 "Nao { }" 替换为 "Nao { translation 2.5 0 0.334 }",它就会恰好生成在那个位置。 还不止于此,以同样的方式,我们还可以定义它的 controller 参数,或者 cameraWidth,或者它的任何其他参数。

动手实践 #3:移除和添加节点。 在上一节中,我们已经看到了如何检索对象的节点引用。 可以使用 [remove(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_node_remove) 方法从场景树中移除一个节点。 if 条件并非必需,它只是在移除之前增加了 10 步的延迟,使效果更明显。 在 CODE PLACEHOLDER 2 中添加以下代码。

if (i == 10)
  bb8Node->remove();

10 个时间步之后,BB-8 将从场景中被移除。 现在,让我们在 20 个时间步之后添加 Nao 机器人。 为了添加一个节点,我们必须知道希望在场景树的哪个位置生成它。 它应该被添加到场景树的顶层吗?还是应该作为另一个节点的字段插入? 这些问题将决定节点如何插入以及需要使用哪个 supervisor 函数,但其中不变的因素是,我们需要这个位置的引用。 在这种情况下,Nao 机器人将被添加到场景树的最后位置,也就是 BB-8 之前出现的位置。 首先,我们需要向 Webots 声明 Nao,作为我们创建的世界的可导入 PROTO。 要继续,请点击位于场景树顶部的 IMPORTABLE EXTERNPROTO 按钮。 位于场景树下方的字段编辑器应该显示一个 IMPORTABLE EXTERNPROTO 面板,其中有一个名为 "Insert new" 的按钮。 点击此按钮,并从 PROTO nodes (Webots Projects) 部分选择 Nao 机器人。 按下 "Insert" 按钮。 如果你把鼠标悬停在 "Insert new" 按钮下方出现的 Nao 项目上,你将在工具提示中看到它的下载 URL。 保存世界文件,以便存储此信息。

尽管并不明显,场景树实际上是一个 [Group(https://cyberbotics.com/doc/reference/group) 节点,场景树中的每个对象——如 WorldInfo、Viewpoint、TexturedBackground 等等——都不过是定义为它子节点的节点。 我们将这个包含一切的 [Group(https://cyberbotics.com/doc/reference/group) 节点称为 root 节点。 为了插入 Nao 机器人,我们所需要的引用实际上是 root 节点的 children 字段的引用。 在 CODE PLACEHOLDER 1 标记的位置,以下代码可以获取该引用。

Node *rootNode = robot->getRoot();
Field *childrenField = rootNode->getField("children");

要生成一个节点,你应该使用 supervisor 函数 [importMFNodeFromString(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_field_import_mf_node_from_string) 或 [importSFNodeFromString(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_field_import_sf_node_from_string)。 这些函数名称中的 "MFNode" 和 "SFNode" 部分指定了对象被插入到其中的节点的类型。 "MFNode" 代表 multi-field node(多字段节点),而 "SFNode" 代表 single-field node(单字段节点)。

如前所述,Nao 应该被添加到 root 节点的 children 字段中,你可能已经猜到,这个 children 字段是多字段类型。

让我们在 20 个时间步之后从字符串添加它,在 CODE PLACEHOLDER 2 中添加以下代码片段:

if (i == 20)
  childrenField->importMFNodeFromString(-1, "Nao { }");

"-1" 指定了我们希望插入节点的位置,在本例中,是把它插入到最后一个位置。 "Nao { }" 是一个描述我们希望生成什么的字符串。 描述对象的方式是使用 VRML97 格式,这也是世界文件中使用的格式。 20 个时间步之后,Nao 机器人将在场景中央生成。

假设我们希望 Nao 生成在 BB-8 原来的位置,我们当然可以按照动手实践 2 的流程把它移动过去,但那并不聪明。 实际上,我们可以直接在字符串中指定 translation 字段! 将字符串 "Nao { }" 替换为 "Nao { translation 2.5 0 0.334 }",它就会恰好生成在那个位置。 还不止于此,以同样的方式,我们还可以定义它的 controller 参数,或者 cameraWidth,或者它的任何其他参数。

动手实践 #3:移除和添加节点。 在上一节中,我们已经看到了如何检索对象的节点引用。 可以使用 [remove(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_node_remove) 方法从场景树中移除一个节点。 if 条件并非必需,它只是在移除之前增加了 10 步的延迟,使效果更明显。 在 CODE PLACEHOLDER 2 中添加以下代码。

if i == 10:
  bb8_node.remove()

10 个时间步之后,BB-8 将从场景中被移除。 现在,让我们在 20 个时间步之后添加 Nao 机器人。 为了添加一个节点,我们必须知道希望在场景树的哪个位置生成它。 它应该被添加到场景树的顶层吗?还是应该作为另一个节点的字段插入? 这些问题将决定节点如何插入以及需要使用哪个 supervisor 函数,但其中不变的因素是,我们需要这个位置的引用。 在这种情况下,Nao 机器人将被添加到场景树的最后位置,也就是 BB-8 之前出现的位置。 首先,我们需要向 Webots 声明 Nao,作为我们创建的世界的可导入 PROTO。 要继续,请点击位于场景树顶部的 IMPORTABLE EXTERNPROTO 按钮。 位于场景树下方的字段编辑器应该显示一个 IMPORTABLE EXTERNPROTO 面板,其中有一个名为 "Insert new" 的按钮。 点击此按钮,并从 PROTO nodes (Webots Projects) 部分选择 Nao 机器人。 按下 "Insert" 按钮。 如果你把鼠标悬停在 "Insert new" 按钮下方出现的 Nao 项目上,你将在工具提示中看到它的下载 URL。 保存世界文件,以便存储此信息。

尽管并不明显,场景树实际上是一个 [Group(https://cyberbotics.com/doc/reference/group) 节点,场景树中的每个对象——如 WorldInfo、Viewpoint、TexturedBackground 等等——都不过是定义为它子节点的节点。 我们将这个包含一切的 [Group(https://cyberbotics.com/doc/reference/group) 节点称为 root 节点。 为了插入 Nao 机器人,我们所需要的引用实际上是 root 节点的 children 字段的引用。 在 CODE PLACEHOLDER 1 标记的位置,以下代码可以获取该引用。

root_node = robot.getRoot()
children_field = root_node.getField('children')

要生成一个节点,你应该使用 supervisor 函数 [importMFNodeFromString(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_field_import_mf_node_from_string) 或 [importSFNodeFromString(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_field_import_sf_node_from_string)。 这些函数名称中的 "MFNode" 和 "SFNode" 部分指定了对象被插入到其中的节点的类型。 "MFNode" 代表 multi-field node(多字段节点),而 "sf_node" 代表 single-field node(单字段节点)。

如前所述,Nao 应该被添加到 root 节点的 children 字段中,你可能已经猜到,这个 children 字段是多字段类型。

让我们在 20 个时间步之后从字符串添加它,在 CODE PLACEHOLDER 2 中添加以下代码片段:

if i == 20:
  children_field.importMFNodeFromString(-1, 'Nao { }')

"-1" 指定了我们希望插入节点的位置,在本例中,是把它插入到最后一个位置。 "Nao { }" 是一个描述我们希望生成什么的字符串。 描述对象的方式是使用 VRML97 格式,这也是世界文件中使用的格式。 20 个时间步之后,Nao 机器人将在场景中央生成。

假设我们希望 Nao 生成在 BB-8 原来的位置,我们当然可以按照动手实践 2 的流程把它移动过去,但那并不聪明。 实际上,我们可以直接在字符串中指定 translation 字段! 将字符串 "Nao { }" 替换为 "Nao { translation 2.5 0 0.334 }",它就会恰好生成在那个位置。 还不止于此,以同样的方式,我们还可以定义它的 controller 参数,或者 cameraWidth,或者它的任何其他参数。

动手实践 #3:移除和添加节点。 在上一节中,我们已经看到了如何检索对象的节点引用。 可以使用 [remove(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_node_remove) 方法从场景树中移除一个节点。 if 条件并非必需,它只是在移除之前增加了 10 步的延迟,使效果更明显。 在 CODE PLACEHOLDER 2 中添加以下代码。

if (i == 10)
  bb8Node.remove();

10 个时间步之后,BB-8 将从场景中被移除。 现在,让我们在 20 个时间步之后添加 Nao 机器人。 为了添加一个节点,我们必须知道希望在场景树的哪个位置生成它。 它应该被添加到场景树的顶层吗?还是应该作为另一个节点的字段插入? 这些问题将决定节点如何插入以及需要使用哪个 supervisor 函数,但其中不变的因素是,我们需要这个位置的引用。 在这种情况下,Nao 机器人将被添加到场景树的最后位置,也就是 BB-8 之前出现的位置。 首先,我们需要向 Webots 声明 Nao,作为我们创建的世界的可导入 PROTO。 要继续,请点击位于场景树顶部的 IMPORTABLE EXTERNPROTO 按钮。 位于场景树下方的字段编辑器应该显示一个 IMPORTABLE EXTERNPROTO 面板,其中有一个名为 "Insert new" 的按钮。 点击此按钮,并从 PROTO nodes (Webots Projects) 部分选择 Nao 机器人。 按下 "Insert" 按钮。 如果你把鼠标悬停在 "Insert new" 按钮下方出现的 Nao 项目上,你将在工具提示中看到它的下载 URL。 保存世界文件,以便存储此信息。

尽管并不明显,场景树实际上是一个 [Group(https://cyberbotics.com/doc/reference/group) 节点,场景树中的每个对象——如 WorldInfo、Viewpoint、TexturedBackground 等等——都不过是定义为它子节点的节点。 我们将这个包含一切的 [Group(https://cyberbotics.com/doc/reference/group) 节点称为 root 节点。 为了插入 Nao 机器人,我们所需要的引用实际上是 root 节点的 children 字段的引用。 在 CODE PLACEHOLDER 1 标记的位置,以下代码可以获取该引用。

Node rootNode = robot.getRoot();
Field childrenField = rootNode.getField("children");

要生成一个节点,你应该使用 supervisor 函数 [importMFNodeFromString(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_field_import_mf_node_from_string) 或 [importSFNodeFromString(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_field_import_sf_node_from_string)。 这些函数名称中的 "MFNode" 和 "sf_node" 部分指定了对象被插入到其中的节点的类型。 "MFNode" 代表 multi-field node(多字段节点),而 "SFNode" 代表 single-field node(单字段节点)。

如前所述,Nao 应该被添加到 root 节点的 children 字段中,你可能已经猜到,这个 children 字段是多字段类型。

让我们在 20 个时间步之后从字符串添加它,在 CODE PLACEHOLDER 2 中添加以下代码片段:

if (i == 20)
  childrenField.importMFNodeFromString(-1, "Nao { }");

"-1" 指定了我们希望插入节点的位置,在本例中,是把它插入到最后一个位置。 "Nao { }" 是一个描述我们希望生成什么的字符串。 描述对象的方式是使用 VRML97 格式,这也是世界文件中使用的格式。 20 个时间步之后,Nao 机器人将在场景中央生成。

假设我们希望 Nao 生成在 BB-8 原来的位置,我们当然可以按照动手实践 2 的流程把它移动过去,但那并不聪明。 实际上,我们可以直接在字符串中指定 translation 字段! 将字符串 "Nao { }" 替换为 "Nao { translation 2.5 0.334 }",它就会恰好生成在那个位置。 还不止于此,以同样的方式,我们还可以定义它的 controller 参数,或者 cameraWidth,或者它的任何其他参数。

动手实践 #3:移除和添加节点。 在上一节中,我们已经看到了如何检索对象的节点引用。 可以使用 [wb_supervisor_node_remove(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_node_remove) 函数从场景树中移除一个节点。 if 条件并非必需,它只是在移除之前增加了 10 步的延迟,使效果更明显。 在 CODE PLACEHOLDER 2 中添加以下代码。

if (i == 10)
  wb_supervisor_node_remove(bb8_node);
end

10 个时间步之后,BB-8 将从场景中被移除。 现在,让我们在 20 个时间步之后添加 Nao 机器人。 为了添加一个节点,我们必须知道希望在场景树的哪个位置生成它。 它应该被添加到场景树的顶层吗?还是应该作为另一个节点的字段插入? 这些问题将决定节点如何插入以及需要使用哪个 supervisor 函数,但其中不变的因素是,我们需要这个位置的引用。 在这种情况下,Nao 机器人将被添加到场景树的最后位置,也就是 BB-8 之前出现的位置。 首先,我们需要向 Webots 声明 Nao,作为我们创建的世界的可导入 PROTO。 要继续,请点击位于场景树顶部的 IMPORTABLE EXTERNPROTO 按钮。 位于场景树下方的字段编辑器应该显示一个 IMPORTABLE EXTERNPROTO 面板,其中有一个名为 "Insert new" 的按钮。 点击此按钮,并从 PROTO nodes (Webots Projects) 部分选择 Nao 机器人。 按下 "Insert" 按钮。 如果你把鼠标悬停在 "Insert new" 按钮下方出现的 Nao 项目上,你将在工具提示中看到它的下载 URL。 保存世界文件,以便存储此信息。

尽管并不明显,场景树实际上是一个 [Group(https://cyberbotics.com/doc/reference/group) 节点,场景树中的每个对象——如 WorldInfo、Viewpoint、TexturedBackground 等等——都不过是定义为它子节点的节点。 我们将这个包含一切的 [Group(https://cyberbotics.com/doc/reference/group) 节点称为 root 节点。 为了插入 Nao 机器人,我们所需要的引用实际上是 root 节点的 children 字段的引用。 在 CODE PLACEHOLDER 1 标记的位置,以下代码可以获取该引用。

root_node = wb_supervisor_node_get_root()
children_field = wb_supervisor_node_get_field(root_node, 'children')

要生成一个节点,你应该使用 supervisor 函数 [wb_supervisor_field_import_[mf/sf]_node_from_string(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_field_import_mf_node_from_string)。 这些函数名称中的 "mf_node" 和 "sf_node" 部分指定了对象被插入到其中的节点的类型。 "mf_node" 代表 multi-field node(多字段节点),而 "sf_node" 代表 single-field node(单字段节点)。

如前所述,Nao 应该被添加到 root 节点的 children 字段中,你可能已经猜到,这个 children 字段是多字段类型。

让我们在 20 个时间步之后从字符串添加它,在 CODE PLACEHOLDER 2 中添加以下代码片段:

if (i == 20)
  wb_supervisor_field_import_mf_node_from_string(children_field, -1, 'Nao { }');
end

"-1" 指定了我们希望插入节点的位置,在本例中,是把它插入到最后一个位置。 "Nao { }" 是一个描述我们希望生成什么的字符串。 描述对象的方式是使用 VRML97 格式,这也是世界文件中使用的格式。 20 个时间步之后,Nao 机器人将在场景中央生成。

假设我们希望 Nao 生成在 BB-8 原来的位置,我们当然可以按照动手实践 2 的流程把它移动过去,但那并不聪明。 实际上,我们可以直接在字符串中指定 translation 字段! 将字符串 "Nao { }" 替换为 "Nao { translation 2.5 0 0.334 }",它就会恰好生成在那个位置。 还不止于此,以同样的方式,我们还可以定义它的 controller 参数,或者 cameraWidth,或者它的任何其他参数。

技巧: 如果你不熟悉 VRML97,定义这些字符串的一个简单技巧就是让 Webots 替你完成。 你可以手动创建对象(使用 Webots 界面),然后保存世界。 保存时,Webots 会把你构建的内容转换为一段文本字符串。 如果你用文本编辑器打开世界文件,就可以直接复制对象的描述。

使用 Supervisor 跟踪物体位置

由于 supervisor 拥有无限的能力,它是跟踪模拟演变的完美工具。 在本节中,我们将利用已学到的知识生成一个球,并在它下落的过程中跟踪它的位置,当它触地时改变它的颜色。

动手实践 #4: 我们需要使用 [wb_supervisor_field_import_mf_node_from_string(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_field_import_mf_node_from_string) 函数在 0 1 1 位置生成球,既然做到这一步,让我们同时获取该节点以及 Ball 的 color 字段的引用。 但首先,我们必须把 Ball 声明为 IMPORTABLE EXTERNPROTO,就像我们之前为 Nao 机器人所做的那样。完成后,在 CODE PLACEHOLDER 1 中添加:

wb_supervisor_field_import_mf_node_from_string(children_field, -1, "DEF BALL Ball { translation 0 1 1 }");
WbNodeRef ball_node = wb_supervisor_node_get_from_def("BALL");
WbFieldRef color_field = wb_supervisor_node_get_field(ball_node, "color");

如果你运行模拟,球应该会出现并开始下落,直到与地面相撞。

为了跟踪球的位置,我们可以获取它 translation 字段的引用并持续读取该值,不过更简单的方法是使用另一个为该任务而创建的 supervisor 函数,即 [wb_supervisor_node_get_position(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_node_get_position)。 在 CODE PLACEHOLDER 2 中添加:

const double *position = wb_supervisor_node_get_position(ball_node);
printf("Ball position: %f %f %f\n", position[0], position[1], position[2]);

现在我们可以跟踪它了,让我们改变球的颜色。由于球有 0.2 的半径,当位置的 "Y" 坐标小于这个值时(即碰撞发生时),我们就改变 color 字段。

if (position[2] < 0.2) {
  const double red_color[3] = {1, 0, 0};
  wb_supervisor_field_set_sf_color(color_field, red_color);
}

动手实践 #4: 我们需要使用 [wb_supervisor_field_import_mf_node_from_string(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_field_import_mf_node_from_string) 函数在 0 1 1 位置生成球,既然做到这一步,让我们同时获取该节点以及 Ball 的 color 字段的引用。 但首先,我们必须把 Ball 声明为 IMPORTABLE EXTERNPROTO,就像我们之前为 Nao 机器人所做的那样。完成后,在 CODE PLACEHOLDER 1 中添加:

childrenField->importMFNodeFromString(-1, "DEF BALL Ball { translation 0 1 1 }");
Node *ballNode = robot->getFromDef("BALL");
Field *colorField = ballNode->getField("color");

如果你运行模拟,球应该会出现并开始下落,直到与地面相撞。

为了跟踪球的位置,我们可以获取它 translation 字段的引用并持续读取该值,不过更简单的方法是使用另一个为该任务而创建的 supervisor 方法,即 [getPosition(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_node_get_position)。 在 CODE PLACEHOLDER 2 中添加:

const double *position = ballNode->getPosition();
std::cout << "Ball position: " << position[0] << " " << position[1] << " " << position[2] << std::endl;

现在我们可以跟踪它了,让球一旦发生碰撞就改变颜色。 由于球有 0.2 的半径,当位置的 "Y" 坐标小于这个值时,我们就可以改变 color 字段。

if (position[2] < 0.2) {
  const double redColor[3] = {1, 0, 0};
  colorField->setSFColor(redColor);
}

动手实践 #4: 我们需要使用 [wb_supervisor_field_import_mf_node_from_string(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_field_import_mf_node_from_string) 函数在 0 1 1 位置生成球,既然做到这一步,让我们同时获取该节点以及 Ball 的 color 字段的引用。 但首先,我们必须把 Ball 声明为 IMPORTABLE EXTERNPROTO,就像我们之前为 Nao 机器人所做的那样。完成后,在 CODE PLACEHOLDER 1 中添加:

children_field.importMFNodeFromString(-1, 'DEF BALL Ball { translation 0 1 1 }')
ball_node = robot.getFromDef('BALL')
color_field = ball_node.getField('color')

如果你运行模拟,球应该会出现并开始下落,直到与地面相撞。

为了跟踪球的位置,我们可以获取它 translation 字段的引用并持续读取该值,不过更简单的方法是使用另一个为该任务而创建的 supervisor 方法,即 [getPosition(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_node_get_position)。 在 CODE PLACEHOLDER 2 中添加:

position = ball_node.getPosition()
print('Ball position: %f %f %f\n' %(position[0], position[1], position[2]))

现在我们可以跟踪它了,让球一旦发生碰撞就改变颜色。 由于球有 0.2 的半径,当位置的 "Y" 坐标小于这个值时,我们就可以改变 color 字段。

if position[2] < 0.2:
  red_color = [1, 0, 0]
  color_field.setSFColor(red_color)

动手实践 #4: 我们需要使用 [wb_supervisor_field_import_mf_node_from_string(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_field_import_mf_node_from_string) 函数在 0 1 1 位置生成球,既然做到这一步,让我们同时获取该节点以及 Ball 的 color 字段的引用。 但首先,我们必须把 Ball 声明为 IMPORTABLE EXTERNPROTO,就像我们之前为 Nao 机器人所做的那样。完成后,在 CODE PLACEHOLDER 1 中添加:

childrenField.importMFNodeFromString(-1, "DEF BALL Ball { translation 0 1 1 }");
Node ballNode = robot.getFromDef("BALL");
Field colorField = ballNode.getField("color");

如果你运行模拟,球应该会出现并开始下落,直到与地面相撞。

为了跟踪球的位置,我们可以获取它 translation 字段的引用并持续读取该值,不过更简单的方法是使用另一个为该任务而创建的 supervisor 方法,即 [getPosition(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_node_get_position)。 在 CODE PLACEHOLDER 2 中添加:

double position[] = ballNode.getPosition();
System.out.println("Ball position: " + position[0] + " " + position[1] + " " + position[2]);

现在我们可以跟踪它了,让球一旦发生碰撞就改变颜色。 由于球有 0.2 的半径,当位置的 "Y" 坐标小于这个值时,我们就可以改变 color 字段。

if (position[2] < 0.2) {
  double redColor[] = {1, 0, 0};
  colorField.setSFColor(redColor);
}

动手实践 #4: 我们需要使用 [wb_supervisor_field_import_mf_node_from_string(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_field_import_mf_node_from_string) 函数在 0 1 1 位置生成球,既然做到这一步,让我们同时获取该节点以及 Ball 的 color 字段的引用。 但首先,我们必须把 Ball 声明为 IMPORTABLE EXTERNPROTO,就像我们之前为 Nao 机器人所做的那样。完成后,在 CODE PLACEHOLDER 1 中添加:

wb_supervisor_field_import_mf_node_from_string(children_field, -1, 'DEF BALL Ball { translation 0 1 1 }');
ball_node = wb_supervisor_node_get_from_def('BALL');
color_field = wb_supervisor_node_get_field(ball_node, 'color');

如果你运行模拟,球应该会出现并开始下落,直到与地面相撞。

为了跟踪球的位置,我们可以获取它 translation 字段的引用并持续读取该值,不过更简单的方法是使用另一个为该任务而创建的 supervisor 方法,即 [wb_supervisor_node_get_position(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_node_get_position)。 在 CODE PLACEHOLDER 2 中添加:

position = wb_supervisor_node_get_position(ball_node);
wb_console_print(strcat('Ball position: ', num2str(position(1)), ' ', num2str(position(2)), ' ', num2str(position(3))), WB_STDOUT);

现在我们可以跟踪它了,让球一旦发生碰撞就改变颜色。 由于球有 0.2 的半径,当位置的 "Y" 坐标小于这个值时,我们就可以改变 color 字段。

if (position(3) < 0.2)
  red_color = [1, 0, 0];
  wb_supervisor_field_set_sf_color(color_field, red_color);
end

将全部内容组合在一起

在这里你可以找到控制器的完整代码。

#include <webots/robot.h>
#include <webots/supervisor.h>
#include <stdio.h>

int main(int argc, char **argv) {
  wb_robot_init();

  const int time_step = (int) wb_robot_get_basic_time_step();

  // [CODE PLACEHOLDER 1]
  WbNodeRef bb8_node = wb_supervisor_node_get_from_def("BB-8");
  WbFieldRef translation_field = wb_supervisor_node_get_field(bb8_node, "translation");

  WbNodeRef root_node = wb_supervisor_node_get_root();
  WbFieldRef children_field = wb_supervisor_node_get_field(root_node, "children");

  wb_supervisor_field_import_mf_node_from_string(children_field, -1, "DEF BALL Ball { translation 0 1 1 }");
  WbNodeRef ball_node = wb_supervisor_node_get_from_def("BALL");
  WbFieldRef color_field = wb_supervisor_node_get_field(ball_node, "color");

  int i = 0;
  while (wb_robot_step(time_step) != -1) {
    // [CODE PLACEHOLDER 2]
    if (i == 0) {
      const double new_value[3] = {2.5, 0, 0};
      wb_supervisor_field_set_sf_vec3f(translation_field, new_value);
    }

    if (i == 10)
      wb_supervisor_node_remove(bb8_node);

    if (i == 20)
      wb_supervisor_field_import_mf_node_from_string(children_field, -1, "Nao { translation 2.5 0 0.334 }");

    const double *position = wb_supervisor_node_get_position(ball_node);
    printf("Ball position: %f %f %f\n", position[0], position[1], position[2]);

    if (position[2] < 0.2) {
      const double red_color[3] = {1, 0, 0};
      wb_supervisor_field_set_sf_color(color_field, red_color);
    }

    i++;
  }

  wb_robot_cleanup();

  return 0;
}
#include <webots/Supervisor.hpp>

// All the webots classes are defined in the "webots" namespace
using namespace webots;

int main(int argc, char **argv) {
  Supervisor *robot = new Supervisor();  // create Supervisor instance

  int timeStep = (int)robot->getBasicTimeStep();

  // [CODE PLACEHOLDER 1]
  Node *bb8Node = robot->getFromDef("BB-8");
  Field *translationField = bb8Node->getField("translation");

  Node *rootNode = robot->getRoot();
  Field *childrenField = rootNode->getField("children");

  childrenField->importMFNodeFromString(-1, "DEF BALL Ball { translation 0 1 1 }");
  Node *ballNode = robot->getFromDef("BALL");
  Field *colorField = ballNode->getField("color");

  int i = 0;
  while (robot->step(timeStep) != -1) {
    // [CODE PLACEHOLDER 2]
    if (i == 0) {
      const double newValue[3] = {2.5, 0, 0};
      translationField->setSFVec3f(newValue);
    }

    if (i == 10)
      bb8Node->remove();

    if (i == 20)
      childrenField->importMFNodeFromString(-1, "Nao { }");

    const double *position = ballNode->getPosition();
    std::cout << "Ball position: " << position[0] << " " << position[1] << " " << position[2] << std::endl;

    if (position[2] < 0.2) {
      const double redColor[3] = {1, 0, 0};
      colorField->setSFColor(redColor);
    }

    i++;
  }

  delete robot;

  return 0;
}
from controller import Supervisor

robot = Supervisor()  # create Supervisor instance

timeStep = int(robot.getBasicTimeStep())

# [CODE PLACEHOLDER 1]
bb8_node = robot.getFromDef('BB-8')
translation_field = bb8_node.getField('translation')

root_node = robot.getRoot()
children_field = root_node.getField('children')

children_field.importMFNodeFromString(-1, 'DEF BALL Ball { translation 0 1 1 }')
ball_node = robot.getFromDef('BALL')
color_field = ball_node.getField('color')

i = 0
while robot.step(timeStep) != -1:
  # [CODE PLACEHOLDER 2]
  if (i == 0):
    new_value = [2.5, 0, 0]
    translation_field.setSFVec3f(new_value)

  if i == 10:
      bb8_node.remove()

  if i == 20:
    children_field.importMFNodeFromString(-1, 'Nao { }')

  position = ball_node.getPosition()
  print('Ball position: %f %f %f\n' %(position[0], position[1], position[2]))

  if position[2] < 0.2:
    red_color = [1, 0, 0]
    color_field.setSFColor(red_color)

  i += 1
import com.cyberbotics.webots.controller.Supervisor;
import com.cyberbotics.webots.controller.Node;
import com.cyberbotics.webots.controller.Field;

public class SupervisorController {

  public static void main(String[] args) {
    Supervisor robot = new Supervisor();  // create Supervisor instance

    int timeStep = (int) robot.getBasicTimeStep();

    // [CODE PLACEHOLDER 1]
    Node bb8Node = robot.getFromDef("BB-8");
    Field translationField = bb8Node.getField("translation");

    Node rootNode = robot.getRoot();
    Field childrenField = rootNode.getField("children");

    childrenField.importMFNodeFromString(-1, "DEF BALL Ball { translation 0 1 1 }");
    Node ballNode = robot.getFromDef("BALL");
    Field colorField = ballNode.getField("color");

    int i = 0;
    while (robot.step(timeStep) != -1) {
      // [CODE PLACEHOLDER 2]
      if (i == 0) {
        double newValue[] = {2.5, 0, 0};
        translationField.setSFVec3f(newValue);
      }

      if (i == 10)
        bb8Node.remove();

      if (i == 20)
        childrenField.importMFNodeFromString(-1, "Nao { }");

      double position[] = ballNode.getPosition();
      System.out.println("Ball position: " + position[0] + " " + position[1] + " " + position[2]);

      if (position[2] < 0.2) {
        double redColor[] = {1, 0, 0};
        colorField.setSFColor(redColor);
      }

      i++;
    }
  }
}
function supervisor_controller

TIME_STEP = wb_robot_get_basic_time_step();

% [CODE PLACEHOLDER 1]
bb8_node = wb_supervisor_node_get_from_def('BB-8');
translation_field = wb_supervisor_node_get_field(bb8_node, 'translation');

root_node = wb_supervisor_node_get_root()
children_field = wb_supervisor_node_get_field(root_node, 'children')

wb_supervisor_field_import_mf_node_from_string(children_field, -1, 'DEF BALL Ball { translation 0 1 1 }');
ball_node = wb_supervisor_node_get_from_def('BALL');
color_field = wb_supervisor_node_get_field(ball_node, 'color');

i = 0;
while wb_robot_step(TIME_STEP) ~= -1
  % [CODE PLACEHOLDER 2]
  if (i == 0)
    new_value = [2.5, 0, 0]
    wb_supervisor_field_set_sf_vec3f(translation_field, new_value);
  end

  if (i == 10)
    wb_supervisor_node_remove(bb8_node);
  end

  if (i == 20)
    wb_supervisor_field_import_mf_node_from_string(children_field, -1, 'Nao { }');
  end

  position = wb_supervisor_node_get_position(ball_node);
  wb_console_print(strcat('Ball position: ', num2str(position(1)), ' ', num2str(position(2)), ' ', num2str(position(3))), WB_STDOUT);

  if (position(3) < 0.2)
    red_color = [1, 0, 0];
    wb_supervisor_field_set_sf_color(color_field, red_color);
  end

  i = i + 1;
end

结论

通过本教程,你学到了:

  1. [Supervisor(https://cyberbotics.com/doc/reference/supervisor) 只不过是一个拥有额外能力的 [Robot(https://cyberbotics.com/doc/reference/robot),因此凡是能用 Robot 实例做到的事,你都能用 Supervisor 实例做到。
  2. [Supervisor(https://cyberbotics.com/doc/reference/supervisor) 不受物理规则的约束,由于它的能力是无限的,它也可以"作弊"。
  3. 每当希望使用 [Supervisor(https://cyberbotics.com/doc/reference/supervisor) 改变场景树时,都需要获取一个引用:
    • 要插入一个节点,你需要将要包含它的那个字段的引用。
    • 要移除一个节点,你需要该节点(即对象)本身的引用。
    • 要改变某个参数(如 translation、color、size 等)的值,你需要所述字段的引用。
  4. 生成一个机器人或对象,可以通过将它定义为 IMPORTABLE EXTERNPROTO 并使用 [wb_supervisor_field_import_mf_node_from_string(https://cyberbotics.com/doc/reference/supervisor#wb_supervisor_field_import_mf_node_from_string) API 函数来实现。