DevOps C++ και "kitchen wars", ή Πώς άρχισα να γράφω παιχνίδια ενώ έτρωγα

«Ξέρω ότι δεν ξέρω τίποτα» Σωκράτης

Για ποιόν: για ανθρώπους πληροφορικής που φτύνουν όλους τους προγραμματιστές και θέλουν να παίξουν τα παιχνίδια τους!

Σχετικά με το τι: πώς να ξεκινήσετε να γράφετε παιχνίδια σε C/C++ αν το χρειάζεστε!

Γιατί πρέπει να διαβάσετε αυτό: Η ανάπτυξη εφαρμογών δεν είναι η ειδικότητά μου στη δουλειά, αλλά προσπαθώ να κωδικοποιώ κάθε εβδομάδα. Γιατί μου αρέσουν τα παιχνίδια!

γεια το όνομά μου είναι Αντρέι Γκράνκιν, Είμαι DevOps στη Luxoft. Η ανάπτυξη εφαρμογών δεν είναι η ειδικότητα της δουλειάς μου, αλλά προσπαθώ να κωδικοποιώ κάθε εβδομάδα. Γιατί μου αρέσουν τα παιχνίδια!

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

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

… είναι καιρός να δημιουργήσετε τους δικούς σας κόσμους, υποκείμενους μόνο σε μένα. Κόσμοι όπου είμαι ο Πατέρας και ο Υιός και το Άγιο Πνεύμα!

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

Σε αυτό το άρθρο θα προσπαθήσω να σας πω πώς ξεκίνησα να γράφω μικρά παιχνίδια σε C / C ++, ποια είναι η διαδικασία ανάπτυξης και πού βρίσκω χρόνο για ένα χόμπι σε ένα πολυάσχολο περιβάλλον. Είναι υποκειμενικό και περιγράφει τη διαδικασία μιας ατομικής εκκίνησης. Υλικό για την άγνοια και την πίστη, για την προσωπική μου εικόνα του κόσμου αυτή τη στιγμή. Με άλλα λόγια, «Η διοίκηση δεν είναι υπεύθυνη για τον προσωπικό σας εγκέφαλο!».

Πρακτική

«Η γνώση χωρίς πρακτική είναι άχρηστη, η πρακτική χωρίς γνώση είναι επικίνδυνη» Κομφούκιος

Το σημειωματάριό μου είναι η ζωή μου!


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

DevOps C++ και "kitchen wars", ή Πώς άρχισα να γράφω παιχνίδια ενώ έτρωγα
Το (ήδη γεμάτο) σημειωματάριό μου. Έτσι φαίνεται. Περιέχει καθημερινές εργασίες, ιδέες, σχέδια, διαγράμματα, λύσεις, μαύρη λογιστική, κώδικα κ.λπ.

Σε αυτό το στάδιο, κατάφερα να ολοκληρώσω τρία έργα (αυτό είναι κατά την αντίληψή μου για την "τελικότητα", επειδή οποιοδήποτε προϊόν μπορεί να αναπτυχθεί σχετικά ατελείωτα).

  • Έργο 0: Αυτή είναι μια τρισδιάστατη σκηνή Architect Demo γραμμένη σε C# χρησιμοποιώντας τη μηχανή παιχνιδιού Unity. Για πλατφόρμες macOS και Windows.
  • Παιχνίδι 1: Παιχνίδι κονσόλας Simple Snake (γνωστό σε όλους ως "Snake") για Windows. γραμμένο στο C.
  • Παιχνίδι 2: Παιχνίδι κονσόλας Crazy Tanks (γνωστό σε όλους ως "Tanks"), ήδη γραμμένο σε C ++ (χρησιμοποιώντας κλάσεις) και επίσης κάτω από τα Windows.

Project 0 Architect Demo

  • Πλατφόρμα: Windows (Windows 7, 10), Mac OS (OS X El Capitan v. 10.11.6)
  • Γλώσσα: C#
  • Μηχανή παιχνιδιού: ενότητα
  • Εμπνευση: Ντάριν Λιλ
  • Αποθήκη: GitHub

DevOps C++ και "kitchen wars", ή Πώς άρχισα να γράφω παιχνίδια ενώ έτρωγα
Επίδειξη 3D Scene Architect

