面向对象的 C++ 设计模式——策略模式在游戏 AI 中的应用

在现代游戏开发中,AI 行为的可扩展性与维护性是衡量项目质量的重要指标。策略模式(Strategy Pattern)通过把一组算法封装为独立的类,让它们可以互相替换,从而实现了行为的可插拔和组合。下面将以 C++ 为例,演示如何在游戏 AI 中实现一个基于策略模式的寻路与决策系统。

1. 需求概述

  • 寻路算法:支持多种寻路策略(A*、Dijkstra、BFS 等)。
  • 攻击决策:根据敌人距离、血量、技能冷却等因素动态选择技能。
  • 行为树的简化:把行为树中的“叶子节点”抽象为策略对象,提升复用性。

2. 设计思路

  1. 抽象策略接口:统一声明策略方法(如 Execute())。
  2. 上下文对象:维护当前状态(如位置、目标、技能列表)。
  3. 具体策略实现:不同算法实现各自的逻辑。
  4. 组合使用:AIController 通过策略组合来决定整体行为。

3. 代码实现

3.1 共同接口

// IStrategy.h
#pragma once
#include <memory>

class AIContext;               // 前向声明
class IStrategy
{
public:
    virtual ~IStrategy() = default;
    virtual void Execute(AIContext& ctx) = 0;
};

3.2 AI 上下文

// AIContext.h
#pragma once
#include <vector>
#include <memory>
#include <unordered_map>
#include <string>
#include "IStrategy.h"

struct Vector2 { float x, y; };

struct Skill
{
    std::string name;
    float cooldown;
    float remainingCD = 0.0f;
};

class AIContext
{
public:
    Vector2 position;
    Vector2 target;
    std::vector <Skill> skills;

    // 方便调试:当前激活的策略类型
    std::string currentStrategy;

    // 统一调度接口
    void Update(float deltaTime);
};
// AIContext.cpp
#include "AIContext.h"

void AIContext::Update(float deltaTime)
{
    // 这里简化为只执行寻路策略
    // 在实际项目中可同时执行多种策略
    // e.g., 路径规划 + 决策
}

3.3 具体策略:A* 寻路

// AStarStrategy.h
#pragma once
#include "IStrategy.h"

class AStarStrategy : public IStrategy
{
public:
    void Execute(AIContext& ctx) override;
};
// AStarStrategy.cpp
#include "AStarStrategy.h"
#include "AIContext.h"
#include <queue>
#include <unordered_set>
#include <cmath>

struct Node
{
    Vector2 pos;
    float g;  // 从起点到该点的代价
    float h;  // 启发式估价
    float f() const { return g + h; }
};

struct NodeComparator
{
    bool operator()(const Node& a, const Node& b) const { return a.f() > b.f(); }
};

float heuristic(const Vector2& a, const Vector2& b)
{
    return std::sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}

void AStarStrategy::Execute(AIContext& ctx)
{
    // 简单的网格化 A*,假设每个单元格为 1.0f
    std::priority_queue<Node, std::vector<Node>, NodeComparator> openSet;
    std::unordered_set<std::string> closedSet;

    Node start{ ctx.position, 0.0f, heuristic(ctx.position, ctx.target) };
    openSet.push(start);

    while (!openSet.empty())
    {
        Node current = openSet.top();
        openSet.pop();

        std::string key = std::to_string(current.pos.x) + "," + std::to_string(current.pos.y);
        if (closedSet.count(key)) continue;
        closedSet.insert(key);

        if (std::abs(current.pos.x - ctx.target.x) < 0.1f &&
            std::abs(current.pos.y - ctx.target.y) < 0.1f)
        {
            // 到达目标,更新 AI 状态
            ctx.currentStrategy = "A* reached target";
            break;
        }

        // 生成四个邻居
        std::vector <Vector2> neighbors = {
            { current.pos.x + 1.0f, current.pos.y },
            { current.pos.x - 1.0f, current.pos.y },
            { current.pos.x, current.pos.y + 1.0f },
            { current.pos.x, current.pos.y - 1.0f }
        };

        for (auto& nb : neighbors)
        {
            Node next{ nb,
                       current.g + 1.0f,               // 假设每步代价为 1
                       heuristic(nb, ctx.target) };
            openSet.push(next);
        }
    }
}

