Το προφίλ μας στο Google Plus
3

Python Game Programming (Part 4.5)

Έχουμε πλέον μπει για τα καλά στο Pygame και στον αντικειμενοστρεφή προγραμματισμό και σας βλέπουμε όλους ανυπόμονους για τη συνέχεια! Από τώρα σας λέμε για να το ξέρετε, ότι θα είναι ακόμα πιο ενδιαφέρουσα από ό,τι φαντάζεστε! Πάμε τώρα να δούμε μερικές από τις ασκήσεις που σας δώσαμε στο άρθρο 3.5 καθώς και στο 4, που δημοσιεύεται στο τεύχος 008 του deltaHacker.

Στο άρθρο 3.5 σας δώσαμε ένα μάλλον κακογραμμένο κομμάτι κώδικα, το οποίο υλοποιούσε ένα bouncing ball με δύο μπάλες. Όπως κι εσείς διαπιστώσατε, δεν είχε σημαντικές διαφορές με το πρωτότυπο. Γιατί, πολύ απλά, για δύο μπάλες χρειάζεστε:

Το ίδιο γραφικό, δύο φορές. Εύκολο, το έχουμε ήδη
Διπλές μεταβλητές που δείχνουν: Θέση μπάλας, απόσταση που διανύθηκε, ταχύτητα σε κάθε άξονα

Αν προσέξετε λοιπόν το listing, θα διαπιστώσετε ότι οι μεταβλητές για τη μια μπάλα κλωνοποιούνται για τη δεύτερη:

Θέση πρώτης μπάλας: μεταβλητές x και y
Θέση δεύτερης μπάλας: x2 και y2
Ταχύτητα πρώτης μπάλας: xspeed και yspeed
Ταχύτητα δεύτερης μπάλας: xspeed2 και yspeed2
Απόσταση που διάνυσε η πρώτη μπάλα: distance_x και distance_y
Απόσταση που διάνυσε η δεύτερη μπάλα: distance_x2 και distance_y2

Αν και μπορεί αρχικά να σας φάνηκε ικανοποιητική η λύση, σίγουρα κολλήσατε όταν σας ζητήσαμε να το φτιάξετε για τρεις, πέντε ή δέκα μπάλες. Όχι ότι δεν γίνεται να συνεχίσετε να προσθέτετε μεταβλητές, αλλά είναι προφανές ότι η λύση αυτή δεν είναι ούτε κομψή, ούτε γενική. Τι θα μπορούσατε να σκεφτείτε;

Έχετε καταλάβει σίγουρα ότι όταν έχουμε δεδομένα ίδιου τύπου, τα οποία θέλουμε να τα χειριστούμε μαζικά και με τον ίδιο τρόπο, βολεύει αντί να τα αποθηκεύσουμε σε απλές μεταβλητές να χρησιμοποιήσουμε κάποια δομή που μας παρέχει η εκάστοτε γλώσσα προγραμματισμού για αυτό το σκοπό. Για παράδειγμα, οι παραδοσιακές γλώσσες όπως η BASIC, από την οποία άλλωστε προέρχεται και το αρχικό μας bouncing ball, χρησιμοποιούν τη δομή των πινάκων. Στην Python, βασική δομή για αυτό το σκοπό είναι οι λίστες.

Όπως και οι πίνακες, έτσι και οι λίστες κάνουν δυνατή τη μαζική επεξεργασία των δεδομένων, καθώς μας επιτρέπουν να εκτελούμε σε κάθε στοιχείο τους την ίδια λειτουργία διατρέχοντάς τες μέσα από κάποιο βρόχο. Πράγματι, μια λύση θα ήταν να δημιουργούσαμε μια λίστα που να περιέχει μέσα τις τιμές των στοιχείων:

<br />
[x, y, xspeed, yspeed]<br />

Επειδή μάλιστα θα χρειαζόμασταν μια λίστα για κάθε μπάλα, θα είχαμε πολλές τέτοιες λίστες μέσα σε μια άλλη (αυτό δεν είναι κάτι καινούριο, θυμηθείτε ότι το έχουμε ήδη κάνει στο Adventure). Παράδειγμα:

<br />
balls = [ [100.0, 100.0, 50.0,50.0],<br />
          [50.0, 50.0, 30.0, 30.0], ...]<br />

Πράγματι, έτσι το πρόγραμμά μας θα γινόνταν αμέσως πολύ καλύτερο: Αντί να γράφουμε συνέχεια τις ίδιες εντολές με άλλα ονόματα μεταβλητών, θα είχαμε έναν βρόχο for που θα διέτρεχε τη λίστα και θα εκτελούσε τις εντολές σε κάθε στοιχείο της λίστας χωριστά. Μια χαρά.

Μια χαρά, μόνο που δεν είμαστε στα 80s! Είπαμε να εμπνευστούμε από τα προγράμματα του TI-99/4A, όχι να τα ξαναγράψουμε στη BASIC της εποχής! Ο κόσμος μάς φωνάζει απεγνωσμένα ότι θέλει να γίνει object oriented!

Τι είναι μια μπάλα-αντικείμενο; Δείτε:

<br />
class Ball:<br />
    def __init__(self,theImage,x,y,xspeed,yspeed):<br />
        self.ballimage = theImage<br />
        self.x = x<br />
        self.y = y<br />
        self.xspeed = xspeed<br />
        self.yspeed = yspeed<br />
        self.shape = pygame.image.load(theImage)</p>
<p>    def Show(self,surface):<br />
        surface.blit(self.shape, (self.x, self.y))</p>
<p>    def GetWidth(self):<br />
        return self.shape.get_width()</p>
<p>    def GetHeight(self):<br />
        return self.shape.get_height()</p>
<p>    def Move(self,time):<br />
        distance_x = time * self.xspeed<br />
        distance_y = time * self.yspeed<br />
        self.x = self.x + distance_x<br />
        self.y = self.y + distance_y</p>
<p>    def IsOutofX(self,xmin,xmax):<br />
        if (self.x &gt;= (xmax - self.GetWidth()) or self.x &lt;= xmin):<br />
            return True<br />
        else:<br />
            return False</p>
<p>    def IsOutofY(self,ymin,ymax):<br />
        if (self.y &gt;= (ymax - self.GetHeight()) or self.y &lt;= ymin):<br />
            return True<br />
        else:<br />
            return False<br />

Μη φωνάζετε! Είναι απλούστατο. Η μπάλα για τον εαυτό της κρατάει τις πληροφορίες που είπαμε πριν: τη θέση (x, y), την ταχύτητα (xspeed, yspeed) και την εικόνα (hint: όχι μόνο μπορείτε να φτιάξετε πολλαπλά bouncing balls, αλλά κάθε μπάλα μπορεί να έχει και δική της όψη). Δείτε λίγο τον constructor, τη συνάρτηση δημιουργίας ενός αντικειμένου τύπου μπάλας:

<br />
def __init__(self,theImage,x,y,xspeed,yspeed):<br />

O συγκεκριμένος constructor παίρνει και κάποιες αρχικές τιμές, οι οποίες αποδίδονται απευθείας στις μεταβλητές ιδιοτήτων της μπάλας μας. Τυπικά, για να δημιουργήσουμε μια μπάλα στο κύριο πρόγραμμά μας, θα γράψουμε κάτι σαν το παρακάτω:

<br />
MyBall = Ball(&quot;soccer.png&quot;,100.0,100.0,50.0,50.0)<br />

Φυσικά, εννοείται ότι αντί για απευθείας τιμές (literal values), θα μπορούσαμε εδώ να έχουμε μεταβλητές. Αλλά για να δούμε τι άλλες συναρτήσεις περιέχει η κλάση μας — με λίγα λόγια τι ξέρει να κάνει η μπάλα μας από μόνη της.

<br />
def Show(self,surface):<br />

Να δείξει τον εαυτό της σε μια επιφάνεια που θα της δώσουμε (με τη μέθοδο blit, φυσικά).

<br />
def GetWidth(self): def GetHeight(self):<br />