Το πρώτο έργο υλοποιήθηκε όχι σε C/C++, αλλά σε C# χρησιμοποιώντας τη μηχανή παιχνιδιού Unity. Αυτός ο κινητήρας δεν ήταν τόσο απαιτητικός σε υλικό όσο Unreal Engine, και επίσης μου φάνηκε πιο εύκολο στην εγκατάσταση και τη χρήση. Δεν υπολόγισα άλλους κινητήρες.

Ο στόχος στο Unity για μένα δεν ήταν να αναπτύξω κάποιο είδος παιχνιδιού. Ήθελα να δημιουργήσω μια τρισδιάστατη σκηνή με κάποιο είδος χαρακτήρα. Αυτός, ή μάλλον Εκείνη (έκανα μοντέλο το κορίτσι που ήμουν ερωτευμένος =) έπρεπε να κινηθεί και να αλληλεπιδράσει με τον έξω κόσμο. Ήταν μόνο σημαντικό να καταλάβουμε τι είναι το Unity, ποια είναι η διαδικασία ανάπτυξης και πόση προσπάθεια χρειάζεται για να δημιουργηθεί κάτι. Έτσι γεννήθηκε το έργο Architect Demo (το όνομα εφευρέθηκε σχεδόν από τις βλακείες). Ο προγραμματισμός, το μόντελινγκ, το animation, το texturing μου πήρε πιθανώς δύο μήνες καθημερινής δουλειάς.

Ξεκίνησα με εκπαιδευτικά βίντεο στο YouTube σχετικά με τον τρόπο δημιουργίας τρισδιάστατων μοντέλων Μίξερ. Το Blender είναι ένα εξαιρετικό δωρεάν εργαλείο για τρισδιάστατη μοντελοποίηση (και πολλά άλλα) που δεν απαιτεί εγκατάσταση. Και εδώ με περίμενε ένα σοκ ... Αποδεικνύεται ότι το μόντελινγκ, το animation, το texturing είναι τεράστια ξεχωριστά θέματα για τα οποία μπορείτε να γράψετε βιβλία. Αυτό ισχύει ιδιαίτερα για τους χαρακτήρες. Για να μοντελοποιήσετε δάχτυλα, δόντια, μάτια και άλλα μέρη του σώματος, θα χρειαστείτε γνώσεις ανατομίας. Πώς είναι διατεταγμένοι οι μύες του προσώπου; Πώς κινούνται οι άνθρωποι; Έπρεπε να «βάλω» κόκαλα σε κάθε χέρι, πόδι, δάχτυλο, αρθρώσεις!

Μοντελοποιήστε την κλείδα, πρόσθετους οστικούς μοχλούς, έτσι ώστε το κινούμενο σχέδιο να φαίνεται φυσικό. Μετά από τέτοια μαθήματα, συνειδητοποιείς τι τεράστια δουλειά κάνουν οι δημιουργοί ταινιών κινουμένων σχεδίων, μόνο και μόνο για να δημιουργήσουν 30 δευτερόλεπτα βίντεο. Αλλά οι ταινίες 3D διαρκούν για ώρες! Και μετά βγαίνουμε από τις αίθουσες και λέμε κάτι σαν: «Τα, ένα σκασμό καρτούν / ταινία! Θα μπορούσαν να τα είχαν κάνει καλύτερα…» Ηλίθιοι!

Και κάτι ακόμα σχετικά με τον προγραμματισμό σε αυτό το έργο. Όπως αποδείχθηκε, το πιο ενδιαφέρον κομμάτι για μένα ήταν το μαθηματικό. Εάν εκτελέσετε τη σκηνή (σύνδεσμος με το αποθετήριο στην περιγραφή του έργου), θα παρατηρήσετε ότι η κάμερα περιστρέφεται γύρω από τον χαρακτήρα του κοριτσιού σε μια σφαίρα. Για να προγραμματίσω μια τέτοια περιστροφή της κάμερας, έπρεπε πρώτα να υπολογίσω τις συντεταγμένες του σημείου θέσης στον κύκλο (2D) και μετά στη σφαίρα (3D). Το αστείο είναι ότι μισούσα τα μαθηματικά στο σχολείο και τα ήξερα με ένα μείον. Εν μέρει, πιθανώς, επειδή στο σχολείο απλά δεν σας εξηγούν πώς στο διάολο εφαρμόζονται αυτά τα μαθηματικά στη ζωή. Όταν όμως έχεις εμμονή με τον στόχο σου, ονειρεύσου, τότε το μυαλό καθαρίζει, αποκαλύπτεται! Και αρχίζετε να αντιλαμβάνεστε τις σύνθετες εργασίες ως μια συναρπαστική περιπέτεια. Και μετά σκέφτεσαι: «Λοιπόν, γιατί ο *αγαπημένος* μαθηματικός δεν μπορούσε κανονικά να πει πού μπορούν να κλίνουν αυτοί οι τύποι;».

