›_ ebskola.lv
← 4.2 4.4 →

4.3 State machines AI uzvedībai

Stundas uzdevums: Implementēt AI ar stāvokļu mašīnu (state machine) reālistiskai uzvedībai.

SR 2.4.18. OOP SR 2.4.19. Algoritmu efektivitāte

Pirms sāc: izmanto iepriekš apgūto un šīs lapas teorijas/koda piemērus. Ja vajadzīga jauna komanda vai rīks, vispirms atrodi tās paraugu teorijas sadaļā.

Teorija: State machine konceptu

State machine (stāvokļu mašīna) ir AI dizaina paraugs, kur objekts vienlaikus ir vienā no diskrētiem stāvokļiem un pāriet starp tiem pēc noteikumiem.

enum class EnemyState {
    IDLE,       // gaida
    PATROL,     // staigā
    CHASE,      // dzenas
    ATTACK,     // uzbrūk
    DEAD
};

class Enemy : public CharacterBody2D {
private:
    EnemyState state = EnemyState::IDLE;
    Vector2 patrol_target;
    Player* player = nullptr;

public:
    void _physics_process(double delta) override {
        switch (state) {
            case EnemyState::IDLE:
                if (player_visible()) state = EnemyState::CHASE;
                else if (idle_too_long()) state = EnemyState::PATROL;
                break;
            case EnemyState::PATROL:
                move_to_patrol_target();
                if (player_visible()) state = EnemyState::CHASE;
                break;
            case EnemyState::CHASE:
                move_toward(player->get_position());
                if (in_attack_range()) state = EnemyState::ATTACK;
                else if (!player_visible()) state = EnemyState::IDLE;
                break;
            case EnemyState::ATTACK:
                attack_player();
                if (!in_attack_range()) state = EnemyState::CHASE;
                break;
        }
    }
};

Kā šo izmantot projektā

Atceries: ar redzamu efektu editorā nepietiek. Paskaidro, kura C++ klase glabā stāvokli, kura metode to maina un kā Godot node struktūra izmanto šo kodu.

Pārbaudi: C++ kodā pamatoti izvēlēti vector/map/set, AI stāvokļi ir skaidri, un sarežģītākas sistēmas var profilēt vai atkļūdot.

#include <godot_cpp/variant/string.hpp>

using namespace godot;

struct AiDataCheckpoint {
    String lesson = "4.3 State machines AI uzvedībai";
    bool uses_cpp = true;
    bool chooses_container = true;
    bool separates_ai_state = true;
    bool handles_removal_safely = true;
};

Praktiskie uzdevumi

1. uzdevums - Iesildies ar gatavu piemēru

Šis ir īss iesildīšanās uzdevums. Nokopē sagatavi, ielīmē to pareizajā koda vietā un palaid. Šeit pietiek droši izmēģināt tēmu 4.3 State machines AI uzvedībai; detalizētu izpratni veidosi nākamajos uzdevumos.

Kopējamais piemērs vai sagatave: izmanto šo bloku kā starta punktu, nevis kā gala risinājumu.

#include <godot_cpp/variant/string.hpp>

using namespace godot;

struct AiDataCheckpoint {
    String lesson = "4.3 State machines AI uzvedībai";
    bool uses_cpp = true;
    bool chooses_container = true;
    bool separates_ai_state = true;
    bool handles_removal_safely = true;
};
  1. Atver darba failu vai rīku. C++ fragmentu ievieto atbilstošajā .cpp vai .hpp failā pie šīs stundas klases.
  2. Nokopē visu piemēra bloku no šī uzdevuma un ielīmē to norādītajā vietā.
  3. Palaid kodu tieši tādu, kāds tas ir, un pārliecinies, ka parādās rezultāts, izvade vai vismaz nav kļūdas paziņojuma.
  4. Atrodi vienu drošu vietu, ko drīkst mainīt: tekstu, skaitli, krāsu, mainīgā vērtību vai testa ierakstu.
  5. Maini tikai šo vienu vērtību un palaid kodu vēlreiz.
  6. Salīdzini rezultātu pirms un pēc izmaiņas. Ja parādās kļūda, atcel pēdējo izmaiņu un palaid vēlreiz.
  7. Turpini pie 2. uzdevuma tikai tad, kad šis mazais piemērs darbojas.

2. uzdevums - Ievieto algoritmu spēles projektā

