jueves, 13 de octubre de 2011

Hola Mundo

... y con ello no me refiero al típico programa de iniciación en todos los lenguajes de programación.

Hasta ahora teníamos un mapa y un personaje que se movía por él sin problemas.

Bien, ¿y si queremos tener más estados de juego y más personajes?, ¿en cada estado del juego tendremos que repetir el código de dibujar el mapas y los personajes?.
Pues no, para eso vamos a crear una Clase Mundo a la que le pasaremos el mapa y los personajes que van a intervenir.

Para ello creamos en el raiz del proyecto un archivo llamado mundo.py con el siguiente codigo:
# -*- coding: utf-8 -*-

import pygame
from pygame.locals import *

from mapa import *
from personaje import *

class Mundo:
    ''' Clase para controlar todo lo que ocurre durante el juego:
        Colisiones, Dibujar personajes, control del jugador, etc ...
    '''

    def __init__(self, mapa, personajes):
        ''' Constructor de la Clase que obtiene el mapa y los personajes que intervendrán en esta pantalla'''

        self._personajes = personajes # Array con los personajes que intervienen en este mundo.

        # En la lista que pasemos de los personajes, el primero debe ser siempre el Jugador.
        self._jugador = personajes[0]

        self._mapa = mapa

        # Variable para saber hacia donde quiere mover el jugador.
        self._direccion_movimiento_jugador = None

    def dibujar (self, surface):
        ''' Dibuja las capas del mapa y TODOS los personajes del juego'''
        self._mapa.dibujar(Mapa.LAYER_SUELO, surface,0,0)
        self._mapa.dibujar(Mapa.LAYER_OBJETOS, surface,0,0)
        self._mapa.dibujar(Mapa.LAYER_OBJETOS_SUPERPUESTOS, surface,0,0)
        # Dibujamos los personajes
        for personaje in self._personajes:
            personaje.dibujar(surface, self._mapa.obtener_centro_celda(personaje.fila,personaje.columna))
        self._mapa.dibujar(Mapa.LAYER_CIELO, surface,0,0)
        
    def update(self):
        ''' Mueve y actualiza las posiciones de los personajes '''
        # Si se ha intentado mover al personaje
        if (self._direccion_movimiento_jugador != None):
            
            # Calculamos donde va a mover el Jugador
            posicion_a_mover = (self._jugador.fila + self._jugador.direcciones[self._direccion_movimiento_jugador][0], self._jugador.columna + self._jugador.direcciones[self._direccion_movimiento_jugador][1])
            
            # Si no hay colision ...
            if (not self._hay_colision(self._jugador,posicion_a_mover)):
                self._jugador.mover(self._direccion_movimiento_jugador) # Movemos al jugador en la dirección indicada
            else:
                # Solo cambiamos las direccion pero sin moverlo
                self._jugador.cambiar_direccion(self._direccion_movimiento_jugador)

            self._direccion_movimiento_jugador = None

            # Ordenamos los personajes por fila, para luego dibujarlos correctamente. Para que no se solapen.
            self._personajes.sort(self._comparar_posicion_personajes)
    

    def mover_jugador(self, direccion):
        ''' Establece hacia donde debe mover el jugador '''
        self._direccion_movimiento_jugador = direccion

    def _hay_colision(self, personaje, destino):
        ''' Comprueba si existe colision de un personajes con otros o con el mapa '''
        hay_colision = False
        # Comprobamos que la celda donde va a mover es pisable
        if (not self._mapa.es_pisable(destino[0],destino[1])):            
            hay_colision = True
            
        # Comprobamos las colisiones con el resto de personajes
        for item in self._personajes:
            if (item.nombre != personaje.nombre):
                if (item.obtener_posicion() == destino):
                    hay_colision = True
            
        return hay_colision
        
    def _comparar_posicion_personajes(self, a, b):
        ''' Compara y la posicion de un personaje con respecto a su fila '''
        return cmp(int(a.fila), int(b.fila))

El código está comentado, pero os indico un par de cosas a tener en cuenta.

