Wir programmieren ein DSA-Computerspiel (Teil 1)

DSA-Computerspiele sind rar gesäht, und seit einigen Jahren herrscht ziemliche Flaute in diesem Bereich. Der Schelm träumt schon lange davon, sein eigenes Mini-DSA-Spielchen zu schreiben. Ohne sich große Hoffnungen zu machen, diesmal über die Prototypen-Phase hinwegzukommen, beginnt er todesmutig eine neue Artikelserie, in der er die Entwicklung eines DSA-Computerspiels beschreibt. Weit wird er dabei nicht kommen - aber schon der Weg ins Debakel könnte unterhaltsam werden!

Das Ziel

Die Vier Helden und der Schelm haben ja schon einige Themenfelder in ihrem merkwürdigen Blog abgegrast: Rezensionen, Con-Berichte, Umfragen, Interviews, Basteltipps - und jetzt auch noch Programmier-Tutorials? Zu Recht dürfte sich da der eine oder andere Leser fragen, was zum Namenlosen denn nun in den Schelm gefahren sein mag, dass er sich nun für einen Programmier-Gott hält, seinen Blog in einen Programmierkurs auf VHS-Niveau verwandelt und vom nächsten Multi-Millionen-Dollar-DSA-Game träumt?

Glücklicherweise können wir diese Ängste direkt lindern: Nein, keine Sorge, die Vier Helden und der Schelm werden auch in Zukunft bei ihren Leisten bleiben, und sich vornehmlich mit Rezensionen, Con-Berichten, Umfragen, Interviews und Basteltipps begnügen. Aber der eine oder andere Exkurs in andere Gefilde wird ja wohl noch erlaubt sein, solange zumindest ein kleiner DSA-Bezug besteht.

Nein, es geht in dieser neuen (hoffentlich mehrere Teile umfassenden) Artikelreihe nicht um die Erschaffung eines neuen DSA-Computerspiel-Klassikers wie Drakensang oder der Nordland-Trilogie. Dafür bräuchte man Dutzende Entwickler, Level-Designer, Grafiker, Tester und Projektverantwortliche, die mindestens einige Jahre Vollzeit an einem den heutigen technischen Minimalanforderungen genügenden Computerspiel arbeiten müssten. Dass das ein einzelner Hobby-Entwickler (oder Blogger!) in seiner wenigen Freizeit nicht stemmen kann, sollte klar sein.

Ein Blick auf den bisherigen Prototypen


Das Ziel dieser Artikelreihe ist es vielmehr, sich mit der Umsetzung der grundlegenden DSA5-Regeln in ein computergestütztes System zu beschäftigen, und hoffentlich auf dieser Basis einen einfachen Dungeon Crawler mit zufallsgenerierten Missionen und Leveln in ASCII-Grafik (ein sogenanntes Roguelike) zu entwickeln. Denn so unglaublich es auch scheinen mag: Bisher hat sich meines Wissens nach niemand erfolgreich an die Entwicklung eines DSA-Roguelikes gewagt. Eine Lücke, die ich schon lange schließen möchte.

Die Historie

Seit über 30 Jahren spiele ich DSA. Und fast genauso alt wie meine Liebe zum Pen-and-Paper-Rollenspiel ist mein Traum, irgendwann einmal ein eigenes Computerspiel fertigzustellen. Es begann auf dem guten alten Commodore 64, den ich zu meinem zehnten Geburtstag im Sommer 1986 bekam. Mit Hilfe des beiliegenden Handbuchs brachte ich mir die Grundkenntnisse der Basic-Programmierung bei, und begann im Laufe der Jahre immer wieder einzelne Spiele-Prototypen zu entwickeln:
  • So gab es die Idee zu einem Klon der genialen Mittelalter-Wirtschafts-Simulation Kaiser, die es aber nie über ein schlecht gemaltes Titelbild und einen Erntebildschirm mit Zufallszahlen hinaus brachte.
  • Lange träumte ich von einem Grafik-Adventure, bei dem man von der sinkenden Titanic entkommen musste, wofür ich das Buch Mythos Titanic von Wolf Schneider quasi auswendig lernte, um detaillierte Tabellen der Aufenthaltsorte aller wichtigen Personen zu erstellen, mit denen man interagieren konnte. Natürlich hielt ich mich erneut viel zu sehr mit Grafik auf, was dazu führte, dass es in der rechten oberen Bildschirmecke eine große Uhr mit sich in beschleunigter Echtzeit drehenden Zeigern als Sprites gab, sowie daneben Platz für schöne Bilder der Örtlichkeiten. Am perspektivischen Zeichnen eines mit Wasser vollaufenden Kabinengangs in Koala Painter blieb ich dann aber hängen.
  • Es folgte ein namenloses Mini-Textadventure, das auf den beiden genialen Adventure-Programmierkursen von Michael Nickles im ehrwürdigen 64er-Magazin basierte, und bei dem man nachts in ein Museum einbrechen und ein wertvolles Kunstwerk stehlen musste, ohne dabei dem Nachtwächter in die Hände zu fallen. Immerhin war das Spiel komplett spielbar - aber in knapp drei Minuten lösbar.
  • Das wohl erfolgreichste, aber auch banalste Programm, dass ich zu C64-Zeiten schrieb, generierte mit Hilfe der Würfeltabellen aus der Box Die Kreaturen des Schwarzen Auges neue Dörfer und berechnete das Wetter. Ich werde nie vergessen, dass wir Das große Donnersturmrennen seinerzeit mit zwei Meistern leiteten, und mein Beitrag zur Spielleitung fast ausschließlich darin bestand, an jedem neuen Renntag die Wetterlage aus einem dicken bedruckten Stapel Endlospapier vorzulesen.
