KI-Programmierung Teil 06: Verhaltensprofile

Wie wird aus einer KI Einheit eine Persönlichkeit und wie wirkt sich diese auf das Verhalten der Einheit aus? Seit dem letzten Artikel kennt unsere KI Einheit bereits drei verschiedene Zustände – Random Walk, Angriff und Flucht. In diesem Zusammenhang hat die CAIEntity-Klassenmethode Update_BehaviourStates() die bedeutsame Aufgabe, für jede nur denkbare Situation den passenden Zustand auszuwählen. In vielen Fällen ist dies auch möglich – und zwar immer dann, wenn es keine Alternativen gibt. Manchmal bleibt einem eben nur die Flucht und manchmal kommt man nicht um einem Angriff herum.
Es gibt jedoch auch Situationen, da hat man die Wahl – entweder Angriff oder Flucht. Und genau hier kommen die Verhaltensprofile ins Spiel. Der vorsichtige Typ wird sich häufiger für die Flucht entscheiden, der Draufgänger für den Angriff.


Hinweis:
In diesem Kapitel betrachten wir ausschließlich statische Verhaltensprofile. Im nächsten Kapitel werden sie darüber hinaus erfahren, wie sich diese Profile mit der Zeit durch erlernte Erfahrungen weiterentwickeln können.

Kommen wir nun zur praktischen Umsetzung. Wir ordnen zunächst jeder Verhaltensweise einen numerischen Wert (ProfileValue) zu und speichern dann eine Reihe dieser Werte in einem einfachen Array ab (Profile[10]). Ausgelesen werden diese Werte nach dem Zufallsprinzip:

long Profile[10] = {0, 0, 1, 0, 1, 1, 1, 0, 1, 0};

ProfileValue = Profile[lrnd(0, 10)];

// aktuellen Zustand festlegen:
if(ProfileValue == 0)
{
    BehaviourState_Chase = true;
    IDOfSelectedEnemy = IDOfSelectedEnemyForChase;
}
else
{
    BehaviourState_Evade = true;
    IDOfSelectedEnemy = IDOfSelectedEnemyForEvade;
}

Die Wahrscheinlichkeit, mit der eine bestimmte Verhaltensweise gewählt wird, ist davon abhängig, wie oft der zugehörige Profilwert im Array gespeichert ist. Hier zwei Extrembeispiele:

Die KI Einheit wird in jedem Fall fliehen:
long Profile[10] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1};

