Sytuacja kobiet w IT w 2024 roku
19.08.20225 min
David Tippett

David TippettSoftware Engineer AGI

Budujemy silnik gry RPG w Pythonie

Sprawdź, jak w prosty sposób zbudować silnik gry RPG w Pythonie, implementując m.in. takie efekty, jak wpisywanie tekstu na bieżąco.

Budujemy silnik gry RPG w Pythonie

Pewnie zastanawiacie się, dlaczego ktoś w ogóle chciałby coś takiego zrobić? Mój przyjaciel, który właśnie uczy się Pythona, wspomniał, że chce stworzyć swoją grę RPG, więc zacząłem się nad tym wszystkim zastanawiać. Minęło osiem lat, odkąd próbowałem stworzyć moją ostatnią tekstową „grę”. Nazywała się monty.py i była prostym skeczem Monty Pythona. Dlatego chciałbym spróbować jeszcze raz. A silnik gry napiszemy w Pythonie. 

Definiujemy wejście i wyjście

Najpierw musimy określić nasze oczekiwania. Myślę, że w swojej najprostszej formie, gra będzie musiała robić następujące rzeczy:

  1. Załadować plik z informacjami o grze
  2. Pokazać wyjście gry w formie tekstowej 
  3. Akceptować wejście od gracza


Pierwsza kwestia jest prosta. Będziemy potrzebować jakiegoś pliku, który będzie zawierał dane. Będziemy używać Pickle do opakowania naszych obiektów. Kształt każdego z tych elementów zostanie bardziej zdefiniowany przez kolejne dwa wymagania. Będziemy chcieli pogrupować wyjścia i wejścia razem. Umieścimy je w czymś, co będziemy nazywać stroną. Każda strona będzie miała numer, abyśmy mogli ją śledzić. Oto jak będzie to wyglądać:

1: {
  'text': 
  'options':
}


Nie chcemy tylko dużych bloków tekstu, więc text powinien być listą zawierającą ciągi znaków. W ten sposób możemy mieć wiele linii dialogowych, które należy kliknąć przed wprowadzeniem danych wejściowych. Ogólnie bardzo mi się podoba, gdy tekst w grze wygląda, jakby był wpisywany na bieżąco, a więc zaimplementujemy tę funkcję później.

'text': [
   "This is out first line",
   "And this is our second"
]


Teraz zajmiemy się danymi wejściowymi. Każda instrukcja wejściowa powinna zawierać jedną linię tekstu, a także wskaźnik informujący o wybraniu danego wejścia, tak aby przejść do właściwej strony. Ponieważ dane te są dobrze zdefiniowane, możemy użyć tablicy krotek do ich przechowania w następujący sposób:

'options': [
    ("Option 1", 2)
    ("Option 2", 3)
]


Kiedy mamy już naszą historię napisaną w odpowiednim formacie, możemy użyć skryptu takiego jak poniższy, aby opakować ją w binarny plik pickle, który rozumie nasz silnik gry.

import pickle
story = {
  1: {
    'Text': [
        "Hello there..",
        "I bet you werent exepecting to hear from me so soon...",
        "...you seem a little confused do you know who I am?"
    ],
    'Options': [
        ("Yeah of course!", 2),
        ("I'm sorry I dont", 3)
    ]
  }
}
with open('chapter1.ch', 'wb') as chapter:
    pickle.dump(story, chapter)

Tekst wyjściowy

Pierwszą rzeczą, jaką chcemy zrobić, jest wymyślenie, w jaki sposób wolno wypisywać tekst na ekranie, żeby to wyglądało, jakby ktoś go rzeczywiście pisał. Na ratunek przybywa Stack Overflow. Powoduje to iterację po każdej literze i umieszczenie jej na terminalu wyjściowym. Wywołania sys.stdout zapewniają niższy poziom dostępu do wiersza poleceń, umożliwiając zastąpienie ustawień domyślnych ustawionych przez Pythona.

import sys,time,random

def slow_type(t):
    typing_speed = 100 #wpm
    for l in t:
        sys.stdout.write(l)
        sys.stdout.flush()
        time.sleep(random.random()*10.0/typing_speed)