1990 wich der Commodore 64 dann seiner großen Schwester, dem Commodore Amiga 500, den ich in Amiga Basic zu programmieren versuchte. Ich erinnere mich leider nur noch an einen Tetris-Klon, einen Minesweeper-Verschnitt und ein Spiel, bei dem man jeweils drei Kreisringsegmente zu vollständigen Kreisen zusammensetzen musste. Leider wieder kein die Welt umwälzendes DSA-Spiel dabei!

Eine wahre Offenbarung war für mich der erste Kontakt mit Visual Basic auf meinem ersten PC, einem 486DX2/50 (vermutlich so gegen 1993). Endlich konnte man direkt in Windows grafische Anwendungen per Maus gestalten und programmieren, was dazu führte, dass sehr schnell erste Prototypen eines DSA-Heldengenerators begonnen wurden. Natürlich war schon damals die Komplexität des DSA-Regelwerks viel zu hoch, um "mal eben" von einem nur bedingt langzeitmotivierten Schüler in ein VB-Programm gezwängt zu werden, und so kam mein DSA-Heldengenerator nie über ein schickes, per Handscanner schwarz-weiß eingescanntes Bild der DSA3-Basisbox sowie ein Eingabeformular für Namen, Rasse und Beruf des Helden hinaus. Immerhin: Beim Anzeigen des Titelbildes wurde die Titelmusik von Schicksalsklinge  abgespielt.

Ach ja: Zu dieser Zeit entstand auch eine vektorisierte Version der Aventurien-Landkarte, die ich durch Übertragen der Kontinents-Konturen mit Nadelstichen auf Millimeter-Paper und anschließendes Eingeben der erhaltenen Koordinaten erstellte. Wohlgemerkt: Nur die Außenkanten! Sämtliche Flüsse, Städte, Gebirge, Wälder, etc. fehlten, wodurch der praktische Nutzen dieser Vektor-Karte gegen 0 ging. Verdammt viel Arbeit für eine verdammt unsinnige Zahlenkolonne. Aber man sieht: Eine gewisse Besessenheit (und Praxisferne!) nannte ich schon damals mein Eigen.

Ebenfalls in Visual Basic entstand zu ähnlicher Zeit ein Prototyp eines Mech-Editors für BattleTech, das wir damals begeistert spielten. Immerhin konnte man hier schon seine Rüstungspunkte in den einzelnen Trefferzonen verteilen und erhielt am Ende einen DIN A4-Ausdruck, wo tatsächlich ein fertig ausgefüllter Mech-Bogen zu sehen war. Leider scheiterte ich damals auf den letzten Metern an Waffen, die mehr als einen Waffenslot einnahmen, und so ist es seit über 25 Jahren ein bei meinen Kollegen höchst beliebter Running Gag, mit schöner Regelmäßigkeit zu fragen, wann ich denn "das BattleTech-Programm fertigmache".

Und damit endet auch endlich die deprimierende Geschichte meiner bisherigen vergeblichen Programmierversuche im Rollenspielbereich. Mittlerweile bin ich über 40, habe meinen Doktor als Ingenieurinformatiker gemacht, und arbeite seit acht Jahren in einer mittelständischen Software-Firma in Bochum, wo ich mich den lieben langen Tag mit Java-Programmierung herumschlage. Man sollte also vermuten, dass ich nun endlich genug Erfahrung besitzen müsste, um endlich mal ein eigenes DSA-Spiel anzugehen. Ob das wirklich so ist, werden die folgenden Artikel dieser Reihe zeigen. Aber genug der Vorrede! Beginnen wir also endlich mit...

