C++ 实现一个简单的状态机和行为树结合示例

0. 概述

用纯 C++ 实现一个简单的状态机和行为树示例,不依赖外部库。

结合有限状态机和行为树,设计一个简单的门控制逻辑。该逻辑不仅展示了 FSM 和 BT 的结合应用,还引入随机性和条件判断。

进一步阅读: 使用 TinyFSM 和 BehaviorTree.CPP 构建状态机与行为树示例
本文示例代码: https://gitee.com/liudegui/simple-fsm-bt-example

1. 设计方案

设计方案的图示表示,包括状态机图、行为树图以及整体架构图。

1.1 状态机图 (FSM)

状态机图展示了门的不同状态以及状态之间的转换。

unlock
lock
open
lock
close
open
close
halfOpen
jam
unjam
close
LOCKED
UNLOCKED
OPENED
CLOSED
HALF_OPEN
JAMMED

状态机(FSM) 将用于管理门的不同状态,并定义相应的状态转换逻辑。定义以下状态和事件:

  • 状态

    • LOCKED(锁定)
    • UNLOCKED(解锁)
    • CLOSED(关闭)
    • OPENED(打开)
    • HALF_OPEN(半开)
    • JAMMED(卡住)
  • 事件

    • lock:将门锁定
    • unlock:将门解锁
    • open:将门打开
    • close:将门关闭
    • halfOpen:将门半开
    • jam:将门卡住
    • unjam:将门解卡

每个状态都有相应的处理方法,确保在状态转换时执行相应的操作

1.2 行为树图 (BT)

行为树图展示了行为树的结构和各个行为节点。

BehaviorTree
Sequence
UnlockAction
OpenAction
HalfOpenAction
JamAction
UnjamAction
LockAction

行为树(BT) 将用于定义门的行为序列,包含多个行为节点,每个节点对应一个具体的操作。包含以下行为节点:

  • 行为节点
    • UnlockAction:尝试解锁门
    • OpenAction:尝试打开门
    • HalfOpenAction:尝试将门半开
    • JamAction:尝试将门卡住
    • UnjamAction:尝试将门解卡
    • LockAction:尝试锁定门

每个行为节点在执行之前会检查当前门的状态,以确保操作的逻辑性。行为树中的每个步骤都有一定的随机性(50% 的概率)以模拟现实中行为的不确定性。

1.3 整体架构图

整体架构图展示了 FSM 和 BT 如何结合在一起工作。

calls
executes
executes
executes
executes
executes
executes
interacts
interacts
interacts
interacts
interacts
interacts
Main Program
BehaviorTree
UnlockAction
OpenAction
HalfOpenAction
JamAction
UnjamAction
LockAction
DoorFSM
LOCKED
UNLOCKED
CLOSED
OPENED
HALF_OPEN
JAMMED

1.4 其它

  • 整体架构图

    • 主程序调用行为树,行为树顺序执行各个行为节点。

    • 每个行为节点根据当前状态与状态机(DoorFSM)进行交互,执行相应的状态转换。

    • 状态机管理门的状态,处理各种事件,并在状态之间进行转换。

  • 输出信息
    为了让系统的行为更具可读性,我们在每个行为节点中添加详细的输出信息,说明当前正在尝试执行的操作。例如:

    • Step 0: Attempting to unlock the door...
    • Step 1: Attempting to open the door...
    • Skipping step 1 due to random choice.

这些图示展示了 FSM 和 BT 的结合应用,能够更直观地理解整个系统的设计和工作流程。

2. 代码实现

2.1 代码结构

.
├── CMakeLists.txt
├── src
│   ├── main.cpp
│   ├── fsm.hpp
│   └── behavior_tree.hpp

2.2 CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(SimpleFSM_BT_DoorExample)

set(CMAKE_CXX_STANDARD 17)

add_executable(SimpleFSM_BT_DoorExample
    src/main.cpp
)

2.3 FSM 实现 src/fsm.hpp

#ifndef FSM_HPP
#define FSM_HPP

#include <iostream>

class DoorFSM {
 public:
  enum State { LOCKED, UNLOCKED, CLOSED, OPENED, HALF_OPEN, JAMMED };

  DoorFSM() : state(LOCKED) {
  }

  void lock() {
    if (state != LOCKED) {
      state = LOCKED;
      std::cout << "Door is now locked." << std::endl;
    }
  }

  void unlock() {
    if (state == LOCKED) {
      state = UNLOCKED;
      std::cout << "Door is now unlocked." << std::endl;
    }
  }

  void open() {
    if (state == CLOSED || state == UNLOCKED || state == HALF_OPEN) {
      state = OPENED;
      std::cout << "Door is now opened." << std::endl;
    }
  }

  void close() {
    if (state == OPENED || state == HALF_OPEN) {
      state = CLOSED;
      std::cout << "Door is now closed." << std::endl;
    }
  }

  void halfOpen() {
    if (state == CLOSED || state == OPENED) {
      state = HALF_OPEN;
      std::cout << "Door is now half open." << std::endl;
    }
  }

  void jam() {
    if (state != JAMMED) {
      state = JAMMED;
      std::cout << "Door is now jammed." << std::endl;
    }
  }

