martes, 15 de febrero de 2011

Estados del juego

Un juego se divide en distintos estados por los que puede pasar.
Por ejemplo:
  • Menú Principal
  • Opciones del Juego
  • Créditos
  • Pantalla del juego
  • etc ...
Todos estos estados comparten unos comportamientos como son:
  • Captura de eventos (teclado, ratón, etc..)
  • Dibujar (mostrar las opciones del menú, mostrar la pantalla del juego ...)
  • Actualizar estados (cambiar la opcion del menu seleccionada, actualizar la posicion del jugador ...)
  • Y otros como pueden ser, ¿que pasa cuando se inicializa el estado?, ¿que pasa cuando se finaliza?, ¿que pasa si se hace un pause?
Para controlar todos estos estados de una forma muy sencilla, existe un método que descubrí hace muchos años que es el siguiente.

Tendremos una clase llamada GameManager que controlará el paso de un estado a otro y sus eventos, y una clase abstracta llamada GameState, de la que heredarán todos los estados por los que pueda pasar nuestro juego.

Para que veais como funciona todo esto, vamos a hacer un pequeño ejemplo que luego nos servirá para nuestro juego.

Necesitamos crear inicialmente un fichero llamada juego.py y dos carpetas una llamada gamemanager y otra dentro de esta llamada states.

Dentro de la carpeta "states" crearemos un archivo llamado gamestate.py con el siguiente codigo:


class GameState(object):

    def __init__(self,parent):
        pass

    def start(self):
        pass

    def cleanUp(self):
        pass

    def pause(self):
        pass

    def resume(self):
        pass

    def handleEvents(self, events, parent):
        pass

    def update(self):
        pass

    def draw(self):
        pass


Ahora dentro de la carpeta "gamemanager" creamos 2 archivos, el primero con el nombre singleton.py con el siguiente codigo:

class Singleton(type):
    def __init__(cls,name,bases,dic):
        super(Singleton,cls).__init__(name,bases,dic)
        cls.instance=None

    def __call__(cls,*args,**kw):
        if cls.instance is None:
            cls.instance=super(Singleton,cls).__call__(*args,**kw)
        return cls.instance


y el otro con el nombre gamemanager.py y con el siguiente codigo:

import os
import sys

import pygame
from pygame.locals import *

from singleton import *

class GameManager(object):

    __metaclass__ = Singleton

    def __init__(self, titulo, size=(320, 200), fullscreen=False):

        print "Inicilizado el GameManager"

        self.states = []
        self.running = True
        os.environ['SDL_VIDEO_CENTERED'] = '1'
        pygame.init();
        self.screen = pygame.display.set_mode(size)

        pygame.display.set_caption(titulo)
        pygame.mouse.set_visible(0)

    def cleanUp (self):
        while len(self.states) > 0:
            state = self.states.pop()
            state.cleanUp()

        sys.exit(0)

    def changeState (self, gameState):
        print "Cambio de Estado"
        if len(self.states) > 0:
            state = self.states.pop()
            state.cleanUp()

        self.states.append(gameState)
        self.states[-1].start()

    def pushState(self, gameState):
        if len(self.states) > 0:
            self.states[-1].pause()

        self.states.append(gameState)
        self.states[-1].start()

    def popState(self):
        if len(self.states) > 0:
            state = self.states.pop()
            state.cleanUp()

        if len(self.states) > 0:
            self.states[-1].resume()

    def handleEvents(self, events):
        self.states[-1].handleEvents(events)

    def update(self):
        self.states[-1].update()

    def draw(self):
        self.states[-1].draw()
        pygame.display.flip()

    def quit(self):
        print "Quit"
        self.running = False


Con estos archivos ya tenemos toda la base para crear nuestro juego.

Ahora solo nos quedará crear un estado inicial para nuestro juego, por ejemplo el menú, y proceder a arrancarlo.

Para ello hacemos lo siguiente, creamos un archivo llamado menustate.py en la carpeta "states" y le añadimos el siguiente código:
import pygame
from pygame.locals import *
from gamemanager.states import  gamestate

class MenuState(gamestate.GameState):


   def __init__(self, parent):
       self.parent = parent
       self.background = pygame.Surface(self.parent.screen.get_size())
       self.background = self.background.convert()
       self.background.fill((255, 0, 0))

   def start(self):
       print "GameState Menu Started"

   def cleanUp(self):
       print "GameState Menu Cleaned"
       pass

   def pause(self):
       print "GameState Menu Paused"
       pass

   def resume(self):
       print "GameState Menu Resumed"
       pass

   def handleEvents(self, events):
       for event in events:
           if event.type == pygame.KEYDOWN :
               if event.key == pygame.K_ESCAPE :
                   self.parent.quit()

   def update(self):
       pass

   def draw(self):
       self.parent.screen.blit(self.background, (0,0))