Das Rechtliche

Auch wenn es hoffentlich jedem klar sein sollte, muss ich hier noch einmal explizit Folgendes betonen:

Die vorliegende Artikel beschreibt die Entwicklung eines DSA-Computerspiel-Prototypen für rein private Zwecke! Ich verfüge über keinerlei Recht bzw. Lizenz zur Entwicklung eines solchen Spiels, und habe nicht die Absicht, irgendwelche spielbaren Dateien ohne Erlaubnis des Rechtinhabers der Öffentlichkeit zugänglich zu machen. Auch die von mir verwendeten Grafiken aus offiziellen DSA5-Publikationen werden nur für meinen privaten Prototypen zweckentfremdet und nur in Form niedrig auflösender Bildschirmfotos bzw. Tutorial-Videos öffentlich gezeigt. Ich hoffe, damit keine Rechte der Künstler und/oder der Rechteinhaber zu verletzen. Falls doch rechtliche Bedenken bestehen, bitte ich um eine Benachrichtigung an frerich.kai@gmail.com. Bitte verklagt mich nicht! Danke!

Die Entwicklungsumgebung

Ein neues Spiel entwickelt man heutzutage nur in den seltensten Fällen komplett aus dem Nichts, meist verwendet man dafür eine der mittlerweile sehr leistungsstarken Game Engines wie Unity oder die Unreal Engine. Was man damit alles erstellen kann, ist wirklich eindrucksvoll. Und das Schönste daran ist, dass man die Engines für eigene Hobbyprojekte auch kostenfrei nutzen kann. Erst wenn es an das Thema Kommerzialisierung geht (etwas, über das ich mir mit diesem Projekt vermutlich niemals Gedanken machen muss), werden ab einem bestimmten Jahresumsatz Lizenzgebühren fällig. Mit Unity entstanden übrigens auch die beiden, mit eher gemischten Reaktionen empfangenen, Neuauflagen von Schicksalsklinge und Sternenschweif.

Eine Warnung vorweg: Wer mit Unity arbeitet kommt (wenn er nicht gerade Mitglied eines großen Teams ist, in dem sich andere Leute um die Programmierung kümmern) nicht darum herum, sich mit dem Schreiben von Programmcode in C# zu beschäftigen. In älteren Versionen von Unity war auch noch JavaScript eine Option, die aber mittlerweile eingestampft wurde. Für mich ist C# eine ideale Programmiersprache, da sie sehr nah an dem mir seit 20 Jahren wohlvertrauten Java ist und einige sehr schöne Konzepte mitbringt, die mir in Java bis heute fehlen. Ich werde mich im Laufe des Tutorials bemühen, den Quelltext so einfach wie möglich zu halten, um auch Einsteigern ein Verständnis zu ermöglichen.

Schritt 001: Ein neues Unity-Projekt anlegen

So, jetzt geht es aber wirklich los! Auf der Webseite von Unity wählen wir die Personal Edition, bestätigen die Lizenzbedingungen, laden das Programm herunter und installieren es. Dafür sollte man etwas Platz auf der Festplatte bereithalten, denn Unity frisst alleine lockere 5 Gigabyte.

Wir starten Unity und klicken im Startfenster auf New..., um ein neues Projekt anzulegen. Als Namen wählen wir DSA_Tutorial, suchen uns ein passendes Workspace-Verzeichnis, wählen das 2D-Template (das Erstellen von 3D-Modellen würde dieses Tutorial sprengen) und schalten die Unity Analytics ab, da wir diese nicht brauchen werden.



Nun muss man wieder ein paar Augenblicke Geduld mitbringen, bis Unity das Projekt angelegt und die Entwicklungsumgebung gestartet hat. Wir werden von einem leeren Fenster begrüßt: In der Mitte sieht man die Spielwelt, die Szene, die bisher nur als graue Fläche mit Gitternetzlinien erscheint. Eine Szene (engl. Scene) kann z.B. ein einzelner Level, ein Startmenü, eine Zwischensequenz etc. sein. Links von diesem Bereich sehen wir die Hierarchie, in der alle Objekte (die sogenannten GameObjects) der aktuellen Szene dargestellt werden. Aktuell sollte dort nur eine einzelne Kamera zu sehen sein. Rechts von der Szene liegt der Inspector, in dem man die Eigenschaften, Komponenten und Skripte des ausgewählten GameObjects bearbeiten kann. Im Project-Fenster unter der Szene können die Assets (Bilder, Modelle, Töne, Musiken), Prefabs (vorgefertigte Objekte), Animationen usw. verwaltet werden.