DevOps C++ και "kitchen wars", ή Πώς άρχισα να γράφω παιχνίδια ενώ έτρωγα
Υπολογισμός τύπων για τον υπολογισμό των συντεταγμένων ενός σημείου σε κύκλο και σε σφαίρα (από το σημειωματάριό μου)

Παιχνίδι 1

  • Πλατφόρμα: Windows (δοκιμασμένο σε Windows 7, 10)
  • Γλώσσα: Νομίζω ότι ήταν γραμμένο σε καθαρό C
  • Μηχανή παιχνιδιού: Κονσόλα των Windows
  • Εμπνευση: javidx9
  • Αποθήκη: GitHub

DevOps C++ και "kitchen wars", ή Πώς άρχισα να γράφω παιχνίδια ενώ έτρωγα
Απλό παιχνίδι φιδιών

Η τρισδιάστατη σκηνή δεν είναι παιχνίδι. Επιπλέον, η μοντελοποίηση και η κίνηση τρισδιάστατων αντικειμένων (ειδικά χαρακτήρων) είναι μακρά και δύσκολη. Αφού έπαιξα με το Unity, συνειδητοποίησα ότι έπρεπε να συνεχίσω, ή μάλλον να ξεκινήσω, από τα βασικά. Κάτι απλό και γρήγορο, αλλά ταυτόχρονα παγκόσμιο, για να κατανοήσουμε την ίδια τη δομή των παιχνιδιών.

Και τι έχουμε απλό και γρήγορο; Σωστά, κονσόλα και 2D. Πιο συγκεκριμένα, ακόμη και η κονσόλα και τα σύμβολα. Και πάλι άρχισα να αναζητώ έμπνευση στο Διαδίκτυο (γενικά θεωρώ το Διαδίκτυο την πιο επαναστατική και επικίνδυνη εφεύρεση του XNUMXου αιώνα). Έσκαψα ένα βίντεο ενός προγραμματιστή που έφτιαξε την κονσόλα Tetris. Και σαν το παιχνίδι του αποφάσισε να κόψει το «φίδι». Από το βίντεο, έμαθα για δύο βασικά πράγματα - τον βρόχο παιχνιδιού (με τρεις βασικές λειτουργίες / μέρη) και την έξοδο στο buffer.

Ο βρόχος του παιχνιδιού μπορεί να μοιάζει κάπως έτσι:

int main()
   {
      Setup();
      // a game loop
      while (!quit)
      {
          Input();
          Logic();
          Draw();
          Sleep(gameSpeed);  // game timing
      }
      return 0;
   }

Ο κώδικας παρουσιάζει ολόκληρη τη συνάρτηση main() ταυτόχρονα. Και ο κύκλος του παιχνιδιού ξεκινά μετά το αντίστοιχο σχόλιο. Υπάρχουν τρεις βασικές συναρτήσεις στον βρόχο: Input(), Logic(), Draw(). Πρώτα, εισαγωγή δεδομένων Εισαγωγή (κυρίως έλεγχος πληκτρολογήσεων), στη συνέχεια επεξεργασία των δεδομένων που εισάγονται Λογική, μετά εμφάνιση στην οθόνη - Σχεδίαση. Και έτσι κάθε καρέ. Το animation δημιουργείται με αυτόν τον τρόπο. Είναι σαν κινούμενα σχέδια. Συνήθως η επεξεργασία των δεδομένων εισόδου απαιτεί τον περισσότερο χρόνο και, από όσο ξέρω, καθορίζει τον ρυθμό καρέ του παιχνιδιού. Αλλά εδώ η συνάρτηση Logic() είναι πολύ γρήγορη. Επομένως, ο ρυθμός καρέ πρέπει να ελέγχεται από τη συνάρτηση Sleep() με την παράμετρο gameSpeed, η οποία καθορίζει αυτόν τον ρυθμό.

DevOps C++ και "kitchen wars", ή Πώς άρχισα να γράφω παιχνίδια ενώ έτρωγα
κύκλος παιχνιδιού. Προγραμματισμός φιδιών στο σημειωματάριο

