Wir programmieren ein DSA-Computerspiel (Teil 2)

Der arme Geron! Irgendwo in einem einsamen Unity-Projekt liegt ein einsames GameObject mit Lebensenergie herum, das nichts weiter kann als Eigenschaftsproben auszuführen. Bringen wir etwas Bewegung in sein Leben! Im zweiten Teil des Programmier-Tutorials versehen wir Geron mit Talenten und lassen ihn per Tastatur oder Gamepad durch die Gegend laufen.

Der Rückblick

Wir erinnern uns: Im ersten Teil unseres kleinen charmanten Programmierkurses haben wir mit staunendem Blick die Spiele-Entwicklungsumgebung Unity kennengelernt, haben mit offenen Mündern beobachtet, wie ein totes Objekt mit Lebensenergie und Eigenschaften versehen werden kann, haben begeistert zur Kenntnis genommen, wie man ihm ein Bild zuweist, damit auf dem Bildschirm auch was zu sehen ist, und haben schließlich bewundernd miterlebt, wie eine echte DSA-Eigenschaftsprobe mit Patzern und kritischen Treffern in C# umgesetzt werden kann. Lange genug musste der arme Geron sich auf diesen bisher erreichten Erfolgen ausruhen, doch nun wird seine Ausbildung endlich fortgesetzt!


Schritt 007: Talente

Was wäre ein echter DSA-Held ohne Gassenwissen, Orientierung, Schlösser knacken oder Tierkunde? Richtig, ein handlungsunfähiges Würstchen, nicht wert auf spannende Abenteuer ausgesandt zu werden. Also müssen Talente her, auf die wir später Proben ablegen können. Auch hier gehen wir ähnlich vor wie bei den Eigenschaften: Nachdem wir Unity gestartet und unser Projekt vom letzten Mal geladen haben, wählen wir das GameObject namens Spieler in der Sicht Hierarchy aus, und klicken im Inspector auf Add Component. Wir wählen New Script und geben dem Skript den Namen Talente. Damit alle Skripte beisammen sind, schieben wir es in den beim letzten Mal angelegten Ordner Skripte, und doppelklicken auf das neue Skript, um es zu öffnen. Einige Kampfrunden später sollte sich die Community Edition von Microsoft Visual Studio öffnen, wo wir unser Skript bearbeiten können.

Jetzt kommt etwas Fleißarbeit auf uns zu: Da die Talente bei DSA5 vorgegeben sind und sich ihre Anzahl nicht ändert, können wir sie wie bei den Eigenschaft in Schritt 004 als öffentliche Attribute unserer Klasse Talente definieren:
using UnityEngine;

public class Talente : MonoBehaviour
{
    [Header("Körper")]
    public int Fliegen;
    public int Gaukeleien;
    public int Klettern;
    public int Körperbeherrschung;
    public int Kraftakt;
    public int Reiten;
    public int Schwimmen;
    public int Selbstbeherrschung;
    public int Singen;
    public int Sinnesschärfe;
    public int Tanzen;
    public int Taschendiebstahl;
    public int Verbergen;
    public int Zechen;
    [Header("Gesellschaft")]
    public int BekehrenUndÜberzeugen;
    public int Betören;
    public int Einschüchtern;
    public int Etikette;
    public int Gassenwissen;
    public int Menschenkenntnis;
    public int Überreden;
    public int Verkleiden;
    public int Willenskraft;
    [Header("Natur")]
    public int Fährtensuchen;
    public int Fesseln;
    public int FischenUndAngeln;
    public int Orientierung;
    public int Pflanzenkunde;
    public int Tierkunde;
    public int Wildnisleben;
    [Header("Wissen")]
    public int BrettUndGlücksspiel;
    public int Geographie;
    public int Geschichtswissen;
    public int GötterUndKulte;
    public int Kriegskunst;
    public int Magiekunde;
    public int Mechanik;
    public int Rechnen;
    public int Rechtskunde;
    public int SagenUndLegenden;
    public int Sphärenkunde;
    public int Sternkunde;
    [Header("Handwerk")]
    public int Alchimie;
    public int BooteUndSchiffe;
    public int Fahrzeuge;
    public int Handel;
    public int HeilkundeGift;
    public int HeilkundeKrankheiten;
    public int HeilkundeSeele;
    public int HeilkundeWunden;
    public int Holzbearbeitung;
    public int Lebensmittelbearbeitung;
    public int Lederbearbeitung;
    public int MalenUndZeichnen;
    public int Metallbearbeitung;
    public int Musizieren;
    public int Schlösserknacken;
    public int Steinbearbeitung;
    public int Stoffbearbeitung;
}
Die hinzugefügten Header-Annotationen dienen dazu, um die zahlreichen Talente nach Gebieten zu gruppieren, sodass sich nach dem Speichern und der Rückkehr in Unity folgendes schönes Bild im Inspector ergibt:



Wer möchte, kann Geron nun auch schon passende Werte verpassen, indem er sie aus dem DSA5-Schnellstarter-Heft abschreibt. Wunderbar! Rein optisch hat unser Held nun schon alles, was er braucht, um sich durch den Abenteuer-Alltag zu würfeln. Aber leider müssen wir dem Programm noch mitteilen, wie eine Talentprobe ablaufen soll.

Schritt 008: Talenteigenschaften

Zunächst gehe ich wieder den etwas umständlichen selben Weg, den ich schon bei den Eigenschaften in Schritt 006 gegangen bin: Ich erstelle eine Aufzählung, die ebenfalls alle 58 Talente beinhaltet. Dies ermöglicht uns später, einfach Folgendes zu schreiben, um eine Talentprobe durchzuführen:

TalentProbe(Talent.Sinnesschärfe, -3)

Damit wir dies schreiben können, müssen wir mal wieder viel tippen. Vor der schließenden Klammer in unserer Klasse Talente fügen wir eine neue Aufzählung (d.h. ein enum) namens Talent ein:

    public enum Talent
    {
        Fliegen,
        Gaukeleien,
        Klettern,
        Körperbeherrschung,
        Kraftakt,
        Reiten,
        Schwimmen,
        Selbstbeherrschung,
        Singen,
        Sinnesschärfe,
        Tanzen,
        Taschendiebstahl,
        Verbergen,
        Zechen,
        BekehrenUndÜberzeugen,
        Betören,
        Einschüchtern,
        Etikette,
        Gassenwissen,
        Menschenkenntnis,
        Überreden,
        Verkleiden,
        Willenskraft,
        Fährtensuchen,
        Fesseln,
        FischenUndAngeln,
        Orientierung,
        Pflanzenkunde,
        Tierkunde,
        Wildnisleben,
        BrettUndGlücksspiel,
        Geographie,
        Geschichtswissen,
        GötterUndKulte,
        Kriegskunst,
        Magiekunde,
        Mechanik,
        Rechnen,
        Rechtskunde,
        SagenUndLegenden,
        Sphärenkunde,
        Sternkunde,
        Alchimie,
        BooteUndSchiffe,
        Fahrzeuge,
        Handel,
        HeilkundeGift,
        HeilkundeKrankheiten,
        HeilkundeSeele,
        HeilkundeWunden,
        Holzbearbeitung,
        Lebensmittelbearbeitung,
        Lederbearbeitung,
        MalenUndZeichnen,
        Metallbearbeitung,
        Musizieren,
        Schlösserknacken,
        Steinbearbeitung,
        Stoffbearbeitung
    }

