Dzisiejsze gry wymagają wsparcia sprzętowego grafiki, aby odciążyć procesor. Dzięki temu, gry są szybsze i ładniejsze. Czy ciekawsze to nie koniecznie, wszystko zależy od pomysłu i realizacji.
Sprzętowe wsparcie graficzne umożliwiają DirectX i OpenGL. Pierwsze jest tylko na Windows, a OpenGL działa na wszystkich graficznych systemach operacyjnych, dlatego skupimy się jedynie na tej bibliotece graficznej.
Pygame, sam w sobie nie posiada obsługi OpenGL, ale można go prosto połączyć z PyOpenGL, zakładam, że masz już go zainstalowany, jak zalecałem w pierwszej części jeśli nie, to zrób to teraz.
Na początek garść teorii. Musisz nauczyć się umieszczać obiekty 2D w przestrzeni 3D.
OpenGL operuje na wielokątach (ang. polygon). Można rysować pojedyńcze piksle, ale operacje takie są bardzo powolne i nie nadają się do tworzenie gier. Aby umieścić obrazek na ekranie, trzeba utworzyć w przestrzeni 3D, czworokąt i nałożyć na niego teksturę, w postaci naszego obrazka. Do utworzenia czworokątu, potrzebujemy współrzędnych czterech wierzchołków.

