Gra izometryczna cz.7 – mysz w świecie izometrii

przez Adam Sobociński w dniu 28 kwietnia 2012 w kategorii: python

Witam Cię, w kolejnej lekcji. Jak wspomniałem poprzednio, dziś zajmiemy się obsługą myszki w świecie izometrycznym. Jest to istotna sprawa, ponieważ musimy wykorzystać pewne przekształcenia. Będą to przekształcenia pozycji ekranowej na izometryczną i izometryczną na ekranową. Jak widzisz na rysunku, współrzędne ekranu inaczej się układają niż współrzędne izometryczne. W związku z tym musimy je przekonwertować.

Brzmi poważnie, ale trudne nie jest. Napiszemy sobie dwie funkcje, które pozwolą na przeliczenia w dowolnym miejscu. Zastosowanie wzoru do wyświetlenie mapy już znasz z poprzedniej lekcji. Zastosujemy go tutaj także, ale wydzielimy do oddzielnej funkcji.

  def iso2screen(self,x,y):
       xx = (x-y)*(TILE_WIDTH/2)
       yy = (x+y)*(TILE_HEIGHT/2)
       return xx,yy

Funkcja iso2screen(), przyjmuje dwie wartości X,Y (jest to pozycja kafla w tablicy), a zwraca nam miejsce, w którym należy narysować podany kafel.
Druga funkcja screen2iso(), działa w przeciwną stronę, po podaniu pozycji kafla na ekranie, wylicza i podaje lokalizację kafla na mapie w tablicy.

 def screen2iso(self,x,y):
      x = x/2
      xx = (y+x)/(TILE_HEIGHT)
      yy = (y-x)/(TILE_HEIGHT)
      return xx,yy

Trochę kombinowałem, aby wymyślić jak najprostszy sposób do konwertowania tych współrzędnych, myślę, że się w miarę udało. Szczegółowo tych funkcji omawiać nie będę, ponieważ wymaga to długiego opisu, skąd się to wszystko bierze, ale wystarczy chwila analizy z kalkulatorem, aby każdy zrozumiał wzór :) Jeśli jednak chcielibyście dokładnie wiedzieć skąd to się bierze, uzupełnię wpis.
Aby funkcja screen2iso() działała poprawnie, musimy ustawić pozycję x,y myszki na kafel 0,0. Modyfikujemy funkcję events()

 def events(self):
      x,y = pygame.mouse.get_pos()

      #obliczamy przesuniecie x,y kafla na ekranie
      x-=(self.map_x+(TILE_WIDTH/2))
      y-=(self.map_y)

      # obliczamy tiles nad ktorym znajduje sie myszka
      self.tx, self.ty = self.screen2iso(x,y)

Do funkcji draw_map() dodamy teraz kod, odpowiedzialny za podświetlenie kafla, po najechaniu na niego wskaźnikiem myszki.

if((self.tx>=0 and self.tx<MAP_WIDTH) and  (self.ty>=0 and self.ty<MAP_HEIGHT)):

     # obliczamy pozycje x,y aktywnego kafla
     mtx, mty = self.iso2screen(self.tx,self.ty)
     self.tiles[1].draw(mtx+self.map_x, mty+self.map_y)

Linia 1, ogranicza wyświetlanie kafli do obszaru planszy. Bez niej, poza planszą, pod kursorem myszki, pojawiały by się kafle. Linia 4, to opisywana wyżej funkcja do przeliczenia współrzędnych i linia 5 wyświetla kafel w innym kolorze, co daje efekt podświetlenia wybranego kafla. Zmienne self.mapx i self.mapy, to przesunięcie układu współrzędnych, czyli całej mapy. Musimy je dodać do pozycji kafla. Jak widać, wszystko jest banalnie proste. Pod warunkiem, że znamy odpowiednie wzory :)
Dodamy troche więcej kafli i większe okno, aby było lepiej widać. Zmodyfikujemy więc kod, z ostatniej lekcji, całość będzie wygląć tak.