Y por ultimo añadimos al archivo principal juego.py el siguiente codigo:

import pygame
from pygame.locals import *

from gamemanager.gamemanager import GameManager
from gamemanager.states import menustate

if __name__ == "__main__":

    game = GameManager('Juego Python',(320,200),False)

    game.changeState(menustate.MenuState(game))

    while game.running:
        game.handleEvents(pygame.event.get())
        game.update()
        game.draw()

    game.cleanUp()


Nota: Por cuestiones del lenguaje de Python debemos crear un archivo llamado __init__.py dentro de cada una de las carpetas que hemos creado (gamemanager, states).
Por ahora podemos dejar en blanco dicho archivo.

Ya podremos ejecutar el programa y veremos como se inicia el estado de Menu.
python juego.py

Al pulsar la tecla ESC saldremos del programa.

Ahora puedes intentar crear un estado nuevo, por ejemplo Opciones, e intentar llamarlo desde el Menu.

Para ello os explicaré un par de cosas.
Podemos pasar de un estado a otro conservando en memoria el estado actual o cambiando por completo el estado.

Para cambiar de un estado a otro, sin mantener el estado actual, se hace de la siguiente forma:

self.parent.changeState(opcionesstate.OpcionesState(self.parent)


y para cambiar de un estado a otro conservando el estado actual:

self.parent.pushState(opcionesstate.OpcionesState(self.parent)


Si cambiamos el estado de esta ultima forma, para recuperar el estado anterior, deberemos ejecutar esto en el estado en el que nos encontramos ahora.

self.parent.popState()


Si no os queda claro, os pongo el codigo necesario para hacer esto que os comento.
El archivo opcionesstate.py tendrá que crearse en la carpeta "states" con el siguiente codigo:

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

class OpcionesState(gamestate.GameState):


   def __init__(self, parent):
       self.parent = parent
       self.background = pygame.Surface(self.parent.screen.get_size())
       self.background = self.background.convert()
       self.background.fill((0, 255, 0))

   def start(self):
       print "GameState Opciones Started"

   def cleanUp(self):
       print "GameState Opciones Cleaned"
       pass

   def pause(self):
       print "GameState Opciones Paused"
       pass

   def resume(self):
       print "GameState Opciones Resumed"
       pass

   def handleEvents(self, events):
        for event in events:
           if event.type == pygame.KEYDOWN :
               if event.key == pygame.K_ESCAPE :
                   self.parent.popState()

   def update(self):
       pass

   def draw(self):
       self.parent.screen.blit(self.background, (0,0))


Y el archivo menustate.py quedará de la siguiente forma:

import pygame
from pygame.locals import *
from gamemanager.states import  gamestate
import opcionesstate

class MenuState(gamestate.GameState):


   def __init__(self, parent):
       self.parent = parent
       self.background = pygame.Surface(self.parent.screen.get_size())
       self.background = self.background.convert()
       self.background.fill((255, 0, 0))

   def start(self):
       print "GameState Menu Started"

   def cleanUp(self):
       print "GameState Menu Cleaned"
       pass

   def pause(self):
       print "GameState Menu Paused"
       pass

   def resume(self):
       print "GameState Menu Resumed"
       pass

   def handleEvents(self, events):
       for event in events:
           if event.type == pygame.KEYDOWN :
               if event.key == pygame.K_ESCAPE :
                   self.parent.quit()
               elif  event.key == pygame.K_o:
                   self.parent.pushState(opcionesstate.OpcionesState(self.parent))

   def update(self):
       pass

   def draw(self):
       self.parent.screen.blit(self.background, (0,0))


Ya podeis probar y jugar con los distintos estados hasta la siguiente entrega.

4 comentarios:

  1. ¿Seguirás con el blog? Es que me estoy iniciando en PyGame y me está siendo muy útil. ¿Podrías subir al menos (algunas partes) del código de tu juego?


    Gracias.

    Gispsan

    ResponderEliminar
  2. Si claro, seguiré con el Blog.
    A ver si esta seman publico algo más ;)

    ¿Has probado todo?
    ¿Te funciona correctamente?

    ResponderEliminar
  3. Muchas gracias por compartir esto pive, estoy siguiendo este curso y voy por esta parte de blog, aunque estoy realizando un juego plataforma ya yo adapatare todo lo que expongas a mi juego ya que creo que basicamente es lo mismo.

    Todo funciona perfecto! por lo menos lo que pones en esta parte de la pagina.

    aunque en el primer code que pusistes se te colo esta etiqueta en la linea 22.

    una pregunta ¿que tecnologia usastes para pegar el codigo con este estylo en el blog?

    ResponderEliminar
  4. Genial!!!, si necesitas ayuda dilo.

    Para que el codigo salga aí utilizo al etiqueta
    pre class="brush: python"

    ResponderEliminar