Organizzazione di livelli / stanze in un mondo basato su testo in stile MUD


12

Sto pensando di scrivere un piccolo gioco di avventura basato su testo, ma non sono particolarmente sicuro di come progettare il mondo da un punto di vista tecnico.

Il mio primo pensiero è di farlo in XML, progettato in modo simile al seguente. Mi scuso per l'enorme mucchio di XML, ma ho ritenuto importante spiegare appieno quello che sto facendo.

<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>

Ovviamente ci dovrebbe essere qualcosa di più. L'interazione con persone e nemici, nonché la morte e il completamento sono aggiunte necessarie. Dato che l'XML è piuttosto difficile da lavorare, probabilmente creerei una sorta di editor mondiale.

Mi piacerebbe sapere se questo metodo ha qualche difetto e se esiste un modo "migliore" o più standard di farlo.


3
Personalmente non tratterei l'XML come qualcosa di più di un formato di serializzazione. Se estrai la domanda "in qualche modo ho intenzione di leggere e scrivere questo su disco" (usando qualcosa come XML, JSON, buffer di protocollo, formato binario personalizzato, qualunque cosa), allora la domanda diventa "di quali dati ho bisogno per archiviare ", che è qualcosa a cui solo tu puoi veramente rispondere a seconda delle tue esigenze di gioco.
Tetrad,

Buon punto. Tuttavia, ho visto giochi usare stili come questo prima e si sono rivelati davvero restrittivi. In questo caso, tuttavia, il flusso e la logica del gioco sono abbastanza semplici, quindi potrebbe funzionare bene e salvarmi dall'implementazione di un motore di scripting. Sono principalmente interessato al fatto che una tale struttura fissa (stanze separate, porte, oggetti, variabili in un file di definizione da qualche parte) sia praticabile o meno.
Polinomio

Cercando di non fare eco a Tetrad, ma se stai pensando di creare un editor mondiale (che suggerirei a meno che il gioco non sia molto breve), il formato del tuo file non fa alcuna differenza poiché lavorerai con esso in l'editore, contro il duro codice delle stanze.
Mike Cluck,

Risposte:


13

Se non sei completamente legato a C #, il modo "più standard" per farlo è quello di utilizzare uno dei tanti strumenti di creazione di avventure testuali già esistenti per aiutare le persone a realizzare esattamente questo tipo di gioco. Questi strumenti offrono un parser già funzionante, gestione della morte, salvataggio / ripristino / annullamento, interazione tra i personaggi e altri bit standard simili di funzionalità di avventura di testo. In questo momento, i sistemi di authoring più popolari sono Inform e TADS (anche se ne sono disponibili anche una mezza dozzina)

Inform può essere compilato nella maggior parte dei set di istruzioni della macchina virtuale Z Machine utilizzati dai giochi Infocom o nei più recenti set di istruzioni della macchina virtuale glulx. TADS, d'altra parte, si compila nel proprio codice di macchina virtuale.

Entrambi i tipi di binari possono essere gestiti dalla maggior parte dei moderni interpreti di narrativa interattiva (ai vecchi tempi, spesso avevi bisogno di interpreti separati per i giochi TADS dai giochi ZMachine dai giochi glulx. Ma per fortuna, quei giorni sono praticamente finiti ora.) Gli interpreti sono disponibili solo per su qualsiasi piattaforma tu voglia; Mac / PC / Linux / BSD / iOS / Android / Kindle / browser / etc. Quindi hai già ben realizzato multipiattaforma e veramente curato.

Per la maggior parte delle piattaforme, l'interprete attualmente raccomandato è Gargoyle , ma ce ne sono molti altri, quindi sentiti libero di sperimentare.

La codifica in Inform (specialmente l'ultima versione) richiede un po 'di tempo per abituarsi, dal momento che si commercializza più verso gli autori che verso gli ingegneri, e quindi la sua sintassi sembra strana e quasi colloquiale. Nella sintassi di Inform 7, il tuo esempio sarebbe simile al seguente:

"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."

Considerando che TADS assomiglia più a un linguaggio di programmazione tradizionale, e lo stesso gioco in TADS assomiglia a questo:

#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
;

Entrambi i sistemi sono disponibili gratuitamente, utilizzati molto frequentemente e dispongono di abbondanti quantità di documentazione tutorial (disponibile dai collegamenti che ho dato sopra), quindi vale la pena controllarli entrambi e scegliere quello che preferisci.

Si noti che i due sistemi hanno comportamenti standard leggermente diversi (sebbene entrambi possano essere modificati). Ecco uno screenshot del gioco in corso, compilato dalla fonte Inform:

Informare screenshot

Ed eccone uno del gioco in corso (all'interno di un terminale - la tipografia può essere molto più bella di così), come compilato dalla fonte Tads:

Schermata TADS3

Punti interessanti da notare: TADS ti dà una visualizzazione del 'punteggio' in alto a destra per impostazione predefinita (ma puoi disattivarlo), mentre Inform non lo fa (ma puoi accenderlo). Per impostazione predefinita, Inform ti dirà gli stati aperti / chiusi degli oggetti nella descrizione della stanza, Tads no. Tads tende ad agire automaticamente per te al fine di eseguire i comandi del giocatore (a meno che tu non gli dica di non farlo), dove Inform tende a non farlo (a meno che tu non glielo dica).

Entrambi possono essere utilizzati per creare qualsiasi tipo di gioco (poiché entrambi sono altamente configurabili), ma Inform è più strutturato per produrre fiction interattiva in stile moderno (spesso con puzzle minimi e più narrativa in stile), in cui TADS è più strutturato verso la produzione di avventure testuali di vecchio stile (spesso fortemente incentrate sui puzzle e sulla definizione rigorosa delle meccaniche del modello mondiale del gioco).


questo è molto interessante e informativo, ma imo non risponde alla domanda. Stavo per fare sostanzialmente questa stessa identica domanda. Mi piacerebbe sapere di più se questo XML è un approccio valido o se ci sono insidie ​​o debolezze che avrebbe.
DLeh

1
@DLeh La domanda era "Mi piacerebbe sapere se questo metodo ha qualche difetto e se esiste un modo" migliore "o più standard di farlo" Questa risposta fornisce il modo migliore e più standard di- farlo .
Trevor Powell,

Ma dal momento che hai chiesto "insidie ​​e punti deboli": l'implementazione di Inform è lunga 19 righe. L'esempio TADS è lungo 40 righe. L'implementazione XML richiede 126 righe (e sarebbe ancora più lunga se fosse racchiusa tra 80 colonne e contenesse spazi bianchi per la leggibilità, come fanno le implementazioni Inform e TADS).
Trevor Powell,

Oltre ad essere molto più brevi, gli esempi Inform e TADS supportano anche più funzionalità. Ad esempio, in entrambi è possibile inserire il coltello negli armadi, che non è affatto supportato nella versione XML.
Trevor Powell,

1
Vale anche la pena notare che la versione XML inserisce il contenuto degli armadi nella descrizione degli armadi. Cioè, c'è un messaggio hardcoded per cosa stampare quando si apre o guardando gli armadietti (aperti), che ti dice che ci sono batterie all'interno. Ma cosa succede se il giocatore ha già preso le batterie? La versione XML ti dirà che ci sono batterie all'interno (perché questa è l'unica stringa che ha a disposizione per visualizzare), mentre le versioni Inform e TADS ti diranno che gli armadietti sono vuoti.
Trevor Powell,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.