################################################################################
# http://blog.vim.pl  - Lekcja 7. Obsluga myszki na mapie izometrycznej        #
# (c) 2012 by Adam /Logic/Sobocinski                                           #
################################################################################
from pygame.locals import Color
import pygame, time, math
from pygame.locals import *
from OpenGL.GL import *
from texture import *

SCREEN_WIDTH  = 800
SCREEN_HEIGHT = 600

MAP = [[1,1,1,1,1,1,1,1,1,1,1,1],
       [1,1,1,1,1,1,1,1,1,1,1,1],
       [1,1,1,1,1,1,1,1,1,1,1,1],
       [1,1,1,1,1,1,1,1,1,1,1,1],
       [1,1,1,1,1,1,1,1,1,1,1,1],
       [1,1,1,1,1,1,1,1,1,1,1,1],
       [1,1,1,1,1,1,1,1,1,1,1,1],
       [1,1,1,1,1,1,1,1,1,1,1,1],
       [1,1,1,1,1,1,1,1,1,1,1,1],
       [1,1,1,1,1,1,1,1,1,1,1,1],
       [1,1,1,1,1,1,1,1,1,1,1,1],
       [1,1,1,1,1,1,1,1,1,1,1,1]]

MAP_WIDTH  = 12
MAP_HEIGHT = 12
TILE_WIDTH = 64
TILE_HEIGHT = 32

