Dungeon-CampusMinden / Dungeon

The "Dungeon" is a tool to gamify classroom content and integrate it into a 2D Rogue-Like role-playing game.
MIT License
18 stars 38 forks source link

[Game] Schnittstelle von Game #582

Closed cagix closed 1 year ago

cagix commented 1 year ago

Bitte mal https://github.com/gurkenlabs/litiengine-docs/blob/master/tutorials/creating-a-platformer.md#our-main-class anschauen. Das nenne ich mal ne saubere Schnittstelle (naja, aber irgendwie intuitiver und stimmiger).

Bitte das Konzept übertragen.

cagix commented 1 year ago

Siehe https://github.com/Programmiermethoden/Dungeon/pull/560/files#r1184735167:

Der TextureHandler wird in Game#setup "aufgerufen", d.h. dort zum ersten Mal initialisiert. Da wir dabei eine checked exception werfen könnten, muss man in Game#setup damit umgehen. Der aktuelle Workaround ist, die Exception in eine Runtime-Exception zu konvertieren und weiter nach oben zu reichen.

AMatutat commented 1 year ago

Wäre es nicht sinnvoller, man macht explizit ein Setup für das Game (also einmalig) und steigt dann in die Game-Loop ein, ohne dort immer wieder zu prüfen, ob man vielleicht doch nochmal ein Setup machen möchte?