Schritt 002: Einen Spieler erzeugen

Beginnen wir mit einem einfachen GameObject, indem wir in der Hierarchie einen Rechtsklick machen und über Create Empty ein leeres GameObject anlegen. Durch Doppelklick auf das neue Objekt oder über das Textfeld Name im Inspector nennen wir das Objekt Spieler.

Der Spieler mag nun da sein, aber sehen kann man ihn noch nicht. Dafür müssen wir erst ein Bild für ihn hochladen. Dazu klicke ich im Project-Fenster mit der rechten Maustaste auf Assets und lege über Create einen neuen Folder namens Bilder an. In diesen Ordner können wir nun ein passendes Spieler-Bild legen. Ich habe mir erlaubt, einfach ein Bild von Geron Waisenmacher, einem der 8 ikonischen DSA5-Helden, aus einem Nandurion-Artikel zu klauen. Das Bild hat glücklicherweise bereits einen Transparenzkanal, sodass wir hinter Geron noch den später hinzuzufügenden Hintergrund sehen können.

Das heruntergeladene Bild wird entweder per Drag&Drop oder über Import New Asset... im Kontextmenü in den Bilder-Ordner hinzugefügt. Damit wir es zum GameObject Spieler hinzufügen können, müssen wir es in ein sogenanntes Sprite umwandeln. Dazu klicken wir auf das Bild und wählen im Inspector als Texture Type den Wert Sprite (2D and UI) aus. Damit das Bild später ungefähr eine Einheit hoch ist, geben wir bei Pixels per Unit 600 ein. Die Änderungen werden übrigens erst übernommen, wenn wir ein anderes Objekt auswählen.

Damit wir das Bild zu unserem Spieler hinzufügen können, müssen wir ihm einen sogenannten Sprite Renderer hinzufügen, der das Zeichnen des Sprites übernimmt. Dazu wählen wir den Spieler aus und klicken im Inspector auf Add Component. In dem sich öffnenen Fenster geben wir Sprite in das Suchfeld ein und wählen den Sprite Renderer in der Auswahlliste aus. Wir sehen nun im Inspector einen neuen Bereich für diesen neu angelegten Sprite Renderer. In das leere Feld neben bem Word Sprite ziehen wir das zuvor vorbereitete Sprite aus dem Bilder Ordner. Nun sollte endlich unser Held Geron zu sehen sein!


Schritt 003: Lebensenergie

Ein DSA-Computerspiel ist ein gefährlicher Ort, und an allen Ecken der Spielwelt können Gefahren lauern, die unserem Helden die wertvolle Lebensenergie rauben können. Wir brauchen also eine Möglichkeit, uns die aktuelle Lebensenergie zu merken, und gegebenenfalls Schadenspunkte davon abziehen zu können. Natürlich sollte auch Regeneration möglich sein. Damit ein Held niemals mehr Lebenspunkte regenerieren kann als er verloren hat, müssen wir uns die maximale Lebensenergie ebenfalls merken.

Wir müssen nun also unser erstes Skript schreiben, das die Verwaltung der Lebensenergie für uns übernimmt. Dazu wählen wir wieder den Spieler aus, klicken im Inspector auf Add Component, und wählen die letzte Option New Script. Wir nennen das Skript Lebensenergie, und warten, bis das Skript erzeugt wurde. Es sollte sich nun ein Editor zur Bearbeitung des Skripts öffnen (normalerweise sollte das Microsoft Visual Studio in der kostenlosen Community Edition 2017 sein, die automatisch mit Unity installiert wurde, wenn Ihr das Häkchen bei der Installation nicht weggeklickt habt).

Unsere neue C#-Klasse trägt den Namen Lebensenergie und ist von der Oberklasse MonoBehaviour abgeleitet. Letztere sorgt dafür, dass wir das Skript als Komponente an unsere GameObjects hängen können. Standardmäßig hat jede neue MonoBehaviour-Klasse die beiden Methoden Start und Update. Start wird, wie der Name sagt, beim ersten Erzeugen des Skripts aufgerufen, Update bei jedem einzelnen Bildaufbau. Da Update potentiell sehr häufig aufgerufen wird, sollte dort niemals sehr rechenintensive Vorgänge stattfinden. Unsere Lebensenergie-Klasse braucht vorerst weder Start noch Update, weshalb wir die Methoden einfach herauslöschen können.