OK, teraz gdy pisanie tekstu mamy już z głowy, możemy zabrać się za prawdziwą robotę. Powiedzieliśmy, że chcemy, aby każda linia tekstu na stronie była drukowana pojedynczo. Po wciśnięciu entera dana linijka powinna przejść do następnego wyjścia. Oto jak wyglądałaby ta funkcja. Funkcja pobiera listę wierszy z tekstowej części strony. Następnie iteruje po liniach, powoli wpisując jedną, a następnie czeka na naciśnięcie klawisza Enter przed przejściem do następnej.

def display_page_text(lines: list):  
    for line in lines:
       slow_type(line)
       # Make the user press enter to see the next line 
       get_input([''])


Chwila, chwila! Wiem, że właśnie użyłem get_input () bez pokazywania, jak nasza funkcja wygląda. Przyjrzyjmy się teraz temu, jak wygląda pobieranie naszych wartości wejściowych.

Pobieranie danych wejściowych

Pobieranie danych wejściowych z wiersza poleceń nie jest takie złe. Najpierw utworzymy funkcję, której jedynym celem jest pobieranie danych wejściowych. Przekażemy jej listę prawidłowych ciągów wejściowych.

Następnie pobieramy to, co wpisał użytkownik. Jeśli nie są one wymienione na liście prawidłowych danych wejściowych, to powiadamiamy użytkownika, wskazując mu prawidłowe, a następnie oczyszczamy input. W przeciwnym razie zwracamy to, co ktoś wpisał.

def get_input(valid_input: list):  
    while True:    
        user_entered = input()    
        if user_entered not in valid_input:      
            print("Invalid input. Please use one \
                   of the following inputs:\n")
            print(valid_input)      
            user_entered = None    
        else:
            return user_entered


Wywołujemy get_input z [‘’], jak wspomniano wcześniej, jeśli spodziewamy się entera. W przeciwnym razie głównym zastosowaniem do pobierania danych wejściowych jest podjęcie decyzji, do której strony przejść dalej. To zadanie funkcji get_response. Przekazywana jest lista krotek, które reprezentują opcje. Zawierają one wybraną opcję, a następnie numer strony, czyli: („Option 1”, 2).

Get_response iteruje po krotkach, wypisując liczbę dla opcji i tekst opcji. Następnie przekazuje prawidłowe dane wejściowe (indeksy opcji), aby uzyskać funkcję input, która pobiera dane wejściowe użytkowników. Na koniec zwraca numer następnej strony.

def get_response(options: list):
    for index, option in enumerate(options): 
        print(index + “. “ + option[0]) 
    
    valid_inputs = [str(num) for num in range(len(options))]
    option_index = int(get_input(valid_inputs))
 
    return options[option_index][1]

Składamy wszystko w całość

Ostatnią rzeczą, którą musimy zrobić, jest zebranie wszystkiego razem. Po załadowaniu aplikacji musimy załadować pierwszą stronę. Następnie tworzymy pętlę programu. Aby wyjść z pętli, ustawimy stronę na none. W rezultacie otrzymamy bieżącą stronę ze słownika story. Jeśli nie ma żadnej strony, musimy wyjść z pętli. Jeśli nie mamy strony z wymienionym indeksem, program bezpiecznie zakończy pracę.

Po pobraniu strony wyświetlimy jej tekst za pomocą funkcji, którą zdefiniowaliśmy wcześniej. Po przejściu przez tekst strony otrzymamy możliwe opcje, które pozwolą przejść do kolejnej strony. Jeśli na liście nie ma żadnych opcji, powiemy, że historia jest zakończona i możemy ją zamknąć. Jeśli istnieją opcje, użyjemy naszej funkcji get_response, aby je uzyskać.

def story_flow(story: dict):  
    curr_page = 1   
    while curr_page != None:    
        page = story.get(curr_page, None)
        if page == None:
            curr_page = None
            break
  
        display_page_text(page['Text'])
        
        if len(page['Options']) == 0:      
            curr_page = None      
            break     
        
        curr_page = get_response(page['Options'])


Na koniec nasz skrypt załaduje plik pickle, aby odtworzyć historię:

import pickle
if __name__ == "__main__":
    story= {}
    with open('chapter1.ch', 'rb') as file:
        story = pickle.load(file)
    story_flow(story)   


To by było na tyle! Sprawdź moje repozytorium na Githubie, aby zobaczyć działający przykład. Jeśli postępowaliście zgodnie z powyższą instrukcją, to właśnie udało Wam się zaimplementować mapę!


Oryginał tekstu w języku angielskim możesz przeczytać tutaj

<p>Loading...</p>