Lo primero es que en la lista de personajes que pasamos al constructor, el primer elemento ha de ser el Jugador, ya que luego haremos una asignación tal que así:

        self._jugador = personajes[0]

Luego, en el método _hay_colision hemos añadido el control de colision con otros personajes de juego.
Es muy sencillo.

Ahora en los estados del juego correspondientes a los mapas tendremos un código como el que sigue:

import pygame
from pygame.locals import *
from gamemanager.states import  gamestate
from utilidades.imagen import *

from mapa import *
from personaje import *
from mundo import *

import mapa2state
 
class Mapa1State(gamestate.GameState):
 
 
    def __init__(self, parent):

        self.parent = parent

        self._mapa = Mapa('mapa1.tmx')
        
        self.ordenador = Personaje('Ordenador1','data/imagenes/policia.png')
        self.ordenador.actualizar_posicion((5,7))                

        self.parent.jugador.actualizar_posicion((6,3))
        self.parent.jugador.cambiar_direccion(Personaje.ESTE)

        self._personajes = [self.parent.jugador, self.ordenador]

        self._mundo = Mundo(self._mapa, self._personajes)        


    def start(self):
        print "GameState Mapa1 Started"
 
    def cleanUp(self):
        print "GameState Mapa1 Cleaned"
        pass
 
    def pause(self):
        print "GameState Mapa1 Paused"
        pass
 
    def resume(self):
        print "GameState Mapa1 Resumed"
        pass

    def handleEvents(self, events):
        for event in events:
            if event.type == pygame.KEYDOWN :
                if event.key == pygame.K_ESCAPE :
                    self.parent.popState()
                elif event.key == pygame.K_UP :
                    self._mundo.mover_jugador(Personaje.NORTE)
                elif event.key == pygame.K_DOWN :
                    self._mundo.mover_jugador(Personaje.SUR)
                elif event.key == pygame.K_RIGHT :
                    self._mundo.mover_jugador(Personaje.ESTE)
                elif event.key == pygame.K_LEFT :
                    self._mundo.mover_jugador(Personaje.OESTE)

    def update(self):
        self._mundo.update()
        # Si el jugador sale por la puerta cargamos el segundo mapa.
        if (self.parent.jugador.obtener_posicion() == (6,0)):
            self.parent.pushState(mapa2state.Mapa2State(self.parent))
 
    def draw(self):
        self.parent.screen.blit(self.parent.background, (0,0))
        self._mundo.dibujar(self.parent.screen)        

De esta forma hemos centralizado en una clase todo el trabajo repetitivo que se producia en cada una de las pantallas del juego.

Otra cosa que también debemos hacer es extraer la creación el Jugador principal del juego a la clase gameengine.py, ya que solo deberemos crearlo una vez y no por cada estado del juego.

Os dejo un enlace para que os descargueis la última version del juego, con todos los cambios que se han introducido.

Descargar Juego

2 comentarios:

  1. Hola, la verdad que muy buenos tus tutoriales, hasta ahora no tuve ningun problema con los manejos de estados, solo pasaba para agradecer tu esfuerzo y dedicacion ya que este tipo de aportes motiva a principiantes a seguir aprendiendo (me incluyo). Mi unica duda hasta ahora es que diferencia hay entre un self.var o un self._var. Busque y no me quedo muy claro en cuanto a la privacidad, por ejemplo con self._mapa, privado respecto a que? Desde ya muchas gracias. Saludos

    ResponderEliminar
  2. Me alegra mucho que sirva de algo este blog que escribí ;), gracias por escribir tu comentario.
    Con respecto a tu pregunta, realmente no hay diferencia. En python no existen variables privadas, y para diferenciarlas se escriben con un guión bajo delante de ellas.
    No te preocupes por eso.

    Si te interesa el mundo de los desarrollos de juegos y estás ahora mismo empezando, despues de ver lo que se puede hacer con Pygame, date una vuelta por el proyecto donde participo, seguro que te gusta.

    Lo encontrarás en http://pilas-engine.com.ar/

    Un Saludo.

    ResponderEliminar