Begrüßung
Hallo liebe Forenmitglieder,
Vorgeplänkel des Vorgeplänkels (kann ausgelassen werden)
Bevor es mit der eigentlichen Vorstellung meines Projekts losgeht, hier erstmal eine kleine Vorgeschichte:
Wie jeder Programmierer hatte natürlich auch ich genug Projekte, die eigentlich viel zu hoch angesetzt waren oder aus anderen Gründen irgendwann keine größere Beachtung meinerseits erhielten. Eins war ein Rollenspiel im Stile der guten alten (SNES-)Rollenspiele, damals noch mit Blitz Basic. Zusätzlich dazu, dass es an und für sich viel zu hoch gegriffen war, hatte ich nicht einen einzigen Fetzen Story/Handlung oder besondere Gameplay-Elemente, die ich umgesetzt wissen wollte, nur dass es eben das Beste aus allem enthalten soll. Lediglich ein paar technische Besonderheiten hatte ich mir bereits ins Auge gefasst: die Kollisionsprüfung soll über eine weitere Ebene mit eigenem Tileset durchgeführt werden und die Prüfung an sich somit Pixelgenau werden - mit dem "Schatten" der Spielfigur, nicht mit der eigentlichen Grafik selbst.
Die Dinge, die ich dabei bereits realisieren konnte, waren das Darstellen der Map und des Spielers, Bewegung, Kollisionsprüfung mit der Map und die Teleportation auf eine andere Map.
Vorgeplänkel
Lernresistent wie immer habe ich, ein paar Jahre nachdem ich dieses Projekt nicht mehr angeschaut hatte, mal wieder mit einem Rollenspiel in Python angefangen. Wann genau das war, kann ich nicht mehr sagen, der älteste automatisch generierte Kommentar weist auf den 01.09.2009. Zu beachten ist dabei, dass Pausen über Monate keine Seltenheit waren. Selbstverständlich hatte ich auch für dieses Spiel keine besonderen Ideen. Zumindest aus der bis dahin gesammelten Erfahrung immer nur von "etwas, was irgendwann mal sowas wie ein Rollenspiel werden soll" gesprochen/geschrieben.
Bis letztes Jahr habe ich auch noch mit dem Ziel, ein Rollenspiel zu entwickeln, an dem Projekt weitergearbeitet, bis ich mir im Groben folgende Überlegung gemacht habe: Man beginnt auf einem bestimmten Level und kommt mit den Gegnern relativ einfach klar. Im Laufe des Spiels werden die Gegner stärker, weshalb man selbst gezwungenermaßen stärker werden muss. Wenn man also die Gegner auf dem gleichen Niveau (leicht bis schwer) hält, ohne dass für den Spieler die Notwendigkeit des Levelns besteht, dann wäre das Spiel einfacher in der Implementierung, im Balancing, im Gameplay und schon schwenkte ich von einem Rollenspiel auf ein Action Adventure um.
Allgemeines
Der Großteil der Ideen, die ich für das Projekt bereits habe, bezieht sich auf die technische Umsetzung. Diese sollen später das Skripten vereinfachen, wie genau wird an den entsprechenden Stellen weiter ausgeführt.
Ich habe bisher noch relativ wenig Vorstellungen für das Gameplay und die Handlung, allerdings orientiere ich mich teilweise auch an Programmen wie dem RPG Maker. Sollte ich in ferner Zukunft der Ansicht sein, dass das Grundgerüst soweit steht, einen Editor aber dafür keinen Plan für das Gameplay haben, könnte ich den Editor immernoch anderen zur Verfügung stellen. Sollte ich es jedoch schaffen, ein Spiel zusammenzubasteln, würde ich dennoch das von mir erstellte Programm auf die Mennschheit loslassen!
muahahahahahahahahaaa...
Aufgrund des Entwicklungsstandes (und mangels hübscher Grafiken), kann ich abgesehen von dem beschreibenden Text nichts vorzeigen.
Game Design
Aesthetics (siehe MDA)
Im Wesentlichen sollen für dieses Spiel Exploration und Challenging im Vordergrund stehen.
- Exploration/Discovery
- die Umgebung und die sich darin aufhaltenden NPCs
- der weiterführende Weg (sobald man einen neuen Gegenstand gefunden hat)
- diverse Geheimnisse
- Challenging
- zu lösende Rätsel
- Hindernisse
- Bosskämpfe
User Interface
Das HUD, also das Interface, welches der Spieler im normalen Spielverlauf und nicht in einem separaten Menü sieht, enthält die Anzeige der Lebensenergie in Form einer abzählbaren Stückzahl (vgl. Herzen in Zelda), die Menge an Geld sowie den ausgerüsteten Gegenstand mit der zugehörigen Stückzahl, sofern notwendig (Bomben, Pfeile, ...). Das HUD soll somit nur einen sehr geringen Teil des Bildschirms einnehmen.
Dialoge werden durch einen einfachen Hintergrund und Rahmen vom Spielgeschehen abgehoben. Der Text kann einfache Formatierungen, wie Hervorhebungen von wichtigen Begriffen oder abweichende Einblendungsgeschwindigkeiten, und Grafiken enthalten. Dialoge unterbrechen den normalen Spielverlauf, Blenden dieses und somit auch das HUD aber nicht aus.
Im Ingame-Menü kann der Spieler seine Gegenstände sehen und ausrüsten und Informationen über den groben Fortschritt im Spiel erhalten.
Technische Umsetzung
Gebiete und Maps
Das Spiel besteht aus einer Vielzahl von Gebieten, die hierarchisch angeordnet sind. Sollten entsprechende Informationen vorhanden sein (Breite, Höhe, Tileset, Mapdaten, ... - eine Map im klassischen Sinne), kann ein Gebiet auch betreten werden (eine Map). Die Besonderheit im Gegensatz zu Maps in anderen Spielen ist, dass die hierarchische Anordnung nicht nur der Übersicht beim Erstellen dient, sondern auch zur Laufzeit eine Relevanz besitzt. Genaueres dazu unter "Variablen" und unter "Skripte". Der Name einer Map leitet sich von seiner Position in der Hierarchie ab: Das oberste Gebiet wird im Spiel über
" angesprochen, ein darunter liegendes Gebiet könnte mit
/test und ein darunter liegendes mit
/test/hello_world angesprochen werden.
Variablen
Jedem Gebiet können Variablen zugewiesen werden. Diese Variablen sind dann in diesem Gebiet und in allen darunter liegenden Gebieten erreichbar. Wenn eins der unterliegenden Gebiete eine Variable mit dem gleichen Namen definiert, überschreibt es die übergeordnete Variable. Änderungen werden somit nicht an der übergeordneten Variable durchgeführt, sondern an der untergeordneten. Wenn man das Gebiet wieder verlässt, ist wieder die unveränderte übergeordnete Variable erreichbar. Dieser Mechanismus lässt sich ggf. durch das Verwenden des vollqualifizierten Namens umgehen. Dieser setzt sich aus dem Mapnamen, einem Punkt und dem Variablennamen zusammen (Beispiel:
/test.enemie_count).
Vordefinierte Variablen
Es wird Namen für Variablen geben, welche eine besondere Bedeutung haben werden. Für Gebiete bzw. Maps sind bisher keine vordefinierten Variablen vorhanden.
Skripte
Einerseits sollen Skripte sich möglichst performant ausführen lassen, andererseits sollen sie sich auch von nicht-Programmierern einfach erstellen lassen. Einfach Code einer beliebigen Programmiersprache zu nehmen würde bei der Erstellung problematischer sein und eine eigene "Skriptsprache" bzw. Bytecode würde bei der Umsetzung Probleme bereiten. Der derzeitige Stand ist, dass ich zur Laufzeit normalen Python-Code ausführe, beim Bearbeiten aber einen grafischen Editor anbieten werde. So generierte Skripte müssten vor der Ausführung nur noch in Python-Code umgewandelt werden.
Die Hierarchie der Maps soll sich auch auf die Skripte auswirken: Je nach Position in der Hierarchie überschreiben sie sich und können ggf. per vollqualifiziertem Namen angesprochen werden.
Events
Folgenden Events einer Map können Skripte zugeordnet werden:
- load - beim Laden der Map (synchrone Ausführung) (nicht implementiert)
- enter - beim Betreten der Map (nicht implementiert)
- leave - beim Verlassen der Map (nicht implementiert)
- unload - beim Entladen der Map (synchrone Ausführung) (nicht implementiert)
Hintergrundmusik
Bisher ist noch nichts in Richtung Musik oder Sound implementiert.
Die Hintergrundmusik soll ebenfalls von dem Konzept der Gebiete profitieren. Wenn für ein Gebiet eine Hintergrundmusik definiert wurde, soll diese automatisch mit den angegebenen Parametern abgespielt werden. Betritt man ein Gebiet, welches eine eigene Definition für die Hintergrundmusik besitzt, soll die vorherige automatisch ausgeblendet und die neue eingeblendet werden. Geht man in ein unter- oder übergeordnetes Gebiet mit der gleichen Definition, soll die Hintergrundmusik weiterlaufen.
Tilesets
Bisher bestehen Tilesets aus einem beliebig großem Bild, welches den nicht-animierten Tiles und weiteren Bildern, die die animierten Tiles beinhalten. Zwischenzeitlich kamen mir Gedanken, in wie weit Tilesets noch notwendig sind, wenn der großteil über Mapobjekte geregelt werden sollte. Deshalb werde ich auf diese vorerst nicht weiter eingehen.
Mapobjekte
Mapobjekte sind alle Objekte, abgesehen von der statischen Umgebung, die sich auf einer Map befinden können. Dazu gehören Gegenstände, Charaktere, Monster und Trigger. Die Objekte zeichnen sich durch einen eindeutigen Namen, Variablen und Skripte aus. Für jede mögliche Aktion eines Charakters kann ein Skript definiert werden, welches Skript ausgeführt werden soll.
Vererbung
Vergleichbar mit den Gebieten, jedoch unabhängig von diesen, gibt es auch für die Mapobjekte eine Vererbungs-Hierarchie. Es werden jedoch keine Vorlagen definiert, von denen dann Instanzen erzeugt werden können (siehe klassenbasierte OOP, wie in C#, Java, Python, ...), sondern die so definierten Mapobjekte selbst sind bereits verwendbar (siehe prototypbasierte OOP wie in JavaScript).
Variablen
Die Variablen sind vergleichbar mit den Variablen der Gebiete, nur setzt sich der Name aus dem Namen des Objekts und dem Variablennamen zusammen (
#hero.health) und Jedes Mapobjekt besitzt seine eigenen Variablen, auch wenn sie geerbt wurden.
Vordefinierte Variablen
Für Mapobjekte gibt es bereits einige Variablen, die eine besondere Bedeutung haben. Nachfolgend eine Übersicht über entsprechende Namen:
- width - Breite eines Mapobjekts
- height - Höhe eines Mapobjekts
- passable - Bestimmt, ob der Spieler über dieses Objekt laufen kann
- charset - das Charset eines Mapobjekts
- faceDir - die Richtung, in die der das Mapobjekt schaut
- name - der Name, der in einem Chat angezeigt wird (später die ID des Namens um Übersetzungen zu ermöglichen)
- visible - Bestimmt, ob das Mapobjekt sichtbar ist
- movementSpeed - Bestimmt die Bewegungsgeschwindigkeit in Tiles je Sekunde
- pushable - Bestimmt, ob das Mapobjekt von anderen verschoben werden kann
- pushDelay - Bestimmt die Dauer in Sekunden, die das Mapobjekt gedrückt werden muss, bevor es sich verschiebt (wenn nicht angegeben, wird die Gebietsvariable verwendet)
- tileBasedPush - Bestimmt, ob das Verschieben tilebasiert erfolgen soll
- pushDirections - wenn angegeben werden damit die Richtungen eingeschränkt, in die ein Block geschoben werden kann
- pushOnce - bestimmt, ob nur ein einmaliges Verschieben möglich ist oder ob es für die Anzahl keine Einschränkung gibt
- liftable - bestimmt, ob das Objekt angehoben werden kann
- rotateOnActivate - bestimmt, ob der Gegenstand sich zum ansprechenden Mapobjekt drehen soll, wenn es "angesprochen" wird
Skripte
Einem Mapobjekt können unter bestimmten Namen Skripte zugewiesen werden, welche aus anderen Skripten heraus oder vom Spiel unter bestimmten Umständen (Events) aufgerufen werden können. Ein so hinzugefügtes Skript wird erst im Verzeichnis der Objektdefinitionen gesucht, vom entsprechenden Mapobjekt aufwärts, und sollte es nicht gefunden werden, wird es zur Laufzeit im aktuellen Gebiet und den darüber liegenden gesucht.
Folgende Events werden automatisch unter bestimmten Umständen ausgeführt:
- activate - wenn passable gesetzt ist, wird es ausgeführt, sobald der Spieler das Mapobjekt betritt (Trigger), ansonsten wenn er es aktiviert (z. B. einen NPC ansprechen).
- pushFinished - wird aufgerufen, wenn das Verschieben des Mapobjekts beendet wurde
Lokale Mapobjekte
Zusätzlich zu den so definierten Mapobjekten können von diesen Instanzen (lokale Mapobjekte) angelegt werden, wobei zwischen benannten und anonymen unterschieden wird. Instanzen sind benannt, wenn sie einen eindeutigen Namen besitzen. Änderungen an den Werten der Variablen werden dauerhaft gespeichert, während die Werte von anonymen Instanzen beim Verlassen der Map verworfen werden. Den Instanzen können mit Hilfe definierbarer Parameter abweichende Werte zugewiesen werden. Die Parameter werden nicht vererbt und müssen ggf. erneut definiert werden. Wenn ein Parameter nicht angegeben wird, wird der Wert der Variable nicht angepasst, d. h., dass die Parameter immer optional sind. Wenn einem Parameternamen ein Bindestrich vorangestellt ist, wird der Name als Skriptname interpretiert. So können den events abweichende Skripte zugeordnet werden.
Charsets
Ein Charset beinhaltet alle Grafiken, die für die Darstellung eines Mapobjekts erforderlich sind. Die Animationen werden in Bildern gespeichert, die für jede Bewegungsrichtung alle Frames beinhaltet. Die Anzahl der Bewegungsrichtungen, der Frames und der Animationsgeschwindigkeit kann für jede Animation unterschiedlich eingestellt werden. für die Bewegungsrichtungen sind die Zahlen 1 (nur runter), 4 und 8 zulässig.
Speichern und Laden
Angedacht ist, dass beim Speichern eines Spielstands alle verwalteten Variablen gespeichert und beim Laden ausgelesen werden. Eine einfache (De-)Serialisierung ist nicht angedacht, da auf diese Art mehr Informationen gespeichert werden würden, als eigentlich notwendig und das Format der gespeicherten Daten wäre zu stark an die Implementierung gebunden.
Implementierte und ausstehende Funktionalität
Nicht ganz alles von den Ideen, die ich bisher beschrieben habe, sind auch bereits so im Spiel vorhanden. Auch die bereits Implementierten Features sind teilweise noch nicht vollständig.
Implementiert:
- Darstellung (Map, Mapobjekte)
- Bewegung (durch Benutzereingaben, durch Skripte)
- Kollisionserkennung (mit Umgebung und Mapobjekten)
- Skriptausführung
- Teleportation zu anderen Maps
- Dialoge (Blockierung von Spielerbewegungen)
- Variablen (Laden, lesend und schreibend aus Skripten daraufzugreifen)
Ausstehend:
- Fading
- Inventar und Items
- Kampfsystem
- weitere GUI-Elemente
- diverse Menüs
- Konfiguration
- Gegner
- unregelmäßige Bewegungen (beispielsweise hüpfende Bewegungen)
- zusammengesetzte Monster (die nicht nur aus einem Bild bestehen)
- Laden und Speichern von Spielständen
- ...
Das nächste Implementierungsziel ist das Inventar und die dafür erforderlichen Items.
Abschließende Worte
Viele der genannten Dinge sind noch in Entwicklung und Änderungen sind i. d. R. noch möglich. Mich würde interessieren, ob offensichtliche Fehler in meinen bisherigen Gedanken erkennbar sind.