Um eine Verbindung zwischen den Attributen unserer Klasse Talente und der Aufzählungsoption unseres enums namens Talent herzustellen, habe ich noch zwei Methoden zum Setzen und Lesen der Talentwerte hinzugefügt:

    public int talentWert(Talent talent)
    {
        switch (talent)
        {
            case Talent.Alchimie: return Alchimie;
            case Talent.BekehrenUndÜberzeugen: return BekehrenUndÜberzeugen;
            case Talent.Betören: return Betören;
            case Talent.BooteUndSchiffe: return BooteUndSchiffe;
            case Talent.BrettUndGlücksspiel: return BrettUndGlücksspiel;
            // ... usw ...
            case Talent.Wildnisleben: return Wildnisleben;
            case Talent.Willenskraft: return Willenskraft;
            case Talent.Zechen: return Zechen;
            case Talent.Überreden: return Überreden;
            default: return -1000;
        }
    }

    public void setze(Talent talent, int wert)
    {
        switch (talent)
        {
            case Talent.Alchimie: Alchimie=wert; break;
            case Talent.BekehrenUndÜberzeugen: BekehrenUndÜberzeugen=wert; break;
            case Talent.Betören: Betören=wert; break;
            case Talent.BooteUndSchiffe: BooteUndSchiffe=wert; break;
            case Talent.BrettUndGlücksspiel: BrettUndGlücksspiel=wert; break;
            // ... usw ...
            case Talent.Wildnisleben: Wildnisleben=wert; break;
            case Talent.Willenskraft: Willenskraft=wert; break;
            case Talent.Zechen: Zechen=wert; break;
            case Talent.Überreden: Überreden=wert; break;
            default: break;
        }
    }

Und leider ist dies immer noch nicht alles: Um eine Talentprobe durchführen zu können, müssen wir auch noch wissen, auf welche drei Eigenschaften gewürfelt werden muss. Als müssen wir noch eine Methode ergänzen, die mir alle drei Eigenschaften als dreidimensionales Array zurückgibt:

    public Eigenschaften.Eigenschaft[] talentEigenschaften(Talent talent)
    {
        switch (talent)
        {
            case Talent.Fliegen: return new Eigenschaften.Eigenschaft[] {
                Eigenschaften.Eigenschaft.MU,
                Eigenschaften.Eigenschaft.IN,
                Eigenschaften.Eigenschaft.GE 
            };
            case Talent.Gaukeleien: return new Eigenschaften.Eigenschaft[] {
                Eigenschaften.Eigenschaft.MU,
                Eigenschaften.Eigenschaft.CH,
                Eigenschaften.Eigenschaft.FF
            };
            case Talent.Klettern: return new Eigenschaften.Eigenschaft[] { 
                Eigenschaften.Eigenschaft.MU, 
                Eigenschaften.Eigenschaft.GE, 
                Eigenschaften.Eigenschaft.KK
            };
            // ... usw. ...
            case Talent.Schlösserknacken: return new Eigenschaften.Eigenschaft[] { 
                Eigenschaften.Eigenschaft.IN, 
                Eigenschaften.Eigenschaft.FF, 
                Eigenschaften.Eigenschaft.FF 
            };
            case Talent.Steinbearbeitung: return new Eigenschaften.Eigenschaft[] { 
                Eigenschaften.Eigenschaft.FF, 
                Eigenschaften.Eigenschaft.FF, 
                Eigenschaften.Eigenschaft.KK
            };
            case Talent.Stoffbearbeitung: return new Eigenschaften.Eigenschaft[] { 
                Eigenschaften.Eigenschaft.KL, 
                Eigenschaften.Eigenschaft.FF, 
                Eigenschaften.Eigenschaft.FF 
            };
            default: return null;
        }
    }

Puh! Wer das alles abgetippt und die fehlenden Zeilen für die  anderen Talente ergänzt hat, dürfte wie ich ein paar wunde Fingerkuppen haben. Da ist sicherlich auch nicht sonderlich hilfreich, wenn ich verrate, dass man das Ganze sicherlich auch noch viel einfacher über eine separate JSON-Datei oder ähnliches hätte erzeugen können. Für ein Einsteiger-Tutorial halte ich diese Variante - so arbeitsintensiv sie auch sein mag - für die einfachste und eingängigste. Ich lasse mich aber auch gerne in den Kommentaren eines Besseren belehren!

