Dzisiaj wejdziemy w buty Developera gier komputerowych który dołączył do projektu gdzie zostają tworzone stare gry retro. Na pierwszy ogień trafiła nam się gra z lat 80, Snake.
Gra ta polega na tym, że gracz steruje wężem, który porusza się po planszy i zbierając jedzenie, próbuje nie uderzyć własną głową o ściany otaczające plansze jak również o część swojego ciała. Kiedy wąż zje jedzenie, wydłuża się co przyczynia się do utrudnienia gry. Gracz kontroluje kierunek ruchu węża za pomocą klawiszy strzałek, wąż porusz się bez przerwy. Zatrzymuje go jedynie zakończenie gry.
Spis treści
Analiza przed pracą
Zanim zaczniemy pisać program przeanalizujmy co wiemy o tej grze i co z tego wynika
- Wiemy, że gracz steruje postacią którą jest tytułowy snake, któremu po zjedzeniu posiłku wydłuża się ogon
- Będziemy potrzebowali również obiektu jakim jest jabłko na które będzie polować nasza postać
- Potrzebujemy również stworzenie planszy po której snake będzie się poruszał
Musimy więc stworzyć klasę która umożliwi nam stworzenie naszego węża i metody w niej zawartej muszą rozwiązać problem poruszania się, wydłużania ogona naszego węża i w wypadku uderzenia głową w siane zresetowania go
Potrzebujemy również klasę jabłka której metody będzie umożliwiać nam wyrysowania go w różnych położeniach na planszy
Będziemy musieli również przygotować funkcję do stworzenia naszej planszy
Jak również na końcu musimy zająć się zarządzeniem wszystkich naszych elementów w pętli głównej
Tworzenie klas i metod
Pierwsze zajmiemy się stworzeniem klasy naszej postaci grywalnej czyli Snaka
class Snake(): def __init__(self): pass def pozycja_Glowy(self): pass def kierunek(self): pass def ruch(self): pass def reset(self): pass def rysowanie(self): pass
Klasa ta będzie posiadać metodę pozycja _Glowy() która będzie nam zwracać pozycje naszego węża, kierunek() będzie służył do określenia kierunku poruszania się węża. Metoda ruch() będzie odpowiedzialna za przemieszczanie się węza a rysowanie() za wyświetlenie go na ekranie. Ostatnia metoda reset() będzie odpowiadała, za zresetowanie węża kiedy uderzy głową o ścianę, albo o swoje ciało
Klasa Jabłko
class Jablko(): def __init__(self): pass def pozycja_Losowa(self): pass def rysuj(self,): pass
Klasa ta jest dość prosta, posiada ona metodę rusuj() która będzie odpowiadać za wyświetlenie naszego celu, a metoda pozycja_Losowa() ma odpowiadać za to by jabłko pokazywało się w losowych miejscach na planszy
I na koniec zostały nam dwie funkcje
def rysowanieSiatki(): pass def main(): pass
rysowanieSiatki() będzie wyrysowywała plansze po której będziemy się poruszać, a w main() znajdzie się definicja okna gry, pętla główna gry, sterowanie itp.
Wypełnianie klas
Klasy Snake
Pierwsze zdefiniujemy zmiennej naszej klasy
def __init__(self): self.__dlugosc = 1 self.__pozycja = [((szerokosc_Okna / 2), (wysokosc_Okna / 2))] self.__kierunek_ruchu = gora self.punkty = 0 self.kolor = (0, 255, 0)
Nie chcemy by nasze zmienne były dostępne poza klasą ( z wyjątkiem koloru i punktów) dlatego zastosujemy tutaj hermetyzacje (enkapsulacje).
- self.__dlugosc – definiuje ilość fragmentów ogona, jago długość
- self.__pozycja – lista w której będą zapisywane pozycja
- self.__kierunek_ruchu – kierunek poruszania się
- self.punkty – ilość zdobytych punktów
- self.kolor – kolor naszej postaci
Następnie wypełniamy trzy metody które będą wykorzystane w metodzie ruch()
def pozycja_Glowy(self): return self.__pozycja[-1] def kierunek(self, kierunek_ruchu): # Jeśli wąż ma ogon nie może się cofnąć if self.__dlugosc > 1 and (kierunek_ruchu[0] * -1, kierunek_ruchu[1] * -1) == self.__kierunek_ruchu: return else: self.__kierunek_ruchu = kierunek_ruchu def reset(self): self.__dlugosc = 1 self.__pozycja = [((szerokosc_Okna / 2), (wysokosc_Okna / 2))] self.__kierunek_ruchu = random.choice([gora, dol, lewo, prawo]) self.__punkty = 0
Metoda pozycja_Głowy() będzie zwracała nam pozycje głowy naszego węża, a kierunek(kierunek_ruchu) zapewnia, że jeśli wąż będzie miał ogon nie będzie mógł zawrócić (warunek gry )
a gdzie reset() jest to metoda odpowiadająca za ustawienie węża w pozycji domyślnej
Teraz wypełnimy metodę odpowiedzialną za poruszanie się naszego węża, będzie ona korzystać z powyższych metod.
def ruch(self): glowa = self.pozycja_Glowy() x,y = self.__kierunek_ruchu # Nowa pozycja Snake na bazie pozycji jego głowy nowa_Pozycja = (((glowa[0] + (x * rozmiar_Kratki)) % szerokosc_Okna), (glowa[1] + (y * rozmiar_Kratki)) % wysokosc_Okna) # Jeśli nowa pozycja jest w miejscu ogona, Snake jest restowany if len(self.__pozycja) > 2 and nowa_Pozycja in self.__pozycja[2:]: self.reset() # Jeśli nowa pozycja będzie się równać krawędzi scian wąż zostanie zresetowany elif ((nowa_Pozycja[1]) == 0 and y == 1) or ((nowa_Pozycja[1]) == 460 and y == -1): self.reset() elif ((nowa_Pozycja[0]) == 0 and x == 1) or ((nowa_Pozycja[0]) == 460 and x == -1): self.reset() else: # Przypisanie nowej pozycji do listy __pozycja self.__pozycja.append(nowa_Pozycja) # Usuwanie starej pozycji lub ominięcie kroku w wyniku snek się wydłuża if len(self.__pozycja) > self.__dlugosc: del self.__pozycja[0]
Pierwszym zadaniem tej metody jest ustalenie nowego położenia naszego węża, które zostanie zapisane w zmiennej nowa_Pozycja
Następnie sprawdza czy wąż nie uderzył głową w którąś ze ścian lub w samego siebie
Jeśli nie to pozycja jest dodana do listy self.__pozycja
Ostatni warunek sprawdza czy długość pozycji jest większa od długości węża
Jeśli tak, oznacza to, że mamy za dużo danych w naszej tablicy i musimy usunąć pierwszy element, ponieważ ostatni to nowa pozycja głowy węza
Jeśli nie, oznacza to, że posiadamy współrzędne dla głowy naszego węża i członów jego ogona
Ostatnie metody w klasie Snake wyglądają następująco
def rysowanie(self, okno_Gry): # Odczytywanie listy od końca ponieważ nowa pozycja głowy jest na końcu listy # Dalsze współżędne na liście wskazują położenie części ogona węża for p in self.__pozycja[::-1]: r = pygame.Rect((p[0], p[1]), (rozmiar_Kratki, rozmiar_Kratki)) pygame.draw.rect(okno_Gry, self.kolor, r) pygame.draw.rect(okno_Gry, (128, 128, 128), r, 1) def zjedzenie(self): self.__dlugosc += 1 self.__punkty += 1
zjedzenie() odpowiada za dodanie długości ogona i punktów kiedy wąż zje jabłko
rysowanie() odpowiadająca za przedstawienie graficzne naszej postaci
Klasa Jablko
Ta klasa w porównaniu do Snake jest prostsza, ale równie ważna dla gry
class Jablko(): def __init__(self): self.pozycja = (0, 0) self.kolor = (255, 0, 0) self.pozycja_Losowa() def pozycja_Losowa(self): self.pozycja = (random.randint(0, szerokosc_Kratki - 1) * rozmiar_Kratki, random.randint(0, wysokosc_Kratki - 1) * rozmiar_Kratki) def rysuj(self, okno_Gry): r = pygame.Rect((self.pozycja[0], self.pozycja[1]), (rozmiar_Kratki, rozmiar_Kratki)) pygame.draw.rect(okno_Gry, self.kolor, r)
Mamy tutaj tylko dwie metody pozycja_Losowa() oraz rysuj(okno_Gry)
pozycja_Losowa() odpowiada za przypisanie do zmiennej self.pozycja losową wartość w wyniku czego otrzymujemy jabłko w różnych miejscach na planszy
Metoda rysuj(okno_Gry) jest już nam znana i odpowiada za wyrysowanie obiektu klasy na planszy
Metody gry
Pierwsze zajmiemy się metodą która będzie rysować nam siatkę na naszej planszy
def rysowanieSiatki(surface): # Linia pozioma surface.fill((0, 0, 0)) for i in range(0, int(szerokosc_Kratki)): pygame.draw.line(surface, (128, 128, 128), (0 , i * rozmiar_Kratki), (szerokosc_Okna, i * rozmiar_Kratki)) # vertical lines for j in range(0, int(wysokosc_Kratki)): pygame.draw.line(surface, (128, 128, 128), (j * rozmiar_Kratki, 0), (j * rozmiar_Kratki, wysokosc_Okna))
Jest to dość prosta metoda której raczej nie trzeba tłumaczyć
W końcu się zajmiemy mechaniką działania naszej gry i zobaczymy co nam wyjdzie
Zdefiniujemy metode main() w której będzie znajdywać się metoda rysowania okna, pętla główna gry i sterowanie wężem
def main(): pygame.init() # Inicjalizacja okna gry okno_Gry= pygame.display.set_mode((szerokosc_Okna, wysokosc_Okna), 0, 32) pygame.display.set_caption("SNAKE") # Wyrasowanie siatki rysowanieSiatki(okno_Gry) # Inicjalizacja obiektów klas snake = Snake() jablko = Jablko() czcionka = pygame.font.SysFont('comicsans', 30)
W pierwszej kolejności zainicjujemy wszystko co jest nam potrzebne, aby nasza gra mogła działać
teraz zabierzemy się za pętle główną
run = True while (run): pygame.time.delay(100) # Wyłączenie gry for event in pygame.event.get(): if event.type == pygame.QUIT: run = False # Sterowanie do Snake'a elif event.type == pygame.KEYDOWN: if event.key == pygame.K_UP: snake.kierunek(gora) elif event.key == pygame.K_DOWN: snake.kierunek(dol) elif event.key == pygame.K_LEFT: snake.kierunek(lewo) elif event.key == pygame.K_RIGHT: snake.kierunek(prawo)
Pierwsze wprowadziliśmy warunki które mają sprawdzać w którym kierunku ma się poruszać wąż, jak również możliwość wyłączenia okna gry
Następnie poniżej dodamy warunek sprawdzający czy wąż zjadł jabłko
snake.ruch() if snake.pozycja_Glowy() == jablko.pozycja: snake.zjedzenie() jablko.pozycja_Losowa()
uruchamiamy metodę snake.ruch() i sprawdzamy czy jego pozycja równa się pozycji jabłka. Po spełnieniu warunku w if’e aktywujemy metodę snake.zjedzenie() co powoduje wydłużenie węża i dodanie pkt graczowi
Jak również używamy jablko.pozycja_Losowa(), aby wyrysować jabłko w nowej pozycji
I na koniec meina wyrysowujemy wszystkie elementy gry
rysowanieSiatki(okno_Gry) snake.rysowanie(okno_Gry) jablko.rysuj(okno_Gry) okno_Gry.blit(okno_Gry, (0, 0)) wynik = czcionka.render("Punkty {0}".format(snake.punkty), 1, (255, 255, 0)) okno_Gry.blit(wynik, (5, 10)) pygame.display.update()
W efekcie cała metoda main() wygląda następująco
def main(): pygame.init() # Inicjalizacja okna gry okno_Gry= pygame.display.set_mode((szerokosc_Okna, wysokosc_Okna), 0, 32) pygame.display.set_caption("SNAKE") # Wyrasowanie siatki rysowanieSiatki(okno_Gry) # Inicjalizacja obiektów klas snake = Snake() jablko = Jablko() czcionka = pygame.font.SysFont('comicsans', 30) run = True while (run): pygame.time.delay(100) # Wyłączenie gry for event in pygame.event.get(): if event.type == pygame.QUIT: run = False # Sterowanie do Snake'a elif event.type == pygame.KEYDOWN: if event.key == pygame.K_UP: snake.kierunek(gora) elif event.key == pygame.K_DOWN: snake.kierunek(dol) elif event.key == pygame.K_LEFT: snake.kierunek(lewo) elif event.key == pygame.K_RIGHT: snake.kierunek(prawo) snake.ruch() if snake.pozycja_Glowy() == jablko.pozycja: snake.zjedzenie() jablko.pozycja_Losowa() rysowanieSiatki(okno_Gry) snake.rysowanie(okno_Gry) jablko.rysuj(okno_Gry) okno_Gry.blit(okno_Gry, (0, 0)) wynik = czcionka.render("Punkty {0}".format(snake.punkty), 1, (255, 255, 0)) okno_Gry.blit(wynik, (5, 10)) pygame.display.update()
Kiedy uruchomimy nasz program naszym oczom ukaże się nasze dzieło, czyli działająca gra. Możemy być z siebie bardzo dumni !
Cały kod gry możesz znaleźć z repozytorium GitHuba
Podsumowanie
Dzisiaj zobaczyliśmy jak za pomocą biblioteki pygame można zrobić naprawdę fajną grę która z 25 lat temu była szczytem marzeń dzieciaków. Zobaczyliśmy, że jesteśmy wstanie tworzyć już naprawdę dość ciekawe projekty i w dalszych częściach artykułów będziemy tworzyć coraz bardziej zaawansowane, ale również ciekawsze gry.