Να μας πει το μέγεθός της (πλάτος και ύψος) σε pixels, χρησιμοποιώντας τα get_width και get_height του pygame.

<br />
def Move(self,time):<br />

Να κινηθεί. Πολύ λογικό: Γνωρίζει τόσο την ταχύτητά της (όπως τη δώσαμε στον constructor) όσο και τις προηγούμενες συντεταγμένες της. Οπότε το μόνο που χρειάζεται για τον υπολογισμό είναι και ο χρόνος, τον οποίο περνάμε ως παράμετρο από το κύριο πρόγραμμα.

<br />
def IsOutofX(self, xmin, xmax):<br />
def IsOutofY(self, ymin, ymax):<br />

Να ελέγξει αν έχει βγει εκτός ορίων της οθόνης, σε οποιονδήποτε από τους δύο άξονες. Το μόνο έξτρα που χρειάζεται είναι φυσικά τα ίδια τα όρια, τα οποία και πάλι τα περνάμε ως παραμέτρους.

Από εκεί και πέρα το πρόγραμμα είναι πολύ απλό (κατεβάστε το από εδώ). Θα διαπιστώσετε ότι χρησιμοποιούμε μια λίστα που αποθηκεύει… μπάλες. Στην κυριολεξία, η λίστα αυτή περιέχει μέσα αντικείμενα τύπου μπάλας, τα οποία προσθέτουμε με τον τρόπο που φαίνεται παρακάτω:

<br />
balls=[]<br />
for i in range(0,8):<br />
    balls.append(Ball(ballimage,x,y,xspeed,yspeed))<br />

Βλέπετε βέβαια την απλή εκδοχή, γιατί μέσα στο βρόχο υπάρχουν και εντολές που δημιουργούν για κάθε μπάλα νέες τυχαίες τιμές θέσης και ταχύτητας. Δείτε το πλήρες πρόγραμμα και θα το καταλάβετε αμέσως. Για να αντιστρέψουμε την ταχύτητα της μπάλας όταν βγει εκτός ορίων, κάνουμε κάτι σαν το παρακάτω:

<br />
if theball.IsOutofX(0,640):<br />
     theball.xspeed = -theball.xspeed<br />

Αυτό είναι εφικτό γιατί οι μεταβλητές self της μπάλας είναι προσβάσιμες *και* στο κύριο πρόγραμμά μας, με το όνομα του αντικειμένου από μπροστά. Αλλά θα μπορούσατε επίσης να δημιουργήσετε συναρτήσεις του τύπου:

<br />
def GetXSpeed(self):<br />
def GetYSpeed(self):<br />
def SetXSpeed(self, xspeed):<br />
def SetYSpeed(self, yspeed):<br />

Θέλετε να το δοκιμάσετε; Δεν θα σας πούμε όχι :)

