Sviluppare un gioco in Python: isometria, Prima parte.

Introduzione

Nella prossima serie di articoli analizzeremo delle tematiche che completano il nostro tour nella realizzazione di un gioco in 2D (per quanto riguarda il codice vero e proprio, altri aspetti, come l’organizzazione del lavoro, saranno trattati più avanti).

Per prima cosa vorrei parlare dei videogiochi isometrici: in questo articolo vedremo brevemente come impostare questo effetto.

L’isometria infatti è una tecnica che permette di visualizzare un mondo tridimensionale da un angolo di 45 gradi (prendete per esempio Freeciv). L’unica limitazione è che non c’è prospettiva e quindi gli oggetti più lontani non diventano pù piccoli ed è anche per questo che si renderizza solo una parte dell’ambiente alla volta. Limitare la visuale infatti garantisce un realismo (non paragonabile al 3D) di gran lunga più appagante del semplice gioco in due dimensioni, ma sia ben chiaro che rimane sempre 2D (il che comporta qualche facilitazione).

Quello di cui abbiamo bisogno quindi è una gestione ottimale della profondità, che determina la visualizzazione degli oggetti sullo schermo. Inoltre tratteremo anche una tassellazione di base, ovvero renderemo il backgroud di gioco piastrellato e sensibile al puntatore/giocatore  (che nel nostro caso sarà una sfera azzura mossa dal mouse, come potete vedere dall’immagine).

Codice

1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3 
4import pygame,sys
5from pygame.locals import*
6 
7pygame.init()
8 
9screen = pygame.display.set_mode((640,480), DOUBLEBUF | HWSURFACE, 32)
10pygame.display.set_caption("Isometria")
11 
12q = "quadrato.png"
13t = "terreno.png"
14cc = "cornicer.png"
15c = "cornice.png"
16a = "albero.png"
17aa = "ombraalbero.png"
18s = "sfera.png"
19 
20quadrato = pygame.image.load(q).convert_alpha()
21terreno = pygame.image.load(t).convert_alpha()
22cornicer = pygame.image.load(cc).convert_alpha()
23cornice = pygame.image.load(c).convert_alpha()
24albero = pygame.image.load(a).convert_alpha()
25ombra = pygame.image.load(aa).convert_alpha()
26sfera = pygame.image.load(s).convert_alpha()
27orologio = pygame.time.Clock()
28 
29pygame.mouse.set_visible(False)
30 
31class giocatore():
32        def __init__(self,xy,img,screen):
33                """
34                Inizializza il giocatore
35                """
36                self.img = img
37                self.coordinate = (xy[0]-24,xy[1]-24)
38                self.screen = screen
39                self.depht = self.coordinate[1]+48
40                self.rect = pygame.Rect((xy[0]-10,xy[1]+30),(28,18))
41        def move(self,xy):
42                """
43                Aggiorna le coordinate del giocatore
44                """
45                self.coordinate = (xy[0]-24,xy[1]-24)
46                self.depht = self.coordinate[1]+48
47 
48        def render(self):
49                """
50                Renderizza il giocatore
51                """
52                self.screen.blit(self.img,self.coordinate)
53 
54class mouse():
55    def __init__(self,xy):
56        """
57        Inizializza il puntatore
58        """
59        self.img = pygame.Surface((1,1))
60        self.coordinate = xy
61        self.mask = pygame.mask.from_surface(self.img)
62        self.mask.fill()
63 
64class tile():
65    def __init__(self,img,cor,ogg,om,xy,screen):
66        """
67        Inizializza la piastrella
68        """
69        img = pygame.transform.rotozoom(img,45.,1)
70        img = pygame.transform.scale(img,(100,70))
71        cor = pygame.transform.rotozoom(cor,45.,1)
72        cor = pygame.transform.scale(cor,(100,70))
73        self.img = img
74        self.om = om
75        self.cor = cor
76        self.ogg = ogg
77        self.coordinate = xy
78        self.cooalbero = (xy[0]+27,xy[1]-10)
79        self.dephtalbero = self.cooalbero[1]+55
80        self.screen = screen
81        self.mask = pygame.mask.from_surface(self.img)
82        self.collide = False
83 
84    def _collide(self,to):
85        """
86        Gestisce le collisioni tra puntatore e piastrella
87        """
88        if self.mask.overlap(to.mask, (to.coordinate[0]-self.coordinate[0],to.coordinate[1]-self.coordinate[1])):
89            self.collide = True
90        else:
91            self.collide = False
92 
93    def render_ogg(self):
94        """
95        Renderizza l'oggetto della piastrella
96        """
97        self.screen.blit(self.ogg, self.cooalbero)
98 
99    def render_om(self):
100        """
101        Renderizza l'obra dell'oggetto della piastrella
102        """
103        self.screen.blit(self.om, self.cooalbero)
104 
105    def render_g(self):
106        """
107        Renderizza lo sfondo della piastrella e la cornice
108        """
109        self.screen.blit(self.img,self.coordinate)
110        if self.collide == True:
111            self.screen.blit(self.cor, self.coordinate)
112 
113p = mouse((0,0))
114g = giocatore((0,0),sfera,screen)
115 
116lista = []
117 
118deltax = 100
119deltay = 35
120plusx = 0
121plusy = 0
122 
123for y in range(0,13):
124    for x in range(0,6):
125        if y < 6:
126            lista.append(tile(quadrato,cornice,albero,ombra,(x*deltax+plusx,deltay*y),screen))
127        else:
128            lista.append(tile(terreno,cornicer,albero,ombra,(x*deltax+plusx,deltay*y),screen))
129    if y%2 == 0:
130        plusx = 50
131    else :
132        plusx = 0
133 
134while True:
135        pos = (-1,-1)
136        for event in pygame.event.get():
137                if event.type == QUIT:
138                        sys.exit()
139                if event.type == MOUSEMOTION:
140                        pos = pygame.mouse.get_pos()
141                        p.coordinate = pos
142                        g.move(pos)
143 
144        tempo_p = orologio.tick(60)
145        screen.fill((0,0,0))
146        for x in lista:
147                x.render_g()
148        for x in lista:
149                x.render_om()
150        g.render()
151        for x in lista:
152                x._collide(p)
153                if x.collide == False:
154                        x.render_ogg()
155                        continue
156                elif x.dephtalbero < g.depht:
157                        x.render_ogg()
158                        g.render()
159                else:
160                        g.render()
161                        x.render_ogg()
162 
163        pygame.display.set_caption("Isometria FPS = "+str(orologio.get_fps()))
164        pygame.display.flip()

