Porządkowanie poziomów / pomieszczeń w świecie tekstowym w stylu MUD

12

Zastanawiam się nad napisaniem małej przygodowej gry tekstowej, ale nie jestem pewien, jak zaprojektować świat z technicznego punktu widzenia.

Moją pierwszą myślą jest zrobienie tego w XML, zaprojektowałem coś takiego. Przepraszamy za stos plików XML, ale czułem, że ważne jest pełne wyjaśnienie tego, co robię.

<level>
    <start>
        <!-- start in kitchen with empty inventory -->
        <room>Kitchen</room>
        <inventory></inventory>
    </start>
    <rooms>
        <room>
            <name>Kitchen</name>
            <description>A small kitchen that looks like it hasn't been used in a while. It has a table in the middle, and there are some cupboards. There is a door to the north, which leads to the garden.</description>
            <!-- IDs of the objects the room contains -->
            <objects>
                <object>Cupboards</object>
                <object>Knife</object>
                <object>Batteries</object>
            </objects>
            </room>
        <room>
            <name>Garden</name>
            <description>The garden is wild and full of prickly bushes. To the north there is a path, which leads into the trees. To the south there is a house.</description>
            <objects>
            </objects>
        </room>
        <room>
            <name>Woods</name>
            <description>The woods are quite dark, with little light bleeding in from the garden. It is eerily quiet.</description>
            <objects>
                <object>Trees01</object>
            </objects>
        </room>
    </rooms>
    <doors>
        <!--
            a door isn't necessarily a door.
            each door has a type, i.e. "There is a <type> leading to..."
            from and to are references the rooms that this door joins.
            direction specifies the direction (N,S,E,W,Up,Down) from <from> to <to>
        -->
        <door>
            <type>door</type>
            <direction>N</direction>
            <from>Kitchen</from>
            <to>Garden</to>
        </door>
        <door>
            <type>path</type>
            <direction>N</direction>
            <from>Garden</type>
            <to>Woods</type>
        </door>
    </doors>
    <variables>
        <!-- variables set by actions -->
        <variable name="cupboard_open">0</variable>
    </variables>
    <objects>
        <!-- definitions for objects -->
        <object>
            <name>Trees01</name>
            <displayName>Trees</displayName>
            <actions>
                <!-- any actions not defined will show the default failure message -->
                <action>
                    <command>EXAMINE</command>
                    <message>The trees are tall and thick. There aren't any low branches, so it'd be difficult to climb them.</message>
                </action>
            </actions>
        </object>
        <object>
            <name>Cupboards</name>
            <displayName>Cupboards</displayName>
            <actions>
                <action>
                    <!-- requirements make the command only work when they are met -->
                    <requirements>
                        <!-- equivilent of "if(cupboard_open == 1)" -->
                        <require operation="equal" value="1">cupboard_open</require>
                    </requirements>
                    <command>EXAMINE</command>
                    <!-- fail message is the message displayed when the requirements aren't met -->
                    <failMessage>The cupboard is closed.</failMessage>
                    <message>The cupboard contains some batteires.</message>
                </action>
                <action>
                    <requirements>
                        <require operation="equal" value="0">cupboard_open</require>
                    </requirements>
                    <command>OPEN</command>
                    <failMessage>The cupboard is already open.</failMessage>
                    <message>You open the cupboard. It contains some batteries.</message>
                    <!-- assigns is a list of operations performed on variables when the action succeeds -->
                    <assigns>
                        <assign operation="set" value="1">cupboard_open</assign>
                    </assigns>
                </action>
                <action>
                    <requirements>
                        <require operation="equal" value="1">cupboard_open</require>
                    </requirements>
                    <command>CLOSE</command>
                    <failMessage>The cupboard is already closed.</failMessage>
                    <message>You closed the cupboard./message>
                    <assigns>
                        <assign operation="set" value="0">cupboard_open</assign>
                    </assigns>
                </action>
            </actions>
        </object>
        <object>
            <name>Batteries</name>
            <displayName>Batteries</displayName>
            <!-- by setting inventory to non-zero, we can put it in our bag -->
            <inventory>1</inventory>
            <actions>
                <action>
                    <requirements>
                        <require operation="equal" value="1">cupboard_open</require>
                    </requirements>
                    <command>GET</command>
                    <!-- failMessage isn't required here, it'll just show the usual "You can't see any <blank>." message -->
                    <message>You picked up the batteries.</message>
                </action>
            </actions>
        </object>
    </objects>
</level>

Oczywiście musiało być coś więcej niż to. Konieczne są interakcje z ludźmi i wrogami, a także śmierć i zakończenie. Ponieważ XML jest dość trudny w obsłudze, prawdopodobnie stworzyłbym jakiś edytor światowy.

