Spielzustand-Automaten

Aus Spieleprogrammierer-Wiki
(Unterschied zwischen Versionen)
Wechseln zu: Navigation, Suche
[unmarkierte Version][gesichtete Version]
(Grundidee und Architektur)
 
(5 dazwischenliegende Versionen von 3 Benutzern werden nicht angezeigt)
Zeile 11: Zeile 11:
 
Die Grundidee eines Spielzustand-Automaten ist es, das Spiel in einzelne Spielzustände einzuteilen - einen <tt>State</tt>. Jeder <tt>State</tt> behandelt seinen eigenen Kontext an Daten, Aktualisierungen und Visualisierungen. Diese Zustände werden von einem so genannten Zustands-Manager <tt>StateManager</tt> verwaltet. Diesem wird jeder Spielzustand registriert, so dass er verwendet werden kann. Der Zustands-Manager bietet eine Funktion zum Wechsel eines Zustandes zu einem anderen an. Bei diesem Zustandswechsel können Informationen vom Quell- zum Zielzustand übergeben werden.  
 
Die Grundidee eines Spielzustand-Automaten ist es, das Spiel in einzelne Spielzustände einzuteilen - einen <tt>State</tt>. Jeder <tt>State</tt> behandelt seinen eigenen Kontext an Daten, Aktualisierungen und Visualisierungen. Diese Zustände werden von einem so genannten Zustands-Manager <tt>StateManager</tt> verwaltet. Diesem wird jeder Spielzustand registriert, so dass er verwendet werden kann. Der Zustands-Manager bietet eine Funktion zum Wechsel eines Zustandes zu einem anderen an. Bei diesem Zustandswechsel können Informationen vom Quell- zum Zielzustand übergeben werden.  
  
=== Funktionsweise und Einsatz des von Spielzustand-Automaten ===
+
=== Funktionsweise und Einsatz von Spielzustand-Automaten ===
  
 
Beim Start des Programms wird Der Zustands-Manager erstellt und alle Zustände, die der Automat einnehmen kann, registriert. Danach wird ein Zustand als aktueller festgelegt. Während des Programmablaufs wird der aktuelle Zustand kontinuierlich ausgeführt, wie Logik aktualisieren und Darstellung.  
 
Beim Start des Programms wird Der Zustands-Manager erstellt und alle Zustände, die der Automat einnehmen kann, registriert. Danach wird ein Zustand als aktueller festgelegt. Während des Programmablaufs wird der aktuelle Zustand kontinuierlich ausgeführt, wie Logik aktualisieren und Darstellung.  
 
Während des Programmablaufs kann der Zustand selbst entscheiden, ob und in welchen Zustand der Zustands-Manager wechseln und welche Informationen übergeben werden sollen. Dazu ein einfaches Beispielszenario:
 
Während des Programmablaufs kann der Zustand selbst entscheiden, ob und in welchen Zustand der Zustands-Manager wechseln und welche Informationen übergeben werden sollen. Dazu ein einfaches Beispielszenario:
  
Dem Zustands-Manager wurden die Zustände ''Hauptmenü'', ''Level wählen'' und ''Hauptspiel'' registriert. Als aktueller Zustand wird ''Hauptmenü'' gesetzt. Während des Programmablaufes wird im Zustand ''Hauptmenü'' die Eingaben abgefragt und das Menu im Fenster dargestellt. Der Zustand erhält die Eingabe, dass ein neues Spiel gestartet werden soll und notifiziert dem Zustands-Manager, dass in den Zustand ''Level wählen'' gewechselt werden soll. Nach dem Wechsel durch den Zustands-Manager wird der Zustand ''Level wählen' aktualisiert und durch die Wahl eines Levels und Schwierigkeitsgrad wird in den Zustand ''Hauptspiel'' gewechselt. Beim Wechsel werden vom Quellzustand die Informationen, welches Level und Schwierigkeitsgrad gewählt worden sind, zum Zielzustand weitergereicht. Dieser Empfängt diese Informationen und kann das Spiel beginnen. Am Ende des Spiels wird wieder in den Zustand Hauptmenü gewechselt.
+
Dem Zustands-Manager wurden die Zustände ''Hauptmenü'', ''Level wählen'' und ''Hauptspiel'' registriert. Als aktueller Zustand wird ''Hauptmenü'' gesetzt.  
 +
