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


9 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.

  5. Phreak Nation pisze:

    If anyone wants to do more with mouse interaction I have just came up with a good solution for gui selection with multiple surfaces and transparent areas. Of course it needs cleaned up but it works. Also if you want you can use it in your tutorials Adam as I am using your tutorials. Give and take :D

    Jeśli ktoś chce zrobić więcej interakcji myszy właśnie wymyślił dobre rozwiązanie dla gui wybór z wielu powierzchni i obszarów przezroczystych. Oczywiście to musi posprzątać, ale to działa. Także jeśli chcesz, możesz używać go w swoim tutoriale Adama jak używam swoich tutoriale. Prawa i obowiązki: D

    http://stackoverflow.com/questions/16976368/python-pygame-detect-if-mouse-is-over-non-transparent-part-of-surface

  6. AB47 pisze:

    Czy ten kurs będzie miał jakąś kontynuację ?

Dodaj komentarz

Current month ye@r day *