3.4 具体策略:BFS 纯行走

// BFSSteeringStrategy.h
#pragma once
#include "IStrategy.h"

class BFSSteeringStrategy : public IStrategy
{
public:
    void Execute(AIContext& ctx) override;
};
// BFSSteeringStrategy.cpp
#include "BFSSteeringStrategy.h"
#include "AIContext.h"
#include <queue>

void BFSSteeringStrategy::Execute(AIContext& ctx)
{
    // 这里仅演示 BFS 走向目标的最短步数
    std::queue <Vector2> q;
    q.push(ctx.position);

    std::unordered_set<std::string> visited;
    visited.insert(std::to_string(ctx.position.x) + "," + std::to_string(ctx.position.y));

    while (!q.empty())
    {
        Vector2 cur = q.front(); q.pop();
        if (std::abs(cur.x - ctx.target.x) < 0.1f &&
            std::abs(cur.y - ctx.target.y) < 0.1f)
        {
            ctx.currentStrategy = "BFS reached target";
            break;
        }

        std::vector <Vector2> dirs = {
            { cur.x + 1, cur.y }, { cur.x - 1, cur.y },
            { cur.x, cur.y + 1 }, { cur.x, cur.y - 1 }
        };

        for (auto& d : dirs)
        {
            std::string key = std::to_string(d.x) + "," + std::to_string(d.y);
            if (visited.count(key)) continue;
            visited.insert(key);
            q.push(d);
        }
    }
}

3.5 AI 控制器

// AIController.h
#pragma once
#include "AIContext.h"
#include <vector>
#include <memory>

class AIController
{
public:
    AIController(AIContext& ctx) : context(ctx) {}
    void RegisterStrategy(std::unique_ptr <IStrategy> strat) { strategies.push_back(std::move(strat)); }
    void Update(float deltaTime);
private:
    AIContext& context;
    std::vector<std::unique_ptr<IStrategy>> strategies;
};
// AIController.cpp
#include "AIController.h"

void AIController::Update(float deltaTime)
{
    // 简单轮询:先执行寻路,再执行攻击决策
    for (auto& strat : strategies)
    {
        strat->Execute(context);
    }

    // 更新技能冷却
    for (auto& skill : context.skills)
    {
        if (skill.remainingCD > 0.0f)
            skill.remainingCD -= deltaTime;
    }
}

4. 运行示例

int main()
{
    AIContext ctx;
    ctx.position = {0, 0};
    ctx.target   = {5, 3};

    AIController controller(ctx);

    controller.RegisterStrategy(std::make_unique <AStarStrategy>());
    controller.RegisterStrategy(std::make_unique <BFSSteeringStrategy>());

    for (int i = 0; i < 10; ++i)
    {
        controller.Update(0.016f);   // 16 ms 每帧
        std::cout << "Step " << i << ": Strategy used -> " << ctx.currentStrategy << '\n';
    }

    return 0;
}

运行后,你会看到 AI 先尝试 A* 寻路,然后若未成功则退回到 BFS,展示了策略模式对不同算法的灵活切换。

5. 进一步扩展

  • 状态模式:把 AI 的整体状态(巡逻、追踪、攻击)封装成状态类,进一步解耦。
  • 行为树:将策略作为行为树的叶子节点,实现更细粒度的组合。
  • 热更新:通过脚本或插件方式动态加载策略,实现快速迭代。

策略模式的核心优势在于“开放-闭合原则”:对扩展开放,对修改封闭。只要添加新的策略实现即可,无需改动现有代码,极大提升游戏 AI 的可维护性和可扩展性。

发表评论