Εάν αναπτύσσετε ένα συμβολικό παιχνίδι κονσόλας, τότε η εμφάνιση δεδομένων στην οθόνη χρησιμοποιώντας τη συνήθη έξοδο ροής 'cout' δεν θα λειτουργήσει - είναι πολύ αργή. Επομένως, η έξοδος πρέπει να πραγματοποιείται στην προσωρινή μνήμη οθόνης. Τόσο πολύ πιο γρήγορα και το παιχνίδι θα λειτουργήσει χωρίς δυσλειτουργίες. Για να είμαι ειλικρινής, δεν καταλαβαίνω ακριβώς τι είναι το buffer οθόνης και πώς λειτουργεί. Αλλά θα δώσω ένα παράδειγμα κώδικα εδώ, και ίσως κάποιος στα σχόλια θα μπορέσει να ξεκαθαρίσει την κατάσταση.

Λήψη του buffer οθόνης (αν μπορώ να το πω):

// create screen buffer for drawings
   HANDLE hConsole = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0,
 							   NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
   DWORD dwBytesWritten = 0;
   SetConsoleActiveScreenBuffer(hConsole);

Απευθείας έξοδος στην οθόνη μιας συγκεκριμένης γραμμής scoreLine (η γραμμή για την εμφάνιση βαθμολογιών):

// draw the score
   WriteConsoleOutputCharacter(hConsole, scoreLine, GAME_WIDTH, {2,3}, &dwBytesWritten);

Θεωρητικά, δεν υπάρχει τίποτα περίπλοκο σε αυτό το παιχνίδι, μου φαίνεται καλό παράδειγμα ενός παιχνιδιού εισαγωγικού επιπέδου. Ο κώδικας είναι γραμμένος σε ένα αρχείο και ταξινομημένος σε πολλές συναρτήσεις. Χωρίς τάξεις, χωρίς κληρονομιά. Μπορείτε να δείτε τα πάντα στον πηγαίο κώδικα του παιχνιδιού μεταβαίνοντας στο αποθετήριο στο GitHub.

Παιχνίδι 2 Crazy Tanks

DevOps C++ και "kitchen wars", ή Πώς άρχισα να γράφω παιχνίδια ενώ έτρωγα
Το παιχνίδι Crazy Tanks

Η εκτύπωση χαρακτήρων στην κονσόλα είναι ίσως το πιο απλό πράγμα που μπορείτε να μετατρέψετε σε παιχνίδι. Αλλά τότε εμφανίζεται ένα πρόβλημα: οι χαρακτήρες έχουν διαφορετικά ύψη και πλάτη (το ύψος είναι μεγαλύτερο από το πλάτος). Έτσι, όλα θα φαίνονται δυσανάλογα και η κίνηση προς τα κάτω ή προς τα πάνω θα φαίνεται πολύ πιο γρήγορη από την κίνηση αριστερά ή δεξιά. Αυτό το αποτέλεσμα είναι πολύ αισθητό στο "Snake" (Παιχνίδι 1). Τα "Tanks" (Game 2) δεν έχουν τέτοιο μειονέκτημα, αφού η έξοδος εκεί οργανώνεται βάφοντας τα pixel της οθόνης με διαφορετικά χρώματα. Θα μπορούσατε να πείτε ότι έγραψα ένα renderer. Είναι αλήθεια ότι αυτό είναι ήδη λίγο πιο περίπλοκο, αν και πολύ πιο ενδιαφέρον.

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

Έτσι, αυτό που βλέπετε στην οθόνη είναι απλώς ένα σύνολο κινούμενων έγχρωμων ορθογωνίων.

DevOps C++ και "kitchen wars", ή Πώς άρχισα να γράφω παιχνίδια ενώ έτρωγα
Σετ ορθογώνιο

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

DevOps C++ και "kitchen wars", ή Πώς άρχισα να γράφω παιχνίδια ενώ έτρωγα
Ένα παράδειγμα μήτρας δεξαμενής παιχνιδιού

DevOps C++ και "kitchen wars", ή Πώς άρχισα να γράφω παιχνίδια ενώ έτρωγα
Αναπαράσταση της μήτρας μιας δεξαμενής παιχνιδιού με μονοδιάστατο πίνακα

DevOps C++ και "kitchen wars", ή Πώς άρχισα να γράφω παιχνίδια ενώ έτρωγα
Ένα πιο ενδεικτικό παράδειγμα αναπαράστασης πίνακα με μονοδιάστατο πίνακα

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

