Gra izometryczna cz.5 – rysowanie mapy 2D

przez Adam Sobociński w dniu 22 listopada 2011 w kategorii: python

Zgodnie z obietnicą, dziś zajmiemy się tworzeniem mapy 2D, za pomocą kafli (ang. tiles).  Jest ona zbudowana z wielu obrazków, ułożonych jeden obok drugiego, tak jak układa się płytki.

Oś Y ekranu, jest w przeciwną stronę, niż uczyłeś się na matematyce. (0,0) to lewy górny róg ekranu. Ułożone w rzędach i kolumnach płytki, tworzą mapę. Mapa oczywiście nie jest zapisywana w postaci płytek, jest w postaci jedno lub dwuwymiarowej tablicy, która informuje jaka płytka znajduję się na danej pozycji. Taka mapa łatwa jest do zapisania na dysku i późniejszego odczytania. Dla uproszczenia, zdefiniujemy mapę, w tablicy.

map = [2,2,2,2,2,2,2,2,2,2,2],
      [2,1,1,1,1,1,1,1,1,1,2],
      [2,1,1,1,1,1,1,1,1,1,2],
      [2,1,1,1,1,1,2,1,1,1,2],
      [2,2,1,1,1,1,1,1,1,1,2],
      [2,1,1,1,1,1,2,1,1,1,2],
      [2,1,1,1,1,1,1,1,1,1,2],
      [2,2,2,2,2,2,2,2,2,2,2]

Mamy utworzoną mapę, o wymiarach 11×8 kafli. Wartości 1 i 2 informują, która płytka będzie w tym miejscu wyświetlona. W naszym przykładzie 1 – trawa, 2 – woda.

for x in range(11):
   for y in range(8):
         tile = map[x][y]
         tiles[tile].draw(x*64,y*64)

Jak widać wyświetlenie mapy w postaci kafli, nie jest trudne. Tworzymy pętle x z 11 elementami i pętle y z 8 elementami, bo taki mamy rozmiar mapy. Linia 3 pobiera numer kafla z pozycji x i y mapy. Linia 4 rysuje kafel na pozycji x,y. Ponieważ kafel ma rozmiar 64x64px, musimy pomnożyć przez ten rozmiar, aby otrzymać pozycje kolejnego kafla. W przeciwnym razie, przesunięcie było by tylko 1 piksel i kafle nachodziły by na siebie.

Poruszanie i kolizje
Świat, który stworzyliśmy składa się z klocków. To rozwiązanie umożliwia tworzyć dowolnej wielkości, zróżnicowane światy, ale również umożliwia poruszanie po nim i wykrywanie kolizji. Poruszanie duszkiem w tym przypadku jest identyczne jak pokazałem w poprzednich lekcjach. Zmienia się sposób wykrywania kolizji.

def collision(self, x, y):
    downY  = int((y + self.image.rect.h)/64)
    upY    = int(y/64)
    leftX  = int(x/64)
    rightX = int((x + self.image.rect.w)/64)
    return MAP[upY][leftX],MAP[downY][leftX],MAP[upY][rightX],MAP[downY][rightX]

Ponieważ duszek (gracz), nie musi być wielkość klocka (tile), stworzyliśmy odpowiednią funkcję, która zwraca z tablicy zwierającej mapę, to co znajduje się pod każdym rogiem duszka. Ma to na celu, wykrywanie kolizji w momencie dotknięcia duszka odpowiedniej płytki na mapie, w innym przypadku kolizje były by bardzo niedokładne.

Na potrzeby dema, stworzyłem klasę Player, aby pogrupować wszystkie funkcje i wartości dotyczące gracza.
Dzięki temu mamy jako taki porządek w kodzie.