Schritt 009: Ergebnis einer Fertigkeitsprobe

Nun haben wir endlich alle Werte zusammen, die wir zum Durchführen einer Fertigkeitsprobe brauchen. Fertigkeitsproben sind quasi die Oberklassen zu allen Talentproben, Zauberproben und Liturgieproben, und alle verhalten sich ähnlich: Man benötigt drei Eigenschaften sowie ggf. einen Modifikator, würfelt mit drei W20 und erhält eines von sieben möglichen Ergebnissen:
  • sollte der effektive Eigenschaftswert einer der drei Einzelproben durch den Modifikator auf 0 oder darunter sinken, ist die Durchführung der Probe nicht möglich.
  • Wurde dreimal eine 1 gewürfelt, gilt die Probe automatisch als Spektakulärer Erfolg.
  • Wurde dreimal eine 20 gewürfelt, gilt die Probe automatisch als Spektakulärer Patzer.
  • Wurde zweimal eine 1 gewürfelt, gilt die Probe automatisch als Kritischer Erfolg.
  • Wurde zweimal eine 20 gewürfelt, gilt die Probe automatisch als Patzer.
  • Ist eine der drei Proben misslungen, ohne durch den Fertigkeitswert ausgeglichen werden zu können, gilt die Probe als Misserfolg.
  • Ansonsten gilt die Probe als Erfolg.
Im einfachen Erfolgsfall interessiert zusätzlich noch die Qualitätsstufe, die sich aus der Anzahl übrig gehaltener Fertigkeitspunkte durch drei (aufgerundet) ergibt. Mindestwert der Qualitätsstufe bei gelungener Probe ist 1.

Um diese verschiedenen Ergebnisse abbilden und später einfach abfragen zu können, definieren wir eine Aufzählung mit den sieben Ergebnismöglichkeiten und eine Klasse, mit der zusätzlich die erreichte Qualitätsstufe abgefragt werden kann.

    public enum ErgebnisWert
    {
        NichtMoeglich, SpektakulaererPatzer, Patzer, Misserfolg,
        Erfolg, KritischerErfolg, SpektakulaererErfolg
    }

    public class Ergebnis
    {
        private ErgebnisWert wert;
        private int qualität;
        public Ergebnis(ErgebnisWert wert)
        {
            this.wert = wert;
            this.qualität = 0;
        }

        public Ergebnis(ErgebnisWert wert, int qualität)
        {
            this.wert = wert;
            this.qualität = qualität;
        }
        public bool istMöglich()
        {
            return wert != ErgebnisWert.NichtMoeglich;
        }
        public bool istErfolg()
        {
            return wert==ErgebnisWert.Erfolg 
                || wert == ErgebnisWert.KritischerErfolg
                || wert == ErgebnisWert.SpektakulaererErfolg;
        }
        public int qualitätsstufe()
        {
            return qualität;
        }
        public bool istPatzer()
        {
            return wert == ErgebnisWert.Patzer
                || wert == ErgebnisWert.SpektakulaererPatzer;
        }
        public bool istKritischerErfolg()
        {
            return wert == ErgebnisWert.KritischerErfolg
                || wert == ErgebnisWert.SpektakulaererErfolg;
        }
        public ErgebnisWert holeErgebnis()
        {
            return wert;
        }
    }

Durch diese Hilfsklasse ist es nun möglich, mit einfachen Abfragen zu prüfen, ob eine Probe gelungen oder misslungen ist, und wie viele Qualitätsstufen ggf. übrig geblieben sind. Nun müssen wir "nur noch" die eigentliche Probe durchführen!

Schritt 010: Fertigkeitsprobe

