Паттерны состояния (State) и стратегии (Strategy) в играх на C++
В мире разработки игр существует множество способов улучшить код и сделать его более гибким и масштабируемым. Два популярных паттерна, которые часто используются в этой области, - это паттерны состояния (State) и стратегии (Strategy). В этой статье мы подробно рассмотрим каждый из них и объясним, как их можно применять в разработке игр.
Паттерн состояния (State)
Паттерн состояния - это поведенческий паттерн проектирования, который позволяет объекту изменять свое поведение в зависимости от своего внутреннего состояния. Вместо того чтобы использовать условные операторы для обработки различных состояний объекта, мы можем делегировать ответственность за каждое состояние отдельному классу. Представьте себе персонажа в игре, который может находиться в разных состояниях: "стоит", "идет", "прыгает", "атакует" и т.д. С помощью паттерна состояния мы можем создать отдельный класс для каждого из этих состояний, и каждый класс будет отвечать за конкретное поведение персонажа.
Преимущества использования паттерна состояния
Улучшение читаемости кода: Код становится более структурированным и понятным, так как логика каждого состояния отделена от основной логики объекта.
Упрощение добавления новых состояний: Добавить новое состояние становится легко, достаточно создать новый класс, реализующий интерфейс состояния.
Повышение гибкости: Изменение поведения объекта в зависимости от состояния становится более гибким и управляемым.
Паттерн стратегии (Strategy)
Паттерн стратегии - это также поведенческий паттерн проектирования, который позволяет выбирать алгоритм или стратегию во время выполнения программы. Вместо того чтобы жестко задавать алгоритм в коде, мы можем предоставить возможность выбора из нескольких доступных стратегий. Рассмотрим игру, в которой персонаж может использовать разные виды оружия: меч, лук, магический посох и т.д. С помощью паттерна стратегии мы можем создать отдельный класс для каждого вида оружия, и каждый класс будет реализовывать свою собственную стратегию атаки.
Преимущества использования паттерна стратегии
Гибкость выбора алгоритма: Мы можем легко менять алгоритм или стратегию во время выполнения программы, не изменяя основной код.
Улучшение масштабируемости: Добавление новых стратегий становится простым и не требует изменения существующего кода.
Повышение производительности: Мы можем выбирать наиболее подходящую стратегию в зависимости от ситуации, что может привести к улучшению производительности игры.
Пример кода моделей поведения игрового бота, используя оба паттерна.
#include <iostream>
#include <memory>
// Предварительное объявление класса Bot
class Bot;
// Базовый интерфейс состояния
class State {
public:
    virtual ~State() = default;
// Метод для обработки состояния бота
    virtual void handle(Bot& bot) = 0;  
};
// --- Реализация состояний ---
// Состояние "Спокойствие" - когда бот не активен, патрулирует.
class IdleState : public State {
public:
    void handle(Bot& bot) override;  // Переход к следующему состоянию
};
// Состояние "Охота" - когда бот преследует цель.
class ChaseState : public State {
public:
    void handle(Bot& bot) override;  // Переход к следующему состоянию
};
// Состояние "Атака" - когда бот готовится атаковать цель.
class AttackState : public State {
public:
    void handle(Bot& bot) override;  // Переход к следующему состоянию
};
// Класс бота
class Bot {
protected:
    std::shared_ptr<State> currentState;  // Текущее состояние бота
public:
    Bot();  // Конструктор, инициализирующий начальное состояние
    void setState(std::shared_ptr<State> newState);  // Установка нового состояния
    virtual void update();  // Обновление состояния бота
    virtual void attack() {}  // Виртуальная функция атаки (будет переопределена)
};
// Конструктор бота: начальное состояние - "Спокойствие"
Bot::Bot() : currentState(std::make_shared<IdleState>()) {}
void Bot::setState(std::shared_ptr<State> newState) {
    currentState = std::move(newState);  // Меняем состояние на новое
}
void Bot::update() {
    if (currentState) {
        currentState->handle(*this);  // Обработка текущего состояния
    }
}
// Реализация методов состояний
// В состоянии "Спокойствие" бот патрулирует, затем переходит в состояние "Охота"
void IdleState::handle(Bot& bot) {
    std::cout << "Бот патрулирует...\n";
    bot.setState(std::make_shared<ChaseState>());  // Переход к состоянию охоты
}
// В состоянии "Охота" бот преследует игрока, затем переходит в состояние "Атака"
void ChaseState::handle(Bot& bot) {
    std::cout << "Бот преследует игрока!\n";
    bot.setState(std::make_shared<AttackState>());  // Переход к состоянию атаки
}
// В состоянии "Атака" бот готовится атаковать, затем выполняет атаку и переходит в состояние "Спокойствие"
void AttackState::handle(Bot& bot) {
    std::cout << "Бот готовится к атаке!\n";
    bot.attack();  // Вызов функции атаки
    bot.setState(std::make_shared<IdleState>());  // Переход в спокойное состояние
}
// --- Реализация стратегий атаки ---
// Интерфейс стратегии атаки
class AttackStrategy {
public:
    virtual ~AttackStrategy() = default;
    virtual void attack() = 0;  // Метод для выполнения атаки
};
// Реализация атаки в ближнем бою
class MeleeAttack : public AttackStrategy {
public:
    void attack() override {
        std::cout << "Бот атакует в ближнем бою!\n";  // Атака в ближнем бою
    }
};
// Реализация атаки дальнего боя
class RangedAttack : public AttackStrategy {
public:
    void attack() override {
        std::cout << "Бот стреляет издалека!\n";  // Атака дальнего боя
    }
};
// Добавление поддержки стратегии атаки в бота
class BotWithStrategy : public Bot {
private:
    std::shared_ptr<AttackStrategy> attackStrategy;  // Стратегия атаки
public:
    BotWithStrategy();  // Конструктор, устанавливающий начальную стратегию атаки
    void setAttackStrategy(std::shared_ptr<AttackStrategy> newStrategy);  // Установка новой стратегии атаки
    void attack() override;  // Переопределенная атака
};
// Конструктор бота с поддержкой стратегии, начальная стратегия - ближний бой
BotWithStrategy::BotWithStrategy()
    : Bot(), attackStrategy(std::make_shared<MeleeAttack>()) {}