class Player(object):
  def __init__(self,tex):
      self.image = tex  # tekstura (obiekt Texture)
      self.posx = 64*1  # poczatkowa pozycja X gracza
      self.posy = 64*1  # poczatkowa pozycja Y gracza
      self.speed = 0.3  # szybkosc poruszania gracz

      # kat, o jaki zostanie obrocona tekstura, pozwala to użyć
      # jednej klatki i poprzez obracanie obrazka zaleznie od kierunku jazdy
      self.rotate = 0

  def move(self, dirx, diry, rotate):
      self.rotate = rotate
      upleft,downleft,upright,downright=self.collision(self.posx,self.posy+self.speed*diry)
      if diry==1:
          if downleft <> 1 or downright <> 1:
              return
      if diry==-1:
          if upleft <> 1 or upright <> 1:
              return
      upleft,downleft,upright,downright=self.collision(self.posx+self.speed*dirx,self.posy)
      if dirx==1:
          if downright <> 1 or upright <> 1:
              return
      if dirx==-1:
          if downleft <> 1 or upleft <> 1:
              return
      self.posx += (dirx*self.speed)
      self.posy += (diry*self.speed)

  def collision(self, x, y):
      downY  = int((y + self.image.rect.h)/64)
      upY    = int(y/64)
      leftX  = int(x/64)
      rightX = int((x + self.image.rect.w)/64)
      return MAP[upY][leftX],MAP[downY][leftX],MAP[upY][rightX],MAP[downY][rightX]

  def draw(self):
      self.image.draw(self.posx, self.posy,self.rotate)

Funkcja move() przesuwa gracza, collision() sprawdza czy wystąpiła kolizja i na koniec draw(), wyświetla gracza, zgodnie z aktualna pozycja self.posx i self.posy.

Jak pokazuje obrazek powyżej, kolizja sprawdzana jest tylko dla niebieskich punktów. Jest to w tym przypadku zupełnie wystarczające. Jeśli któryś z tych punktów najedzie na pole zabronione, to nastąpi kolizja.

Ktoś spostrzegawczy, może zauważył, że funkcja move() ma dwa razy wywoływaną funkcję collision(). Celowo tak zrobiłem, aby nasz pojazd nie blokował się przy ścianie i była możliwość poruszania góra i dół.
Aby nie tworzyć wielu klatek, z kierunkiem patrzenia gracza, można użyć właściwości, jaką posiada OpenGL, czyli obroty. Ustawiając kąt, o jaki obrócony zostanie obrazek, możemy symulować kierunek patrzenia duszka.
Do tego celu musimy troszkę zmodyfikować klasę Texture, dodając do niego glRotatef()

glPushAttrib(GL_TRANSFORM_BIT)
glMatrixMode(GL_TEXTURE)       # ustawiamy aktywna macierz
glLoadIdentity()               # resetujemy układ współrzędnych
glRotatef(rotate,0,0,1)        # obracamy o kat "rotate"
glPopAttrib()

I cieszymy się ładnie obracanym duszkiem.
Cały kod do pobrania tutaj isogame_tank_1

Po uruchomienie programu, pojawi się takie okno

W tej lekcji to wszystko. Zachęcam do samodzielnego analizowania przykładowego kodu. W następnej lekcji, zajmiemy się mapą w rzucie izometrycznym.

Podziel się na:
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Blogplay
  • Blogger.com
  • Gadu-Gadu Live


5 odpowiedzi na „“Gra izometryczna cz.5 – rysowanie mapy 2D””

  1. MW pisze:

    Witam,

    czy ten blog umarł śmiercią naturalną? Bo z chęcią poczytałbym więcej!!!

    Pozdrawiam
    Michał

    • logic pisze:

      Witaj Michał,

      Nie umarł, będzie więcej :)
      Muszę tylko zakończyć bieżące zlecenia, bo zajmują mi sporo czasu. Kolejny wpis jest już prawie gotowy.

  2. Waroth pisze:

    „Nie umarł, będzie więcej”
    To naprawdę fajnie ;) Dobrze się czyta i jest motywacja do nauczenia Pythona ;)

  3. logic pisze:

    Cieszę się, że się podoba, to motywuje, do dalszego pisania :)

  4. pawo2500 pisze:

    Fajny tutorial, ale ważna rzecz: w nowym OpenGL musi być osobno glClear(GL_COLOR_BUFFER_BIT)i glClear(GL_DEPTH_BUFFER_BIT), inaczej error

Dodaj komentarz

Current month ye@r day *