  void unjam() {
    if (state == JAMMED) {
      state = CLOSED;
      std::cout << "Door is now unjammed and closed." << std::endl;
    }
  }

  State getState() const {
    return state;
  }

 private:
  State state;
};

#endif  // FSM_HPP

2.4 行为树实现 src/behavior_tree.hpp

#ifndef BEHAVIOR_TREE_HPP
#define BEHAVIOR_TREE_HPP

#include <cstdlib>
#include <ctime>
#include <functional>
#include <vector>

#include "fsm.hpp"

class BehaviorTree {
 public:
  using Action = std::function<void()>;

  explicit BehaviorTree(DoorFSM& fsm) : fsm(fsm), currentStep(0) {
    actions.push_back([this, &fsm]() {
      std::cout << "Step 0: Attempting to unlock the door..." << std::endl;
      if (fsm.getState() == DoorFSM::LOCKED) {
        fsm.unlock();
      }
    });
    actions.push_back([this, &fsm]() {
      std::cout << "Step 1: Attempting to open the door..." << std::endl;
      if (fsm.getState() == DoorFSM::UNLOCKED || fsm.getState() == DoorFSM::CLOSED) {
        fsm.open();
      }
    });
    actions.push_back([this, &fsm]() {
      std::cout << "Step 2: Attempting to half open the door..." << std::endl;
      if (fsm.getState() == DoorFSM::OPENED) {
        fsm.halfOpen();
      }
    });
    actions.push_back([this, &fsm]() {
      std::cout << "Step 3: Attempting to jam the door..." << std::endl;
      if (fsm.getState() == DoorFSM::HALF_OPEN) {
        fsm.jam();
      }
    });
    actions.push_back([this, &fsm]() {
      std::cout << "Step 4: Attempting to unjam the door..." << std::endl;
      if (fsm.getState() == DoorFSM::JAMMED) {
        fsm.unjam();
      }
    });
    actions.push_back([this, &fsm]() {
      std::cout << "Step 5: Attempting to lock the door..." << std::endl;
      if (fsm.getState() == DoorFSM::CLOSED) {
        fsm.lock();
      }
    });

    std::srand(std::time(nullptr));  // 初始化随机数生成器
  }

  void run() {
    while (currentStep < actions.size()) {
      if (std::rand() % 2 == 0) {  // 50%的概率执行当前步骤
        actions[currentStep]();
      } else {
        std::cout << "Skipping step " << currentStep << " due to random choice." << std::endl;
      }
      currentStep++;
    }
    currentStep = 0;
  }

 private:
  DoorFSM& fsm;
  std::vector<Action> actions;
  int currentStep;
};

#endif  // BEHAVIOR_TREE_HPP

2.5 主程序 src/main.cpp

#include "behavior_tree.hpp"
#include "fsm.hpp"
#include <thread>

int main() {
  DoorFSM fsm;
  BehaviorTree bt(fsm);

  while (true) {
    bt.run();  // 执行带有随机性的行为树
    std::this_thread::sleep_for(std::chrono::seconds(1));
  }

  return 0;
}

2.6 代码解释

  1. 状态机(FSM):定义了 DoorFSM 类,包含六个状态(LOCKED, UNLOCKED, CLOSED, OPENED, HALF_OPEN, JAMMED)和相应的方法。

  2. 行为树(BT):行为树在每个动作节点中捕获 fsm 对象的引用,确保在执行 lambda 函数时可以访问 fsm 对象。通过 [this, &fsm] 捕获 this 指针和 fsm 引用。

  3. 条件判断和随机性:行为树在每一步操作之前,先检查当前状态是否允许执行该操作,同时保留了随机性。

  4. 跳过步骤:如果当前步骤被随机选择跳过,则输出 Skipping step X due to random choice.,其中 X 是当前步骤的编号。

通过这种方式,我们可以确保行为树中的每个动作节点都能正确访问 fsm 对象,并根据当前状态执行相应的操作。再次尝试编译和运行程序,这些修改应该可以解决捕获问题。

3. 结果

行为树会在执行每一步之前检查当前状态,确保操作的逻辑一致性。这使得系统在引入随机性的同时,保持状态转换的合理性。现在运行程序时,会看到更加合理的状态变化输出:

Step 0: Attempting to unlock the door...
Door is now unlocked.
Step 1: Attempting to open the door...
Door is now opened.
Step 2: Attempting to half open the door...
Door is now half open.
Step 3: Attempting to jam the door...
Door is now jammed.
Step 4: Attempting to unjam the door...
Door is now unjammed and closed.
Step 5: Attempting to lock the door...
Door is now locked.
Skipping step 0 due to random choice.
Step 1: Attempting to open the door...
Step 2: Attempting to half open the door...
Step 3: Attempting to jam the door...
Skipping step 4 due to random choice.
Step 5: Attempting to lock the door...
Step 0: Attempting to unlock the door...
Door is now unlocked.
Skipping step 1 due to random choice.
Step 2: Attempting to half open the door...
Step 3: Attempting to jam the door...
Step 4: Attempting to unjam the door...
Step 5: Attempting to lock the door...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橘色的喵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值