void BotWithStrategy::setAttackStrategy(std::shared_ptr<AttackStrategy> newStrategy) {
    attackStrategy = std::move(newStrategy);  // Устанавливаем новую стратегию атаки
}
void BotWithStrategy::attack() {
    if (attackStrategy) {
        attackStrategy->attack();  // Выполняем атаку с текущей стратегией
    }
    else {
        std::cout << "Стратегия атаки не установлена!\n";  // Если стратегия не установлена
    }
}
// --- Тестирование ---
int main() {
    system("chcp 1251>null");
    BotWithStrategy bot;  // Создаем объект бота с поддержкой стратегии
    // Симуляция игрового цикла
    bot.update();  // Бот патрулирует...
    bot.update();  // Бот преследует игрока!
    bot.update();  // Бот готовится к атаке!
    // Бот атакует в ближнем бою!
    // Меняем стратегию атаки на дальний бой
    bot.setAttackStrategy(std::make_shared<RangedAttack>());
    // Ещё один цикл для демонстрации изменения стратегии
    bot.update();  // Бот патрулирует...
    bot.update();  // Бот преследует игрока!
    bot.update();  // Бот готовится к атаке!
    // Бот стреляет издалека!
    system("pause");
    return 0;
}Заключение
Паттерны состояния и стратегии - это мощные инструменты, которые могут значительно улучшить качество вашего кода при разработке игр. Они помогают сделать код более читаемым, гибким и масштабируемым. Использование этих паттернов может привести к созданию более сложных и интересных игровых механик.