2012-08-01 10 views
5

Attualmente sto scrivendo un gioco molto semplice usando python e pygame. Ha cose che si muovono. E per far muovere questa roba senza problemi ho organizzato il ciclo di gioco principale come detto in Fix Your Timestep, con interpolazione.Come gestireste l'interpolazione in un gioco Python?

Ecco come gestisco l'interpolazione ora.

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] 

I lerp_xy() funzioni e tipi di dati

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') 

I tipi di dati sono ovviamente temporanea, ma ho ancora aspettare avranno la x ed y attributi in futuro. Aggiornamento : lerper.alpha viene aggiornato ogni frame.

La nave viene aggiunta come un singolo oggetto: è la nave giocatore. Le stelle vengono aggiunte come elenco. I proiettili sono aggiunti come dict {id, Bullet}, poiché i proiettili vengono aggiunti e rimossi in continuazione, devo tenere traccia di quale pallino è che, interpolare se entrambi sono presenti e fare qualcosa se è stato appena aggiunto o eliminato.

In ogni caso questo codice qui è schifo. Cresceva man mano che aggiungevo funzionalità, e ora voglio riscriverlo per essere più generico, in modo che possa continuare a crescere e non diventare un non banale mucchio di cacca puzzolente.

Ora sono ancora piuttosto nuovo in Python, anche se mi sento abbastanza a mio agio con le list comprehensions, i generatori e le coroutine già.

Quello che ho meno esperienza è il lato OO di Python e la progettazione di un'architettura di qualcosa di più grande di uno script usa e getta di 10 righe.

La domanda non è una domanda come in qualcosa che non conosco e non posso farci nulla. Sono sicuro di essere in grado di riscrivere questo codice piuttosto semplice che funzionerà in qualche modo vicino a ciò che voglio.

Quello che voglio sapere, è il modo in cui i programmatori Python esperti risolvono questo semplice problema in modo pironico, così io (e naturalmente altri) potrei imparare quello che è considerato un modo elegante di gestire un caso simile tra Python sviluppatori.

Quindi, quello che circa voglio raggiungere, in pseudocodice:

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()) 

Tuttavia non lasciatevi pseudocodice si ferma se si dispone di una soluzione migliore in mente =)

So. Rendi tutti gli oggetti di gioco come classe separata/ereditata? Costringili tutti ad avere l'id? Aggiungili tutti come lista/dict lerper.add([ship])? Crea una classe contenitore speciale ereditando da Dict/Qualunque? Cosa consideri un modo elegante e pitonioso per risolvere questo? Come lo faresti?

risposta

1

Ecco come ho finito per gestire l'interpolazione:

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

Ho scritto un clone di Breakout mezzo scaduto che ha un simile codice "oggetti in movimento" come il tuo, quindi condividerò come l'ho fatto.

Qualsiasi elemento con una posizione e velocità viene istanziato dalla classe del proiettile. Anche gli oggetti non in movimento possono essere proiettili. Un proiettile è responsabile dell'aggiornamento della propria posizione quando qualcuno chiama tick su di esso.

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 

In seguito, si consiglia di sostituire x e y con qualcosa di simile a un axis-aligned bounding box, in modo da poter fare il rilevamento delle collisioni tra i proiettili.

Tutti i proiettili che interagiscono tra loro vivono in un livello, che è responsabile di tick in ciascun proiettile.

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 

L'installazione consiste semplicemente nel creare oggetti di gioco con le proprietà desiderate e aggiungerli a un livello.

#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) 

disegno è dove il vostro programma e la mia divergono - tutto in breakout è un rettangolo, quindi ogni oggetto di gioco può essere disegnato nello stesso modo. Ma un invasore di spazio non sembra una stella, e un proiettile non sembra una nave spaziale, quindi tutti avrebbero bisogno di un codice univoco. A questo punto, dovresti prendere in considerazione la creazione di Ship, Star, Bullet, Enemy, ecc., Che sono sottoclassi di Projectile. Quindi ognuno può specificare il proprio aspetto. Potrebbero anche avere comportamenti individuali oltre a muoversi in linea retta - es. Le navi rispondono ai tasti, i nemici accelerano verso le navi, ecc.

+1

Hai letto tutta la questione? Non vedo nulla relativo all'interpolazione o al problema che ho presentato nella risposta. L'unica "idea" che vedo nella tua risposta è "fai in modo che ogni oggetto di gioco gestisca la propria interpolazione", ma non lo menzioni nemmeno O.o – Mikka

+0

Forse ho frainteso la tua domanda. Intendevo dire: "Come posso modificare il mio gioco in modo che sia più generico e possa essere facilmente ampliato?". Se la tua domanda era in realtà "Come posso modificare il mio algoritmo di interpolazione per essere più generico?", Hai già indovinato la mia opinione: fai in modo che ogni oggetto di gioco gestisca la propria interpolazione. (preferibilmente solo una volta, nella classe base di Projectile) – Kevin

2

Questo potrebbe non essere quello che stai cercando, ma spera che ti spinga in una direzione utile nel tentativo di scrivere un gioco. Le seguenti ricette scritte per Python 3.x forniscono un esempio di scrittura di moduli di lavoro.

  • vector (fornisce una classe per lavorare con le coordinate 2D e fa più di complex numeri)
  • processing (fornisce un framework estensibile per la creazione di un'animazione o fare un gioco semplice)
  • boids (mostra come crea un'animazione strutturata che gira indipendentemente dalla frequenza dei fotogrammi)

Potresti guardare il codice sopra riportato e usarlo come ispirazione per scrivere il tuo framework o str ucturing il tuo codice in modo che diventi riutilizzabile. Il progetto di riferimento è stato ispirato da Processing.org.

Problemi correlati