# Textadventure (gähn!) Designfragen



## temi (8. Okt 2017)

Hallo zusammen,
als Übung würde ich gerne ein kleines Textadventure schreiben. Der Text könnte etwas länger werden 

Das erste Ziel ist recht niedrig gesteckt, der Spieler soll sich in verschiedenen Szenen hin- und herbewegen können.
Damit es nicht zu banal wird, dachte ich mir, das als ECS (Entity Component System) zu realisieren. Die Vorlage ist diese: http://t-machine.org/index.php/2007/09/03/entity-systems-are-the-future-of-mmog-development-part-1/ und die kurze Zusammenfassung lautet wie folgt:

Ein Entity ist eine ID und sonst nichts
Eine Komponente enthält Daten und sonst nichts.
Einem Entity können eine oder mehrere Komponenten zugeordnet werden, um das Verhalten zu definieren.

Die Spiellogik steckt in Systemen
Die Kommunikation zwischen den System soll per EventBus geschehen, also ereignisbasiert.

Für das erste definierte Ziel führt das ungefähr zu folgenden Komponenten:

*Description* enthält einen kurzen Bezeichner und einen Langtext.
*Direction* enthält die Richtungsangabe für einen Ausgang.
Damit führt das zu folgenden Entities und gleich zu einem Problem:

*Scene* (Description)
*Exit* (Description, Direction)
Das Problem ist ersichtlich, ein Entity kann nur jeweils eine Komponente eines Typs enthalten, aber eine Szene kann z.B. mehrere Ausgänge haben. Meine derzeit favorisierte Lösung besteht in einer neuen Komponente

*Parent* enthält einen Verweis auf ein übergeordnetes Entity.
Damit sieht ein Exit nun so aus:

*Exit* (Description, Direction, Parent)
In einem *EntityManager* werden alle Entities und Komponenten als Tabellen verwaltet, der Inhalt mit drei Räumen könnte also ungefähr so aus sehen:

Tabelle Entities (sind nur ID's):
1 (Scene)
2 (Scene)
3 (Scene)
4 (Exit)
5 (Exit)
6 (Exit)
7 (Exit)

Tabelle Komponenten (Entity, Daten)
1, Flur, Du stehst im Flur
2, Wohnzimmer, Du stehst im Wohnzimmer
3, Balkon, Du bist auf dem Balkon
4, Tür, Eine Tür führt ins Wohnzimmer
4, Norden
4, 1
5, Tür, Eine Tür führt in den Flur
5, Süden
5, 2
6, Tür, Eine Tür führt zum Balkon
6, Norden
6, 2
7, Tür, Eine Tür führt ins Wohnzimmer
7, Süden
7, 3

Ein erster Einstieg sieht ungefähr so aus:

Ein *InputHandler* nimmt Spielereingaben von der Konsole entgegen und gibt einen rohen String weiter (*PlayerInputEvent*).

Ein *OutputHandler* nimmt einen Ausgabetext entgegen und gibt ihn auf der Konsole aus (handle *PlayerOutputEvent*).

Ein *ParserSystem* reagiert auf eine Spielereingabe vom InputHandler und ermittelt das eingegebene Kommando mit seinen Parametern => "gehe nach norden" (*MoveEvent*).

Ein *SceneSystem* reagiert auf das entsprechende Kommando, prüft, ob ein entsprechender Ausgang in existiert und wechselt die Szene => PlayerOutputEvent.

Was mir dabei nicht so gut gefällt ist folgendes:
Wenn der Parser zwischen Input und SceneSystem sitzt, kann er zwar auf Kommandos wie "gehe nach Norden" reagieren, aber ich würde gerne auch "gehe ins Wohnzimmer" sagen können. Das Wort "Wohnzimmer" ist aber kontextbezogen, d.h. hängt von der aktuellen Szene ab.
Eine Möglichkeit wäre es das Parsen der Benutzereingabe direkt im SceneSystem zu erledigen, da geht mir die Zuständigkeit allerdings etwas zu weit.
Oder das ParserSystem fordert sich Daten aus dem SceneSystem an, z.B. auch durch eine neue Komponente *Keywords*.
Oder der Input wird zuerst vom SceneSystem entgegen genommen, das die Aufgabe mit der Angabe einer Keywordliste an das ParserSystem delegiert.

Hat jemand noch eine andere Idee? Was haltet ihr grundsätzlich davon?

Mir geht es grundsätzlich darum, eine etwas komplexere Anwendung zu realisieren und Textadventures sind so schön retro. Außerdem schweben mir da noch einige Erweiterungen vor, die sich durch zusätzliche Systeme und Komponenten relativ einfach integrieren lassen sollten:

Gegenstände, Aufgaben, Kampf, Personen(KI, Chatbot)
Multiplayer mit Ein- und Ausgabe über Telnet-Client
Danke an Alle, die bis hierher gekommen sind!
temi


----------



## Tobse (8. Okt 2017)

Zwei Dinge:

1. Du sagst "ein Entity kann nur jeweils eine Komponente eines Typs enthalten"
Ich habe mir die Vorlage angesehen. Aber so wie ich ECS kenne ist das eine unnötige Limitierung. Aber selbst wenn diese Limitierung besteht, würde ich das anders Lösen:

Definiere keine Exits sondern Transitions oder Passages. In der Szenen-Komponente definierst du die Passages als Collection:


```
class SceneComponent {
  Set<Transition> transitions;
  String name; // bspw. "Wohnzimmer"
  class Transition {
    Direction direction;
    Entity targetScene;
    enum Direction { NORTH, WEST, SOUTH, EAST }
  }
}
```

2. Wie sieht denn dein MoveEvent aus? Du musst dort ja noch nicht den Datentyp des Zielorts festzurren:


```
class MoveEvent {
   String destination;
}

class SceneSystem {
  Entity currentScene;
  onMove(MoveEvent event) {
    if (e.destination.equals("Norden") || ... /* S, O, W */) {
      SceneComponent.Transition.Direction targetDirection = ...;
      // jetzt mithilfe der targetDirection die richtige transition finden
      Optional<Scene> targetScene = getComponentOfEntity(currentScene, SceneComponent.class).transitions.filter(t -> t.direction == targetDirection).findFirst()
      // und dann die scene wechseln
      if (targetScene.isPresent()) { /* wechseln */ } else { /* Fehlermeldung an Spieler: in dieser Richtung gibts keine Tür */ }
    }
    else
    {
      // die namen aller Entities mit SceneComponent durchsuchen
      Optional<Scene> targetScene = getEntitiesWith(SceneComponent::class).filter(e -> getComponentOfEntity(e, SceneComponent.class).name.equals(event.destination)).findFirst();
      if (!targetScene.isPresent()) // Fehlermeldung an den Spieler: Es gibt keinen Raum mit dem Namen event.destination
      // prüfen, ob man von der aktuellen scene in die targetScene kommen kann (benutze transitions)
      // falls ja: scene wechseln.
      // falls nein: Fehlermeldung an den Spieler: Von hier aus kommst du nicht zu targetScene
    }
  }
}
```

Wenn du willst, kannst du die Erkennung, ob es sich um eine Himmelsrichtung oder einen Bezeichner handelt auch im ParserSystem machen. Dann würde ich einfach zwei Subklassen von MoveEvent benutzen:


```
abstract class MoveEvent {}
class DirectionedMoveEvent {
  Direction direction;
}
class TargetSceneMoveEvent {
  Entity targetSceneEntity;
}
```


----------



## JuKu (10. Okt 2017)

Ist ein ECS für ein Text Adventure überhaupt sinnvoll?
Ich finde das ist viel zu viel Overhead und bringt dir hier nicht wirklich was, da dein Entity keine Components mit verschiedenen Aufgaben / Zuständigkeiten enthält.


----------



## temi (12. Okt 2017)

JuKu hat gesagt.:


> Ist ein ECS für ein Text Adventure überhaupt sinnvoll?
> Ich finde das ist viel zu viel Overhead und bringt dir hier nicht wirklich was, da dein Entity keine Components mit verschiedenen Aufgaben / Zuständigkeiten enthält.


Naja, es geht ja in erster Linie nicht um das Spiel, sondern darum, dass ich mal was anderes ausprobieren möchte, aber den "Ballast" einer graphischen UI erstmal beiseite lassen will. Aber abgesehen davon, finde ich nicht, dass der Overhead durch ein ECS größer ist als bei einer "normalen" Klassenstruktur. Fast schon im Gegenteil. Du hast natürlich Recht, solange es nur Räume gibt und nichts anderes passiert als Herumlaufen, wäre das schon etwas sinnlos. Aber das muss ja nicht so bleiben...


----------



## temi (12. Okt 2017)

Tobse hat gesagt.:


> Du sagst "ein Entity kann nur jeweils eine Komponente eines Typs enthalten"
> Ich habe mir die Vorlage angesehen. Aber so wie ich ECS kenne ist das eine unnötige Limitierung.
> 
> Wenn du willst, kannst du die Erkennung, ob es sich um eine Himmelsrichtung oder einen Bezeichner handelt auch im ParserSystem machen.


Es gibt vom Autoren des Artikels eine Beispielimplementation für den EntityManager, daraus geht hervor, dass es nur eine Komponente per Entity gibt. Es ist aber sicherlich zu erwägen, das zu ändern.
Ich habe auch darüber nachgedacht, ob ein Exit (oder Transition) ein Entity oder eine Komponente ist und bin zum Schluss gekommen, ein Entity daraus zu machen. Denn ebenso wie eine Scene (oder ein noch nicht existierendes Item) hat es eine beschreibende Komponente (der Text, den der Spieler angezeigt bekommt) und später kommen vielleicht noch weitere Komponenten dazu (Ausgang sichtbar/unsichtbar, Ausgang verschlossen, Regel zum Öffnen des Ausgangs, usw.) 

Ein Problem bei deiner Scene-Entity (ich nehme an du meinst Entity, weil es keine Scene-Component gibt) ist, dass es diese Klasse gar nicht gibt. Ein Entity ist nur eine ID. Was zur aktuellen Scene gehört, das verwaltet das SceneSystem oder MoveSystem. Ist aber eigentlich nebensächlich.

Ich habe mir überlegt, dass es ideal wäre komplett auf "Nord", "Süd" usw. zu verzichten, im normalen Leben würde das kaum jemand so sagen, wenn er vom Flur in das Wohnzimmer gehen möchte. Das würde dazu führen, dass der Parser nur das Grundkommando festlegen könnte, aber nicht das Ziel, denn er kennt ja nicht die möglichen Ziele, z.B. gehe ins Wohnzimmer => "gehe" => moveCommand => "ins Wohnzimmer". Ob im Rest des Satzes ein mögliches Ziel steckt, könnte dann nur das MoveSystem feststellen. Der "Parser" würde im Prinzip nur vorsortieren, um schon mal das richtige System anzusprechen. Das könnte eine ganz gute Lösung sein.


----------



## Tobse (12. Okt 2017)

temi hat gesagt.:


> Ein Problem bei deiner Scene-Entity (ich nehme an du meinst Entity, weil es keine Scene-Component gibt) ist, dass es diese Klasse gar nicht gibt.


Doch, ich meine genau das. Eine Scene kann doch eine Entity mit einer Scene-Komponente sein.


----------



## Tobse (12. Okt 2017)

temi hat gesagt.:


> Ein Problem bei deiner Scene-Entity (ich nehme an du meinst Entity, weil es keine Scene-Component gibt) ist, dass es diese Klasse gar nicht gibt.


Doch, ich meine genau das. Eine Scene kann doch eine Entity mit einer Scene-Komponente sein. Die Komponente enthält dann alle nötigen Infos.



temi hat gesagt.:


> Es gibt vom Autoren des Artikels eine Beispielimplementation für den EntityManager, daraus geht hervor, dass es nur eine Komponente per Entity gibt. Es ist aber sicherlich zu erwägen, das zu ändern.


Aber 100%ig. Wenn eine Entity nur eine Komponente haben kann kannst du auch gleich einfach eine Klasse pro Komponente erstellen (Klasse Room, Klasse Exit, ...) anstatt dir den Aufriss mit Entity-IDs zu geben.


----------



## temi (13. Okt 2017)

Tobse hat gesagt.:


> Wenn eine Entity nur eine Komponente haben kann kannst du auch gleich einfach eine Klasse pro Komponente erstellen (Klasse Room, Klasse Exit, ...) anstatt dir den Aufriss mit Entity-IDs zu geben.


Nur das wir nicht aneinander vorbei reden: Ein Entity kann natürlich 1..n Komponenten enthalten, aber nur eine von jedem Typen.


----------