Wie bei Schritt 009 beschrieben besteht eine Fertigkeitsprobe im Prinzip aus drei einzelnen Eigenschaftsproben, die alle um den Modifikator erschwert sind, aber um einen Pool von Fertigkeitspunkten ausgeglichen werden können. Es liegt also nahe, eine for-Schleife über alle drei Eigenschaften abzulegen und jeweils zu schauen, ob eine davon schiefgegangen ist, oder ob ein Patzer oder kritischer Erfolg eingetreten ist. Hier der Quelltext der Methode für die Fertigkeitsprobe:

    public Ergebnis TalentProbe(Talent talent, int modifikator)
    {
        return FertigkeitsProbe(talentEigenschaften(talent), modifikator);
    }
 
    public Ergebnis FertigkeitsProbe(Eigenschaft[] eigenschaftsListe, int modifikator)
        Würfel würfel = new Würfel();
        Eigenschaften eigenschaften = GetComponent<Eigenschaften>();

        int rest = talentWert(talent);
        int anzahl1 = 0;
        int anzahl20 = 0;
        foreach (Eigenschaften.Eigenschaft eigenschaft in eigenschaftsListe)
        {
            int effektiverEigenschaftswert = eigenschaften.Wert(eigenschaft) + modifikator;
            if (effektiverEigenschaftswert < 1)
                return new Ergebnis(ErgebnisWert.NichtMoeglich);

            int wurf = würfel.W20();
            if (wurf == 1)
                anzahl1++;
            if (wurf == 20)
                anzahl20++;

            if (wurf > effektiverEigenschaftswert)
                rest -= wurf - effektiverEigenschaftswert;
        }

        if (anzahl1 >= 3)
            return new Ergebnis(ErgebnisWert.SpektakulaererErfolg);
        if (anzahl20 >= 3)
            return new Ergebnis(ErgebnisWert.SpektakulaererPatzer);
        if (anzahl1 >= 2)
            return new Ergebnis(ErgebnisWert.KritischerErfolg);
        if (anzahl20 >= 2)
            return new Ergebnis(ErgebnisWert.Patzer);

        if (rest < 0)
            return new Ergebnis(ErgebnisWert.Misserfolg);

        return new Ergebnis(ErgebnisWert.Erfolg, Mathf.Max(1,Mathf.Min(6,(rest+2) / 3)));
    }

Eigentlich ist es ganz einfach: In der for-Schleife ziehen wir alle zwischen Wurf und effektivem Eigenschaftswert fehlenden Punkte vom Fertigkeitswert, und zählen gleichzeitig die Anzahl der gewürfelten Einsen und Zwanziger. Wurde mehr als eine 1 oder 20 gewürfelt, wird ein entsprechender Erfolg oder Patzer zurückgegeben. Ansonsten wird überprüft, ob die verbliebenen Fertigkeitswerte kleiner als 0 sind und die Probe somit misslungen ist. Ansonsten liegt ein Erfolg vor, für den wir auch noch die Qualitätsstufe ermitteln müssen, indem wir die Fertigkeitspunkte durch 3 teilen und dabei aufrunden (d.h. 2 addieren und abrunden). Die Qualitätsstufe kann nie kleiner als 1 oder größer als 6 werden und wird daher über die Min- und Max-Funktion abgeschnitten. Ganz einfach, oder?

Wer das Ergebnis testen möchte, kann dies gerne in der Methode Start tun:

public void Start()
{
    Ergebnis ergebnis = Talentprobe(Talent.Schlösserknacken, -3);
    if( ergebnis.istGelungen() )
        Debug.log("Das Schloss springt auf!");
    else if( ergebnis.istPatzer())
        Debug.log("Autsch!");
    else
        Debug.log("Das Schloss erweist sich als störrisch!");
}

Bei jedem Programmstart sollte nun eine Talentprobe auf Schlösser knacken ausgeführt und das Ergebnis in der Console angezeigt werden. Alternativ kann man statt der Methode Start auch gerne Update nehmen - dann werden allerdings sehr viele Proben pro Sekunde durchgeführt.

