Closed AMatutat closed 11 months ago
Wir rennen scheinbar andauernd in solche oder ähnliche Situationen, und wieder ist es die Doppel-Verzeigerung der Strukturen.
Das Problem hier ist, dass wir einen Task haben und TaskContent, wobei wir letzteres einem Item umhängen. Beim Aufsammeln bleibt nur der Kern des Items übrig (ItemData).
Ich sehe eigentlich nur einen Weg:
(1) Das TaskContent ist ein Untertyp von ItemData, d.h. beim Aufsammeln wandert das TaskContent ins Inventar. (2) Es muss irgendwo eine Stelle ("Task-Verwaltung") geben, wo man die TaskContent-Objekte "registriert" und dabei mit einem Task verknüpft. Über diese Stelle könnte man später nach dem Aufsammeln des Items den zu dem aufgesammelten TaskContent gehörigen Task wieder herausfinden. (3) Das Handling (Abfragen der TaskContent-Components, Anstoßen der Auswerte-Routine, ...) sollte in einem System stattfinden (TaskSystem?). (4) Das Game könnte analog zur Verwaltung der Entitäten auch die Tasks verwalten. Vielleicht will man die Task-Verwaltung aber auch beim Petri-Netz ansiedeln, welches dann aber wieder bei Game aufgehängt sein sollte.
Wir rennen scheinbar andauernd in solche oder ähnliche Situationen, und wieder ist es die Doppel-Verzeigerung der Strukturen.
Ich weiß das dich die Doppel-Verzeigerung sehr stört, aber dieses Problem hätten wir auch mit einen einfachen Zeiger.
Edit: Ich hab dir dafür mal #905 angelegt und direkt mal eine erste Idee zum auflösen mit reingepackt. Ist für mich aber grade wirklich ein later.
Das Problem hier ist, dass wir einen Task haben und TaskContent, wobei wir letzteres einem Item umhängen. Beim Aufsammeln bleibt nur der Kern des Items übrig (ItemData).
Ich sehe eigentlich nur einen Weg:
(1) Das TaskContent ist ein Untertyp von ItemData, d.h. beim Aufsammeln wandert das TaskContent ins Inventar.
Das würde ich gerne vermeiden.
Ich würde hier ansonsten eine "Code-Abhängigkeit" schaffen. Das game
-Projekt/Framework müsste dann Code aus dem dungeon
-Projekt verwenden, und das wollten wir eigentlich vermeiden, damit das Framework auch alleine kompilieren kann.
Eine Möglichkeit wäre, ein Interface-Konstrukt zu erstellen, und ItemData
speichert dann eine Referenz auf eine Interface-Instanz, während TaskContent
das Interface implementiert. Allerdings ist das auch nicht die ideale Lösung.
(2) Es muss irgendwo eine Stelle ("Task-Verwaltung") geben, wo man die TaskContent-Objekte "registriert" und dabei mit einem Task verknüpft. Über diese Stelle könnte man später nach dem Aufsammeln des Items den zu dem aufgesammelten TaskContent gehörigen Task wieder herausfinden.
Vielleicht habe ich grade einen hänger, aber ich glaube das brauchen wir nicht, wofür denn?
Es muss irgendwo eine Stelle ("Task-Verwaltung") geben, wo man die TaskContent-Objekte "registriert" und dabei mit einem Task verknüpft.
Klar, das ist ja der Task
selbst der die TaskContent
speichert.
Über diese Stelle könnte man später nach dem Aufsammeln des Items den zu dem aufgesammelten TaskContent gehörigen Task wieder herausfinden.
Wenn das Item auf dem Boden liegt (also eine Entität ist), dann hat es auch das TaskContentComponent
, darüber komm ich an den TaskContent
und den Task
.
Und wenn ich mir (wie ich oben beschrie eigentlich vermeiden möchte), den TaskContent
mit in die itemData
schreibe, dann komm ich von der DSL-Funktion die in die Kiste guckt ob das richtige Item drinne liegt auch vom itemData´ ->
TaskContent`.
Ich sehe jetzt keinen Bedarf für irgendeine weitere zentrale Verwaltungsstelle, aber vielleicht hab ich dich auch miss verstanden.
(3) Das Handling (Abfragen der TaskContent-Components, Anstoßen der Auswerte-Routine, ...) sollte in einem System stattfinden (TaskSystem?).
Das wird nicht möglich sein. Die Frage, wie die Auswertungs-Routine aussieht und wann diese ausgelöst wird, ist so stark von dem Szenario abhängig (welches rein in der DSL definiert ist), dass wir dafür kein allgemeingültiges System bauen können. Die Auswertungs-Routine wird über Callbacks implementiert, welche in den jeweiligen Events hinterlegt werden (Interaktion, Kollision, etc.). Die Bewertung ist nämlich eine Reaktion auf ein bestimmtes Event, da ist das nur konsequent. Darum haben wir uns damals auch für das Strategy/Callback-Pattern-Konstrukt entschieden, so kann die DSL relativ einfach in die Game-Loop integriert werden. Der Ansatz wurde bis jetzt auch so verfolgt und umgesetzt.
Edit: Das hatten wir auch hier festgehalten. EInmal etwas unkonkret unter Ziele und etwas konkreter unter Event-Handler und DSL-Callbacks
(4) Das Game könnte analog zur Verwaltung der Entitäten auch die Tasks verwalten. Vielleicht will man die Task-Verwaltung aber auch beim Petri-Netz ansiedeln, welches dann aber wieder bei Game aufgehängt sein sollte.
Die Tasks werden im Petri-Netz liegen, dort sollen ja auch (aktuell noch leicht magische) Steuermechanismen des Netzes liegen. Ob das Game das Netz verwaltet oder das Netz das Game verwaltet, da bin ich mir noch nicht sicher. Finde zweiteres eigentlich recht sexy, weiß nur noch nicht, wie man das richtig umsetzt.
Wir rennen scheinbar andauernd in solche oder ähnliche Situationen, und wieder ist es die Doppel-Verzeigerung der Strukturen.
Ich weiß das dich die Doppel-Verzeigerung sehr stört, aber dieses Problem hätten wir auch mit einen einfachen Zeiger.
Edit: Ich hab dir dafür mal #905 angelegt und direkt mal eine erste Idee zum auflösen mit reingepackt. Ist für mich aber grade wirklich ein later.
Das Problem hier ist, dass wir einen Task haben und TaskContent, wobei wir letzteres einem Item umhängen. Beim Aufsammeln bleibt nur der Kern des Items übrig (ItemData). Ich sehe eigentlich nur einen Weg: (1) Das TaskContent ist ein Untertyp von ItemData, d.h. beim Aufsammeln wandert das TaskContent ins Inventar.
Das würde ich gerne vermeiden. Ich würde hier ansonsten eine "Code-Abhängigkeit" schaffen. Das
game
-Projekt/Framework müsste dann Code aus demdungeon
-Projekt verwenden, und das wollten wir eigentlich vermeiden, damit das Framework auch alleine kompilieren kann.
Hmmm? Das ist doch genau anders herum? Das dungeon
-Projekt würde Code aus game
referenzieren?
(2) Es muss irgendwo eine Stelle ("Task-Verwaltung") geben, wo man die TaskContent-Objekte "registriert" und dabei mit einem Task verknüpft. Über diese Stelle könnte man später nach dem Aufsammeln des Items den zu dem aufgesammelten TaskContent gehörigen Task wieder herausfinden.
Vielleicht habe ich grade einen hänger, aber ich glaube das brauchen wir nicht, wofür denn?
Naja, irgendwer muss mir doch sagen, wo mein TaskContent hingehört? Wenn ich nur die TaskComponent in der Hand habe?
Es muss irgendwo eine Stelle ("Task-Verwaltung") geben, wo man die TaskContent-Objekte "registriert" und dabei mit einem Task verknüpft.
Klar, das ist ja der
Task
selbst der dieTaskContent
speichert.
Jaaaa. Aber wenn ich später in einer Entität nur noch die Component, also den TaskContent habe, möchte ich doch immer noch herausfinden, wo diese Component hingehört?
(3) Das Handling (Abfragen der TaskContent-Components, Anstoßen der Auswerte-Routine, ...) sollte in einem System stattfinden (TaskSystem?).
Das wird nicht möglich sein. Die Frage, wie die Auswertungs-Routine aussieht und wann diese ausgelöst wird, ist so stark von dem Szenario abhängig (welches rein in der DSL definiert ist), dass wir dafür kein allgemeingültiges System bauen können. Die Auswertungs-Routine wird über Callbacks implementiert, welche in den jeweiligen Events hinterlegt werden (Interaktion, Kollision, etc.). Die Bewertung ist nämlich eine Reaktion auf ein bestimmtes Event, da ist das nur konsequent. Darum haben wir uns damals auch für das Strategy/Callback-Pattern-Konstrukt entschieden, so kann die DSL relativ einfach in die Game-Loop integriert werden. Der Ansatz wurde bis jetzt auch so verfolgt und umgesetzt.
Hmmm? Was hat das eine mit dem anderen zu tun? Irgendwer muss doch die Callbacks anstoßen? Und bei uns (ECS) sind das Systeme, die die Eigenschaften von Entitäten auswerten und dann Dinge anstoßen?
(4) Das Game könnte analog zur Verwaltung der Entitäten auch die Tasks verwalten. Vielleicht will man die Task-Verwaltung aber auch beim Petri-Netz ansiedeln, welches dann aber wieder bei Game aufgehängt sein sollte.
Die Tasks werden im Petri-Netz liegen, dort sollen ja auch (aktuell noch leicht magische) Steuermechanismen des Netzes liegen. Ob das Game das Netz verwaltet oder das Netz das Game verwaltet, da bin ich mir noch nicht sicher. Finde zweiteres eigentlich recht sexy, weiß nur noch nicht, wie man das richtig umsetzt.
Dann wird es wohl mal Zeit für ein Konzept, oder? Aktuell ist das Game die zentrale Drehscheibe, die alles steuert. Jetzt haben wir daneben irgendwie noch die DSL bzw. den "Interpreter", der aber nach meinem Verständnis bisher eher eine Art Konfigurationsmaschine ist. Und dann kommt noch das Petri-Netz. Wer steuert jetzt das Spiel und wie bleibt das Spiel unabhängig vom Dungeon-Code, damit es weiter in PM einsetzbar ist?
Hmmm? Das ist doch genau anders herum? Das dungeon-Projekt würde Code aus game referenzieren?
ItemData
liegt in game
und wenn du jetzt TaskContent
aus dungeon
in ItemData
speicherst, dann musst du dir das in ItemData
importieren.
Hmmm? Was hat das eine mit dem anderen zu tun? Irgendwer muss doch die Callbacks anstoßen? Und bei uns (ECS) sind das Systeme, die die Eigenschaften von Entitäten auswerten und dann Dinge anstoßen?
Ja, aber die Callbacks sind reaktionen auf ein Event, z.B eine Kollision. Was das Callbkack macht (also ob du da jetzt das Monster schupst oder eine Frage auswertest) ist dem ECS und den KollisionsSystem herzlich egal.
Du hattest aber explizit von einem TaskSystem
gesprochen welches die Bewertung anstößt, dass passt da nicht rein.
Vielleicht reden wir auch aneinander vorbei.
Dann wird es wohl mal Zeit für ein Konzept, oder? Aktuell ist das Game die zentrale Drehscheibe, die alles steuert. Jetzt haben wir daneben irgendwie noch die DSL bzw. den "Interpreter", der aber nach meinem Verständnis bisher eher eine Art Konfigurationsmaschine ist. Und dann kommt noch das Petri-Netz. Wer steuert jetzt das Spiel und wie bleibt das Spiel unabhängig vom Dungeon-Code, damit es weiter in PM einsetzbar ist?
Yep.
Für mich steht Fest: Das Framework wird vom Game
als zentrale Einheit gesteuert und das soll auch so bleiben (dann ist das in PM auch ohne dungeon
nutzbar),
Die Frage ist jetzt, ordnet sich das Netz dem Game unter (lässt sich also auch irgendwie mit steuern) oder liegt es über den Game und zieht heimlich im Hintergrund die Fäden.
Das fände ich eigentlich ganz sexy, aber auch nicht Teil dieses Issues.
ItemData liegt in game und wenn du jetzt TaskContent aus dungeon in ItemData speicherst, dann musst du dir das in ItemData importieren.
Ich habe nie "speichern" gesagt (hoffe ich). Das eine soll ein Untertyp des anderen sein und quasi stattdessen genutzt werden?!
ItemData liegt in game und wenn du jetzt TaskContent aus dungeon in ItemData speicherst, dann musst du dir das in ItemData importieren.
Ich habe nie "speichern" gesagt (hoffe ich). Das eine soll ein Untertyp des anderen sein und quasi stattdessen genutzt werden?!
Du hast recht
(1) Das TaskContent ist ein Untertyp von ItemData, d.h. beim Aufsammeln wandert das TaskContent ins Inventar.
ich hab das nur falsch gelesen/interpretiert.
Jetzt muss ich neu grübeln.
Was ist dann mit TaskContent
der kein Item ist?
@malt-r ich habe mal versucht, unsere Idee zu modellieren und habe dabei ein paar Kleinigkeiten gerade ausgebessert.
Das Ergebniss:
Wie besprochen speichert das InventoryComponent
die Items nicht mehr direkt, sondern eine Instanz der Klasse Inventory
. Die Klasse Inventory
entspricht der Implementierung des aktuellen InventoryComponent
und dient als Standard für das Framework.
Im Dungeon
erstellen wir uns ein spezielles Inventar, das QuestInventory
, welches dann von Quest-Entitäten (Held, Kisten) im InventoryComponent
gespeichert wird.
Um das klar hervorzuheben: Das QuestInventory
leitet von Inventory
ab und kann daher auch normal die itemData
speichern.
Das QuestInventory
fügt dem Inventar jetzt noch Funktionen hinzu, um Entitäten hinzuzufügen. In den jeweiligen Funktionen muss dann vorher überprüft werden, ob die Entität auch das ItemComponent
hat, also ein Item ist.
Um das hier auch nochmal aufzuschreiben: Dass das QuestInventory
Entitäten "speichert", ist ein Bug, der langfristig behoben werden sollte. Wir sollten auch nochmal die anderen Möglichkeiten untersuchen, bevor wir das so umsetzen.
Ich würde beide Sets, also das mit dem ItemData
und das mit den Entitäten getrennt speichern (also wenn ich #add(Entity)
aufrufe, packe ich das nur in das Entity-Set und hole mir nicht die ItemData
, um das auch im anderen Set zu speichern). Dadurch erspare ich mir komplexe Zuordnungsversuche.
Die belegten Inventarplätze würden sich dann aus inventory.size() + itemEntity.size()
ergeben. Die Funktion Inventory#item
würde sowohl den Inhalt aus inventory
zurückgeben als auch die itemData
aus den Entitäten rausholen und mit übergeben. Dadurch gibt die Funktion wirklich alle Items zurück, die im Inventar sind (besonders wichtig für die GUI, damit wirklich alle Items gezeichnet werden und mir der Inventory-Typ egal ist).
Die Funktion QuestInventory#itemEntity
ist dann "für die DSL" da. Dort werden nur Entitäten zurückgeholt. Diese Entitäten haben dann im Falle von QuestItems auch das TaskContentComponent
.
Vermutlich müssen die anderen Funktionen auch überschrieben werden, damit das alles so funktioniert.
Wir müssen dann noch die Default-Methoden für Items erweitern.
Normale Item-Entitäten (das sind Entitäten mit ItemComponent
) werden beim Aufsammeln zerstört, und das ItemData
wird ins Inventar gelegt.
Im Falle von QuestItems (das sind Entitäten mit ItemComponent
und TaskContentComponent
) muss das anders implementiert werden. Die QuestItem-Entität würde das TaskContentComponent
und ItemComponent
behalten und dann ins Inventar gelegt werden. Ich bin mir noch nicht sicher, ob die Entität aus der Game
-Entity-Liste entfernt werden muss (ich tendiere zu ja).
Vorteil an dieser Variante:
TaskContent
-Entitäten in der DSL bleibt konstant, egal ob es ein Item ist oder ein Wizard o.ä.Dungeon
-Projekt ausgelagert, das Framework bleibt sauber.Nachteil:
Edit:
Mal ne dumme Idee, was wäre denn wenn ich anstellen eines Set von Entitäten eine Map <itemData,TaskContent> speicher und wenn die Methode QuestInventory#itemEntity
aufgerufen wird, erzeuge ich aus der Map einfach neue Entitäten mit einen neuen TaskContentComponent und konfiguriere das.
Anmerkung: Dafür dürften Entiäten sich nicht mehr automatisch im Game registrieren, da sonst auch die "Helper-Entities" registiert werden. Aber dafür macht sich @cagix schon länger stark, von daher sollte das passen.
Das klingt für mich (ohne tief in den Dungeon-Interna drin zu stecken) erstmal gut. Dann könnte sich die DSL vollkommen auf die Behandlung von Entitäten beschränken und muss keine widersprüchliche Semantik für zwei Arten von Items (einmal als Entity
, einmal nur als ItemData
, wenn ein Item in einem Inventar liegt) implementieren. Die Methode QuestInventory::itemEntity
dann als Schnittstelle zur DSL zu verwenden, erscheint mit auch sinnvoll.
Mal ne dumme Idee, was wäre denn wenn ich anstellen eines Set von Entitäten eine Map <itemData,TaskContent> speicher und wenn die Methode
QuestInventory#itemEntity
aufgerufen wird, erzeuge ich aus der Map einfach neue Entitäten mit einen neuen TaskContentComponent und konfiguriere das.
Hierzu kann ich nicht so viel sagen (ich weiß auch nicht, ob ich das muss), für die DSL macht es vermutlich keinen Unterschied, ob die Entitäten schon vorher bestehen oder on-the-fly erstellt werden.
@AMatutat Mal ins unreine gedacht: Was würde passieren, wenn es nur einen Typ Inventory
gibt und wo die Schnittstelle auf Entitäten arbeitet, also add(item: Entity)
bzw. remove(item: Entity)
?
"Normale" Items haben intern ein ItemData
, was sich das Inventory
bei add
holen könnte bzw. aus dem es bei remove
wieder eine Entität macht.
Quest-Items haben dann ein anderes BlaData
, womit das Inventory
ganz analog umgehen kann.
Damit das klappt, müsste es nur einen gemeinsamen Obertyp (Interface) für ItemData
und BlaData
geben?
Edit: Mit Abstand betrachet: Warum machen wir uns das eigentlich so kompliziert? Das ItemData
wird dann wieder in einer passenden Component gespeichert ... Warum holt sich das Inventory
nicht gleich die Component aus der Entität und speichert das bzw. macht daraus wieder die passende Entität beim "Rausgeben"? Dann wäre auch der Unterschied "Item" und "Quest" polymorph auf JVM-Ebene.
@AMatutat Mal ins unreine gedacht: Was würde passieren, wenn es nur einen Typ
Inventory
gibt und wo die Schnittstelle auf Entitäten arbeitet, alsoadd(item: Entity)
bzw.remove(item: Entity)
?
Auf die Idee bin ich eben auch gekommen, aber ich bin noch unentschlossen.
Aktuell muss sich die Item-Entity erstmal selbst auseinanderbauen (ItemData#onCollect
) oder zusammenbauen (ItemData#onDrop
). Das sind Bi/Tri-Consumer mit einer default Implementierung als Vorgabe.
Das erlaubt theoretisch den items beim aufheben noch was machen zu lassen (Spieler vergiften, oderso). Hat aber eine gewisse Komplexität und ist erstmal nicht so Verständlich.
"Normale" Items haben intern ein
ItemData
, was sich dasInventory
beiadd
holen könnte bzw. aus dem es beiremove
wieder eine Entität macht.Quest-Items haben dann ein anderes
BlaData
, womit dasInventory
ganz analog umgehen kann.Damit das klappt, müsste es nur einen gemeinsamen Obertyp (Interface) für
ItemData
undBlaData
geben?
Klingt auf den ersten Blick gut, aber auf DSL Ebene müsste man dann Sonderverhalten implementieren, oder @malt-r ?
Man müsste beim Betrachten in der DSL dann erst gucken ob das ItemData-Objekt in der Kiste ein QuestItemData
ist, wenn ja kann man auf das TaskContent
-Objekt zugreifen.
Edit: Mit Abstand betrachet: Warum machen wir uns das eigentlich so kompliziert? Das
ItemData
wird dann wieder in einer passenden Component gespeichert ... Warum holt sich dasInventory
nicht gleich die Component aus der Entität und speichert das bzw. macht daraus wieder die passende Entität beim "Rausgeben"? Dann wäre auch der Unterschied "Item" und "Quest" polymorph auf JVM-Ebene.
Wenn ich das richtig vestehe, dann willst du das ItemComponent
im Inventar speichern. Components können ohne Entität nicht existieren und Items im Inventar sind keine Entitäten.
@AMatutat Mal ins unreine gedacht: Was würde passieren, wenn es nur einen Typ
Inventory
gibt und wo die Schnittstelle auf Entitäten arbeitet, alsoadd(item: Entity)
bzw.remove(item: Entity)
?Auf die Idee bin ich eben auch gekommen, aber ich bin noch unentschlossen. Aktuell muss sich die Item-Entity erstmal selbst auseinanderbauen (
ItemData#onCollect
) oder zusammenbauen (ItemData#onDrop
). Das sind Bi/Tri-Consumer mit einer default Implementierung als Vorgabe. Das erlaubt theoretisch den items beim aufheben noch was machen zu lassen (Spieler vergiften, oderso). Hat aber eine gewisse Komplexität und ist erstmal nicht so Verständlich.
Wann werden diese Aktionen denn ausgeführt? Es würde Dich doch niemand hindern, diese Aktion beim add
ins Inventory
von just diesem ausführen zu lassen und sich hinterher das Data-Objekt aus der Entität rauszufischen?
"Normale" Items haben intern ein
ItemData
, was sich dasInventory
beiadd
holen könnte bzw. aus dem es beiremove
wieder eine Entität macht. Quest-Items haben dann ein anderesBlaData
, womit dasInventory
ganz analog umgehen kann. Damit das klappt, müsste es nur einen gemeinsamen Obertyp (Interface) fürItemData
undBlaData
geben?Klingt auf den ersten Blick gut, aber auf DSL Ebene müsste man dann Sonderverhalten implementieren, oder @malt-r ? Man müsste beim Betrachten in der DSL dann erst gucken ob das ItemData-Objekt in der Kiste ein
QuestItemData
ist, wenn ja kann man auf dasTaskContent
-Objekt zugreifen.
Das meinte ich mit "polymorphen Handling". Sollte eigentlich auf der Seite von Inventory
oder Entity
erschlagbar sein, ohne dass sich die DSL darum kümmern muss. Stichwort: Überladene Methoden.
Edit: Mit Abstand betrachet: Warum machen wir uns das eigentlich so kompliziert? Das
ItemData
wird dann wieder in einer passenden Component gespeichert ... Warum holt sich dasInventory
nicht gleich die Component aus der Entität und speichert das bzw. macht daraus wieder die passende Entität beim "Rausgeben"? Dann wäre auch der Unterschied "Item" und "Quest" polymorph auf JVM-Ebene.Wenn ich das richtig vestehe, dann willst du das
ItemComponent
im Inventar speichern. Components können ohne Entität nicht existieren und Items im Inventar sind keine Entitäten.
Merkst Du nicht, wie Du Dir selbst die Sachen verkomplizierst? Wenn Du Dich mal von diesem Dogma der Doppel-Verkettung löst und die Beziehungen zw. Game und Entität und Component als Baum betrachtest, wird irgendwie vieles deutlich einfacher :)
Jaaaa, das ist das alte Dogma. Aber wenn Du Dich mal davon löst und das wirklich als Baum betrachtest, wird irgendwie vieles deutlich einfacher :)
Dann könnte ich doch gleich die Entity
im Inventar speichern und sparr mir das geraffel, oder überseh ich grade etwas?
Der Baum-Gedanke bringt mich grade etwas aus dem Konzept
@AMatutat Mal ins unreine gedacht: Was würde passieren, wenn es nur einen Typ
Inventory
gibt und wo die Schnittstelle auf Entitäten arbeitet, alsoadd(item: Entity)
bzw.remove(item: Entity)
?Auf die Idee bin ich eben auch gekommen, aber ich bin noch unentschlossen. Aktuell muss sich die Item-Entity erstmal selbst auseinanderbauen (
ItemData#onCollect
) oder zusammenbauen (ItemData#onDrop
). Das sind Bi/Tri-Consumer mit einer default Implementierung als Vorgabe. Das erlaubt theoretisch den items beim aufheben noch was machen zu lassen (Spieler vergiften, oderso). Hat aber eine gewisse Komplexität und ist erstmal nicht so Verständlich.Wann werden diese Aktionen denn ausgeführt? Es würde Dich doch niemand hindern, diese Aktion beim
add
insInventory
von just diesem ausführen zu lassen und sich hinterher das Data-Objekt aus der Entität rauszufischen?
Ich hab mir die onCollect
jetzt nochmal angeguckt.
Die Funktion ist der Callback für das InteractionComponent
und implementiert (zumindest in der bereitgestellten Default-Implementierung) das grundsätzliche Aufheben und Einstecken Verhalten.
Der Name onCollect
ist eigentlich unpassend, es müsst ehrcollectOnInteraction
sein.
Das auseinanderbauen der Entität könnte m.M tatsächlich im Inventar passieren.
Jaaaa, das ist das alte Dogma. Aber wenn Du Dich mal davon löst und das wirklich als Baum betrachtest, wird irgendwie vieles deutlich einfacher :)
Dann könnte ich doch gleich die
Entity
im Inventar speichern und sparr mir das geraffel, oder überseh ich grade etwas? Der Baum-Gedanke bringt mich grade etwas aus dem Konzept
Ja, nein - Entitäten sollen ja nur an einer Stelle verwaltet/referenziert werden. Aber Components sind ja nur Datencontainer - warum sollte es nicht auch Components ohne Entität geben bzw. erst später einer Entität zugeordnet werden?
"Normale" Items haben intern ein
ItemData
, was sich dasInventory
beiadd
holen könnte bzw. aus dem es beiremove
wieder eine Entität macht. Quest-Items haben dann ein anderesBlaData
, womit dasInventory
ganz analog umgehen kann. Damit das klappt, müsste es nur einen gemeinsamen Obertyp (Interface) fürItemData
undBlaData
geben?Klingt auf den ersten Blick gut, aber auf DSL Ebene müsste man dann Sonderverhalten implementieren, oder @malt-r ? Man müsste beim Betrachten in der DSL dann erst gucken ob das ItemData-Objekt in der Kiste ein
QuestItemData
ist, wenn ja kann man auf dasTaskContent
-Objekt zugreifen.Das meinte ich mit "polymorphen Handling". Sollte eigentlich auf der Seite von
Inventory
oderEntity
erschlagbar sein, ohne dass sich die DSL darum kümmern muss. Stichwort: Überladene Methoden.
@malt-r und ich haben heute über einen allgemeinen Ablauf von Entity nach TaskContent gesprochen. @malt-r möchte diesen immer gleich gestalten, damit das Handling innerhalb der DSL (vom Lehrenden) für solche Sachen immer identisch ist (also egal, ob er gerade ein Item anschaut oder einen Zauberer).
Wir sind da bei Entity -> TaskContentComponent -> TaskContent
gelandet.
Für mich/das Dungeon bedeutet das, egal wie. Sobald die DSL in die Kiste gucken und die Items anschauen will, muss ich eine Entität mit einem TaskContentComponent
bereithalten.
Jetzt kommt meine zweite Bedingung: Ich möchte keinen Code vom dungeon
in game
haben. Mein InventoryComponent
kann TaskContent
, TaskContentComponent
oder das BlaData
daher nicht direkt kennen.
Anmerkung: Mein Kopf ist grade etwas in alle Richtungen gelaufen, also vielleicht löse ich grade ein Problem ohne das klar ist wo das Problem liegt:
Wenn mein Obertyp IItemData
eine Entity toEntity
Methode hat, könnte das default ItemData
einfach ein 0815 WorldItem erzeugen und zurückgeben.
QuestData
würde der Entity zusätzlich ein TaskContentComponent
verpassen.
Meinem Inventar ist das im konkreten egal und kann mit beiden Typen gleich umgehen.
In der DSL müsste @malt-r bzw. der Lehrende für jedes IItemData
in der Kiste #toEntity
aufrufen und dann (über das ggf. vorhandene TaskContentComponent
) auf das TaskContent
Objekt zugreifen.
Das find ich irgendwie unschön. In der DSL sollten nur Items mit dem TaskContentComponent
ankommen.
Jaaaa, das ist das alte Dogma. Aber wenn Du Dich mal davon löst und das wirklich als Baum betrachtest, wird irgendwie vieles deutlich einfacher :)
Dann könnte ich doch gleich die
Entity
im Inventar speichern und sparr mir das geraffel, oder überseh ich grade etwas? Der Baum-Gedanke bringt mich grade etwas aus dem KonzeptJa, nein - Entitäten sollen ja nur an einer Stelle verwaltet/referenziert werden. Aber Components sind ja nur Datencontainer - warum sollte es nicht auch Components ohne Entität geben bzw. erst später einer Entität zugeordnet werden?
Wenn wir das erlauben, haben wir viele Probleme gelöst Ja. Bevor ich da aber was zu sage, will ich mich erstmal schlau machen.
Oder? Das Inventory speichert dann ItemComponent
, aber davon hab ich noch kein TaskComponent
.
Ich glaub ich hänge gedanklich irgendwo fest.
Ok je mehr ich drüber nachdenke, desto weniger Vorteile sehe ich durch das speichern vom ItemComponent
im Inventar.
Wenn ich damit nicht die Referenz auf die Entität "mit speicher" (ohne doppelte Zeiger komm ich da schließlich nicht dran), dann kann ich mir weiterhin das ItemData
Objekt speichern und habe genau den selben Status.
@malt-r und ich haben heute über einen allgemeinen Ablauf von Entity nach TaskContent gesprochen. @malt-r möchte diesen immer gleich gestalten, damit das Handling innerhalb der DSL (vom Lehrenden) für solche Sachen immer identisch ist (also egal, ob er gerade ein Item anschaut oder einen Zauberer).
@AMatutat @malt-r Wenn ich mir das als Lehrender anschaue, möchte ich eigentlich gar nichts von dem internen Aufbau des Spiels wissen. Ich möchte meine Fragen stellen und die Antworten bewerten können. Dafür muss die DSL mir geeignete Mittel zur Verfügung stellen, und der Interpreter/Compiler muss den Rest dann intern hinkriegen. Ich will mich doch gar nicht mit Entitäten und Components und TaskContentComponent und TaskContent und so einem Kram auf DSL-Ebene rumschlagen? Dann bin ich doch sofort wieder nah am Java-Code und dann stellt sich die Frage, warum ich das nicht gleich direkt in Java mache?!
@malt-r und ich haben heute über einen allgemeinen Ablauf von Entity nach TaskContent gesprochen. @malt-r möchte diesen immer gleich gestalten, damit das Handling innerhalb der DSL (vom Lehrenden) für solche Sachen immer identisch ist (also egal, ob er gerade ein Item anschaut oder einen Zauberer).
@AMatutat @malt-r Wenn ich mir das als Lehrender anschaue, möchte ich eigentlich gar nichts von dem internen Aufbau des Spiels wissen. Ich möchte meine Fragen stellen und die Antworten bewerten können. Dafür muss die DSL mir geeignete Mittel zur Verfügung stellen, und der Interpreter/Compiler muss den Rest dann intern hinkriegen. Ich will mich doch gar nicht mit Entitäten und Components und TaskContentComponent und TaskContent und so einem Kram auf DSL-Ebene rumschlagen? Dann bin ich doch sofort wieder nah am Java-Code und dann stellt sich die Frage, warum ich das nicht gleich direkt in Java mache?!
Will jetzt nichts falsches sagen, aber ist hier nicht genau der Gedanke der native-lib der DSL. Wenn ich will kann ich alles im detail konfigurieren, ja fast schon programmieren. Auf dieser eben sollte sich trotzdem alles sinnvoll und konstant verhalten. Und dann kommt da noch die hübsche Wrapper-Schicht drüber, damit du dir den ganzen kram nicht angucken musst.
@AMatutat @malt-r Wenn ich mir das als Lehrender anschaue, möchte ich eigentlich gar nichts von dem internen Aufbau des Spiels wissen. Ich möchte meine Fragen stellen und die Antworten bewerten können. Dafür muss die DSL mir geeignete Mittel zur Verfügung stellen, und der Interpreter/Compiler muss den Rest dann intern hinkriegen. Ich will mich doch gar nicht mit Entitäten und Components und TaskContentComponent und TaskContent und so einem Kram auf DSL-Ebene rumschlagen? Dann bin ich doch sofort wieder nah am Java-Code und dann stellt sich die Frage, warum ich das nicht gleich direkt in Java mache?!
Wir haben wirklich schon häufig darüber gesprochen, dass es möglich sein sollte, die Szenarien in der DSL zu definieren, aber das nicht zwingend nötig ist, da das in einer "Standardbibliothek" gekapselt wird. Kernbegriff ist hier "Taskbuilder-Methode" #197, die dafür zuständig sein soll, das Szenario zu definieren und die in der DSL definierten Entitätstypen/Prototypen (die dann eben auch vorgeben, welche Komponenten enthalten sind) und Event-Handler Funktionen zu verknüpfen.
Wie wir dann von der DSL-Definition zum konkreten Szenario kommen wollen, ist auch schon länger hier dokumentiert (Kasten 3 zeigt da unter anderem diese "Standard-Bibliothek" die im Wesentlichen aus DSL-Definitionen besteht).
Dass es in der einfachsten Form einer DSL-Definition ausreichen sollte, nur die Aufgaben und die Art der Bewertung zu definieren, stimmt. In dem Fall kommt die Definition des Szenarios mit allen Event-Handler-Funktionen usw. die nötig sind, um die Verbindung zwischen Aufgabendefinition und konkretem Szenario herzustellen aus besagter Standardbibliothek (also DSL-Definitionen, welche Default-Implementierungen für Bewertungsfunktionen etc. enthalten). Wie z.B. die Bewertungsfunktion mit der Szenario-Definition zusammen hängt ist hier schon länger dokumentiert.
Falls wir die Möglichkeit schaffen wollen, auch eigene Szenarien per DSL-Eingabe definieren zu lassen (und da haben wir uns relativ früh im Projekt für entschieden, das #197 Issue ist schon etwas älter), dann bedeutet das in der Konsequenz auch, dass man über die DSL eine gewisse Konfigurierbarkeit der im Szenario enthaltenen Entitäten/Komponenten bietet. Dazu gehört dann bspw. in der Event-Handler Funktion einer Truhe in der DSL über alle enthaltenen Items zu iterieren und zu überprüfen, ob die Items enthalten sind, die laut Aufgabendefinition in der Truhe erwartet werden. Wenn wir die Freiheit der Szenariodefinition per DSL bieten wollen, dann können wir das nicht einfach in eine Blackbox verpacken, die von Nutzenden nicht mehr einsehbar ist, sondern müssen das irgendwie zugänglich machen, weil diese Abläufe zu spezifisch an ein bestimmtes Szenario gekoppelt sind.
@malt-r Ist ja alles richtig. Aber ich frage mich im Moment angesichts der obigen Diskussion (Entity -> TaskContentComponent -> TaskContent
), ob wir wirklich auf dem richtigen Weg sind. Vielleicht ist das auch nur der Urlaubsmodus ;)
Wir bauen Java-seitig ziemlich komplexe Strukturen auf, und die wollen wir wirklich an Lehrende weiterreichen, die ihrerseits eigentlich nicht programmieren können sollen? Und das soll dann auch noch alles in einer DSL gemacht werden (können), die dann (auf mich) wie eine Schatten-Variante des Java-Codes wirkt?
Für mich wäre es zunächst wichtig, die Aufgaben plus Bewertungen und so definieren zu können. Danach wäre es noch schön, in der DSL einen gewissen Einfluss auf das Spiel bzw. dessen Konfiguration nehmen zu können - aber dazu möchte ich mich dann eigentlich nicht mit den Strukturen des Spiels beschäftigen müssen. Das müsste also über entsprechende Primitive der DSL abgebildet werden.
Die Argumentation, dass die DSL ein bestimmtes Feature unterstützt, ist für mich nicht direkt damit zusammenhängend, ob wir den Lehrenden zumuten, das auch zu nutzen zu müssen. Wir haben immer die Möglichkeit, die nötige DSL-Logik in Funktionen in der Standard-Bibliothek, oder nativen Funktionen zu kapseln und so irgendwie die Komplexität der nötigen Eingaben zu verringern, aber eben mit "dsl-eigenen Mitteln" und damit theoretisch im Zugriff der Nutzenden.
Ich sehe den Punkt, dass man mit dieser Argumentation auch die Verpackung von diesen Komplexitäten in den Interpreter (damit dann aber DSL-Nutzenden unzugänglich) rechtfertigen kann und darauf, wo da die richtige Grenze ist, habe ich auch keine 100%ige Antwort :)
Nach dem gespräch mit Malte:
QuestItem extends Item
private TaskContentComponent tcc;
QuestItem(......, TaskContentComoponent)
.....
public TaskContentComponent taskContentComponent() return tcc;
@Lena241 in #1047 hab ich dir einen Fix dafür auf dein Item-Rework Branch erstellt. Damit übergebe ich an dich (musst eigentlich nichts hierfür mehr machen)
Wir nutzen das
TaskContentComponent
, um Entitäten mit denTask
und denTaskContent
(also z.B. Lösungen) zu verknüpfen.Für die meisten unserer Aufgabentypen und Szenarien nutzen wir Items, um diese als Lösungsoption darzustellen. Solange ein Item sich im Level befindet, ist es eine Entität, und wir können es auch mit dem
TaskContentComponent
mit einemTaskContent
verknüpfen.Sobald das Item vom Spieler aufgesammelt wird, wird die Item-Entität jedoch zerstört, und nur noch das
ItemData
imInventoryComponent
des Helden gespeichert. => Wir verlieren dasTaskContentComponent
und damit die Verbindung zumTaskContent
undTask
.Selbst wenn aus dem
ItemData
imInventoryComponent
eine neue Entität gemacht wird (z.B. wenn man das Item auf den Boden wirft), ist diese Entität eine neue Entität und daher dem DSL und demTaskContentComponent
nicht bekannt.Auf Anhieb sehe ich zwei Optionen, die beide nicht sonderlich schön sind:
InventoryComponent
speichert nichtItemData
, sondern Entitäten mit einemItemComponent
. Dann könnten wir das Task-Item unverändert im Inventar ablegen und dort dasTaskContentComponent
auslesen. Dann speichern wir aber wieder Referenzen auf Entitäten in anderen Entitäten, das ist weder ECS noch besonders schön und stellt viele konzeptionelle Probleme auf, die wir eigentlich mit den Konzept beheben wollten (siehe auch #447).TaskContentComponent
müsste irgendwie in dasItemData
-Konstrukt eingefügt werden, um so auch die Verbindung zwischenTaskContent
undItemData
zu haben. Das geht aber irgendwie in die Richtung, dass ein non-Entity-Konstrukt ein Component implementiert, was nicht gerade ECS ist.Im Gespräch mit @malt-r kam auch die Idee auf, die Funktionalität von
TaskContentComponent
nicht alsComponent
umzusetzen (wie konkret wussten wir noch nicht), aber das sei auf der Seite der DSL mit erheblichem Aufwand verbunden und sollte vermieden werden.