2012-08-01 7 views
5

Actualmente estoy escribiendo un juego muy simple usando python y pygame. Tiene cosas que se mueven. Y para hacer que estas cosas se movieran sin problemas, arreglé el bucle principal del juego como se dice en Fix Your Timestep, con interpolación.¿Cómo manejarías la interpolación en un juego de pitón?

Así es como manejo la interpolación ahora.

class Interpolator(object): 
    """Handles interpolation""" 
    def __init__(self): 
     self.ship_prev = None 
     self.ship = None 

     self.stars_prev = [] 
     self.stars = [] 

     self.bullets_prev = {} 
     self.bullets = {} 

     self.alpha = 0.5 

    def add_ship(self, ship): 
     self.ship_prev = self.ship 
     self.ship = ship 

    def add_stars(self, stars): 
     self.stars_prev = self.stars 
     self.stars = stars[:] 

    def add_bullets(self, bullets): 
     self.bullets_prev = self.bullets 
     self.bullets = bullets.copy() 

    def add_enemies(self, enemies): 
     self.enemies_prev = self.enemies 
     self.enemies = enemies # to be continued 

    def lerp_ship(self): 
     if self.ship_prev is None: 
      return self.ship 
     return lerp_xy(self.ship_prev, self.ship, self.alpha) 

    def lerp_stars(self): 
     if len(self.stars_prev) == 0: 
      return self.stars 
     return (lerp_xy(s1, s2, self.alpha) for s1, s2 in izip(self.stars_prev, self.stars)) 

    def lerp_bullets(self): 
     keys = list(set(self.bullets_prev.keys() + self.bullets.keys())) 
     for k in keys: 
      # interpolate as usual 
      if k in self.bullets_prev and k in self.bullets: 
       yield lerp_xy(self.bullets_prev[k], self.bullets[k], self.alpha) 
      # bullet is dead 
      elif k in self.bullets_prev: 
       pass 
      # bullet just added 
      elif k in self.bullets: 
       yield self.bullets[k] 

Las funciones y tipos de datos lerp_xy()

def lerp_xy(o1, o2, alpha, threshold=100): 
    """Expects namedtuples with x and y parameters.""" 
    if sqrt((o1.x - o2.x) ** 2 + (o1.y - o2.y) ** 2) > 100: 
     return o2 
    return o1._replace(x=lerp(o1.x, o2.x, alpha), y=lerp(o1.y, o2.y, alpha)) 

Ship = namedtuple('Ship', 'x, y') 
Star = namedtuple('Star', 'x, y, r') 
Bullet = namedtuple('Bullet', 'x, y') 

Los tipos de datos son, por supuesto temporal, pero todavía esperan que tendrán la X e Y. atributos en el futuro. actualización: lerper.alpha se actualiza cada cuadro.

El envío se agrega como un único objeto: es la nave del jugador. Las estrellas se agregan como una lista. Las viñetas se agregan como dict {id, Bullet}, ya que las viñetas se agregan y eliminan todo el tiempo, tengo que rastrear qué viñeta es la que, interpolar si ambas están presentes y hacer algo si solo se ha agregado o eliminado.

De todos modos este código aquí es una mierda. Creció a medida que añadí funciones, y ahora quiero reescribirlo para que sea más genérico, para que pueda seguir creciendo y no convertirse en una pila de poo unpythonic apestosa.

Ahora sigo siendo bastante nuevo en Python, aunque ya me siento bastante cómodo con la lista de comprensiones, generadores y corutinas.

Lo que menos experiencia tengo es el lado OO de Python, y el diseño de una arquitectura de algo más grande que un script desechable de 10 líneas.

La pregunta no es una pregunta como en algo que no sé y no puedo hacer nada al respecto. Estoy seguro de que soy capaz de volver a escribir este código bastante simple que funcionará de alguna manera cerca de lo que quiero.

Lo que sí quiero saber, es la forma en que los programadores experimentados de Python resolverán este problema simple de manera pitónica, así que (y por supuesto otros) podría aprender lo que se considera una forma elegante de manejar este caso entre Python desarrolladores.

Por lo tanto, lo que quiero lograr aproximadamente, en pseudocódigo:

lerper = Interpolator() 
# game loop 
while(1): 
    # physics 
    # physics done 
    lerper.add(ship) 
    lerper.add(stars) 
    lerper.add(bullets) 
    lerper.add(enemies) # you got the idea 

    # rendering 
    draw_ship(lerper.lerp('Ship')) 
    # or 
    draw_ship(lerper.lerp_ship()) 

Sin embargo no deje que eso le impida pseudocódigo si usted tiene una mejor solución en mente =)

So. Hacer todos los objetos del juego como una clase separada/heredada? ¿Forzarlos a todos a tener id? Agrégalos todos como list/dict lerper.add([ship])? Haz una clase de contenedor especial heredando de dict/whatever? ¿Qué consideras una forma elegante y pitónica de resolver esto? ¿Como lo harias?

Respuesta

1