Wie gesagt, soll unsere Klasse vorerst zwei Eigenschaften bekommen: Die aktuelleLebensenergie des Helden sowie die mögliche maximaleLebensenergie. Beide Werte sind Ganzzahlen (keine Kommazahlen), und müssen daher vom Typ int (Abkürzung für Integer, d.h. Ganzzahl) sein. Wir machen beide Werte public, d.h. sie können von anderen Komponenten gelesen und geändert werden.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Lebensenergie : MonoBehaviour
{
    public int aktuelleLebensenergie;
    public int maximaleLebensenergie;
}

Nebenbei hat dieses public noch einen anderen sehr angenehmen Nebeneffekt, den wir erst sehen, wenn wir die Änderungen an der Klasse speichern (über das Disketten-Icon oder die Tastenkombination STRG+S), und zu Unity zurückkehren. Nach einer kurzen Kompilationspause (die man am sich drehenden Kreissymbol in der rechten unteren Ecke erkennt) erscheinen im Inspector zwei neue Eingabefelder, die den Namen aktuelle Lebensenergie und maximale Lebensenergie tragen.



Nur durch das Anlegen einer öffentlichen Eigenschaft in einem MonoBehaviour erhalten wir im Inspector sofort einen passenden Editor. Somit kann man nun für einen Spieler komfortabel einen Wert für die Lebensenergie angeben, ohne den Programmcode anpassen zu müssen. Das ist eines meiner Lieblings-Features in Unity!

Schritt 004: Eigenschaften

Das Hinzufügen einer Lebensenergie war ja schonmal schön einfach. Von diesem Erfolg motiviert, wollen wir das gleiche Prinzip auf die acht Eigenschaften eines Helden anwenden. Also klicken wir im Inspector erneut mit ausgewähltem Spieler auf Add Component, wählen New Script und geben als Namen Eigenschaften ein.

In Visual Studio geben wir die folgenden Zeilen ein:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Eigenschaften : MonoBehaviour
{
    public int mut;
    public int klugheit;
    public int intuition;
    public int charisma;
    public int fingerfertigkeit;
    public int gewandheit;
    public int konstitution;
    public int körperkraft;
}

Damit kann man nun schon einmal die Eigenschaftswerte des Helden über den Inspector setzen. Gute Arbeit!



Doch egal, ob die Werte nun hoch oder niedrig, eingetragen oder leer sind - viel machen kann man damit noch nicht. Das wollen wir im nächsten Schritt ändern!

Schritt 005: Würfeln

Pen'n'Paper-Rollenspiel ist nicht denkbar ohne Würfel. Viele Würfel, in den verschiedensten Formen, Farben, Größen und Materialien. Rollenspieler lieben ihre Würfel, und so sollte klar sein, dass auch unser Computerspiel nicht ohne virtuelles Würfeln auskommen kann.

Wir brauchen also eine weitere Klasse, die die Funktion eines virtuellen Würfels übernimmt. Sie soll in der Lage sein, sowohl die Funktionalität es W6 als auch eines W20 abzubilden, und idealerweise auch direkt komplexere Terme wie "3W20+5" auswürfeln zu können.

Beginnen wir mit dem einfachen Werfen eines sechseitigen Würfels:

public class Würfel
{
    public int W6()
    {
        return UnityEngine.Random.Range(1, 6);
    }
}

Wie man sieht, braucht man zum Würfeln die Unity-Klasse Random, die uns über die Methode Range das Erzeugen einer Pseudo-Zufallszahl zwischen 1 und 6 ermöglicht. Nach dem gleichen Prinzip können wir natürlich auch das Werfen eines W20 simulieren:

public class Würfel
{
    public int W6()
    {
        return UnityEngine.Random.Range(1, 6);
    }
    public int W20()
    {
        return UnityEngine.Random.Range(1, 20);
    }
}

Um bei Würfen mit mehrere Würfeln nicht mehrfach eine der beiden Methoden aufrufen zu müssen, bauen wir uns zusätzlich eine Komfort-Funktion, die direkt das mehrfache Würfeln eines beliebigen Würfels mit einem Modifikator ermöglicht:

public class Würfel
{
    public int W6()
    {
        return W(1,6,0);
    }

    public int W20()
    {
        return W(1,20,0);
    }

    public int W(int anzahl, int art, int modifikator)
    {
        int resultat = modifikator;
        for (int i = 0; i < anzahl; i++)
            resultat += UnityEngine.Random.Range(1, art);
        return resultat;
    }
}