Analisi

  • Fino alla linea 30 non ci sono novità, importiamo pygame e tutto il necessario per l’esempio
  • La classe giocatore definisce la nostra sfera blu che si muoverà nel bosco. In particolare bisogna notare la presenza del parametro depht, che corrisponde alla profondità dell’oggetto sullo schermo calcolando la base dell’immagine utilizzata (infatti sommiamo 48 perché l’immagine della sfera è alta 48 pixel; non dimenticate che l’asse delle ordinate è rivolto verso il basso). La profondità viene aggiornata con lo spostamento del giocatore, sempre in base all’immagine utilizzata. Da notare che teniamo conto solo delle coordinate sull’asse y.
  • La classe mouse ci servirà per gestire le collisioni con le piastrelle dello sfondo. Infatti è composta solo da 1 pixel (per semplificare) che viene utilizzato dalla maschera. Quest’ultima è necessaria per le collisioni punto punto tra superfici; in questo caso sarà utilizzata per le collisioni tra il puntatore del mouse e la piastrella. Il puntatore non è visibile, ma corrisponderà al centro della nostra sfera.
  • La classe tile corrisponde alla nostra piastrella. Quest’ultima memorizza la sua immagine (che sarà ruotata e schiacciata per dare l’effetto isometrico), l’immagine della cornice (quando viene selezionata), le proprie coordinate, l’oggetto che si trova su di essa e le sue coordinate, lo schermo dove deve renderizzare ed una variabile che memorizza se l’oggetto collide o no. Il metodo _collide() controlla se l’oggetto passato (ovvero il puntatore) collide con la maschera della piastrella. Le maschere non hanno coordinate spaziali ma sono “generate” tutte nell’angolo in alto a sinistra dello schermo, ovvero a partire dalle coordinate (0,0); ecco perché sottraggo alle coordinate del puntatore quelle della posizione della pistrella, così le coordinate saranno adatte a controllare se il pixel del puntatore si trova all’interno della maschera della piastrella. Le varie funzioni di render, renderizzano rispettivamente l’oggetto che si trova sulla piastrella (in questo caso un albero), l’ombra dell’oggetto e lo sfondo (cioè la piastrella stessa).
  • Prima di entrare nel loop di gioco, prepariamo la tassellazione del terreno (metà terreno sarà rosso, mentre l’altra metà con una texture simile ad un terreno erboso, già utilizzata in precedenti esempi), il puntatore ed il giocatore. Non fate molto caso alla funzione utilizzata per generare il terreno, perché serve esclusivamente per questo esempio.
  • Nel loop dei controlli aggiorniamo la posizione del giocatore e del puntatore.
  • Nel loop principale possiamo vedere che si renderizza il gioco in questa sequenza: terreno, ombre degli oggetti, giocatore, albero con giocatore avanti o viceversa.
  • Infine aggiorniamo lo schermo e controlliamo i frame per secondo che verrano visualizzati sul titolo della finestra.

Per prima cosa devo precisare che il giocatore viene renderizzato dopo le ombre  poiché deve comparire sopra di esse nell’eventualità che il puntatore vada fuori dalla tasselazione.

La cosa più importante da notare è che il puntatore (quindi anche il giocatore, nel nostro caso), se non è presente in una specifica casella, quest’ultima renderizza solamente l’oggetto che ha sopra di essa; se invece il puntatore collide con la maschera della piastrella, allora si visualizza l’albero e la sfera in base alle loro ordinate, per capire chi deve andare prima o dopo.

L’effetto risultante sarà un primo approccio a questa tecnica non banale ma neanche di difficile utilizzo.

Conclusioni

Come avete ben capito, per gestire la “profondità” di gioco, c’è bisogno di “qualcuno” che decida come renderizzare le immagini sullo schermo. Inoltre in questo tipo di videogame, abbiamo bisogno anche di un gestore del backgroud per le eventuali operazioni che si andranno a fare su di esso, come gli spostamenti degli oggetti e/o dei personaggi, selezioni ecc…

Questo problema è di fondamentale importanza e va affrontato prima di qualsiasi altra cosa. Pensate infatti che scelte prese su questo “gestore” (per ora definiamolo così) andranno ad influire tutta la meccanica di gioco.

Con questo esempio ho lasciato aperte molte porte per non limitare la vostra fantasia, poiché non esistono solo piastrelle quadrate, ma il metodo che abbiamo visto in precedenza per le collisioni con il puntatore (per individuare il tassello selezionato), funziona bene lo stesso (se qualcuno ha un’idea più veloce, che funzioni con qualsiasi tipo di piastrella come quella che ho scritto, lo prego di scrivermi).

Detto questo non mi resta che darvi appuntamento al prossimo articolo e mi scuso per il ritardo.

Sorgenti:

Press ESC to close