Spielzustand-Automaten

Aus Spieleprogrammierer-Wiki
(Unterschied zwischen Versionen)
Wechseln zu: Navigation, Suche
[gesichtete Version][gesichtete Version]
K (StateManager)
 
(Eine dazwischenliegende Version von einem Benutzer wird nicht angezeigt)
Zeile 43: Zeile 43:
 
# Zustände ausführen
 
# Zustände ausführen
 
# Zustand wechseln mit optionaler Informationsübergabe
 
# Zustand wechseln mit optionaler Informationsübergabe
 +
 +
==== Zustands-Manager muss vorhanden sein ====
  
 
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.
 
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.
Zeile 48: Zeile 50:
 
==== Spielzustände dem Zustands-Manager registrieren ====
 
==== Spielzustände dem Zustands-Manager registrieren ====
  
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.
+
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.
  
 
==== Aktuellen Zustand setzen ====
 
==== Aktuellen Zustand setzen ====
Zeile 54: Zeile 56:
 
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.
 
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.
  
[[Datei:uml_states.png|miniatur|UML-Klassendiagramm eines Spielzustand-Automaten]]
+
==== Zustände ausführen ====
  
== Beispielimplementierung ==
+
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.
  
=== State ===
+
Zusätzlich muss das <tt>State</tt>-Interface
  
{{Spoiler|
+
[[Datei:uml_states.png|miniatur|UML-Klassendiagramm eines Spielzustand-Automaten]]
<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|
+
== Beispielimplementierung ==
<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|
+
<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
+
  std::map<std::string, State*> _statesById;
+
  //Aktueller Zustand
+
  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 ==

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

Klicke hier, um diese Version anzusehen.

Meine Werkzeuge
Namensräume
Varianten
Aktionen
Navigation
Werkzeuge