DevOps C++ και "kitchen wars", ή Πώς άρχισα να γράφω παιχνίδια ενώ έτρωγα
Διέλευση μονοδιάστατου πίνακα σε διπλό βρόχο. Y είναι το αναγνωριστικό της σειράς, το X είναι το αναγνωριστικό της στήλης

Λάβετε υπόψη ότι αντί για τα συνηθισμένα αναγνωριστικά μήτρας i, j, χρησιμοποιώ τα αναγνωριστικά x και y. Έτσι, μου φαίνεται, πιο ευχάριστο στο μάτι και πιο καθαρό για τον εγκέφαλο. Επιπλέον, μια τέτοια σημείωση καθιστά δυνατή την βολική προβολή των πινάκων που χρησιμοποιούνται στους άξονες συντεταγμένων μιας δισδιάστατης εικόνας.

Τώρα σχετικά με τα pixel, το χρώμα και την οθόνη. Η συνάρτηση StretchDIBits (Header: windows.h; Library: gdi32.lib) χρησιμοποιείται για έξοδο. Μεταξύ άλλων, σε αυτήν τη λειτουργία μεταβιβάζονται τα εξής: η συσκευή στην οποία εμφανίζεται η εικόνα (στην περίπτωσή μου, αυτή είναι η κονσόλα των Windows), οι συντεταγμένες της έναρξης εμφάνισης της εικόνας, το πλάτος / ύψος της και η εικόνα η ίδια με τη μορφή ενός bitmap (bitmap), που αντιπροσωπεύεται από έναν πίνακα byte. Bitmap ως συστοιχία byte!

Η συνάρτηση StretchDIBits() στην εργασία:

// screen output for game field
   StretchDIBits(
               deviceContext,
               OFFSET_LEFT, OFFSET_TOP,
               PMATRIX_WIDTH, PMATRIX_HEIGHT,
               0, 0,
               PMATRIX_WIDTH, PMATRIX_HEIGHT,
               m_p_bitmapMemory, &bitmapInfo,
               DIB_RGB_COLORS,
               SRCCOPY
               );

Η μνήμη εκχωρείται εκ των προτέρων για αυτό το bitmap χρησιμοποιώντας τη συνάρτηση VirtualAlloc(). Δηλαδή, ο απαιτούμενος αριθμός byte δεσμεύεται για την αποθήκευση πληροφοριών για όλα τα pixel, τα οποία στη συνέχεια θα εμφανίζονται στην οθόνη.

Δημιουργία bitmap μνήμης m_p_bitmap:

// create bitmap
   int bitmapMemorySize = (PMATRIX_WIDTH * PMATRIX_HEIGHT) * BYTES_PER_PIXEL;
   void* m_p_bitmapMemory = VirtualAlloc(0, bitmapMemorySize, MEM_COMMIT, PAGE_READWRITE);

Σε γενικές γραμμές, ένα bitmap αποτελείται από ένα σύνολο pixel. Κάθε τέσσερα byte στον πίνακα είναι ένα εικονοστοιχείο RGB. Ένα byte ανά κόκκινη τιμή, ένα byte ανά πράσινη τιμή (G) και ένα byte ανά μπλε χρώμα (B). Επιπλέον, υπάρχει ένα byte ανά εσοχή. Αυτά τα τρία χρώματα - Κόκκινο / Πράσινο / Μπλε (RGB) - αναμειγνύονται μεταξύ τους σε διαφορετικές αναλογίες - και προκύπτει το χρώμα pixel που προκύπτει.

Τώρα, πάλι, κάθε ορθογώνιο, ή αντικείμενο παιχνιδιού, αντιπροσωπεύεται από έναν αριθμητικό πίνακα. Όλα αυτά τα αντικείμενα παιχνιδιού τοποθετούνται σε μια συλλογή. Και στη συνέχεια τοποθετούνται στον αγωνιστικό χώρο, σχηματίζοντας έναν μεγάλο αριθμητικό πίνακα. Αντιστοίχισα κάθε αριθμό στη μήτρα σε ένα συγκεκριμένο χρώμα. Για παράδειγμα, ο αριθμός 8 είναι μπλε, ο αριθμός 9 είναι κίτρινος, ο αριθμός 10 είναι σκούρο γκρι και ούτω καθεξής. Έτσι, μπορούμε να πούμε ότι έχουμε μια μήτρα του αγωνιστικού χώρου, όπου κάθε αριθμός είναι κάποιου είδους χρώμα.