Λύσεις σε επιλεγμένες ασκήσεις του άρθρου 4, στο deltaHacker 008
Έχουμε την εντύπωση ότι σας κακομαθαίνουμε, δίνοντάς σας τις λύσεις των ασκήσεων κάθε άρθρου στο περιοδικό. Όχι τίποτα άλλο, αλλά φοβόμαστε μήπως κάποιοι τις χρησιμοποιούν ως τυφλοσούρτη :( Τέλος πάντων, όπως και να είναι αυτή τη φορά θα σας δώσουμε λύσεις μόνο σε συγκεκριμένες ασκήσεις.

Προσθήκη cheat (κερδίστε το GraphicsMatch)
Για να γίνει το αδύνατο δυνατό, δηλαδή να βγείτε κερδισμένοι στο GraphicsMatch, υπάρχει ένας μόνο τρόπος: το cheating.

<br />
if keyboardinput == K_c:<br />
    if score &lt; 0:<br />
        score = -score<br />

Δεν νομίζουμε ότι χρειάζεται να σας πούμε σε ποιο σημείο θα βάλετε αυτές τις γραμμές, βέβαια.

Προσθήκη γραμμής… πανηγυρισμού
Θα τη φτιάξετε μόνοι σας, αλλά θα σας δώσουμε ένα hint. Αποθηκεύστε τους βαθμούς, αντί να τους αθροίζετε άμεσα, στο score:

<br />
if spacepressed:<br />
    slotmachine.Spin(screen,x,y,framerate,clock)<br />
    points = slotmachine.GetScore()<br />
    score += points<br />

Καταφύγετε στο γνωστό font.render όπως και στο score, για να δημιουργήσετε το μήνυμα Win points ή το Lose points κ.λπ. Χρησιμοποιήστε κατάλληλες συντεταγμένες στο blit, ώστε να εμφανίζεται το μήνυμα πάνω από το score.

Μια άλλη εκδοχή της Spin
Μεταφέρουμε το pygame.display.update() στο κύριο πρόγραμμα, μαζί με το βρόχο. Η συνάρτηση Spin απλοποιείται ως εξής:

<br />
def Spin(self, surface, x, y):<br />
    self.luck = (randint(0,5),randint(0,5), randint(0,5))<br />
    for i in self.luck:<br />
        surface.blit(self.slot[i], (x, y))<br />
        x = x + self.slot[i].get_width() + 3<br />

Το x1 δεν χρειάζεται πλέον, καθώς το γεγονός ότι η Spin αλλάζει την τιμή του x που λαμβάνει ως παράμετρο από το κύριο πρόγραμμα δεν έχει καμιά επίδραση στην τιμή του x — ας μην ξεχνάμε ότι η Python κάνει κλήσεις κατά τιμή (by value). Το κύριο πρόγραμμα διαμορφώνεται κάπως έτσι:

<br />
if spacepressed:<br />
    for draws in range(0,50):<br />
        slotmachine.Spin(screen,x,y)<br />
        pygame.display.update()<br />
        time=clock.tick(framerate)<br />
    score += slotmachine.GetScore()<br />

Στο τεύχος 009 του deltaHacker θα ξεκινήσουμε να γράφουμε ένα ωραίο μικρό shoot-em-up, πάνω στο οποίο θα δούμε λεπτομέρειες σχετικά με την κίνηση, την ανίχνευση συγκρούσεων, τη μουσική και τον ήχο, την ανίχνευση πίεσης πολλαπλών πλήκτρων και την επίδραση μεγάλων δόσεων καφεΐνης στον ανθρώπινο οργανισμό! Προετοιμαστείτε όμως κατάλληλα, γιατί καθώς καταλαβαίνετε τα objects θα πέφτουν βροχή ;)

3 Responses to “Python Game Programming (Part 4.5)”

  1. lonas | 09/06/2012 at 00:30

    Μια παραλλαγή του slotmachine -γνωστή σε όσους έπαιζαν pokemon- βασισμένη στον κώδικα του τεύχους 008 http://pastebin.com/pGE6jQew

    • sonic2000gr | 09/06/2012 at 01:40

      Aha, ενδιαφέρουσα παραλλαγή!
      Χάνω συνέχεια και σε αυτό πάντως. Ευτυχώς έχει cheat mode…

  2. kon | 20/03/2014 at 13:00

    για τον sonic
    πως μπορώ να βάλω την σωστή διεύθυνση (συντακτικά) στο παραθυρικό περιβάλλον της python 2.7

    (η έκδοση 3.3 βγάζει ένα λάθος στον πρώτο βρόχο for
    “index = 0
    print “Έξοδοι:”,
    for i in destinations:”
    (μήπως φταίει η έκδοση του pygame;))

    για το παιχνίδι adventure, ώστε να τρέξει το πρόγραμμα και να φτάσει στο τέλος;
    τι μπορεί να κάνω λάθος στην εισαγωγή της εντολής μετά το
    “The Adventure!
    ==============
    Βρισκεσαι στον κήπο. Παντού σκοτάδι.
    Έξοδοι: Νότια;;

    που ΞΈΞµΟ‚ Ξ½Ξ± πας; where do you want to go?’
    ευχαριστώ

Leave a Reply

You must be logged in to post a comment.

Σύνδεση

Αρχείο δημοσιεύσεων