Chciałbym wiedzieć, czy ta metoda ma jakieś wady i czy istnieje „lepszy” lub bardziej standardowy sposób jej wykonania.

Wielomian
źródło
3
Osobiście nie traktowałbym XML jako czegoś więcej niż formatu serializacji. Jeśli wyodrębnisz pytanie „jakoś to przeczytam i zapiszę na dysk” (używając czegoś takiego jak XML, JSON, bufory protokołów, niestandardowy format binarny, cokolwiek), wtedy pytanie brzmi „jakie dane muszę przechowywać ”, na co tylko Ty naprawdę możesz odpowiedzieć w zależności od wymagań gry.
Tetrad
Słuszna uwaga. Jednak widziałem, że gry używają już takich stylów i okazały się bardzo restrykcyjne. Jednak w tym przypadku przebieg i logika gry jest dość prosta, więc może działać dobrze i uratować mnie przed wdrożeniem silnika skryptowego. Interesuje mnie przede wszystkim to, czy taka stała struktura (oddzielne pokoje, drzwi, obiekty, zmienne w pliku definicji gdzieś) jest wykonalna, czy nie.
Wielomian
Próbujesz nie przypominać Tetradowi, ale jeśli planujesz stworzyć edytor światowy (co sugerowałbym, chyba że gra będzie bardzo krótka), to twój format pliku nie ma znaczenia, ponieważ będziesz z nim pracować w redaktor, a nie kodowanie pokoi na stałe.
Mike Cluck

Odpowiedzi:

13

Jeśli nie jesteś całkowicie przywiązany do C #, wówczas „bardziej standardowym” sposobem jest skorzystanie z jednego z wielu narzędzi do tworzenia tekstowych przygód, które już istnieją, aby pomóc ludziom stworzyć dokładnie taką grę. Narzędzia te zapewniają już działający parser, obsługę śmierci, zapisywanie / przywracanie / cofanie, interakcję między postaciami i inne podobne standardowe funkcje tekstowej przygody. Obecnie najpopularniejszymi systemami do tworzenia treści są Inform i TADS (choć dostępnych jest również pół tuzina innych)

Inform może zostać skompilowany do większości zestawów instrukcji maszyny wirtualnej Z Machine używanych przez gry Infocom lub do najnowszych zestawów instrukcji maszyny wirtualnej glulx. Z drugiej strony TADS kompiluje się do własnego kodu maszyny wirtualnej.

Każdy typ pliku binarnego może być obsługiwany przez większość współczesnych interaktywnych interpretatorów fikcji (w dawnych czasach często potrzebowaliśmy osobnych interpretatorów dla gier TADS z gier ZMachine z gier glulx. Ale na szczęście te dni już minęły.) Tłumacze są dostępni tylko dla o każdej platformie, którą chcesz; Mac / PC / Linux / BSD / iOS / Android / Kindle / browser / itp. Dzięki temu masz już wiele platform i dobrze się o nie troszczysz.

Na większości platform obecnie polecanym tłumaczem jest Gargoyle , ale jest wiele innych, więc nie krępuj się eksperymentować.

Kodowanie w Inform (szczególnie w najnowszej wersji) wymaga trochę czasu, aby się przyzwyczaić, ponieważ sam marketing jest bardziej skierowany do autorów niż do inżynierów, a więc jego składnia wygląda dziwnie i prawie konwersacyjnie. W składni Inform 7 przykład będzie wyglądał następująco:

"My Game" by Polynomial

Kitchen is a room. "A small kitchen that looks like it hasn't been used in a 
while. It has a table in the middle, and there are some cupboards. There is a 
door to the north, which leads to the garden."

In the Kitchen is a knife and some cupboards.  The cupboards are fixed in 
place and closed and openable.  In the cupboards are some batteries.

Garden is north of Kitchen. "The garden is wild and full of prickly bushes. 
To the north there is a path, which leads into the trees. To the south there 
is a house."

Woods is north of Garden.  "The woods are quite dark, with little light bleeding 
in from the garden. It is eerily quiet."  

Trees are scenery in the Woods.  "The trees are tall and thick. There aren't any 
low branches, so it'd be difficult to climb them."

Podczas gdy TADS wygląda bardziej jak tradycyjny język programowania, a ta sama gra w TADS wygląda następująco:

#charset "us-ascii"
#include <adv3.h>
gameMain: GameMainDef
    initialPlayerChar = me
;
versionInfo: GameID
    name = 'My Game'
    byline = 'by Polynomial'