Έτσι, έχουμε μια αριθμητική μήτρα ολόκληρου του αγωνιστικού χώρου από τη μια και ένα bitmap για την εμφάνιση της εικόνας από την άλλη. Μέχρι στιγμής, το bitmap είναι "άδειο" - δεν έχει ακόμη πληροφορίες για τα pixel του επιθυμητού χρώματος. Αυτό σημαίνει ότι το τελευταίο βήμα θα είναι η πλήρωση του bitmap με πληροφορίες για κάθε pixel με βάση τον αριθμητικό πίνακα του αγωνιστικού χώρου. Ένα ενδεικτικό παράδειγμα ενός τέτοιου μετασχηματισμού είναι στην παρακάτω εικόνα.

DevOps C++ και "kitchen wars", ή Πώς άρχισα να γράφω παιχνίδια ενώ έτρωγα
Ένα παράδειγμα συμπλήρωσης ενός bitmap (μήτρας Pixel) με πληροφορίες που βασίζονται στον αριθμητικό πίνακα (Ψηφιακός πίνακας) του αγωνιστικού χώρου (οι δείκτες χρωμάτων δεν ταιριάζουν με τους δείκτες του παιχνιδιού)

Θα παρουσιάσω επίσης ένα κομμάτι αληθινού κώδικα από το παιχνίδι. Στη μεταβλητή colorIndex σε κάθε επανάληψη του βρόχου εκχωρείται μια τιμή (δείκτης χρώματος) από τον αριθμητικό πίνακα του αγωνιστικού χώρου (mainDigitalMatrix). Στη συνέχεια, το ίδιο το χρώμα γράφεται στη μεταβλητή χρώματος με βάση το ευρετήριο. Περαιτέρω, το προκύπτον χρώμα χωρίζεται στην αναλογία κόκκινου, πράσινου και μπλε (RGB). Και μαζί με την εσοχή (pixelPadding), αυτές οι πληροφορίες γράφονται στο pixel ξανά και ξανά, σχηματίζοντας μια έγχρωμη εικόνα στο bitmap.

Ο κώδικας χρησιμοποιεί δείκτες και λειτουργίες bitwise, οι οποίες μπορεί να είναι δύσκολο να κατανοηθούν. Σας συμβουλεύω λοιπόν να διαβάσετε χωριστά κάπου πώς λειτουργούν τέτοιες δομές.

Συμπλήρωση ενός bitmap με πληροφορίες που βασίζονται στον αριθμητικό πίνακα του αγωνιστικού χώρου:

// set pixel map variables
   int colorIndex;
   COLORREF color;
   int pitch;
   uint8_t* p_row;
 
   // arrange pixels for game field
   pitch = PMATRIX_WIDTH * BYTES_PER_PIXEL;     // row size in bytes
   p_row = (uint8_t*)m_p_bitmapMemory;       //cast to uint8 for valid pointer arithmetic
   							(to add by 1 byte (8 bits) at a time)   
   for (int y = 0; y < PMATRIX_HEIGHT; ++y)
   {
       uint32_t* p_pixel = (uint32_t*)p_row;
       for (int x = 0; x < PMATRIX_WIDTH; ++x)
       {
           colorIndex = mainDigitalMatrix[y * PMATRIX_WIDTH + x];
           color = Utils::GetColor(colorIndex);
           uint8_t blue = GetBValue(color);
           uint8_t green = GetGValue(color);
           uint8_t red = GetRValue(color);
           uint8_t pixelPadding = 0;
 
           *p_pixel = ((pixelPadding << 24) | (red << 16) | (green << 8) | blue);
           ++p_pixel;
       }
       p_row += pitch;
   }

Σύμφωνα με τη μέθοδο που περιγράφεται παραπάνω, μια εικόνα (κάδρο) σχηματίζεται στο παιχνίδι Crazy Tanks και εμφανίζεται στην οθόνη στη συνάρτηση Draw(). Μετά την καταχώρηση των πλήκτρων στη συνάρτηση Input() και την επακόλουθη επεξεργασία τους στη συνάρτηση Logic(), σχηματίζεται μια νέα εικόνα (πλαίσιο). Είναι αλήθεια ότι τα αντικείμενα παιχνιδιού μπορεί να έχουν ήδη διαφορετική θέση στον αγωνιστικό χώρο και, κατά συνέπεια, σχεδιάζονται σε διαφορετική θέση. Έτσι συμβαίνει το animation (κίνηση).