Während des Programmablaufes wird im Zustand ''Hauptmenü'' die Eingaben abgefragt und das Menu im Fenster dargestellt.  
 +
Der Zustand erhält die Eingabe, dass ein neues Spiel gestartet werden soll und notifiziert dem Zustands-Manager, dass in den Zustand ''Level wählen'' gewechselt werden soll. Nach dem Wechsel durch den Zustands-Manager wird der Zustand ''Level wählen'' aktualisiert und durch die Wahl eines Levels und Schwierigkeitsgrad wird in den Zustand ''Hauptspiel'' gewechselt.  
 +
Beim Wechsel werden vom Quellzustand die Informationen, welches Level und Schwierigkeitsgrad gewählt worden sind, zum Zielzustand weitergereicht. Dieser Empfängt diese Informationen und kann das Spiel beginnen. Am Ende des Spiels wird wieder in den Zustand Hauptmenü gewechselt.
  
=== Variante Stapelverarbeitung von Zuständen ===
+
=== Variante: Stapelverarbeitung von Zuständen ===
  
 
Neben dem simplen Setzen von Zuständen ist die Abfolge von Zustandswechsel ein interessantes Thema. Es bietet sich hier die Benutzung eines Stapels (''Stack'') an. Der <tt>StateManager</tt> besitzt einen Stapel, der eine Menge an Zuständen referenziert. Dabei gilt das [http://de.wikipedia.org/wiki/Last_In_–_First_Out LIFO-Prinzip] (''Last In – First Out''). Mit Hilfe der Methoden <tt>PushState</tt> lässt sich ein neuer Zustand auf den Stapel packen, der auch gleichzeitig der Aktive ist. Das Gegenstück lautet <tt>PopState</tt> und nimmt den zuletzt eingefügten Zustand vom Stapel. Durch dieses Prinzip gestalten sich Zustandsübergänge durch eine Kombination aus <tt>PushState</tt> und <tt>PopState</tt> ziemlich einfach. Hier ein einfaches Beispiel:
 
Neben dem simplen Setzen von Zuständen ist die Abfolge von Zustandswechsel ein interessantes Thema. Es bietet sich hier die Benutzung eines Stapels (''Stack'') an. Der <tt>StateManager</tt> besitzt einen Stapel, der eine Menge an Zuständen referenziert. Dabei gilt das [http://de.wikipedia.org/wiki/Last_In_–_First_Out LIFO-Prinzip] (''Last In – First Out''). Mit Hilfe der Methoden <tt>PushState</tt> lässt sich ein neuer Zustand auf den Stapel packen, der auch gleichzeitig der Aktive ist. Das Gegenstück lautet <tt>PopState</tt> und nimmt den zuletzt eingefügten Zustand vom Stapel. Durch dieses Prinzip gestalten sich Zustandsübergänge durch eine Kombination aus <tt>PushState</tt> und <tt>PopState</tt> ziemlich einfach. Hier ein einfaches Beispiel:
Zeile 25: Zeile 28:
  
 
[[Datei:Stack states.png]]
 
[[Datei:Stack states.png]]
 
=== Informationsaustausch zwischen Spielzuständen ===
 
 
Ein Zustandswechsel findet statt, wenn ein neuer Zustand auf dem Stapel abgelegt oder entfernt wird. Oftmals ist es dann notwendig, dass Informationen von dem alten zum neuen Zustand übergeben werden. Einfaches Beispiel: <br /><br />
 
 
Ein [http://de.wikipedia.org/wiki/Tower_Defense Tower-Defense]-Spiel befindet sich gerade im Zustand ''Level und Schwierigkeitsgrad wählen''. Nachdem die Einstellungen durch den Benutzer getroffen worden sind und das Spiel starten soll, so wird auf dem Spielzustands-Stapel der Spielzustand ''HauptSpiel'' gelegt. Dieser Zustand muss wissen, welches Level und welcher Schwierigkeitsgrad gewählt worden ist, um die entsprechenden Daten zu laden und Objekte zu erzeugen. <br /><br />
 
 
In diesem Fall muss bei dem Zustandstandswechsel eine Informationsübergabe vom Spielzustand ''Level und Schwierigkeitsgrad wählen'' zu ''HauptSpiel'' durchgeführt werden. Es gibt verschiedene Wege, dieses Problem zu lösen:
 
 
# Globale Daten bereitstellen, auf den alle States zugreifen können
 
# Übergeben der konkreten Daten vom Quellzustand zum Zielzustand über den StateManager
 
  
 
==== Transparente Zustände ====
 
==== Transparente Zustände ====
Zeile 43: Zeile 35:
 
=== Technische Umsetzung ===
 
=== Technische Umsetzung ===
  
Bei einer objektorientierten Umsetzung bilden sich zwei Elemente heraus: die Klasse <tt>StateManager</tt> und die Schnittstelle <tt>State</tt>.
+
Um Spielzustand-Automaten umzusetzen, ist es hilfreich, sich stichpunktartig zu überlegen, was alles benötigt wird:
  
[[Datei:uml_states.png|miniatur|UML-Klassendiagramm eines Spielzustand-Automaten]]
+
# Zustands-Manager muss vorhanden sein
 +
# Spielzustände erstellen
 +
# Spielzustände dem Zustands-Manager registrieren
 +
# aktuellen Zustand setzen
 +
# Zustände ausführen
 +
# Zustand wechseln mit optionaler Informationsübergabe
  
==== State ====
+
==== Zustands-Manager muss vorhanden sein ====
  
<tt>State</tt> definiert die Schnittstelle für Spielzustände, die vom <tt>StateManager</tt> verwaltet werden. Für jeden konkreten Spielzustand wird eine Klasse benötigt, die die <tt>State</tt>-Schnittstelle implementiert. Die Schnittstelle enthält eine Reihe von Methoden, die beim Eintritt bestimmter Ereignisse vom <tt>StateManager</tt> aufgerufen werden:
+
Bei einer objektorientierten Umsetzung bilden sich eine Klasse und eine Schnittstelle heraus: Die Klasse <tt>StateManager</tt> und die Schnittstelle <tt>State</tt>. Durch diese beiden Elemente sind Punkt 1 und 2 erledigt. Jetzt müssen diese beiden Elemente noch mit Feldern und Methoden bestückt werden, um alle Anforderungen zu erfüllen.
  
* <tt>'''OnEnter'''</tt>: Der <tt>StateManager</tt> ruft diese Methode auf, wenn der Zustand auf den Stapel gelegt wird. Dadurch können z.B. Variablen zurückgesetzt, Eingangsanimationen gestartet oder ein Musikstück abgespielt werden.
+
==== Spielzustände dem Zustands-Manager registrieren ====
* <tt>'''OnLeave'''</tt>: Dieses Ereignis wird '''vor''' dem Entfernen des aktuellen Zustands vom Stapel ausgelöst. Das bedeutet bei <tt>OnLeave</tt> befindet sich das Spiel noch in dem alten Zustand, ist aber im Begriff, danach diesen sofort zu verlassen.Dadurch können wichtige Nacharbeiten durchgeführt werden (Musik stoppen, Ressourcen freigeben, ...).
+
* <tt>'''OnUpdate'''</tt>: In dieser Funktion wird die Logik des Zustands berechnet. Im Falle des Zustands ''Spiel'' werden üblicherweise KI, Animationen und Physik aktualisiert.
+
* <tt>'''OnRender'''</tt>: Hier findet das gesamte Zeichnen für den aktuellen Zustand statt.
+
* <tt>'''OnFocus'''</tt>: Wird ein Spielzustand vom Stapel genommen und der darunter liegende wieder aktiv, wird dieses Ereignis ausgelöst.
+
* <tt>'''OnLostFocus'''</tt>: Wird ein Spielzustand auf den Stapel gelegt, wird dieses Ereignis ausgelöst. In Abhängigkeit von transparenten Zuständen wird hier Aktualisierungs- bzw. Visualisierungslogik weiterhin durchgeführt.
+
  
==== StateManager ====
+
Das lässt sich mit Hilfe einer öffentlichen Methode der Klasse <tt>StateManager</tt> realisieren. Die könnte <tt>RegisterState</tt> heißen und erhält als Parameter eine Instanz auf ein Objekt, welches die Schnittstelle <tt>State</tt> implementiert und eine Zeichenkette als Name des registrierten Zustandes. Über diesen Namen lässt sich der Zustand im Spiel identifizieren.
  
Der <tt>StateManager</tt> verwaltet alle für das Spiel möglichen Zustände. Dazu werden ihm alle Zustände durch <tt>AddState</tt> bekannt gemacht. Für den <tt>StateManager</tt> ist immer genau ein Zustand aktuell. Dieser lässt sich mit der Methode <tt>SetState</tt> festlegen.
+
==== Aktuellen Zustand setzen ====
  
Prinzipiell verwendet man bei Spielen getrennte Logik für Update- und Render-Funktionalität. Daher unterscheidet der <tt>StateManager</tt> auch zwischen diesen beiden Vorgängen, indem er getrennte Methoden besitzt. Diese rufen vom aktuellen Zustand <tt>OnUpdate</tt> bzw. <tt>OnRender</tt> auf.
+
Auch dafür bietet sich eine öffentliche Methode an, die <tt>SetCurrentState</tt> heißen könnte. Diese erhält eine Zeichenkette mit dem Namen eines registrierten Zustandes. Der Zustands-Manager sucht dann nach einem Zustand mit diesem Namen und setzt ihn als aktuellen Zustand. Das merken lässt sich durch anlegen eines privaten Feldes der Klasse <tt>StateManager</tt> realisieren. Das Feld könnte <tt>currentState</tt> heißen.
  
== Beispielimplementierung ==
+
==== Zustände ausführen ====
  
=== State ===
+
Der aktuelle Zustand muss natürlich aktualisiert und gerendert werden. Üblicherweise legt man dafür auch getrennte öffentliche Methoden an, die zum Beispiel <tt>UpdateState</tt> und <tt>RenderState</tt> heißen könnten. Abhängig von verwendeten Bibliotheken werden hier eventuell noch Parameter benötigt.
  
{{Spoiler|
+
Zusätzlich muss das <tt>State</tt>-Interface
<xh4>C++</xh4>
+
|
+
<sourcecode lang="cpp" tab="4">
+
class State
+
{
+
public:
+
virtual ~State() { };
+
virtual void OnEnter() = 0;
+
virtual void OnLeave() = 0;
+
virtual bool OnUpdate(float elapsedTime) = 0;
+
virtual void OnRender() = 0;
+
virtual std::string NextState() = 0;
+
};
+
</sourcecode>
+
}}
+
  
{{Spoiler|
+
[[Datei:uml_states.png|miniatur|UML-Klassendiagramm eines Spielzustand-Automaten]]
<xh4>C#</xh4>
+
|
+
<sourcecode lang="csharp" tab="4">
+
public interface State
+
{
+
void OnEnter();
+
void OnLeave();
+
bool OnUpdate(float elapsedTime);
+
void OnRender();
+
string NextState { get; }
+
}
+
</sourcecode>
+
}}
+
  
{{Spoiler|
+
== Beispielimplementierung ==
<xh4>Java</xh4>
+
|
+
<sourcecode lang="java" tab="4">
+
public interface State
+
{
+
void onEnter();
+
void onLeave();
+
boolean onUpdate(float elapsedTime);
+
void onRender();
+
String getNextState();
+
}
+
</sourcecode>
+
}}
+
 
+
=== StateManager ===
+
 
+
{{Spoiler|
+
<xh4>C++</xh4>
+
|
+
<sourcecode lang=cpp tab=4>/* h-Datei: StateManager.h*/
+
class StateManager
+
{
+
private:
+
  //Speichert alle Zustände als Assoziatives Array
+
  private std::map<std::string, State*> _statesById;
+
  //Aktueller Zustand
+
  private State *_currentState;
+
 
+
public:
+
  StateManager();
+
  ~StateManager();
+
 
+
  void AddState(const std::string &id, State *state);
+
  void SetState(const std::string &id);
+
  bool Update(float elapsedTime);
+
  void Render();
+
};
+
 
+
/* cpp-Datei: StateManager.cpp */
+
 
+
//erzeugt ein StateManager-Objekt
+
StateManager::StateManager():
+
  _currentState(NULL)
+
{
+
}
+
 
+
//Löscht das StateManager-objekt mit allen Zuständen.
+
StateManager::~StateManager()
+
{
+
  for(std::map<std::string, State*>::iterator it = _statesById.Begin(); it != _statesById.end(); ++it)
+
  {
+
    delete (*it);
+
  }
+
}
+
 
+
//Registriert einen Zustand für den Manager an Hand der ID. Diese muss
+
//eindeutig und nicht leer sein.
+
void StateManager::AddState(const std::string &id, State *state)
+
{
+
  if (id.length() == 0)
+
    throw std::exception("Id has to be a valid string");
+
  if (state == NULL)
+
    throw std::exception("State must not be null");
+
 
+
  std::map<std::string, State*>::iterator it = _statesById.find(id);
+
  if (it != _statesById.end())
+
  {
+
    std::string error("State with ID ");
+
    error.append(id);
+
    error.append(" already exist.");
+
    throw std::exception(error.c_str());
+
  }
+
   
+
  _statesById[id] = state;
+
}
+
 
+
//Setzt den aktuellen Zustand
+
void StateManager::SetState(const std::string &id)
+
{
+
  std::map<std::string, State*>::iterator nextStateIt = _statesById.find(id);
+
 
+
  //Wenn der Zustand im Manager registriert ist
+
  if (nextStateIt != _statesById.end())
+
  {
+
    //und nicht der aktuelle ist
+
    if (*nextStateIt != _currentState)
+
    {
+
      //Rufe OnLeave() vom alten Zustand auf, falls es einen gab
+
      if (_currentState != NULL)
+
        _currentState->OnLeave();
+
       
+
      //setze den neuen aktuellen Zustand
+
      _currentState = *nextStateIt;
+
     
+
      //Da zu einem neuen Zustand gewechselt worden ist, rufe das Eingangsereignis auf
+
      _currentState->OnEnter();
+
    }
+
  }
+
  else
+
      //ansonsten gibt es diesen Zustand nicht!
+
      throw new InvalidOperationException(string.Format("State with Id {0} does not exist.", id));
+
}
+
 
+
//Aktualisert den Spielzustand. der Rückgabewert gibt an, ob das Programm weiterläuft.
+
//Es läuft nicht weiter, wenn der aktuelle Zustand verlassen und kein weiterer Zustand zugewiesen wird.
+
bool StateManager::Update(float elapsedTime)
+
{
+
  //Es muss ein Zustand gesetzt sein!
+
  if (_currentState == NULL)
+
    throw std::exception("Current state not set.");
+
   
+
  //aktualisere den Zustand und schaue, ob der Zustand eventuell verlassen werden soll
+
  bool running = _currentState->OnUpdate(elapsedTime);
+
 
+
  //wenn er verlassen werden soll und der Folgezustand gesetzt ist
+
  if (!running && !_currentState->NextState().length() > 0)
+
  {
+
    //Wechsle den Zustand
+
    SetState(_currentState->NextState());
+
   
+
    //Programm läuft weiter
+
    return true;
+
  }
+
 
+
  //
+
  return running;
+
}
+
 
+
//Zeichnet den aktuellen Zustand.
+
void StateManager::Render()
+
{
+
  //Es muss ein Zustand gesetzt sein!
+
  if (_currentState == NULL)
+
    throw std::exception("Current state not set.");
+
   
+
  //Zeichne den aktuellen Zustand
+
  _currentState->OnRender();
+
}
+
</sourcecode>
+
}}
+
 
+
{{Spoiler|
+
<xh4>C#</xh4>
+
|
+
<sourcecode lang=csharp tab=4>public class StateManager
+
{
+
  //Speichert alle Zustände als Assoziatives Array
+
  private Dictionary<string, State> _statesById;
+
  //Aktueller Zustand
+
  private State _currentState;
+
 
+
  //erzeugt ein StateManager-Objekt
+
  public StateManager()
+
  {
+
    _statesById = new Dictionary<string, State>();
+
  }
+
 
+
  //Registriert einen Zustand für den Manager an Hand der ID. Diese muss
+
  //eindeutig und nicht leer sein.
+
  public void AddState(string id, State state)
+
  {
+
    if (string.IsNullOrEmpty(id))
+
      throw new InvalidOperationException("Id has to be a valid string");
+
    if (state == null)
+
      throw new ArgumentNullException("state");
+
    if (_statesById.ContainsKey(id))
+
      throw new InvalidOperationException(string.Format("State with ID {0} already exists.", id));
+
     
+
    _statesById.Add(id, state);
+
  }
+
 
+
  //Setzt den aktuellen Zustand
+
  public void SetState(string id)
+
  {
+
    State nextState;
+
    //Wenn der Zustand im Manager registriert ist
+
    if (_statesById.TryGetValue(id, out nextState))
+
    {
+
      //und nicht der aktuelle ist
+
      if (nextState != _currentState)
+
      {
+
        //Rufe OnLeave() vom alten Zustand auf, falls es einen gab
+
        if (_currentState != null)
+
          _currentState.OnLeave();
+
         
+
        //setze den neuen aktuellen Zustand
+
        _currentState = nextState;
+
         
+
        //Da zu einem neuen Zustand gewechselt worden ist, rufe das Eingangsereignis auf
+
        _currentState.OnEnter();
+
      }
+
    }
+
    else
+
        //ansonsten gibt es diesen Zustand nicht!
+
        throw new InvalidOperationException(string.Format("State with Id {0} does not exist.", id));
+
  }
+
 
+
  //Aktualisert den Spielzustand. der Rückgabewert gibt an, ob das Programm weiterläuft.
+
  //Es läuft nicht weiter, wenn der aktuelle Zustand verlassen und kein weiterer Zustand zugewiesen wird.
+
  public bool Update(float elapsedTime)
+
  {
+
    //Es muss ein Zustand gesetzt sein!
+
    if (_currentState == null)
+
      throw new InvalidOperationException("Current state not set.");
+
     
+
    //aktualisere den Zustand und schaue, ob der Zustand eventuell verlassen werden soll
+
    bool running = _currentState.OnUpdate(elapsedTime);
+
   
+
    //wenn er verlassen werden soll und der Folgezustand gesetzt ist
+
    if (!running && !string.IsNullOrEmpty(_currentState.NextState))
+
    {
+
      //Wechsle den Zustand
+
      SetState(_currentState.NextState);
+
     
+
      //Programm läuft weiter
+
      return true;
+
    }
+
   
+
    //
+
    return running;
+
  }
+
 
+
  //Zeichnet den aktuellen Zustand.
+
  public void Render()
+
  {
+
    //Es muss ein Zustand gesetzt sein!
+
    if (_currentState == null)
+
      throw new InvalidOperationException("Current state not set.");
+
     
+
    //Zeichne den aktuellen Zustand
+
    _currentState.OnRender();
+
  } 
+
}
+
</sourcecode>
+
}}
+
 
+
{{Spoiler|
+
<xh4>Java</xh4>
+
|
+
<sourcecode lang="java" tab="4">
+
import java.util.HashMap;
+
  
public class StateManager {
+
TODO: erst, wenn die Theorie festgehalten ist.
// Speichert alle Zustände als Assoziatives Array
+
private HashMap<String, State> _statesById;
+
// Aktueller Zustand
+
private State _currentState;
+
+
// erzeugt ein StateManager-Objekt
+
public StateManager() {
+
_statesById = new HashMap<String, State>();
+
}
+
+
// Registriert einen Zustand für den Manager an Hand der ID. Diese muss
+
// eindeutig und nicht leer sein.
+
public void AddState(String id, State state) {
+
if (id == null || id.equals(""))
+
throw new IllegalArgumentException("Id has to be a valid string");
+
if (state == null)
+
throw new NullPointerException("state");
+
if (_statesById.containsKey(id))
+
throw new IllegalArgumentException("State with ID " + id + " already exists.");
+
+
_statesById.put(id, state);
+
}
+
+
// Setzt den aktuellen Zustand
+
public void SetState(String id) {
+
State nextState = _statesById.get(id);
+
// Wenn der Zustand im Manager registriert ist
+
if (nextState != null) {
+
// und nicht der aktuelle ist
+
if (nextState != _currentState) {
+
// Rufe OnLeave() vom alten Zustand auf, falls es einen gab
+
if (_currentState != null) {
+
_currentState.onLeave();
+
}
+
+
// setze den neuen aktuellen Zustand
+
_currentState = nextState;
+
+
// Da zu einem neuen Zustand gewechselt worden ist, rufe das Eingangsereignis auf
+
_currentState.onEnter();
+
}
+
} else {
+
// ansonsten gibt es diesen Zustand nicht!
+
throw new IllegalArgumentException("State with Id " + id + " does not exist.");
+
}
+
}
+
+
// Aktualisert den Spielzustand. der Rückgabewert gibt an, ob das Programm weiterläuft.
+
// Es läuft nicht weiter, wenn der aktuelle Zustand verlassen und kein weiterer Zustand zugewiesen wird.
+
public boolean Update(float elapsedTime) {
+
// Es muss ein Zustand gesetzt sein!
+
if (_currentState == null)
+
throw new IllegalArgumentException("Current state not set.");
+
+
// aktualisere den Zustand und schaue, ob der Zustand eventuell verlassen werden soll
+
boolean running = _currentState.onUpdate(elapsedTime);
+
+
// wenn er verlassen werden soll und der Folgezustand gesetzt ist
+
String nextState = _currentState.getNextState();
+
if (!running && (nextState == null || nextState.equals(""))) {
+
// Wechsle den Zustand
+
SetState(nextState);
+
+
// Programm läuft weiter
+
return true;
+
}
+
+
//
+
return running;
+
}
+
+
// Zeichnet den aktuellen Zustand.
+
public void Render() {
+
// Es muss ein Zustand gesetzt sein!
+
if (_currentState == null)
+
throw new IllegalArgumentException("Current state not set.");
+
+
// Zeichne den aktuellen Zustand
+
_currentState.onRender();
+
}
+
}
+
</sourcecode>
+
}}
+
  
 
== Vor- und Nachteile ==
 
== Vor- und Nachteile ==
 +
Ein Vorteil des Zustandsautomats ist, dass einzelne Zustände an mehreren Stellen des Spiels verwendet werden können. Dies könnten die Zustände für Menüs sein, wie ein Optionsmenü oder die Menüs für das Speichern und Laden des Spielstands. Mittels des Zustandsautomaten kann auf einfache Art eine Trennung der einzelnen Spielzustände vorgenommen werden, wie beispielsweise dem Hauptmenü, dem Spiel selbst, einzelnen Menüs oder Unterzustände des Spielgeschehens, wie einem Pausenmenü. Durch diese Trennung ist es einfacher möglich, die Übergänge zwischen den Zuständen sauberer zu implementieren.
  
 
== Erweiterungsmöglichkeiten ==
 
== Erweiterungsmöglichkeiten ==

Aktuelle Version vom 19. Juli 2013, 12:30 Uhr

Klicke hier, um diese Version anzusehen.

Meine Werkzeuge
Namensräume
Varianten
Aktionen
Navigation
Werkzeuge