Schritt 011: Umherlaufen

Puh, das war alles ganz schön viel Tipparbeit, nur um eine einfache Talentprobe durchführen zu können. Man merkt, dass die DSA-Regeln nicht wirklich für Computerspiele gemacht sind. Aber weil wir so fleißig durchgehalten haben und viel Arbeit für vergleichsweise wenig befriedigende Resultate investiert haben, wollen wir zum Abschluss der heutigen Folge noch eine vergleichsweise einfache Erweiterung einbauen, die dafür aber auch erfreulich anschauliche Resultate hervorzaubert: Wir wollen unseren Helden per Gamepad oder Tastatur steuerbar machen!

Glücklicherweise ist Unity auf diesen Anwendungsfall gut vorbereitet, und bietet uns komfortable Funktionen zum Abfragen der Steuerung und zum Bewegen eines Körpers. Wie in Schritt 007 beginnen wir damit, ein neues Skript zu unserem Spieler hinzuzufügen, das diesmal den Namen Steuerung trägt, und wieder in das Verzeichnis Skripte geschoben wird. In Visual Studio geben wir den folgenden Code ein:

using UnityEngine;

public class Steuerung : MonoBehaviour
{
    private Rigidbody2D rigidBody;

    void Start()
    {
        this.rigidBody = GetComponent<Rigidbody2D>();
    }

    void Update()
    {
        gehe(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
    }

    private void gehe(float x, float y)
    {
        float faktor = 8 * Time.deltaTime;
        rigidBody.MovePosition(transform.position + new Vector3(x*faktor,y*faktor));
    }

}

Was macht diese Klasse nun genau? Beginnen wir mit der Methode Update, die zu jedem einzelnen Zeitschritt aufgerufen wird. Wir wollen, dass der Benutzer sich bewegt, und zwar in Abhängigkeit davon, wie stark das Gamepad in eine Richtung bewegt wird. Die Methode Input.GetAxis liefert den aktuellen Zustand des Gamepad für die Richtungen Horizontal und Vertical jeweils als Wert zwischen -1 und +1. Statt Gamepad kann auch die Tastatur verwendet werden: Wie bei 3D-Shootern üblich entspricht die Standard-Tastaturbelegung für die horizontale Bewegung den Tasten A und D, während die vertikale Richtung durch die Tasten W und S abgebildet wird. Bei Tastatursteuerung liefert die Methode Input.GetAxis nur jeweils einen der drei möglichen Werte -1, 0 und 1.

In der Methode gehe müssen wir nun zunächst einmal dafür sorgen, dass sich unser Benutzer nicht zu schnell bewegt, und dass er sich überall gleich schnell bewegt, egal ob der Computer eine lahme Möhre mit 5 Bildern pro Sekunde oder ein Hochleistungs-Spiele-PC mit 200 Bildern pro Sekunde ist. Dazu wird ein faktor bestimmt, der sich aus der Geschwindkeit des Spielers (hier fest 8 Schritt pro Sekunde - das muss natürlich später noch an den tatsächlichen GS-Wert angepasst werden) und der Dauer zwischen dem aktuellen und dem vorherigen Bild ergibt. Damit können wir unsere aktuelle Position nun um die Bewegung in x und y Richtung anpassen.

Moment? Was ist denn eigentlich dieser RigidBody2D? Nun, unser Spieler soll als physikalisches Objekt durch den Dungeon laufen, der nicht einfach so durch Wände durchlaufen kann und von Hindernissen abprallt. Diese einfache Physik-Simulation übernimmt das Skript RigidBody2D, das wir einfach über die Schaltfläche Add Component zu unserem Spieler hinzufügen. Damit sich der Spieler nicht wie verrückt im Kreis dreht, sollte man unter Constraints noch die Option Z Rotation anhaken. Und damit er nicht dauernd nach unten verschwindet, sollte die Schwerkraft-Skalierung (Gravity scale) auf 0 gestellt werden.



Und das war's auch schon! Nun sollte sich der Spieler per Tastatur oder Gamepad wild umher steuern lassen, wenn das Spiel über die Play-Taste gestartet wird. Maussteuerung oder Gestensteuerung funktioniert allerdings noch nicht - das ist Thema für eine spätere Folge.

Das Fazit

Das war es auch schon wieder! Wir haben nun einen kleinen pixeligen Helden, der sich wild durch die Gegend steuern lässt. Theoretisch könnte er auch schon Talentproben oder Eigenschaftsproben durchführen, um mit seiner Umgebung zu interagieren. Leider gibt es aber noch nichts zum Interagieren, und so könnt Ihr Euch schon jetzt auf die nächste Folge freuen, in der wir einen kleinen Dungeon für den Helden bauen und vielleicht schon mit ersten Fallen spicken.

Wenn Euch das Tutorial gefallen hat, oder Ihr Probleme oder Fragen beim Nachprogrammieren habt, schreibt doch bitte in den Kommentarbereich. Wie immer bin ich auf jede Rückmeldung gespannt, und freue mich schon jetzt auf den nächsten Teil dieser unsinnigen, aber für Informatik-Nerds wie mich spaßigen Artikelreihe!

Kommentare