Θεωρητικά (αν δεν έχετε ξεχάσει τίποτα), η κατανόηση του βρόχου παιχνιδιού από το πρώτο παιχνίδι ("Snake") και του συστήματος για την εμφάνιση pixel στην οθόνη από το δεύτερο παιχνίδι ("Tanks") είναι το μόνο που χρειάζεστε για να γράψετε οποιοδήποτε των δισδιάστατων παιχνιδιών σας για Windows. Αθόρυβος! 😉 Τα υπόλοιπα μέρη είναι απλά μια φανταχτερή.

Φυσικά, το παιχνίδι "Tanks" είναι σχεδιασμένο πολύ πιο περίπλοκο από το "Snake". Χρησιμοποίησα ήδη τη γλώσσα C++, δηλαδή περιέγραψα διαφορετικά αντικείμενα παιχνιδιού με κλάσεις. Δημιούργησα τη δική μου συλλογή - μπορείτε να δείτε τον κώδικα στο headers/Box.h. Παρεμπιπτόντως, η συλλογή έχει πιθανότατα διαρροή μνήμης. Χρησιμοποιημένοι δείκτες. Δούλεψε με μνήμη. Πρέπει να πω ότι το βιβλίο με βοήθησε πολύ. Έναρξη C++ μέσω προγραμματισμού παιχνιδιών. Αυτή είναι μια εξαιρετική αρχή για αρχάριους στη C++. Είναι μικρό, ενδιαφέρον και καλά οργανωμένο.

Χρειάστηκαν περίπου έξι μήνες για να αναπτυχθεί αυτό το παιχνίδι. Έγραφα κυρίως κατά τη διάρκεια του μεσημεριανού γεύματος και των σνακ στη δουλειά. Κάθισε στην κουζίνα του γραφείου, πάτησε φαγητό και έγραψε κώδικα. Ή στο σπίτι για δείπνο. Έτσι πήρα τέτοιους «κουζινοπόλεμους». Όπως πάντα, χρησιμοποίησα ενεργά ένα σημειωματάριο και όλα τα εννοιολογικά πράγματα γεννήθηκαν σε αυτό.

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

DevOps C++ και "kitchen wars", ή Πώς άρχισα να γράφω παιχνίδια ενώ έτρωγα
Σχεδιασμός εικόνας δεξαμενής. Και ο ορισμός του πόσα pixel θα πρέπει να καταλαμβάνει κάθε δεξαμενή στην οθόνη

DevOps C++ και "kitchen wars", ή Πώς άρχισα να γράφω παιχνίδια ενώ έτρωγα
Υπολογισμός του αλγορίθμου και των τύπων για την περιστροφή της δεξαμενής γύρω από τον άξονά της

DevOps C++ και "kitchen wars", ή Πώς άρχισα να γράφω παιχνίδια ενώ έτρωγα
Διάγραμμα της συλλογής μου (αυτή με τη διαρροή μνήμης, πιθανότατα). Η συλλογή δημιουργείται ως Συνδεδεμένη λίστα

DevOps C++ και "kitchen wars", ή Πώς άρχισα να γράφω παιχνίδια ενώ έτρωγα
Και αυτές είναι μάταιες προσπάθειες να μπει η τεχνητή νοημοσύνη στο παιχνίδι

Θεωρία

"Ακόμα και ένα ταξίδι χιλίων μιλίων ξεκινά με το πρώτο βήμα" (Αρχαία κινεζική σοφία)

Ας περάσουμε από την πράξη στη θεωρία! Πώς βρίσκεις χρόνο για το χόμπι σου;

  1. Προσδιορίστε τι θέλετε πραγματικά (αλίμονο, αυτό είναι το πιο δύσκολο).
  2. Θέστε προτεραιότητες.
  3. Θυσιάστε όλα τα «περιττά» για χάρη των υψηλότερων προτεραιοτήτων.
  4. Προχωρήστε προς τους στόχους σας κάθε μέρα.
  5. Μην περιμένετε ότι θα υπάρχουν δύο ή τρεις ώρες ελεύθερου χρόνου για ένα χόμπι.