Współrzędne są przyjmują wartości 0 i 1, są to wartości symboliczne, określające dany wierzchołek za pomocą polecenia glTexCoord2f(x,y). Do określenia współrzędnych tekstury, użyjemy polecenia glVertex3f(x,y,z).
Wyświetlenie tekstury (duszka), będzie to wyglądać tak.
# ustawiamy teksturę, która chcemy przypisać do czworokąta glBindTexure(GL_TEXTURE_2D,texture) #tworzymy polygon glBegin(GL_QUADS) glTexCoord2f(0,0); glVertex3f(x,y,0) glTexCoord2f(0,1); glVertex3f(x,y+h,0) glTexCoord2f(1,1); glVertex3f(x+w,y+h,0) glTexCoord2f(1,0); glVertex3f(x+w,y,0) glEnd()
Linia nr.2, wybiera i ustawia aktywną teksturę, która chcemy nałożyć na prostokąt. Zmienna „texture” zawiera dane obrazka (tekstury).
Linia 5-10, tworzy prostokąt, gdzie każda linia 6-9 określa jeden wierzchołek i przypisuje do niego jeden z narożników tekstury. Wartość x,y to pozycja tekstury na ekranie, w i h to wysokość i szerokość tekstury (najczęściej wysokość i szerokość obrazka załadowanego z pliku)
Zanim jednak wyświetlimy obrazek musimy przygotować teksturę. Tekstura musi być wielokrotnością potęgi 2.
Nowoczesne karty potrafią obsługiwać dowolny rozmiar tekstury odpowiednio je skalując, ale optymalnie będzie przygotować odpowiednią wielkość, aby nie było problemów z kartami, które tego nie potrafią.
image = pygame.image.load('tree64.png')
# mamy obrazek w postaci znanej pygame, musimy
# przystosować ją dla OpenGL
texdata = pygame.image.tostring(image,"RGBA",flip)
# tworzymy obiekt tekstury
texid = glGenTextures(1)
# aktywujemy obiekt tekstury
glBindTexture(GL_TEXTURE_2D, texid)
#okreslamy sposob filtrowania tekstury
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
# tworzymy obraz tekstury
glTexImage2D(GL_TEXTURE_2D, lev, GL_RGBA, w, h, b, GL_RGBA, GL_UNSIGNED_BYTE, texdata)
Doszło troche nowych poleceń, dlatego wyjaśnimy sobie po koleji, do czego one służą.
Linia 1, jest już wszystkim znana, polecenie które ładuje obrazek. Jest on w formacie pygame, musimy mieć czyste dane binarne, do tego służy polecenie tostring(), z linii 5. Przyjmuje ona 3 parametry. Pierwszy, to dane załadowanego obrazka, drugi, to format danych, na jaki ma zostać zwrócony przez funkcje. OpenGL przyjmuje dane w formacie RGBA, więc będziemy go najczęściej używać. Ostatni parametr „flip” odwraca obrazek „do góry nogami”, ponieważ polygon, na który nakładany jest obrazek zaczyna się najczęściej od lewego dolnego rogu.
W linii 8, tworzymy obiekt tekstury, a w linii 11, ustawiamy go jako aktywny, wszystkie kolejne operacje, bedą dotyczyć tej tekstury.
W linii 14 i 15 ustawiamy fitry interpolacji liniowej. Pierwszy określa, jak ma wyglądać tekstura gdy jest większa, niż załadowany obrazek, drugi, określa wygląd gdy tekstura jest mniejsza niż załadowany obrazek. Ma to miejsce, w przypadku oddalania lub przybliżania kamery do tekstury. Ponieważ my tworzymy obraz 2D, nie bedziemy korzystać z tej funkcji, jednak OpenGL wymaga określenia tych filtrów. Mozna zastosować filtr GL_LINEAR, wtedy tekstury wyglądają gładko, bez względu na to czy są blisko czy daleko, wymaga to wiekszej mocy komputera, lub GL_NEAREST wyglądają gorzej, ale słabsze komputery lepiej sobie radzą z wyświetlaniem ich na ekranie.
Ostatnie polecenie z linii 18, tworzy teksturę. Wartość lev, określa rozdzielczość tekstury, w naszym przypadku, będzie to zawsze 0. Jeśli będę opisywać tekstury wielowymiarowe opiszę to dokładniej. Kolejny parametr, GL_RGBA, określa format pikseli. To także wyjaśnie innym razem. Dla ciekawskich, proszę zajrzęć do dokumentacji OpenGL. Parametry w i h, określają wysokośc i szerokość tekstury, musi ona być potęgą liczby 2. Parametr b, określa czy wokół tekstury znajduje się ramka 0-brak ramki, 1- jest ramka.
Kolejny parametr GL_RGBA, oraz GL_UNSIGNED_BYTE, to format i typ danych obrazka, na razie nie będziemy się nimi zajmować
. Ostatni parametr to dane obrazka.
Po tej ilości informacji, pora na kod. Napiszemy sobie klasę do ładowania i wyświetlania tekstur, przyda się ona wielokrotnie. To jest nasza pierwsza wersja klasy Texture. Będziemy ją ulepszać i sprawiać, że będzie ona coraz bardziej uniwersalna. Tworzymy plik texture.py i wstawiamy tam kod:
class Texture(object):
def __init__(self,src):
"""src - sciezka/nazwa_obrazka.png"""
image = pygame.image.load(src)
self.w, self.h = image.get_width(), image.get_height()
texdata = pygame.image.tostring(image,"RGBA",0)
# tworzymy obiekt tekstury
self.texid = glGenTextures(1)
# aktywujemy obiekt tekstury
glBindTexture(GL_TEXTURE_2D, self.texid)
# ustawiamy filtry tekstury
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
# tworzymy obraz tekstury
glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,self.w,self.h,0,GL_RGBA,GL_UNSIGNED_BYTE,texdata)
def draw(self, x, y):
glBindTexture(GL_TEXTURE_2D, self.texid)
glBegin(GL_QUADS)
glTexCoord2f(0,0); glVertex3f(x,y,0)
glTexCoord2f(0,1); glVertex3f(x,y+self.h,0)
glTexCoord2f(1,1); glVertex3f(x+self.w,y+self.h,0)
glTexCoord2f(1,0); glVertex3f(x+self.w,y,0)
glEnd()
Aby można było korzystać z OpenGL, musimy poinformować o tym pygame, słuzy do tego flaga OPENGL. Należy też ustawić flagę DOUBLEBUF, ponieważ OpenGL korzysta z podwójnego buforowania.
SCREEN_WIDTH = 800 SCREEN_HEIGHT = 600 pygame.display_set_mode((SCREEN_WIDTH,SCREEN_HEIGHT),OPENGL|DOUBLEBUF)
I już możemy cieszyć się obsługą OpenGL. Musimy jeszcze przygotować OpenGL do wyświetlania grafiki 2D.
def init_opengl(): # czyscimy bufory glClearColor(0.0, 0.0, 0.0, 1.0) glClear(GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT) glMatrixMode(GL_PROJECTION) # ustawiamy macierz rzutowania glLoadIdentity() # resetujemy ja # ustawiamy rzutowanie ortograficzne o wielkosci okna glOrtho(0,SCREEN_WIDTH,SCREEN_HEIGHT,0,0,1) glMatrixMode(GL_MODELVIEW) # ustawiamy macierz modelowania # ustawiamy tekstury glEnable(GL_TEXTURE_2D) glEnable(GL_BLEND) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
OpenGL korzysta z trzech wbudowanych macierzy. Nas interesują tylko dwie z nich: GL_PROJECTION, GL_MODELVIEW.
GL_PROJECTION ustawia macierz rzutowania, pozwala nam to na ustawienie perspektywy, albo rzutowania ortograficznego, które nie posiada perspektywy, czyli obiekty nie są mniejsze zależnie od odległości od kamery, ale są cały czas takie same. Do grafiki 2D nadaje bardzo dobrze. Ustawiamy więc macierz rzutowania i ją resetujemy , aby mieć pewność, że żadne przekształcenia nie pozostały w pamięci. Następnie przechodzimy do macierzy modelowania. Macierz pozwala na tworzenie i przekształcanie obiektów (modeli), które będziemy umieszczać na ekranie.
Linie 14-16 to przygotowanie do wyświetlania tekstur. W linii 14 włączamy tekstury dwuwymiarowe. Następnie GL_BLEND włącza mieszanie kolorów, a glBlendFunc(zródlo, cel) wskazuje jak będzie wyglądać ich mieszanie. GL_SRC_ALPHA ustawia mnożenie koloru źródła, przez wartość alfa źródła, GL_ONE_MINUS_SRC_ALPHA mnoży kolor celu przez dopełnienie wartości alfa żródła. Pozwala to na uzyskanie przeźroczystego tła, na obrazkach (duszkach), które posiadają kanał alfa.
Mamy już wszystko co potrzebne, aby zrobić jakiś działający przykład. Aby nie komplikować zbytnio kodu, wyświetlimy obrazek i dodamy obsługę klawiatury aby nim poruszać, wykorzystamy do tego, nasz obiekt Texture.
import pygame
from pygame.locals import *
from OpenGL.GL import *
from texture import *
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
class Game(object):
def __init__(self):
pygame.init()
flag = OPENGL | DOUBLEBUF
self.surface = pygame.display.set_mode((SCREEN_WIDTH,SCREEN_HEIGHT),flag)
self.opengl_init()
self.x = 100
self.y = 100
self.speed = 0.3
self.stategame = 1
self.image = Texture('tree64.png')
self.loop()
def opengl_init(self):
#init gl
glClearColor(0.0,0.0,0.0,1.0)
glClear(GL_COLOR_BUFFER_BIT,GL_DEPTH_BUFFER_BIT)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(0,SCREEN_WIDTH,SCREEN_HEIGHT,0,0,1)
glMatrixMode(GL_MODELVIEW)
#set textures
glEnable(GL_TEXTURE_2D)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA)
def draw(self):
self.image.draw(self.x,self.y)
def move(self, x, y):
self.x += (x*self.speed)
self.y += (y*self.speed)
def event(self):
keys = pygame.key.get_pressed();
if keys[K_s]:
self.move(0,1)
if keys[K_w]:
self.move(0,-1)
if keys[K_a]:
self.move(-1,0)
if keys[K_d]:
self.move(1,0)
def loop(self):
while self.stategame==1:
for event in pygame.event.get():
if event.type == QUIT \
or (event.type == KEYDOWN and event.key == K_ESCAPE):
self.stategame = 0
self.event() # obsluga klawiatury
glClearColor(0.0,0.0,0.0,1.0)
glClear(GL_COLOR_BUFFER_BIT,GL_DEPTH_BUFFER_BIT)
self.draw()
pygame.display.flip()
if __name__ == '__main__':
Game()
Pobierz gotowy program: isogame_opengl_tut1.zip
Nie ma nic nowego, co nie opisywałem jeszcze to pominę szczegółowy opis kodu. Da się jednak zauważyć, że wiele jego części, można zoptymalizować. Zajmijmy się najpierw naszą klasą Texture.
Klasa Texture ma poważną wadę, jest dość powolna, ponieważ do każdego przerysowania duszka, wykonujemy polecenia glBegin()…glEnd(). Spowalnia to znacznie cały program. Dla jednego obiektu nie jest to istotne, ale jak będziemy wyswietlać ich kilkanaście, albo kilkaset, to program będzie miał co najwyżej kilka FPS.
Na początek użyjemy obiektu Rect. Uporządkuje, to nam ładnie kod.
self.rect = image.get_rect()
oraz dostosowujemy kod odpowiedzialny za tworzenie tekstury, aby korzystał z tego obiektu.
glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,self.rect.w,self.rect.h,0,GL_RGBA,GL_UNSIGNED_BYTE,texdata)
Zamiany jak na razie nie wpływają na zwiekszenie szybkości działania, poprawmy więc funkcję, która wyświetla teksturę. Zamiast, tworzyć za każdym razem glBegin(), glEnd(), wykorzystamy do tego glCallList().
Lista z parametrem GL_COMPILE, pozwalam przygotować teksturę do wyświetlenia. Jest trzymana w pamięci karty graficznej, więc wykonanie takiej listy, trwa dużo szybciej niż, użycie glBindTexture. Przenosimy kod nakładający teksturę, do __init__.
self.newList = genLists(1) glNewList(self.newList, GL_COMPILE) glBindTexture(GL_TEXTURE_2D, self.texid) glBegin(GL_QUADS) glTexCoord2f(0, 0); glVertex3f(0, 0, 0) glTexCoord2f(0, 0); glVertex3f(0, self.rect.h, 0) glTexCoord2f(0, 0); glVertex3f(self.rect.w, self.rect.h, 0) glTexCoord2f(0, 0); glVertex3f(self.rect.w, 0, 0) glEnd() glEndList()
Wszystko co znajduje się między glNewList() a glEndList() zostanie zapamiętane jako podprogram i zachowane w pamięci karty graficznej. Lista dostępna jest pod zmienną self.newList. Teraz wystarczy wywołać listę glCallList(self.newList), aby wyświetlić obrazek na ekranie.
Zmieniamy więc funkcję draw(), aby korzystała z nowych możliwości.
def draw(self,x,y):
glLoadIdentity() # resetujemy macierz widoku
glTranslatef(x, y, 0) # ustawiamy teksturę w pozycji x,y,z
glCallList(self.newList) # wyswietlamy teksture
Dzięki takiej zmianie, otrzymaliśmy o około 50% szybsze wyświetlanie duszka.
Gotowa klasa Texture wygląda tak
import pygame
from OpenGL.GL import *
from OpenGL.GLU import *
class Texture(object):
def __init__(self, src):
"""src - sciezka/nazwa_obrazka.png"""
image = pygame.image.load(src)
self.rect = image.get_rect()
texdata = pygame.image.tostring(image,"RGBA",0)
print self.rect
# tworzymy obiekt tekstury
self.texid = glGenTextures(1)
# aktywujemy obiekt tekstury
glBindTexture(GL_TEXTURE_2D, self.texid)
# ustawiamy filtry tekstury
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
# tworzymy obraz tekstury
glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,self.rect.w,self.rect.h,0,GL_RGBA,GL_UNSIGNED_BYTE,texdata)
self.newList = glGenLists(1)
glNewList(self.newList, GL_COMPILE)
glBindTexture(GL_TEXTURE_2D, self.texid)
glBegin(GL_QUADS)
glTexCoord2f(0, 0); glVertex3f(0, 0 ,0)
glTexCoord2f(0, 1); glVertex3f(0, self.rect.h, 0)
glTexCoord2f(1, 1); glVertex3f(self.rect.w, self.rect.h, 0)
glTexCoord2f(1, 0); glVertex3f(self.rect.w, 0, 0)
glEnd()
glEndList()
def draw(self,x,y):
glLoadIdentity()
glTranslatef(x, y, 0)
glCallList(self.newList)
Miałem opisać obsługę myszki, ale lekcja wyszła spora, dlatego omówimy ją przy okazji generowania świata 2D, który będzie już w kolejnej lekcji. A tym czasem, proponuje dokładnie zapoznać się z dzisiejszą lekcją i przykładowym programem i spróbować jeszcze bardziej zoptymalizować kod, oraz podzielenia się swoimi rozwiązaniami
Lekcje są dość dobrze opisane, co jest plusem dla takiego amatora jak ja
może z moim winrarem jest coś nie tak, ale żaden z plików do ściągnięcia u mnie nie działa…:/
Jaka wersja Winrara?
4.2