Betrachten wir dazu ein Beispiel: Wenn der Meister von 3W+5 redet, weiß jeder DSA-Spieler sofort, dass er nun drei sechsseitige Würfel werfen, die Augenzahlen addieren und zur Summe noch den Wert 5 addieren soll. Genau das macht die Methode W: In unserem Beispiel hätte der Parameter anzahl den Wert 3, der Parameter art den Wert 6 (für einen sechsseitigen Würfel), und modifikator den Wert 5. In der Methode werden nun in einer for-Schleife, die so oft durchlaufen wird, wie der Parameter anzahl angibt, jedesmal ein Würfel mit art Seiten gewürfelt und zu der Variable resultat hinzugefügt. Die Variable wird direkt mit dem Wert von modifikator initialisiert, sodass wir nach der Schleife das Ergebnis direkt zurückgeben können.

Da diese Methode so schön allgemeingültig ist und so ziemlich alle Würfelwürfe mit nur einer Würfelart abdecken dürfte, verwenden wir sie auch für die beiden Methoden W6 und W20 wieder, um die Logik zum Werfen eines Würfels fortan nur noch in der Methode W pflegen zu müssen.

Für Fortgeschrittene habe ich noch eine weitere Methode hinzugefügt, die als Parameter eine Zeichenkette (d.h. einen string) erwartet, der einen Text der Form "3W20-5" enthält. Der String wird über eine sogenannte Regular Expression geparset (wer nicht weiß, was das bedeuten soll: Ist auch nicht so wichtig!) und in die drei Gruppen anzahl, art und modifikator zerlegt, die dann wiederum zum Aufruf der gerade geschriebenen Methode W verwendet werden kann. Hier die finale Form unserer Würfel-Klasse:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Text.RegularExpressions;
using System;

public class Würfel
{
    private static string regex = "^([0-9]*)[wW]([0-9]*)[+]?([-]?[0-9]*)$";

    public int W6()
    {
        return W(1,6,0);
    }

    public int W20()
    {
        return W(1,20,0);
    }

    public int W(int anzahl, int art, int modifikator)
    {
        int resultat = modifikator;
        for (int i = 0; i < anzahl; i++)
            resultat += UnityEngine.Random.Range(1, art);
        return resultat;
    }

    public int W(string text)
    {
        GroupCollection groups = Regex.Match(text, regex).Groups;
        int anzahl;
        int art;
        int modifikator;
        if( !Int32.TryParse(groups[1].Value, out anzahl)) anzahl=1;
        if (!Int32.TryParse(groups[2].Value, out art)) art=6;
        if (!Int32.TryParse(groups[3].Value, out modifikator)) modifikator=0;
        return W(anzahl, art, modifikator);
    }
}

Fertig ist unsere Würfelklasse! Wer die Funktionsfähigkeit unseres virtuellen Würfels testen möchte, kann dies z.B. in der Start-Methode unseres Eigenschaften-Skripts tun:

public void Start() {
    Würfel würfel = new Würfel();
    Debug.Log("Test W6: "+würfel.W6());
    Debug.Log("Test W20: "+würfel.W20());
    Debug.Log("Test 3W-4: "+würfel.W("3W-4"));
}

Im sogenannten Console-Fenster (findet sich neben dem Project-Reiter in Unity) sollten die ausgewürfelten Zahlen als Ausgabe erscheinen, wenn wir unser Programm mit der Play-Taste über dem Scene-Fenster starten.

Im nächsten Schritt wollen wir unsere neue Würfel-Klasse nutzen, um eine einfache Eigenschaftsprobe durchzuführen!

Schritt 006: Eigenschaftsproben

Eigentlich ist es ja ganz einfach: Um in DSA eine Eigenschaftsprobe durchzuführen, wirft man einen W20. Ist der gewürfelte Wert kleiner oder gleich dem Eigenschaftswert, ist die Probe gelungen, ansonsten misslungen. Sehr simpel, oder?

Leider nicht ganz: Es müssen einige Sonderfälle beachtet werden, die das Durchführen einer Probe durchaus komplex machen:
  • Eigenschaftsproben können mit einem Modifikator erleichtert oder erschwert werden.
  • Ist der effektive Eigenschaftswert (d.h. Eigenschaftswert+Modifikator) kleiner als 1, darf die Probe nicht durchgeführt werden.
  • Wurde eine 1 gewürfelt, muss die Probe wiederholt werden: Ist sie auch beim zweiten Mal gelungen, wurde ein Kritischer Erfolg erzielt, sonst nur ein normaler Erfolg.
  • Wurde eine 20 gewürfelt, muss die Probe ebenfalls wiederholt werden: Ist sie auch beim zweiten Mal misslungen, wurde ein Patzer erzielt, sonst nur ein normaler Misserfolg.
  • In allen anderen Fällen ist die Probe ein Erfolg, wenn der Würfelwurf kleiner oder gleich dem effektiven Eigenschaftswerk ist, sonst ein Misserfolg.