Από τη μία πλευρά, πρέπει να καθορίσετε τι θέλετε και να δώσετε προτεραιότητα. Από την άλλη πλευρά, είναι δυνατό να εγκαταλειφθούν ορισμένες περιπτώσεις / έργα υπέρ αυτών των προτεραιοτήτων. Με άλλα λόγια, θα πρέπει να θυσιάσετε κάθε τι «περιττό». Κάπου άκουσα ότι στη ζωή πρέπει να υπάρχουν το πολύ τρεις κύριες δραστηριότητες. Τότε θα μπορέσετε να τα αντιμετωπίσετε με τον καλύτερο δυνατό τρόπο. Και πρόσθετα έργα/κατευθύνσεις θα αρχίσουν να υπερφορτώνουν το κύμα. Αλλά όλα αυτά είναι, πιθανώς, υποκειμενικά και ατομικά.

Υπάρχει ένας συγκεκριμένος χρυσός κανόνας: ποτέ μην έχετε 0% ημέρα! Το έμαθα σε ένα άρθρο ενός indie προγραμματιστή. Εάν εργάζεστε σε ένα έργο, τότε κάντε κάτι για αυτό κάθε μέρα. Και δεν έχει σημασία πόσα βγάζεις. Γράψτε μια λέξη ή μια γραμμή κώδικα, παρακολουθήστε ένα εκπαιδευτικό βίντεο ή σφυρηλατήστε ένα καρφί στον πίνακα—απλώς κάντε κάτι. Το πιο δύσκολο είναι να ξεκινήσετε. Μόλις ξεκινήσετε, πιθανότατα θα κάνετε λίγο περισσότερα από όσα θέλατε. Έτσι θα προχωράτε συνεχώς προς τον στόχο σας και, πιστέψτε με, πολύ γρήγορα. Εξάλλου, το βασικό φρένο σε όλα τα πράγματα είναι η αναβλητικότητα.

Και είναι σημαντικό να θυμάστε ότι δεν πρέπει να υποτιμάτε και να αγνοείτε το ελεύθερο «πριονίδι» του χρόνου σε 5, 10, 15 λεπτά, να περιμένετε μερικά μεγάλα «κούτσουρα» που διαρκούν μια ή δύο ώρες. Στέκεσαι στην ουρά; Σκεφτείτε κάτι για το έργο σας. Ανεβαίνεις την κυλιόμενη σκάλα; Γράψε κάτι σε ένα τετράδιο. Τρώτε στο λεωφορείο; Εντάξει, διάβασε ένα άρθρο. Χρησιμοποιήστε κάθε ευκαιρία. Σταματήστε να βλέπετε γάτες και σκύλους στο YouTube! Μην τα βάζεις με τον εγκέφαλό σου!

Και το τελευταίο. Εάν, αφού διαβάσατε αυτό το άρθρο, σας άρεσε η ιδέα της δημιουργίας παιχνιδιών χωρίς τη χρήση μηχανών παιχνιδιών, τότε θυμηθείτε το όνομα Casey Muratori. Αυτός ο τύπος έχει δικτυακός τόπος. Στην ενότητα "παρακολουθήστε -> ΠΡΟΗΓΟΥΜΕΝΑ ΕΠΕΙΣΟΔΙΑ" θα βρείτε καταπληκτικά δωρεάν εκπαιδευτικά βίντεο για το πώς να δημιουργήσετε ένα επαγγελματικό παιχνίδι από την αρχή. Μπορεί να μάθετε περισσότερα σε πέντε μαθήματα Intro to C για Windows παρά σε πέντε χρόνια σπουδών στο πανεπιστήμιο (κάποιος έγραψε γι 'αυτό στα σχόλια κάτω από το βίντεο).

Ο Casey εξηγεί επίσης ότι αναπτύσσοντας τη δική σας μηχανή παιχνιδιών, θα κατανοήσετε καλύτερα τυχόν υπάρχουσες μηχανές. Στον κόσμο των πλαισίων, όπου όλοι προσπαθούν να αυτοματοποιηθούν, θα μάθετε πώς να δημιουργείτε και όχι να χρησιμοποιείτε. Κατανοήστε την ίδια τη φύση των υπολογιστών. Και θα γίνετε επίσης ένας πολύ πιο έξυπνος και ώριμος προγραμματιστής - επαγγελματίας.

Καλή τύχη στο δρόμο που επιλέξατε! Και ας κάνουμε τον κόσμο πιο επαγγελματικό.

Συντάκτης: Grankin Andrey, DevOps



Πηγή: www.habr.com