Del creador de "Back To Kowloon"

dimarts, 9 de novembre del 2010

Creant un editor de mapes (I)

Podeu descarregar el paquet corresponent a aquest post d'aqui -> tar.gz rar
(Recordeu que el codi estarà disponible en aquesta web)

Genial idea, eh? I tant que si.

Bàsicament, es tractaria de crear un petit "joc" que ens servirà com a eina de suport. L'objectiu del joc, per dir-ho d'alguna manera, és fer mosaics. Com els romans, si. Tenim unes poques rajoles i les podem anar combinant sobre una superfície buida per tal de conformar un mosaic (en aquest cas, un mapa per al nostre RPG). 

Més endavant, s'intentarà fer que el joc sigui capaç de guardar aquests mosaics a un fitxer. D'aquesta manera, tindrem un editor de mapes totalment funcional al que se li poden afegir altres funcionalitats (col·locació dels NPC, etc).

Ostres! La de feina que estalvia això! :D


Oi que si? n_n

La cosa queda més o menys així:


Vale, de conya. Ensenya'ns coses.

El programa de moment només pinta. No esborra, no opera amb fitxers ni tampoc et canta nadales. Els controls són:
  • Tecles direccionals: moure el cursor principal (el que pinta el mapa)
  • A / S: moure el cursor del selector de rajoles a esquerra/dreta
  • W: pinta tot el mapa amb la rajola seleccionada
  • Intro: selecciona una rajola
Abans d'entrar al tema...us voldria comentar un parell de coses que he descobert sobre Python.
  • No existeix una sentència switch-case. Enlloc d'això s'utilitzen diccionaris (probablement això ho veurem aquí més endavant).
  • En programació hi ha una famosa tècnica per comprovar que les coses funcionen que consisteix en col·locar missatges a llocs estratègics. Per exemple, imprimint els resultats d'uns càlculs intermitjos. A Python podem utilitzar la paraula clau "print" per a aquesta feina. Si executem el programa amb la consola de comandes, veurem aparèixer els missatges allà.
  • En cas de crear un mètode buit, per fer tests o per implementar més endavant, es pot utilitzar la paraula clau "pass" per indicar que el mètode no fa res. Per exemple: 
def metode_buit(self):
       pass
OK, fins aqui. Entrem a comentar una mica el funcionament del programa.

Si mireu l'arxiu world_editor.py, veureu que és bàsicament el mateix que game.py. Els únics canvis són els referents als controls del teclat i aquest fragment de codi:
    "Pintem l'escenari i el cursor"
    screen.blit(background,(0,0))
    scn.paint_cursor(screen)
    scn.paint_selector(screen)
Noteu que es podria posar dins del bucle del joc. Però això implicaria repintar el fons i els cursors un munt de vegades per segon. El rendiment baixarà i, si us hi fixeu, no és necessari fer-ho. Només ens cal pintar-ho un únic cop.

Si mireu la imatge que es fa servir com a fons, veureu que disposa d'un espai buit al mig per a pintar el mapa. Però, en canvi, no hi ha cap forat per al selector. Per tant, si pinteu el fons dins del bucle...el selector no es veurà : )

Proveu diverses combinacions i veureu de què coi us parlo.

Molt bé, el que tu diguis. Què més tens?