Die stelle ärgert mich auch jeden Tag im Schlaf. Das Kernproblem ist, dass libGDX erst irgendwelche Magie machen muss, damit wir in setup auf bestimmte Sachen von libGDX zugreifen können. Man müsste sich das Problem mal systematisch vorknöpfen:

  1. Brauchen wir in Setup wirklich etwas von dieser libGDX Magie? Wenn ja, wo? Kann man das auslagern?
  2. Wenn wir nicht darum kommen, ist render denn wirklich der erste Einstiegspunkt dafür? Können wir uns vielleicht auch mit setup woanders anschließen?`
AMatutat commented 1 year ago

Bitte mal https://github.com/gurkenlabs/litiengine-docs/blob/master/tutorials/creating-a-platformer.md#our-main-class anschauen. Das nenne ich mal ne saubere Schnittstelle (naja, aber irgendwie intuitiver und stimmiger).

Hmm... ich glaube so groß anders als wir machen die das gar nicht. Die verteilen das einfach nur "besser" (wobei ich mir nicht sicher bin, ob ich das besser finde).

Wenn wir unsere Klasse Game wieder (im alten Framework war das ja so) abstract machen, dann könnte das irgendwie so aussehen

package content;

import configuration.Configuration;
import configuration.KeyboardConfig;
import ecs.entities.Hero;
import java.io.IOException;

public class MyGame extends Game {
    public static void main(String[] args) {
        // start the game
        try {
            Configuration.loadAndGetConfiguration("dungeon_config.json", KeyboardConfig.class);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        DesktopLauncher.run(new MyGame());
    }

    @Override
    public void myOnLevelLoad() {
        // z.B spawne Monster und Schatzkisten
    }

    @Override
    public void mySetup() {
        hero = new Hero();
    }

    @Override
    public void mySystems() {
        // new WuppiSystem();
    }
}

Wenn man richtig fancy ist, könnte mann vielleicht in das systems package gucken und für jedes System das dort liegt eine Instanz erzeugen. Quasi automatisiert. Dann brächte es mySystems gar nicht mehr.

cagix commented 1 year ago

@AMatutat Ich meinte eher das Beispiel direkt in "Our Main Class".

Die machen mehrere Aufrufe ala Game.info().setName("wuppie"); um die allgemeinen Metainformationen zu setzen. Dito sowas wie Game.init(args); oder Game.window().setIcon(Resources.images().get("icon.png")); oder Game.world().loadEnvironment("level1");. Das finde ich vom Frameworkgedanken her wesentlich lesbarer als eine Klasse im Framework überschreiben zu müssen.

AMatutat commented 1 year ago

Ich glaube interessanter ist dieses Szenario, denn da wird zumindest Logik implementiert:

// add default game logic for when a level was loaded 
    Game.world().onLoaded(e -> { 
      // spawn the player instance on the spawn point with the name "enter" 
      Spawnpoint enter = e.getSpawnpoint("enter"); 
      if (enter != null) { 
        enter.spawn(Player.instance()); 
      } 
    }); 

das wäre jetzt quasi unser, onLevelLoad Ich denke man könnte das auf alle Methoden anwenden, die man ansonsten @Override würde.

Vorteil:

Nachteil:

Der Metainfo Kram könnte man auch übernehmen/integrieren, sehe ich jetzt aber nicht als wirkliches Problem bei uns, da sich die Werte einfach austauschen lassen und die default Werte eigentlich recht gut eingestellt sind.

cagix commented 1 year ago

Genau. Man erbt nicht von einer Klasse und überschreibt dabei Methoden und muss das dann irgendwie wieder in das Framework zurücktun, sondern man hat einen festen Ansprechpartner im Framework (Game), wo man über Methodenaufrufe die ganzen Einstellungen macht und auch CallBacks hinterlegt oder Objekte (Systems, ...) übergibt/registriert.

Die Frage ist, wie weit man damit kommt. Jetzt haben wir mit render() ja komplette Narrenfreiheit. Klappt das auch noch mit Lambda-Ausdrücken?

Die Studis müssen lambdas verstehen

Ist nicht so schlimm aus meiner Sicht - das Thema ist eh in der ersten Woche mit dran und gehört auch in ein fortführendes Programmiermodul prima mit rein. Und ich muss nächstes Jahr eh im Praktikum "kleiner" anfangen, d.h. das würde aus der Perspektive passen.

AMatutat commented 1 year ago

Ich Bau mal einen PoC. Dann sehen wir am ehesten wo es knallt.

AMatutat commented 1 year ago

Hier mal ein vereinfachtes Beispiel für die Methode onLevelLoad

In Game

    private Runnable myOnLevelLoad;

    public void myOnLevelLoad(Runnable function) {
        myOnLevelLoad = function;
    }

    @Override
    public void onLevelLoad() {
        currentLevel = levelAPI.getCurrentLevel();
        entities.clear();
        getHero().ifPresent(this::placeOnLevelStart);
        myOnLevelLoad.run();
    }

in Main

    public static void main(String[] args) {

        Game game = new Game();
        game.myOnLevelLoad(() -> System.out.println("Test"));

        // start the game
        try {
            Configuration.loadAndGetConfiguration("dungeon_config.json", KeyboardConfig.class);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        Game.LibgdxSetup.run(game);
    }

@cagix hast du dir das so vorgestellt? Und ist Runnable hier wirklich der richtige Typ? Mir stößt das irgendwie auf. aber das Internet sagt mir das und Reflection will ich nun echt nicht machen.

cagix commented 1 year ago

Runnable ist was für die Erzeugung von Threads. Ich dachte, wir haben hier ne Game-Loop? Wieso kommt IJ auf den Trichter? Vielleicht wg. der run-Methode?

AMatutat commented 1 year ago

Runnable ist was für die Erzeugung von Threads. Ich dachte, wir haben hier ne Game-Loop?

Yep. Deswegen bin ich da auch drüber gestolpert. Im Grunde will ich eigentlich einen Consumer ohne Parameter.

cagix commented 1 year ago

Runnable ist was für die Erzeugung von Threads. Ich dachte, wir haben hier ne Game-Loop?

Yep. Deswegen bin ich da auch drüber gestolpert. Im Grunde will ich eigentlich einen Consumer ohne Parameter.

Naja, eigentlich willst Du eine Function<Void,Void> :). ...

Tatsächlich ist java.lang.Runnable ein funktionales Interface, d.h. das könntest Du hier verwenden. Allerdings wird es üblicherweise im Zusammenhang mit Multithreading eingesetzt, und das könnte die Leser verwirren.

Und warum auch immer ist in java.util.function nichts Äquivalentes definiert. But that's Java for you :/

Ich habe sogar eine Diskussion gefunden: https://stackoverflow.com/questions/14319787/how-to-specify-function-types-for-void-not-void-methods-in-java8#comment66743981_14338333 ...

cagix commented 1 year ago

Aber im Prinzip wäre das so ungefähr der Mechanismus: Game wird nicht mehr abgeleitet (könnte sogar final sein) und man ruft auf einem Objekt vom Typ Game ein paar Builder-Methoden auf, u.a. auch vielleicht myOnLevelLoad(). Und dann wird das Ding gestartet.

Edit: Ob das in main() dann aber wirklich Game.LibgdxSetup.run(game); sein muss und nicht einfach Game.run() sein könnte, sollten wir nochmal besprechen. Die run()-Methode könnte der letzte Aufruf im Sinne des Builder-Patterns sein.

AMatutat commented 1 year ago

Siehe auch: https://github.com/Programmiermethoden/Dungeon/pull/710#discussion_r1216973262