Aquí es como terminé el manejo de la interpolación:

class Thing(object): 
    """Generic game object with interpolation""" 
    def __init__(self, x=0, y=0): 
     self._x = self.x = x 
     self._y = self.y = y 

    def imprint(self): 
     """call before changing x and y""" 
     self._x = self.x 
     self._y = self.y 

    def __iter__(self): 
     """handy to unpack like a tuple""" 
     yield self.x 
     yield self.y 

Ship = Thing 
Bullet = Thing 


class Star(Thing): 
    """docstring for Star""" 
    def __init__(self, x, y, r): 
     super(Star, self).__init__(x, y) 
     self.r = r 

    def __iter__(self): 
     yield self.x 
     yield self.y 
     yield self.r 


def lerp_things(things, alpha, threshold=100): 
    """Expects iterables of Things""" 
    for t in things: 
     if sqrt((t._x - t.x) ** 2 + (t._y - t.y) ** 2) > threshold: 
      yield (t.x, t.y) 
     else: 
      yield (lerp(t._x, t.x, alpha), lerp(t._y, t.y, alpha)) 
1

Escribí un clon de Breakout a medio terminar que tiene un código similar de "objetos en movimiento" como el suyo, así que compartiré cómo lo hice.

Cualquier cosa con una posición y velocidad se instancia de la clase Proyectil. Los objetos que no se mueven también pueden ser proyectiles. Un proyectil es responsable de actualizar su propia posición cuando alguien llama al tick.

class Projectile: 
    def __init__(self): 
     self.x = 0 
     self.y = 0 
     self.vel_x = 0 
     self.vel_y = 0 
    def tick(self, dt): 
     self.x += dt * self.vel_x 
     self.y += dt * self.vel_y 

Más tarde, es posible que desee reemplazar x y y con algo así como un axis-aligned bounding box, por lo que puede hacer la detección de colisiones entre los proyectiles.

Todos los proyectiles que interactúan entre sí viven en una capa, que es responsable de tick cada proyectil.

class Layer: 
    def __init__(self): 
     self.projectiles = [] 
    def addProjectile(self, p): 
     self.projectiles.add(p) 
    def tick(self, dt): 
     for p in self.projectiles: 
      p.tick(dt) 
     #possible future expansion: put collision detection here 

La configuración es solo una cuestión de crear objetos de juego con las propiedades que desee y agregarlos a una capa.

#setup 
l = Layer() 

ship = Projectile() 
#not shown: set initial position and velocity of ship 
l.addProjectile(ship) 

for i in range(numStars): 
    star = Projectile() 
    #not shown: set initial position and velocity of stars 
    l.addProjectile(star) 

#not shown: add bullets and enemies to l, the same way stars were 

#game loop 
while True: 
    #get dt somehow 
    dt = .42 
    l.tick(dt) 
    for projectile in l.projectiles: 
     draw(l) 

El dibujo es, donde su programa y la mía divergen - todo en la ruptura es un rectángulo, por lo que todos los objetos del juego se pueden extraer de la misma manera. Pero un invasor espacial no se ve como una estrella, y una bala no se parece a una nave espacial, por lo que todos necesitarían un código único. En este punto, deberías considerar hacer Ship, Star, Bullet, Enemy, etc., que son subclases de Projectile. Entonces cada uno puede especificar su propia apariencia. También podrían tener comportamientos individuales más allá de moverse en línea recta, ej. Los barcos responden a las pulsaciones de teclas, los enemigos se aceleran hacia los barcos, etc.

+1

¿Leyó usted toda la cuestión? No veo nada relacionado con la interpolación o el problema que presenté en su respuesta. La única "idea" que veo en tu respuesta es "haz que cada objeto del juego maneje su propia interpolación", pero ni siquiera lo mencionas O.o – Mikka

+0

Quizás malentendí tu pregunta. Lo interpreto en el sentido de "¿Cómo reviso mi juego para que sea más genérico y se pueda expandir fácilmente?". Si su pregunta fue en realidad "¿Cómo reviso mi algoritmo de interpolación para que sea más genérico?", Entonces ya ha adivinado mi opinión: haga que cada objeto del juego maneje su propia interpolación. (preferiblemente solo una vez, en la clase base del Proyectil) – Kevin

2

Esto puede no ser lo que estás buscando, pero es de esperar que te ayude a escribir un juego. Las siguientes recetas escritas para Python 3.x proporcionan un ejemplo de escritura de módulos de trabajo.

  • vector (proporciona una clase para trabajar con coordenadas 2D y hace más de complex números)
  • processing (proporciona un marco extensible para crear una animación o hacer un juego simple)
  • boids (muestra cómo crear una animación estructurada que se ejecute independientemente de la velocidad de fotogramas)

Puede consultar el código proporcionado anteriormente y utilizarlo como inspiración para escribir su marco o str ucturing tu código para que se vuelva reutilizable. El proyecto al que se hace referencia fue inspirado por Processing.org.

Cuestiones relacionadas