;
startroom: Room                  /* we could call this anything we liked */ 
    roomName = 'Kitchen'         /* the displayed "name" of the room */ 
    desc = "A small kitchen that looks like it hasn't been used 
            in a while. It has a table in the middle, and there 
            are some cupboards. There is a door to the north, 
            which leads to the garden." 
    north = garden         /* where 'north' will take us */ 
; 

+me: Actor
; 

cupboards: OpenableContainer
    vocabWords = 'cupboard/cupboards' 
    name = 'cupboards' 
    isPlural = true
    location = startroom 
; 
battery: Thing
    name = 'battery'
    location = cupboards
;
knife: Thing
    name = 'knife'
    location = startroom
;
garden: Room
    roomName = 'Garden'
    desc = "The garden is wild and full of prickly bushes. To the 
            north there is a path, which leads into the trees. To 
            the south there is a house." 
    north = woods
    south = startroom
; 
woods: Room
    roomName = 'Woods'
    desc = "The woods are quite dark, with little light bleeding 
            in from the garden. It is eerily quiet."
    south = garden
;
trees: Decoration
    desc = "The trees are tall and thick. There aren't any low 
            branches, so it'd be difficult to climb them."
    location = woods
;

Oba systemy są swobodnie dostępne, bardzo często używane i mają mnóstwo dokumentacji instruktażowej (dostępnej z linków, które podałem powyżej), więc warto sprawdzić oba i wybrać ten, który wolisz.

Zauważ, że oba systemy mają subtelnie różne standardowe zachowania (chociaż oba mogą być modyfikowane). Oto zrzut ekranu z gry, opracowany ze źródła Inform:

Poinformuj zrzut ekranu

A oto jedna z rozgrywanej gry (w terminalu - typografia może być o wiele ładniejsza niż ta), jak skompilowano ze źródła Tads:

Zrzut ekranu TADS3

Ciekawe punkty do zapamiętania: TADS domyślnie wyświetla „wynik” w prawym górnym rogu (ale możesz go wyłączyć), a Inform nie (ale możesz go włączyć). Inform poinformuje domyślnie, że pozycje w opisie pokoju wskazują stan otwarty / zamknięty, a Tads nie. Chłopcy mają tendencję do automatycznego podejmowania działań w celu wykonania poleceń gracza (chyba, że ​​mówisz, żeby tego nie robić), a Inform zwykle tego nie robi (chyba, że ​​tak mówisz).

Każda z nich może być wykorzystana do stworzenia dowolnej gry (ponieważ obie są wysoce konfigurowalne), ale Inform ma bardziej ukierunkowaną konstrukcję na tworzenie interaktywnej fikcji w nowoczesnym stylu (często z minimalnymi łamigłówkami i więcej narracji w stylu), gdzie TADS jest bardziej uporządkowany w kierunku tworzenia przygód tekstowych w starym stylu (często silnie skoncentrowanych na zagadkach i rygorystycznie definiujących mechanikę światowego modelu gry).

Trevor Powell
źródło
jest to bardzo fajne i pouczające, ale imo nie odpowiada na pytanie. Chciałem zadać w zasadzie to dokładnie to samo pytanie. Chciałbym dowiedzieć się więcej o tym, czy ten kod XML jest prawidłowym podejściem, czy też miałby jakieś pułapki lub słabości.
DLeh
1
@DLeh Pytanie brzmiało: „Chciałbym wiedzieć, czy ta metoda ma jakieś negatywne skutki i czy istnieje„ lepszy ”lub bardziej standardowy sposób robienia tego. Ta odpowiedź zapewnia lepszy i bardziej standardowy sposób robiąc to .
Trevor Powell,
Ale skoro pytasz o „pułapki i słabości”: wdrożenie Inform ma długość 19 linii. Przykład TADS ma 40 linii. Implementacja XML wymaga 126 wierszy (i byłaby jeszcze dłuższa, gdyby była zawinięta w słowa w 80 kolumnach i zawierała spacje dla czytelności, tak jak robią to implementacje Inform i TADS).
Trevor Powell,
Oprócz tego, że są znacznie krótsze, przykłady Inform i TADS obsługują także więcej funkcji. Na przykład w obu z nich możesz włożyć nóż do szafek, co w ogóle nie jest obsługiwane w wersji XML.
Trevor Powell,
1
Warto również zauważyć, że wersja XML wypiera zawartość szafek do opisu szafek. Oznacza to, że na stałe zapisano komunikat, który należy wydrukować podczas otwierania lub patrzenia na (otwarte) szafki, co oznacza, że ​​w środku są baterie. Ale co, jeśli odtwarzacz już zabrał baterie? Wersja XML powie ci, że w środku są baterie (ponieważ jest to jedyny ciąg, który ma do wyświetlenia), natomiast wersje Inform i TADS poinformują, że szafki są puste.
Trevor Powell,