Die KI Einheit wird in jedem Fall angreifen:
long Profile[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

Die zufällige Auswahl von Profilwerten ist jedoch nicht ganz unproblematisch, denn sie führt zu einer unnatürlichen Wankelmütigkeit im Verhalten. Von Frame zu Frame ändert sich das Verhalten (Angriff, Angriff, Flucht, Angriff, Flucht, Flucht, usw.).
Um diesem Problem entgegenzuwirken, muss die jeweils zuletzt gewählte Verhaltensweise zwischengespeichert werden. Zu diesem Zweck wird die CAIEntity-Klasse um eine zusätzliche Membervariable erweitert (SavedBehaviorDecision). Der Parameter PossibilityOfSavedBehaviorDecisionUsage legt nun fest, wie häufig die zuletzt gewählte Verhaltensweise neu „überdacht“ bzw. beibehalten werden soll:

if(frnd(0.0f, 100.0f) > PossibilityOfSavedBehaviorDecisionUsage)
{
    // Verhalten "überdenken"
    ProfileValue = Profile[lrnd(0, 10)];

    SavedBehaviorDecision = ProfileValue;
}
else // altes Verhalten beibehalten
    ProfileValue = SavedBehaviorDecision;

Für den flexiblen Umgang mit den Verhaltensprofilen kommt im weiteren Verlauf die CAIEntityBehaviourProfile-Klasse zum Einsatz:

class CAIEntityBehaviourProfile
{
public:

    long  ProfileSize;
   
long* Profile;

    CAIEntityBehaviourProfile()
    {
        Profile = NULL;
    }

    ~CAIEntityBehaviourProfile()
    {
        SAFE_DELETE_ARRAY(Profile)
    }

    void Set_ProfileSize(long size)
    {
        SAFE_DELETE_ARRAY(Profile)

        ProfileSize = size;

        Profile = new long[ProfileSize];

        for(long i = 0; i < ProfileSize; i++)
            Profile[i] = 0;
    }

    void New_ProfileEntry(long entry, long IDOfProfileElement)
    {
        if(Profile == NULL)
            return;

        if(IDOfProfileElement < 0)
            return;

        if(IDOfProfileElement >= ProfileSize)
            return;

        Profile[IDOfProfileElement] = entry;
    }
};

Im Vergleich zum vorherigen Kapitel wurde in der CAIEntity-Klasse lediglich die Update_BehaviourStates()-Methode erweitert. Neu hinzugekommen ist zudem die Membervariable SavedBehaviorDecision vom Typ long zum Speichern der zuletzt gewählten Verhaltensweise.

// PossibilityOfBehaviourProfileUsage: Legt die Wahrscheinlichkeit
// für die Nutzung des gewählten Verhaltensprofils fest.

// PossibilityOfSavedBehaviorDecisionUsage: Legt die Wahrscheinlichkeit
// fest, wie häufig die zuletzt gewählte Verhaltensweise neu "überdacht"
// bzw. beibehalten werden soll.
// Viele Änderungen in schneller Folge wirken unnatürlich!!

void CAIEntity::Update_BehaviourStates(
                float PossibilityOfBehaviourProfileUsage,
                float PossibilityOfSavedBehaviorDecisionUsage,
                CAIEntityBehaviourProfile* pAIEntityBehaviourProfile)
{
    // Zunächst befindet sich die Einheit im Random-Walk-Zustand:
    BehaviourState_RandomWalk = true;
    BehaviourState_Chase = false;
    BehaviourState_Evade = false;

    bool PossibleBehaviourState_Chase = false;
    bool PossibleBehaviourState_Evade = false;

    IDOfSelectedEnemy = -1;

    long IDOfSelectedEnemyForChase = -1;
    long IDOfSelectedEnemyForEvade = -1;

    long i;

    // Hat die Einheit einen Angreifer wahrgenommen
    // (mittels ihrer ungerichteter Wahrnehmung),
    // dann wäre ein Wechsel in den Fluchtmodus möglich:

    for(i = 0; i < NumPerceptionObjects; i++)
    {
        if(AllAroundPerception[i] == true && EntityIsChased == true)
        {
            BehaviourState_RandomWalk = false;
            IDOfSelectedEnemyForEvade = i;
            PossibleBehaviourState_Evade = true;
            break;
        }
    }

    // Hat die Einheit selbst ein Angriffsziel ausgemacht
    // (mittels ihrer gerichteten Wahrnehmung),
    // dann wäre ein Wechsel in den Angriffsmodus möglich:

    for(i = 0; i < NumPerceptionObjects; i++)
    {
        if(DirectedPerception[i] == true)
        {
            BehaviourState_RandomWalk = false;
            IDOfSelectedEnemyForChase = i;
            PossibleBehaviourState_Chase = true;
            break;
        }
    }

    if(frnd(0.0f, 100.0f) > PossibilityOfBehaviourProfileUsage)
    {
        // Verhaltensprofil nicht verwenden:

        if(PossibleBehaviourState_Evade == true)
        {
            BehaviourState_Evade = true;
            IDOfSelectedEnemy = IDOfSelectedEnemyForEvade;
        }
        else
        {
            if(PossibleBehaviourState_Chase == true)
            {
                BehaviourState_Chase = true;
                IDOfSelectedEnemy = IDOfSelectedEnemyForChase;
            }
        }
    }
    else
   
{
        // Auswahl des Verhaltens anhand des Verhaltensprofils:

        long ProfileValue;

        // Bei eindeutigen Entscheidungen wird kein
        // Verhaltensprofil benötigt!

       
if(PossibleBehaviourState_Evade == true &&
           PossibleBehaviourState_Chase == false)
        {
            BehaviourState_Evade = true;
            IDOfSelectedEnemy = IDOfSelectedEnemyForEvade;
        }
        else if(PossibleBehaviourState_Evade == false &&
                PossibleBehaviourState_Chase == true)
        {
            BehaviourState_Chase = true;
            IDOfSelectedEnemy = IDOfSelectedEnemyForChase;
        }

        // Angriff oder Flucht gleichsam möglich.
        // Das Verhaltensprofil muss entscheiden!!

        else if(PossibleBehaviourState_Evade == true &&
                PossibleBehaviourState_Chase == true)
        {
           if(frnd(0.0f, 100.0f) > PossibilityOfSavedBehaviorDecisionUsage)
           {
               // Verhalten "überdenken"
               ProfileValue = pAIEntityBehaviourProfile->Profile[
                  lrnd(0, pAIEntityBehaviourProfile->ProfileSize)];

               // long SavedBehaviorDecision; einzig neue Variable
               // von CAIEntity
               SavedBehaviorDecision = ProfileValue;
           }
           else // altes Verhalten beibehalten
               ProfileValue = SavedBehaviorDecision;

           // aktuellen Zustand festlegen:
           if(ProfileValue == 0)
           {
               BehaviourState_Chase = true;
               IDOfSelectedEnemy = IDOfSelectedEnemyForChase;
           }
           else
          
{
               BehaviourState_Evade = true;
               IDOfSelectedEnemy = IDOfSelectedEnemyForEvade;
           }
        }
    }
}