Tinc la classe scene_editor. Està basada en la clase scene, però fa altres coses. Per exemple, el mètode paint_scene() aqui pinta TOT el mapa amb la rajola indicada. (Noteu que cal repintar el cursor després). Explicaré alguns aspectes dels mètodes més problemàtics.  
Els mètodes move_selector() i move_cursor() poden donar més d'un maldecap a més d'un (i d'una). La idea bàsica és:
  • Per a move_selector(): Moure el cursor a dreta i esquerra per poder seleccionar una rajola d'entre x disponibles. Com a afegit estètic, en arribar a un dels dos extrems volem que el cursor salti a l'altre. De moment, ho he fet amb les set primeres disponibles al tileset que vaig escollir. Més endavant miraré d'afegir-les totes d'alguna forma.
  • Per a move_cursor(): Moure el cursor a dreta, esquerra, amunt i avall pel que seria l'espai del mapa (és a dir, la part negra de la finestra). Recordeu que vaig escollir fer un mapa de 18x9 columnes, per emular l'estil d'alguns antics RPG. Per tant, cal que el cursor es mantingui dintre d'aquestes 18x9 columnes i que no aparegui mai fora d'aquestes.
Fixem-nos en move_selector(). A partir d'aquest, implementar move_cursor() és trivial.
    def move_selector(self, screen, keyname):
        if keyname=="a":
            if self.selector_rect.x>34:
                self.selector_rect.move_ip(-34,0)
            else:
                self.selector_rect.x=238
        elif keyname=="s":
            if self.selector_rect.x<238:
                self.selector_rect.move_ip(34,0)
            else:
                self.selector_rect.x=34
        self.paint_selector(screen)
Com podeu veure és ben senzill: un cop es detecta que el cursor està a un dels extrems del seu espai (x<34 i x>238 col·locaria el cursor fora del selector), s'actualitza la posició del cursor per tal de que es mogui a l'altre extrem. Fixeu-vos que no he fet servir move_ip() en tots dos casos. Sabeu què? Funciona, però hauria d'haver fet servir move_ip() en tot per qüestions d'estil -_-

Amb mètodes com aquest es descobreix com de útils son els objectes Rect. Al mètode __init__() de scene_editor s'han inicialitzat els dos cursors i, a més, un parell de rectangles associats a aquests. Aqui teniu la raó: un cop has "copiat" (amb blit()) la imatge a la finestra, ja no la pots moure. Per moure-la cal retenir una còpia d'aquesta imatge en algun lloc, pintar sobre la posició antiga i copiar-la novament a la nova posició. 

Com veieu al final del mètode fem una crida a paint_selector() que repinta el selector un cop ja hem mogut el rectangle del cursor. En repintar el selector, es torna a copiar el cursor a la finestra. Però, com que hem mogut el seu rectangle, ara apareixerà en una altra posició.

El funcionament de move_cursor() us el deixo a vosaltres, com feia el meu profe de mates.


NOTA: Penseu que, amb move_cursor(), cal fer una altra cosa en moure el cursor. El selector és sempre el mateix, però el mapa el podem repintar i sempre canvia...

I què passa amb paint_selector() i paint_tile()? Què son tilerects[] i last_tile EH? ¬_¬

Doncs bé. Amb paint_tile() cal adonar-se que s'ha de "recordar" la darrera casella per tal de que es mantingui a la finestra. Aqui entra last_tile, que és un objecte Surface on es guarda la rajola que s'ha pintat en l'ultim moviment. Amb això aconseguim dues coses: ser capaços de repintar sobre el cursor en moure, i fer que el cursor pinti a mesura que es mou.

Si no es guarda aquesta informació...bé, comproveu-ho vosaltres mateixos. Es podria implementar de forma que last_tile fes una comprovació primer per veure si hi ha o no una rajola pintar. D'aquesta forma, el cursor només pintaria quan nosaltres li indiquéssim (per exemple, prement INTRO).

L'objecte tilerects[] s'utilitza per guardar les rajoles disponibles (en aquest cas set) i pintar-les sobre el fons amb el mètode paint_selector(); que s'encarrega de pintar també el cursor.

Pero quina merda d'explicacions que fas últimament!!! >_<'

Fent l'indio una estona : D

Bé, no tinc gaire temps -_- Però la veritat és que aquest programa és ben senzill ¬_¬ El problema està en que no domino el llenguatge i, també, que no estava habituat a com es representen les coses a la pantalla fins fa ben poc.

De totes formes, hi ha un sistema de comentaris ¬_¬


Wu Ying Ren 死