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...
  • 21
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,以下是一个简单行为树示例实现一个小游戏角色的行为逻辑: ```c #include <stdio.h> #include <stdbool.h> // 行为树节点类型定义 typedef struct behavior_tree_node { bool (*condition)(); void (*action)(); struct behavior_tree_node* left_child; struct behavior_tree_node* right_child; } behavior_tree_node; // 行为树节点创建函数 behavior_tree_node* create_behavior_tree_node(bool (*condition)(), void (*action)(), behavior_tree_node* left_child, behavior_tree_node* right_child) { behavior_tree_node* node = (behavior_tree_node*)malloc(sizeof(behavior_tree_node)); node->condition = condition; node->action = action; node->left_child = left_child; node->right_child = right_child; return node; } // 行为树执行函数 void run_behavior_tree(behavior_tree_node* node) { if (node == NULL) { return; } if (node->condition()) { node->action(); run_behavior_tree(node->left_child); } else { run_behavior_tree(node->right_child); } } // 条件函数 bool is_enemy_nearby() { // 判断敌人是否在附近 return true; } bool is_health_low() { // 判断角色是否生命值低 return false; } // 行为函数 void attack_enemy() { // 攻击敌人 printf("Attack enemy!\n"); } void rest() { // 休息治疗 printf("Rest!\n"); } // 创建行为树 void create_behavior_tree() { behavior_tree_node* root = create_behavior_tree_node(is_enemy_nearby, attack_enemy, NULL, NULL); behavior_tree_node* right_child = create_behavior_tree_node(is_health_low, rest, NULL, NULL); root->right_child = right_child; run_behavior_tree(root); } // 主函数 int main() { create_behavior_tree(); return 0; } ``` 此行为树示例中,我们创建了两个条件函数(`is_enemy_nearby` 和 `is_health_low`)和两个行为函数(`attack_enemy` 和 `rest`)。我们的角色在行为树中有两个可能的行为:如果附近有敌人,则攻击敌人;否则,如果生命值较低,则休息治疗。`run_behavior_tree` 函数负责执行整个行为树,根据条件函数的返回值选择执行左子树或右子树。整个行为树的根节点为 `root`,其左子树为空,右子树为 `right_child`,`right_child` 的左右子树也为空。 当我们运行程序时,将执行 `create_behavior_tree` 函数,创建行为树并执行。在此示例中,由于 `is_enemy_nearby` 的返回值为 `true`,因此将执行 `attack_enemy` 行为函数,最终输出 `Attack enemy!`。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

橘色的喵

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

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

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

打赏作者

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

抵扣说明:

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

余额充值