Wiemy już jak tworzyć duszki i poruszać nimi. Ale dało się zauważyć, że duszkom coś brakuje. Kolizji.
Czym są kolizje? Jeśli próbujesz wyjść z pokoju przez ścianę, to nastąpi kolizja ze ścianą, która nie wypuści cie z niego. Jeśli kolizji by nie było, jak w naszym ostatnim przykładzie, to można by było przechodzić przez ściany jak duchy.
Kiedy więc zachodzi kolizja? Gdy dwa obiekty w grze, zachodzą na siebie, czyli jakaś część duszka, znajduje się w tym samym miejscu ekranu, co inny duszek.
Metody wykrywania kolizji
Jest wiele sposobów wykrywania kolizji. Najdokładniejszym jest sprawdzanie każdego piksela, duszka z innym duszkiem. Metoda ta ma jednak wady. Jest bardzo czasochłonna. W grze, gdzie wszystko dynamicznie się zmienia, nie jest konieczna aż taka dokładność.
Kolizja okręgów
Najprostszym sposobem jest przybliżenie obiektu do okręgu.

Jak to zaimplementować? Ponieważ każdy obrazek jest czworokątem, dzielimy jego przekątną przez 2. W wyniku otrzymamy promień okręgu dla kolizji. Przekątna tworzy 2 trójkąty prostokątne, więc do obliczania jej długości, możesz wykorzystać wzór promien = SQRT(a2+b2)/2. Gdzie SQRT to pierwiastek kwadratowy, a2 i b2 to przyprostokątne trójkąta podniesione do kwadratu.
Jeśli teraz dodamy promień jednego obiektu, do promienia drugiego obiektu, otrzymamy odległość, na którą mogą się maksymalnie zbliżyć duszki do siebie. Pozostaje nam tylko obliczyć odległość między środkami tych okręgów. Pomoże nam tu twierdzenie Pitagorasa, które mówi ze suma kwadratów długości przyprostokątnych, równa się kwadratowi długości przeciwprostokątnej.

Jak widać na rysunku, aby obliczyć odległość środków, musimy obliczyć długość przyprostokątnych. Aby wyznaczyć te długości, odejmujemy od siebie współrzędne (x2-x1) i (y2-y1) i zgodnie z prawem Pitagorasa podnosimy do kwadratu bok1 = (x2-x1)^2, bok2 = (y2-y1)^2. Po dodaniu do siebie obu boków bok1+bok2, otrzymamy długość przeciwprostokątnej, która jest odległością, między środkami tych okręgów, podniesioną do kwadratu.
Mamy więc odległości między punktami i zgodnie z tym co napisałem wyżej o promieniach, musimy sumę promieni obu okręgów podnieść do kwadratu (r1+r2)^2. Jest to konieczne, ponieważ obie długości przyprostokątnych także są podniesione do kwadratu. Ostatnią rzeczą jaką trzeba zrobić, to sprawdzić czy odległość między środkami, jest większa lub równa sumie promieni okręgów, wtedy mamy kolizję.
odleglosc = (x2-x1)^2 + (y2-y1)^2
promienie = (r1+r2)^2
kolizja nastąpi, gdy odleglosc jest wieksza niż promienie
Poniżej gotowa funkcja wykrywania kolizji między okręgami.
def collision(x1,y1,r1,x2,y2,r2):
if (x2-x1)**2+(y2-y1)**2<=(r1+r2)**2:
return True
else:
return False
Jak widać po kodzie jest to naprawdę proste. Do funkcji przekazujemy współrzędne xy obiektów oraz promień r. Przykładowy kod użycia kolizji okręgów:
import pygame # importujemy biblioteki pygame
from pygame.locals import * # importujemy nazwy [QUIT, KEYDOWN,K_ESCAPE] itp.
from sys import exit # importujemy funkcje systemowa exit
screen_size = (800,600) # ustalamy rozmiar ekranu
class IsoGame(object):
def __init__(self):
pygame.init() # incjalizujemy biblioteke pygame
flag = DOUBLEBUF # wlaczamy tryb podwojnego buforowania
# tworzymy bufor na grafike
self.surface = pygame.display.set_mode(screen_size,flag)
# zmienna stanu gry
self.gamestate = 1 # 1 - run, 0 - exit
self.images = [None] *2
self.images[0] = pygame.image.load('circle1.png')
self.images[1] = pygame.image.load('circle2.png')
self.circle_x = 220 # pozycja x duszka
self.circle_y = 150 # pozycja y duszka
# obliczamy promien duszka
self.radius = self.images[0].get_width()/2
self.speed = 1.2 # szybkosc poruszania duszka
self.player_x = 50 # pozycja x duszka na ekranie
self.player_y = 30 # pozycja y duszka na ekranie
self.loop() # glowna petla gry
def move(self,dirx,diry):
""" poruszanie duszkiem """
dx = self.player_x + (dirx * self.speed)
dy = self.player_y + (diry * self.speed)
if self.collision(dx,dy,self.radius,self.circle_x,self.circle_y,self.radius):
return
self.player_x = dx
self.player_y = dy
def game_exit(self):
""" funkcja przerywa dzialanie gry i wychodzi do systemu"""
exit()
def collision(x1,y1,r1,x2,y2,r2):
if (x2-x1)**2+(y2-y1)**2<=(r1+r2)**2:
return True
else:
return False
def loop(self):
""" glowna petla gry """
while self.gamestate==1:
player_anim = 0
for event in pygame.event.get():
if event.type==QUIT or (event.type==KEYDOWN and event.key==K_ESCAPE):
self.gamestate=0
keys = pygame.key.get_pressed() # odczytujemy stan klawiszy
if keys[K_s]:
self.move(0,1) # ruch w dol
if keys[K_w]:
self.move(0,-1) # ruch w gore
if keys[K_d]:
self.move(1,0) # ruch w prawo
if keys[K_a]:
self.move(-1,0) # ruch w lewo
self.surface.fill((0,0,0)) # czyscimy ekran, malo wydajne ale wystarczy
# umieszczamy gracza
self.surface.blit(self.images[1],(self.player_x,self.player_y))
self.surface.blit(self.images[0], (self.circle_x,self.circle_y))
pygame.display.flip() # przenosimy bufor na ekran
self.game_exit()
if __name__ == '__main__':
IsoGame()
Pobierz paczkę z przykładem isogame_tut3.zip
Wykrywanie kolizji okręgów nadaje się dobrze, gdy duszki mają zbliżony kształt do okręgu. Gdy są kwadratami, prostokątami, czy w postaci igły, to będzie bardzo niedokładne. Do takich obiektów, można zastosować kolizje między prostokątami.
Kolizja prostokątów
W tej metodzie, kolizja nastąpi wtedy, gdy jeden róg prostokąta znajdzie się w polu innego prostokąta.

