Простые приложения на С++
February 2

Паттерны состояния (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;
}

Заключение

Паттерны состояния и стратегии - это мощные инструменты, которые могут значительно улучшить качество вашего кода при разработке игр. Они помогают сделать код более читаемым, гибким и масштабируемым. Использование этих паттернов может привести к созданию более сложных и интересных игровых механик.

Телеграмм канал - Программирование игр С++/С#