Insgesamt gibt es also fünf verschiedene Ergebnisse der Probe, die wir uns als enum (d.h. Aufzählung) definieren:

public enum Probenergebnis
{
    Erfolg, KritischerErfolg, Misserfolg, NichtErlaubt, Patzer
}

Auch für die acht Eigenschaften definieren wir uns noch ein enum, um unserer Methode zum Durchführen der Probe leicht mitteilen zu können, auf welche Eigenschaft die Probe durchgeführt werden soll:

public enum Eigenschaft { MU, KL, IN, CH, FF, GE, KO, KK }

Nun haben wir jedoch zwei Repräsentationen der Eigenschaften: Einmal als öffentliche Ganzzahlen, die wir über den Inspector eingeben können, und einmal als Aufzählung, mit der wir angeben, welche Eigenschaft gemeint ist. Wir brauchen noch zwei Methoden, um beide miteinander nutzen zu können:

    public int Wert(Eigenschaft eigenschaft)
    {
        switch (eigenschaft)
        {
            case Eigenschaft.MU: return mut;
            case Eigenschaft.KL: return klugheit;
            case Eigenschaft.CH: return charisma;
            case Eigenschaft.IN: return intuition;
            case Eigenschaft.FF: return fingerfertigkeit;
            case Eigenschaft.GE: return gewandheit;
            case Eigenschaft.KO: return konstitution;
            case Eigenschaft.KK: return körperkraft;
            default: return 0;
        }
    }
    public void Setze(Eigenschaft eigenschaft, int wert)
    {
        switch (eigenschaft)
        {
            case Eigenschaft.MU: mut = wert; break;
            case Eigenschaft.KL: klugheit = wert; break;
            case Eigenschaft.CH: charisma = wert; break;
            case Eigenschaft.IN: intuition = wert; break;
            case Eigenschaft.FF: fingerfertigkeit = wert; break;
            case Eigenschaft.GE: gewandheit = wert; break;
            case Eigenschaft.KO: konstitution = wert; break;
            case Eigenschaft.KK: körperkraft = wert; break;
        }
    }

Nun können wir z.B. über Wert(KK) den aktuellen Wert der Körperkraft abfragen und über Setze(KK, 14) den Wert der Körperkraft auf 14 setzen. Wunderbar! Das können wir nun nutzen, um endlich unsere Eigenschaftsprobe durchführen zu können:

    public Probenergebnis Probe(Eigenschaft eigenschaft)
    {
        return Probe(eigenschaft, 0);
    }

    public Probenergebnis Probe(Eigenschaft eigenschaft, int modifikator)
    {
        // Würfel besorgen
        Würfel würfel = new Würfel();

        // Proben, deren effektiver Eigenschaftswert 0 oder kleiner ist, sind nicht erlaubt
        int effektiverEigenschaftswert = Wert(eigenschaft) - modifikator;
        if (effektiverEigenschaftswert < 1)
            return Probenergebnis.NichtErlaubt;

        // die eigentliche Probe
        int wurf = würfel.W20();

        // bei einer 1 muss ein Bestätigungswurf erfolgen
        if (wurf == 1)
        {
            if (würfel.W20() <= effektiverEigenschaftswert)
                return Probenergebnis.KritischerErfolg;
            else
                return Probenergebnis.Erfolg;
        }
        // auch ein Patzer muss bestätigt werden
        if (wurf == 20)
        {
            // eine 20 beim Bestätigungswurf ist immer ein Patzer
            if (würfel.W20() <= Mathf.Min(19, effektiverEigenschaftswert))
                return Probenergebnis.Misserfolg;
            else
                return Probenergebnis.Patzer;
        }

        // ist die Probe gelungen?
        if (effektiverEigenschaftswert >= wurf)
            return Probenergebnis.Erfolg;
        else
            return Probenergebnis.Misserfolg;
    }
Wie man sieht, gibt es zwei Varianten der Methode: Eine unmodifizierte und eine modifizierte. Wenn wir die Methode testen wollen, können wir das z.B. wieder in der Start-Methode unserer Komponente Eigenschaften machen:

public void Start()
{
    Probenergebnis ergebnis = Probe(Eigenschaft.MU, -3);
    if( ergebnis==Probenergebnis.ERFOLG || ergebnis==Probenergebnis.KRITISCHER_ERFOLG )
        Debug.log("Ich bin mutig!");
    else
        Debug.log("Ich habe Angst!");
}

Das Fazit

Damit soll es für heute auch erstmal genug sein. Wir haben nun einen Spielerhelden als GameObject, der in der Lage ist, Eigenschaftsproben durchzuführen. Im nächsten Artikel wollen wir uns dann mit Talentproben beschäftigen und wollen unseren Helden steuerbar machen. Was mich bis dahin natürlich brennend interessieren würde, ist Eure Meinung zu dieser Artikelreihe: Interessiert Euch das Thema überhaupt? Könnt Ihr meinen Ausführungen folgen? Habt Ihr gerade selbst am Rechner gesessen, um die Beispiele nachzuprogrammieren? Habt Ihr Verbesserungsvorschläge? Mache ich gar irgendetwas eklatant falsch?



Ich bin auf Eure Kommentare gespannt, und hoffe, dass mich zumindest einige von Euch auf meiner beschwerlichen Reise hin zu meinem lang herbeigesehnten Ziel, einem spielbaren DSA-Roguelike, begleiten werden. Mögen die Zwölfe uns beistehen!

Kommentare

  1. Wow, das war echt interessant. Wer wollte denn nicht einmal ein eigenes DSA-Spiel programmieren? Ich selbst studiere Informatik, hatte also keine Probleme dem Artikel zu folgen, fand aber die Vorgehensweise und den Aufbau sehr instruktiv. Das Projekt wird von mir auf jeden Fall weiter verfolgt!

    AntwortenLöschen
    Antworten
    1. Ich gehe davon aus, dass nicht sonderlich viele Leute davon träumen, ein DSA-Spiel zu programmieren, denn zum Einen dürfte die Schnittmenge aus Menschen, die DSA spielen und gleichzeitig programmieren können, eher überschaubar sein, und zum anderen hat meines Wissens nach in fast 35 Jahren niemand ein DSA-Roguelike veröffentlicht, was ich echt schade finde. Danke für's Lesen und Folgen!

      Löschen
  2. Referenzen zu Praxisferne ist mein Highlight. Du schonst uns wieder nicht mit intimen Details, lieber Dr. Schelm! Die Programmierung kannst Du gern weitermachen. Vielleicht wäre eine sehr knappe Roadmap, was gemacht werden soll hilfreich. Anhand der Überschriften etwas wie "ein Objekt erstellen, das einen Charakter darstellt, Lebensenergie und Eigenschaften besitzt sowie Würfel und damit Eigenschaftsproben ablegen kann" und anschließend die Ausführungen.

    AntwortenLöschen
    Antworten
    1. Das mit der Roadmap würde aber voraussetzen, dass ich vor oder während des Schreibens so etwas wie eine Roadmap hätte. Habe ich aber nicht, die Artikel werden so lang oder kurz, wie mein Bauchgefühl mir das gerade sagt. Und wen die Artikel interessieren, der kann auch gerne einmal über die Überschriften scrollen. Eine Gliederung für einen Blog-Artikel fände ich ziemlich albern. Aber wie immer Danke für's Feedback!

      Löschen
  3. Liest sich gut... Hätte schon länger vor was mit unity zu machen. Leider fehlt mir die Zeit. Bitte mehr davon

    AntwortenLöschen
    Antworten
    1. Ich gebe mir Mühe! Für ein paar weitere Artikel ist schon genug programmiert worden - jetzt heißt es hoffen und zu den Zwölfen beten, dass ich neben dem Schreiben auch noch genug Zeit zum Weiterprogrammieren finde! Danke für's Lesen!

      Löschen
  4. Bitte unbedingt weiter machen!
    Sobal ich nächste Woche wieder Zeit hab werf ich VS 2019 an und versuche mitzuprogrammieren. Ich entwickle jetzt schon über 10 Jahre mit C# (aber „nur“ langweilige Business-Anwendungen) jetzt wird’s mal langsam Zeit such was spaßigeres zu machen. Bitte weiter so! :-)

    AntwortenLöschen
    Antworten
    1. Danke, ich versuche dran zu bleiben. Da ich noch praktisch keine Erfahrung mit C# habe, bitte ich etwaige umständliche Herangehensweisen eines langjährigen Java-Entwicklers (seit über 20 Jahren) zu entschuldigen. Aber mit Unity zu entwickeln macht echt Spaß! Den ich Dir hiermit beim Nachprogrammieren auch wünsche!

      Löschen

Kommentar veröffentlichen