  1. Unbedingt weitermachen! :-)

    PS: ich bin mir ziemlich sicher das man die ganzen Switch/case eleganter lösen kann. Allerdings sollte ich einfach still sein bis ich selbst Zeit hätte zu programmieren.... :-)

    AntwortenLöschen
    Antworten
    1. Ich bin mir nicht nur sicher, dass man die switch-case-Anweisungen hätte vermeiden können - ich weiß es! So wäre die Methode talentWert(Talent talent) über Reflektion problemlos in einer leicht lesbaren Zeile lösbar (irgendwas wie return (int)GetType().GetProperty(talent.ToString()).GetValue(this) oder so). Aber wie soll ich sowas in einem Einsteiger-Tutorial erklären?

      Außerdem wäre es vermutlich viel sinnvoller, die Talente in einer JSON-Datei oder so zu definieren, um flexibler zu sein, müsste dann aber einen eigenen Editor für den Inspector bauen, was auch nicht gerade intuitiv ist, und vielleicht nur zur Laufzeit funktionieren würde.

      Da ich auch bekennender Noob in C# bin, gibt es vermutlich 1000 elegantere Lösungen, um die Codewüsten hier zu vermeiden. Ich hoffe, dass niemand meine Ergüsse hier als bestmögliche Implementierung betrachtet, und es lieber als Anregung zum Selberbasteln sieht.

      Daher wäre ich auch sehr glücklich, Eure Lösungen zu sehen! Und wer weiß, vielleicht kann ich ja schon bald ein tolles DSA-Roguelike von irgendjemandem da draußen spielen, der besser programmieren kann als ich, ein besserer Künstler und Designer ist, der mehr Freizeit hat als ich, und der im Idealfall von dieser Artikelreihe zu seinem Werk inspiriert worden ist...

      Vielen Dank in jedem Fall für das Feedback, schön dass jemand mitliest!

      Löschen
    2. falls ich es tatsächlich schaffen sollte sowas ähnliches wie ein Spiel fertig zu bekommen stelle ich es gern zur Verfügung. Zuviel erwarten würde ich da allerdings nicht, bin wahrlich kein großer Designer oder Künstler. Meine Stärken liegen eher auf der Optimierung von vorhandener Architektur, Workflow-prozessen und vor allem natürlich Code(Performance, Wartbarkeit, etc). Je nachdem was gebraucht wird. Aber ein toller Designer wird aus mir nicht mehr! :-)

      Löschen

Kommentar veröffentlichen