Table of Contents

Einheitenverhalten und -kontrolle

Wenn der Spieler seinen Einheiten einen Befehl gibt, muss dieser Befehl in entsprechende Reaktionen umgesetzt werden. Auf dem niedrigsten Level verfügt eine bewegliche Einheit über ein Set von aktiven Steering Behaviours, die die Bewegung der Einheit vorgeben. Eine bewaffnete Einheit kann ihre Waffe in einem begrenzten Rahmen drehen und abfeuern. Die Übersetzung der Spielerbefehle auf dieses Basislevel übernimmt eine Finite State Machine.

Charaktere (Robotertypen) und Hüllen

Aus Gameplaysicht sind es die Roboter, die alle Aktionen im Spiel ausführen. Sie können als Individuen unterwegs sein, ein Fahrzeug steuern oder einen Geschützturm besetzen. Potentiell verhält sich dabei jeder Roboter seinem Typ gemäß etwas anders. Technisch gesehen ist ein Roboter aber auch nur eine bewegliche und bewaffnete Entity, genau wie ein Fahrzeug auch. Wäre der Roboter direkt für die Entscheidungen verantwortlich, müsste man eine Reihe Fallunterscheidungen machen, was der Roboter gerade tut, ob er sich eigenständig bewegt oder ein Fahrzeug oder ein Geschütz bemannt. Besser ist der Ansatz, die State Machine vom Roboter zu entkoppeln, sie repräsentiert dann quasi sein “Gehirn” bzw. eben seinen Charakter. Der Roboter selbst wird damit zu nichts weiter als einem Untersatz, einem “Chip”, auf dem die “Software” (State Machine) laufen kann. Steigt der Roboter in ein Fahrzeug, dann wechselt die State Machine vom Roboter in den “Fahrchip” des Vehikels, diese Position nimmt der Roboter dann nur visuell ein. Folgende “Hüllen” gibt es für einen Charakter:

Befehle vom Spieler können nur an die oberste Entity gegeben werden, bei einem Fahrzeug also nur an das Fahrzeug selbst, nicht an die Roboter darin. Die Entity wiederum leitet den Befehl einfach weiter an alle betreffenden Charaktere (Bewegungsbefehle an den Fahrer, Angriffsbefehle an alle). Zwar muss die State Machine unterscheiden, ob ihre Hülle beweglich und bewaffnet ist, da entsprechend verschiedene Verhaltensmöglichkeiten zur Verfügung stehen. Sie muss aber nie den Umweg durch den Roboter gehen, um zu prüfen, an welche Stelle sie die Befehlsprimitiven senden muss.

Aufbau der FSM

Ausgangsbasis: (State-Pattern nach Gamma et al)

class Fsm
{
   private Entity entity;
   private FsmState state;

   private FsmWalkState walkState;
   private FsmIdleState idleState;
   private FsmGuardState guardState;

   friend class FsmState;

   Guard(entity)    { state.Guard(entity); }
   MoveTo(target)   { state.MoveTo(target); }
   Stop()           { state.Stop(); }
   Update(elapsed)  { state.Update(elapsed); }
}

class FsmState
{
   protected Fsm fsm;

   // Mehr oder weniger sinnvolle Standard-Implementierungen.
   virtual Guard(entity)
   {
      if (fsm.state == fsm.guardState) return;
      fsm.guardState.Guard(entity);
      fsm.state = fsm.guardState;
   }

   virtual MoveTo(target)
   {
      if (fsm.state == fsm.moveState) return;
      fsm.moveState.MoveTo(target);
      fsm.state = fsm.moveState;
   }

   virtual Stop()
   {
      if (fsm.state == fsm.idleState) return;
      fsm.idleState.Stop();
      fsm.state = fsm.idleState;
   }

   virtual Update() { }
}

clsss FsmIdleState : FsmState { }

class FsmMoveState : FsmState
{
   Vector target;
   float speed = 10;

   MoveTo(target)
   {
      this.target = target;
   }

   Update(elapsed)
   {
      Vector pos  = fsm.entity.getPosition();
      Vector diff = target - pos;
      float  len  = diff.getLength();

      if (len < speed)
      {
         fsm.entity.setPosition(target);
         fsm.state = fsm.idleState;
         return;
      }

      fsm.entity.setPosition(pos + diff / len * speed);
   }
}

// [...]

Designentscheidungen