Opis problemu:
Stworzyć prosty generator cząsteczek. Ma to być okno w którym po kliknięciu myszką we wszystkie kierunki lecą kolorowe kuleczki.Zastosowane technologie:
Python, Pygame - biblioteka oparta na SDL, z którym miałem do czynienia przy okazji jednego projektu z C++.Potyczka!
Od pewnego czasu przymierzam się do nauczenia się Python'a. W tym tygodniu z racji braku konkretnych zajęć i poważniejszych obowiązków stwierdziłem, że będzie świetna okazja żeby się w końcu do tego zabrać. Ale jak to bywa z nauką nowego języka, samą teorią nic się nie zdziała, trzeba coś w tym napisać. Padło na prosty generator cząsteczek.Co do wyboru pygame jako biblioteki to nie bardzo miałem ochotę na robienie jakiegoś projektu backendowego, za to do robienia około-growych rzeczy zawsze znajduje w sobie zacięcie. No i jak już wspominałem miałem bazę, bo kiedyś zrobiłem sidesroller'a w C++ z wykorzystaniem SDL.
Świetne materiały do nauki tej biblioteki można znaleźć TUTAJ. Gorąco polecam, darmową książkę Albert'a Sweigart'a, miło się czyta i wszystko jest prosto wytłumaczone.
Teraz już do samego projektu.
Dla przejrzystości kod podzieliłem na dwa moduły:
class Particle():
"""simple particle class"""
def __init__(self,x,y,xVel,yVel,color,screenWidth,screenHeight):
self.x = x
self.y = y
self.startX = x
self.startY = y
self.xVel = xVel
self.yVel = yVel
self.scrWidth = screenWidth
self.scrHeight = screenHeight
self.color = color
def update(self):
self.x += self.xVel
self.y += self.yVel
if self.x > self.scrWidth
or self.y > self.scrHeight
or self.x < 0 or self.y < 0:
self.x = self.startX
self.y = self.startY
Jak widać nie jest to jakoś specjalnie rozbudowane. W konstruktorze inicjujemy podstawowe wartości - 'x' i 'y' to nasze współrzędne początkowe, 'xVel' i 'yVel' to wektory przesunięcia reszty już nie będę tłumaczył. Powiem tylko, że 'self.x' i 'self.y' to aktualne położenie cząsteczki, natomiast 'self.startX' i 'self.startY' to jej punkt startowy. Będzie do niego wracać po wypadnięciu z ekranu.Co się dzieje w metodzie update? Aktualizujemy pozycję cząsteczki i, jeżeli jest poza ekranem, sprowadzamy ją z powrotem do punktu startowego.
Jak widać sama cząsteczka nie jest specjalnie skomplikowana - nie używa też żadnych metod z pygame.
Moduł particlesMaker
Zaczynamy kilkoma importami i deklaracją potrzebnych zmiennych:
import pygame,sys,particles,random from pygame.locals import * from collections import deque #definitions of global constans SCRWIDTH = 800 SCRHEIGHT = 600 MAXPARTICLES = 800 FPS = 40 NEWPARTICLES = 2 MAXXVEL = 4 MAXYVEL = 4 #colors #name R G B GRAY = (100, 100, 100) NAVYBLUE = ( 60, 60, 100) WHITE = (255, 255, 255) RED = (255, 0, 0) GREEN = ( 0, 255, 0) BLUE = ( 0, 0, 255) YELLOW = (255, 255, 0) ORANGE = (255, 128, 0) PURPLE = (255, 0, 255) CYAN = ( 0, 255, 255) ALLCOLORS = [RED, GREEN, GRAY, BLUE, YELLOW, ORANGE, PURPLE, CYAN, NAVYBLUE]No dobra, w pierwszej linijce importujemy potrzebne nam moduły, w tym nasz moduł particles. W drugiej linijce mamy import wszystkiego z pygame.locals - są tam deklaracje stałych takich jak typy eventów i dzięki temu importowi możemy się do nich odwoływać bezpośrednio, a nie przez pygame.locals.NAZWA_STALEJ.
W trzeciej linii importuje klasę deque, będącą zgrabną implementacją kolejki (my wykorzystamy ją konkretnie jako FIFO). Będziemy w niej przechowywać nasze cząsteczki.
Ze stałych wyjaśnię tylko 'MAXPARTICLES' - będzie to liczba cząsteczek do wygenerowania w każdej klatce.
Dalej mamy definicję kolorów i wrzucenie ich do jednej listy. Kolory w pygame są reprezentowane przez trójkę wartości z zakresu 0-255, odpowiadającą wartością RGB.
Lecimy dalej.
def main():
pygame.init()
SCREEN = pygame.display.set_mode((SCRWIDTH,SCRHEIGHT))
pygame.display.set_caption("Particles generator")
CLOCK = pygame.time.Clock()
particles = deque([],MAXPARTICLES)
mouseDown = False
mousePos = (None,None)
running = True
while running:
SCREEN.fill(WHITE)
#events handling
for event in pygame.event.get():
if event.type == QUIT:
running = False
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
pause()
elif event.type == MOUSEBUTTONDOWN:
mouseDown = True
mousePos = event.pos
elif event.type == MOUSEBUTTONUP:
mouseDown = False
elif event.type == MOUSEMOTION:
mousePos = event.pos
#add new particles if mouse button is hold
if mouseDown:
generateParticles(particles,mousePos)
#draw particles
for part in particles:
part.update()
pygame.draw.circle(SCREEN,part.color,(part.x,part.y),2)
pygame.display.flip()
CLOCK.tick(FPS)
pygame.quit()
sys.exit()
main()Zaczynamy od odpalenia pygame przez funkcję 'init()'. Bez tego nic nie ruszy.
Dalej inicjujemy "powierzchnię" do której będziemy rysować. Ogólnie wygląda to tak, że na monitor rzucamy przygotowany wcześniej pojedynczy obraz. Dla przykładu gdy mamy 3 drzewa, gracza i pieska to nie wyświetlamy najpierw tła, potem drzew, potem... Wrzucamy to wszystko na jeden "obrazek" i dopiero ten "obrazek" wyświetlamy na monitorze.
4 linijka to ustawienie tytułu okna, 5 to inicjacja zegara (wyjaśnię po co za chwilę). Dalej mamy konstrukcję naszej kolejki, pierwszy argument to dane jakie zostaną dodane, chcemy pustą kolejkę więc jest tam pusta lista. Drugi argument to maksymalny rozmiar kolejki, bardzo fajna sprawa, bo myślałem, że będę musiał sprawdzać ręcznie czy się nie przepełniła. A tak gdy przekroczymy dozwoloną ilość cząsteczek to kolejka pozbędzie się kilku "najstarszych" elementów.
Dalej mamy zmienne sterujące.
Gra będzie działała w pętli while, z każdym przebiegiem wykonując 3 główne typy zadań:
Zaczynamy od wypełnienia całego naszego ekrany białym tłem - kasujemy w ten sposób to co działo się w poprzedniej klatce. W pętli for obsługujemy zdarzenia. Ich listę zwraca nam pygame.event.get(). Dalej sprawdzamy jaki event nastąpił i przeprowadzamy odpowiednią akcję. Nazwy eventów raczej mówią same za siebie: QUIT - dla przykładu to kliknięcie "krzyżyka" zamykającego okno. Na kliknięcie escape wywołuję pauzę, ale jeszcze nie chciało mi się jej implementować, więc to nic nie robi. Kliknięcie przycisku myszy przełącza flagę mouseDown na true i pobiera pozycję kursora na ekranie. Przy zwolnieniu przycisku myszki przestawiam flagę na false, a przy ruchu aktualizuję pozycję kursora. Nic trudnego.
Jeżeli przycisk myszki jest wciśnięty wywołuje generator cząsteczek. Opisze jego działanie za chwilę. Na koniec wywołuję metodę update() dla każdej cząsteczki w naszej kolejce. W linii 34 rysuję okrąg odpowiedniego koloru we współrzędnych x i y naszego obiektu. Tak robię dla wszystkich naszych cząsteczek.
pygame.display.filp() jest funkcją wrzucającą nasz SCREEN na monitor (wcześniej rysowaliśmy do SCREEN).
No i jest nasz zegar - jego zadaniem jest tutaj zapewnienie, żeby pętla nie wykonała się więcej razy niż 'FPS' w ciągu sekundy. Gdyby tego tu nie było pętla leciałaby tak szybko jak mogła - w przypadku tego programu pewnie i z 200 razy na sekundę - zżerając niepotrzebnie zasoby.
Po wyjściu z pętli while wywołujemy pygame.quit() aby zamknąć wszystkie moduły i sys.exit(), które kończy działanie aplikacji.
generateParticles
Zostało tylko wyjaśnić jedną funkcję pomocniczą.
def generateParticles(list,pos):
"""generates new particles and maintaining there is not to much of then"""
for i in range(NEWPARTICLES):
list.append(particles.Particle(pos[0],pos[1],random.randint(-MAXXVEL,MAXXVEL)
,random.randint(-MAXYVEL,MAXYVEL)
,ALLCOLORS[random.randint(0,len(ALLCOLORS)-1)],SCRWIDTH,SCRHEIGHT))
Dużo znowu się tutaj nie dzieje. Jako że nasza kolejka sama dba o zachowanie odpowiedniej wielkości, wystarczy że dodamy do niej określoną liczbę cząsteczek. To się dzieje w pętli for i jest w niej wywoływany konstruktor Particle z aktualną pozycją myszki, randomowymi wektorami przesunięć, losowym kolorem i naszymi rozmiarami ekranu. Tyle.Małe podsumowanie
--Przemyślenia - można pominąć--Nie jest to specjalnie zaawansowany projekt, w sam raz dla kogoś kto startuje z pythonem i pygame. Ja sam mam zamiar rozwinąć go o kilka dodatkowych typów cząsteczek i jakiś interfejs, w którym użytkownik będzie mógł trochę namieszać ze zmiennymi - szybkością, ilością, typem itd.
Kod źródłowy jest dostępny w moim repozytorium na gitHub.
Serdecznie zachęcam do zabawy z kodem!