Poniżej funkcja Collision(), która zwraca informacje, czy wystąpiła kolizja między prostokątami.
def collision(self,x1,y1,w1,h1,x2,y2,w2,h2):
if x1 >= x2+w2: return True
if x1+w1 <= x2: return True
if y1 >= y2+h2: return True
if y1+h1 <= y2: return True
return False
Kod jest prosty i myślę, że nie wymaga komentarza. Funkcja przyjmuje argumenty w postaci pozycji prostokąta x1,x2 – współrzędna x lewego górnego rogu pierwszego i drugiego prostokąta. y1,y2 – współrzędna y lewego górnego rogu pierwszego i drugiego prostokąta. h1,h2 – wysokość pierwszego i drugiego prostokąta. w1,w2 – szerokość pierwszego i drugiego prostokąta. Zasadza działania polega na tym, że przy każdym ruchu, porównywana jest pozycja prostokątów względem siebie, jeśli któryś warunek jest spełniony, to znaczy, że nastąpiła kolizja. Poniżej kod, który pozwala przetestować działanie kolizji prostokątów.
import pygame # importujemy biblioteki pygame
from pygame.locals import * # importujemy nazwy [QUIT, KEYDOWN,K_ESCAPE] itp.
from sys import exit # importujemy funkcje systemowa exit
screen_size = (800,600) # ustalamy rozmiar ekranu
class IsoGame(object):
def __init__(self):
pygame.init() # incjalizujemy biblioteke pygame
flag = DOUBLEBUF # wlaczamy tryb podwojnego buforowania
# tworzymy bufor na grafike
self.surface = pygame.display.set_mode(screen_size,flag)
# zmienna stanu gry
self.gamestate = 1 # 1 - run, 0 - exit
self.images = [None] *2
self.images[0] = pygame.image.load('rectangle1.png')
self.images[1] = pygame.image.load('rectangle2.png')
self.sprite_x = 220 # pozycja x duszka
self.sprite_y = 130 # pozycja y duszka
self.speed = 1.2 # szybkosc poruszania duszka
self.player_x = 320 # pozycja x duszka na ekranie
self.player_y = 250 # pozycja y duszka na ekranie
self.loop() # glowna petla gry
def move(self,dirx,diry):
""" poruszanie duszkiem """
dx = self.player_x + (dirx * self.speed)
dy = self.player_y + (diry * self.speed)
if not self.collision(dx,dy,100,100,self.sprite_x,self.sprite_y,100,100):
return
self.player_x = dx
self.player_y = dy
def game_exit(self):
""" funkcja przerywa dzialanie gry i wychodzi do systemu"""
exit()
def collision(self,x1,y1,w1,h1,x2,y2,w2,h2):
if x1 >= x2+w2:
return True
if x1+w1 <= x2:
return True
if y1 >= y2+h2:
return True
if y1+h1 <= y2:
return True
return False
def loop(self):
""" glowna petla gry """
while self.gamestate==1:
player_anim = 0
for event in pygame.event.get():
if event.type==QUIT or (event.type==KEYDOWN and event.key==K_ESCAPE):
self.gamestate=0
keys = pygame.key.get_pressed() # odczytujemy stan klawiszy
if keys[K_s]:
self.move(0,1) # ruch w dol
if keys[K_w]:
self.move(0,-1) # ruch w gore
if keys[K_d]:
self.move(1,0) # ruch w prawo
if keys[K_a]:
self.move(-1,0) # ruch w lewo
self.surface.fill((0,0,0)) # czyscimy ekran, malo wydajne ale wystarczy
# umieszczamy gracza
self.surface.blit(self.images[1],(self.player_x,self.player_y))
self.surface.blit(self.images[0], (self.sprite_x,self.sprite_y))
pygame.display.flip() # przenosimy bufor na ekran
self.game_exit()
if __name__ == '__main__':
IsoGame()
Dla uproszczenia kodu, wpisałem na stałe wysokość i szerokość prostokątów, ale jest to w naszym przypadku, rozmiar wczytaj grafiki. Wiec możemy ustawić dynamiczną wielkość prostokąta, zależnie od wczytanego obrazka.
sprite_width = images[0].get_width() sprite_height = images[0].get_height()
Przykładowy kod z obrazkami do pobrania: isogame_tut3a
Jako zadanie domowe, przerobić przykładowy program, aby wielkość prostokąta była pobierana bezpośrednio z wczytanego obrazka
Kolizja okręgu z prostokątem
Ostatnią metodę, jaką opiszę, to kolizje prostokąta z okręgiem.