class Game(object):
  def __init__(self):
      self.stategame = 1

      # miejsce na obrazki kafli
      self.tiles = []   

      # pozycja poczatkowa planszy
      self.map_x = 360
      self.map_y = 32

      # pozycja myszki w rzucie izometrycznym
      self.mousex=0
      self.mousey=0      

      pygame.init()
      flag = OPENGL | DOUBLEBUF
      self.surface = pygame.display.set_mode((SCREEN_WIDTH,SCREEN_HEIGHT),flag)
      self.opengl_init()
      self.tiles.append(Texture('grass.png'))
      self.tiles.append(Texture('ground.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 events(self):
      x,y = pygame.mouse.get_pos()

      #wyznaczamy pozycje x,y kafla 0,0 na ekranie
      x-=(self.map_x+(TILE_WIDTH/2))
      y-=(self.map_y)

      # obliczamy tiles nad ktorym znajduje sie myszka
      self.tx, self.ty = self.screen2iso(x,y)

  def iso2screen(self,x,y):
       xx = (x-y)*(TILE_WIDTH/2)
       yy = (x+y)*(TILE_HEIGHT/2)
       return xx,yy

  def screen2iso(self,x,y):
      x = x/2
      xx = (y+x)/(TILE_HEIGHT)
      yy = (y-x)/(TILE_HEIGHT)
      return xx,yy

  def draw_map(self):
      for x in range(MAP_WIDTH):
        for y in range(MAP_HEIGHT):
          tile = MAP[y][x] - 1
          tile_x, tile_y = self.iso2screen(x,y)
          self.tiles[tile].draw(tile_x+self.map_x,tile_y+self.map_y)

      if((self.tx>=0 and self.tx<MAP_WIDTH) and  (self.ty>=0 and self.ty<MAP_HEIGHT)):
          # obliczamy pozycje x,y aktywnego kafla
          mtx, mty = self.iso2screen(self.tx,self.ty)
          self.tiles[1].draw(mtx+self.map_x, mty+self.map_y)

  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.events()
        self.draw_map()
        pygame.display.flip()

if __name__ == '__main__':
    Game()

Ok, mamy już podświetlenie kafla. Teraz dodamy kolejne zdarzenie, czyli kliknięcia. Celem będzie, aby pod lewy klawiszem myszy, było możliwe postawienie klocka, a pod prawym klawiszem jego usuwanie.
Do odczytywania stanu klawiszy myszki, słuzy polecenie pygame.mouse.get_pressed(). Zwraca ono tablicę stanu przycisków [0][1][2]. Dodajemy od funkcji events()

but = pygame.mouse.get_pressed()
if((self.tx>=0 and self.tx=0 and self.ty     # ustawiamy klocek
     if but[0]:
         MAP[self.ty][self.tx] = 2;

     # usuwamy klocek
     if but[2]:
         MAP[self.ty][self.tx] = 1;

I tyle, prawda, że proste? :)
Jeszcze tylko musimy zmodyfikowac klasę texture.py, aby brała pod uwagę obiekty wyższe niż 32px.
W tym celu w funkcji draw(), zamieniamy linijke

glTranslatef(x,y,0)

na

glTranslatef(x,y-self.rect.h+32,0)

Oznacza to tyle, że wszystkie elementy rysujemy od dolnego punktu kafla. +32 dodalem, ponieważ nie chcę, aby plansza także się podniosła. Jest to oczywiście metoda prowizoryczna, ale swoją funkcję spełnia. W późniejszym czasie, poprawimy tą funkcję i omówię dokładniej sposób umieszczania dużych obiektów na mapie.

Oba przykłady dostępne są w jednej paczce do pobrania isogame_tut7. Tutorial.py zawiera pierwszą część, Tutorial_1.py zawiera drugą część wpisu, dotyczącego ustawiania klocków na planszy.

I efekt działania przykładu

W tej lekcji to tyle, mam nadzieję, że wszystko jest zrozumiałe i nie przynudzałem. Daje się we znaki, brak możliwości wyświetlania tekstu. Dlatego w kolejnej lekcji, będziemy obsługiwać fonty. Może uda się zrobić jakieś grywalne demko gry :) . Zapraszam do komentowania i przysyłania własnych uwag.

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


6 odpowiedzi na „“Gra izometryczna cz.7 – mysz w świecie izometrii””

  1. TT pisze:

    Bardzo Ciekawa stronka. Chyba zainteresuję się tworzeniem gier i pythonem ;) Oby tak dalej!

    • Adam Sobociński pisze:

      Python jest bardzo przyjemny w użyciu, nadaje się nie tylko do gier, ale praktycznie do wielu rzeczy, również do tworzenia stron. Cieszę się, że moje pisanie zmotywowało kogoś do zainteresowania się pythonem :)

  2. puniek pisze:

    wielkie dzięki szczegolnie za ten wpis – problem myszki w izometrii nurtowal mnie juz od dawna, a teraz widze ze nie potrzebnie sie z tym pieprzylem tyle, bo można to jak widać zalatwić w 4 linijkach. Byłoby zaje…..e, gdybyś napisał coś jeszcze o wykorzystaniu opengl-a w pygame. wielkie dzięki jeszcze raz

  3. Adam Sobociński pisze:

    Wpisy doyczące pygame i OpenGL bedą, jak zapowiadałem, najbliższy będzie o wyświetlaniu napisów w OpenGL, w następnych będzie o scrollowaniu i tworzeniu nieskonczonej mapy, poruszanie postacią, kolizje w izometrii i wiele innych rzeczy. Niekoniecznie w tej kolejności :) . Życzę powodzenia.

  4. eric83pl pisze:

    Witam, Dzięki za świetny kurs, dołączam do pozostałych czekających na więcej :)
    co do uwag to np. tworzenie stałej MAP zmieniłbym tak:

    MAP_WIDTH = 12
    MAP_HEIGHT = 12
    MAP = [[1 for i in range(MAP_WIDTH)] for j in range(MAP_HEIGHT)]

    • Adam Sobociński pisze:

      Dzięki za komentarz, wpisów będzie więcej :) . Proponowana zmiana, oczywiście jest dobra, ale na potrzeby tutoriala wolałem to rozpisać, aby każdy początkujący widział skąd się to bierze.

Dodaj komentarz