User Tools

Site Tools


z:verhalten

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:

  • Roboter
  • Fahrer (Fahrzeug)
  • Schütze (Fahrzeug, entweder identisch mit Fahrer oder separat, wie beim Transporter)
  • Schütze (Geschütz)

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

  • State-Klassen vorab instanziieren und als Member-Variablen der FSM speichern, oder erst beim Zustandswechsel erstellen?
  • Lohnt sich ein klarer definierter Übergang zwischen den Status evtl. mit der Möglichkeit, die Nachricht direkt an den neuen Status zu übergeben? (fsm.SwitchTo(fsm.idleState) und fsm.PassTo(fsm.idleState) o. ä.?) Damit könnte man auch Extra-Nachrichten wie EnterState() oder LeaveState() für die States generieren.
  • friend-Klasse, oder eine Art “shared”/“static”-Status in einem extra Objekt, welches nur der FSM und dem Status bekannt ist? Könnte dann auch als Statusübergreifender Kontext dienen (wobei das die FSM im Moment auch ermöglicht, dank friend). Man könnte auch ohne beides auskommen, wenn man den neuen Status per return zurückgibt, allerdings ist man dann was den Zustandswechsel angeht recht eingeschränkt. (Nachricht weitergeben etc.)
  • Komplexere Abläufe? Wie könnte man z. B. einen Status “Harvest” umsetzen, der sich aus mehreren Befehlen, wie Walk, Collect, etc. zusammensetzt? Brauchen wir das? Wäre eine zweite FSM innerhalb des Harvest-State denkbar, die von Harvest gesteuert wird und an die Harvest ggf. Nachrichten durchreicht?
  • Sollte man Code (Zustandswechsel etc.), den man ggf. in verschiedenen FSM-Typen braucht (Building, Unit etc., wer weiß wo wir noch FSM brauchen) in Basisklassen auslagern?
z/verhalten.txt · Last modified: 2015/08/23 13:59 (external edit)