Najpierw wyznaczamy najbliższy wierzchołek prostokąta do środka okręgu. A następnie obliczamy odległość między tym wierzchołkiem a środkiem okręgu, za pomocą twierdzenia Pitagorasa. Funkcja wykrywająca kolizje wygląda tak:
def collision(x,y,w,h,cx,cy,r): # ustalamy pozycje najblizszego wierzcholka do srodka okregu tx = cx ty = cy if cx < x: tx = x if cx >= (x + w): tx = x + w if cy < y: ty = y if cy >= (y + h): ty = y + h # sprawdzamy odleglosc miedzy wierzcholkiem a srodkiem okregu # jesli mniejsza niz promien, oznacza to kolizje if (cx-tx)**2+(cy-ty)**2 < r**2: return True return False
Kod jest prosty, odpowiednio skomentowany, widać co i jak. Udostępniam przykładowy program, który pokazuje wykorzystanie naszej funkcji, kolizji okręgu z prostokątem.
Pobierz przykładowy program isogame_tut3b
Uff. Doszliśmy do końca. Na koniec coś przyjemniejszego, wykorzystanie naszej wiedzy w małym demku, w którym poruszasz się statkiem i omijasz asteroidy. W programie jest wiele uproszczeń, które w normalnej produkcji się nie robi, ale może być bazą do dalszego rozwoju. Zapraszam do analizy kodu. Ja tylko wyjasnię linie 82
if self.collision(self.player.x+40,self.player.y,self.player.width-40,\
self.player.height,self.asteroids[n].x+32,self.asteroids[n].y+32,32):
Zastosowałem tu wspomniane uproszczenia, dodałem do pozycji sprawdzania kolizji na osi x +40, aby nie było kolizji z ogniem, wydobywającego sie z silnika statku, automatycznie trzeba obciąć width, aby kolizja mieściła się w zakresie wielkości obrazka. Podobnie zrobiłem przy asteroidzie, dodałem do pozycji x i y +32. Ponieważ asteroida ma 64x64px, a x,y wskazuje na lewy górny róg obrazka, trzeba dodać połowa wysokości i szerokość obrazka, aby kolizja była sprawdzana od środka okręgu a nie od krawędzi. Ostatnia wartość to promień asteroidy. Można to to zrobić bardziej elegancko, i pobierać te wartości dynamicznie, ale nie chciałem zaciemniać i tak dość rozbudowanego jak na początek kodu, a który nie jest celem tego kursu
Pobierz demo asteroids.zip

Pygame, ma wbudowany moduł RECT, który obsługuje kolizje prostokątów, jako zadanie domowe, zastosować ten moduł w naszym demie Asteroids.
Mam nadzieję, że z ciekowością i zapałem przebrnęliście przez te 3 lekcje, bo na tym kończymy obsługę grafiki w czystym Pygame. Kolejny etap, to przejście na obsługę OpenGL, który daje wyraźną różnice w wydajności i do naszych celów bardziej się nada. W kolejnej lekcji dowiesz się jak wyświetlać duszki w OpenGL.
Strona jest fajna i bardzo przejrzysta, ale pod artykuami brakuje linku „Dalej” albo „Przejdz do nastepnej lekcji”
Na razie lekcji jest mało, wszystkie widać z boku na liście
Ale dzięki za spostrzeżenie, dodam odpowiednie linki.
Hello, nice tutorial.
But when I import the sample .py’s there are often indent errors.
Thanks for info, i’ll try to fix it