Pievieno šīs stundas paņēmienu kā nelielu, strādājošu spēles mehānikas daļu.

  1. Izvēlies vienu projekta vietu: spēlētāju, pretinieku, kameru, UI, datu glabāšanu, sadursmi vai līmeņa ģenerēšanu.
  2. Nosauc klases, metodes un mainīgos pēc to lomas, piemēram, PlayerState, velocity, score vai update_ui().
  3. Uzraksti metodi, kas nolasa stāvokli, veic vienu skaidru darbību un atgriež vai parāda rezultātu.
  4. Izsauc šo metodi no piemērotas vietas: _ready(), _process(), _physics_process(), signāla apstrādātāja vai projekta palīgklases.
  5. Pievieno vienu īsu komentāru pie sarežģītākās loģikas vietas.
  6. Pārkompilē projektu un pārbaudi, ka editorā nav kļūdu.
  7. Salabo pirmo atrasto kļūdu pirms pievieno nākamo mehānikas daļu.
  8. Veic Git commit ar īsu tehnisku ziņu par pievienoto C++ funkcionalitāti.

3. uzdevums - Testē mehāniku un izdari secinājumu

Pārbaudi, vai C++ algoritms darbojas paredzami spēles vidē.

  1. Izveido trīs testa scenārijus: parastu darbību, robežgadījumu un kļūdainu vai neērtu spēlētāja darbību.
  2. Palaid parasto scenāriju un pārbaudi rezultātu spēles logā vai Godot konsolē.
  3. Palaid robežgadījumu ar mazāko, lielāko, tukšo vai nulles vērtību, ko mehānika pieļauj.
  4. Palaid kļūdaino scenāriju un pārbaudi, vai projekts neavarē.
  5. Izlabo vienu konkrētu problēmu C++ kodā, node sasaistē vai datu inicializācijā.
  6. Pārkompilē un pārbaudi labojumu vēlreiz ar visiem trim scenārijiem.
  7. Beigās pieraksti vienu secinājumu: kura metode, klase vai algoritma solis vislabāk palīdzēja saprast tēmu 4.3 State machines AI uzvedībai.

Papildu uzdevums - Pievieno mazu mehāniku

Ja pamatdarbs ir pabeigts, paplašini spēli ar vienu nelielu C++ uzlabojumu.

  1. Izvēlies vienu mazu papildinājumu, kas izmanto to pašu šīs stundas paņēmienu.
  2. Pievieno vienu jaunu atribūtu, metodi, signāla apstrādi, datu elementu vai UI atjauninājumu.
  3. Savieno papildinājumu ar esošo scēnu vai klasi, nevis atstāj to atsevišķā demonstrācijā.
  4. Pārkompilē un pārliecinies, ka pamatmehānika joprojām darbojas.
  5. Saglabā izmaiņas ar Git commit tikai pēc veiksmīgas pārbaudes.

Biežākās kļūdas

Godot ekrānuzņēmumi

State machine diagramma: IDLE → PATROL (timer), IDLE/PATROL → CHASE (player vision), CHASE → ATTACK (range), ATTACK → CHASE (out of range), * → DEAD (hp 0).
State machine diagramma: IDLE → PATROL (timer), IDLE/PATROL → CHASE (player vision), CHASE → ATTACK (range), ATTACK → CHASE (out of range), * → DEAD (hp 0).
Spēles aina: spēlētājs centrā, ienaidnieks ar 'CHASE' debug label tuvojas no kreisās puses.
Spēles aina: spēlētājs centrā, ienaidnieks ar 'CHASE' debug label tuvojas no kreisās puses.

Koda piemērs (paplašināts)

enum class EnemyState { IDLE, PATROL, CHASE, ATTACK, DEAD };

void Enemy::_physics_process(double delta) {
    state_timer += delta;

    switch (state) {
        case EnemyState::IDLE:
            if (player_in_vision) change_state(EnemyState::CHASE);
            else if (state_timer > 3.0) change_state(EnemyState::PATROL);
            break;

        case EnemyState::PATROL: {
            Vector2 dir = (patrol_target - get_position()).normalized();
            velocity = dir * 100.0f;
            move_and_slide();
            if (get_position().distance_to(patrol_target) < 10) {
                swap_patrol_target();
            }
            if (player_in_vision) change_state(EnemyState::CHASE);
        } break;

        case EnemyState::CHASE: {
            Vector2 dir = (player_pos - get_position()).normalized();
            velocity = dir * 200.0f;
            move_and_slide();
            if (get_position().distance_to(player_pos) < 50) {
                change_state(EnemyState::ATTACK);
            }
        } break;

        case EnemyState::ATTACK:
            if (state_timer >= 1.0) {
                player->take_damage(10);
                state_timer = 0;
            }
            break;
    }
}

void Enemy::change_state(EnemyState new_state) {
    state = new_state;
    state_timer = 0;
}
Ienaidnieks staigā patrol līdz pamana spēlētāju, dzenas un uzbrūk; izvairoties - atgriežas patrol režīmā.
⬅ Iepriekšējā stunda Nākamā stunda ➡