Back to Question Center
0

Δοκιμαστικά διαμορφωμένο έδαφος παιχνιδιών με React, PHP και WebSockets            Δοκιμαστικά δημιουργημένο έδαφος παιχνιδιών με React, PHP και WebSocketsΣχετικά θέματα: ΠλαίσιαAPIsSecurityPatterns & PracticesDebugging & Semalt

1 answers:
Δοκιμασμένο έδαφος παιχνιδιού με React, PHP και WebSockets

Ανάπτυξη παιχνιδιών με PHP και ReactJS

  • Ανάπτυξη παιχνιδιών με React και PHP: Πόσο συμβατά είναι αυτά;
  • Τερματικά Παιχνίδια που παράγονται από το React, PHP και WebSockets

Για μια υψηλής ποιότητας, σε βάθος εισαγωγή στο React, δεν μπορείτε να περάσετε από τον καναδικό πλήρες προγραμματιστή Wes Bos. Δοκιμάστε το μάθημά του εδώ και χρησιμοποιήστε τον κώδικα SITEPOINT για να λάβετε 25% έκπτωση και να βοηθήσετε στην υποστήριξη του SitePoint.

Την τελευταία φορά, άρχισα να σας λέω την ιστορία του πώς ήθελα να κάνω ένα παιχνίδι. Περιγράψαμε πώς εγκατέστησα τον εξυπηρετητή PHP async, την αλυσίδα κατασκευής Laravel Mix, το front end του React και τα WebSockets που συνδέουν όλα αυτά μαζί. Τώρα, επιτρέψτε μου να σας πω για το τι συνέβη όταν άρχισα να οικοδομώ τη μηχανική του παιχνιδιού με αυτό το μείγμα React, PHP και WebSockets .


Ο κώδικας για αυτό το μέρος βρίσκεται στο github - ainste wallet ebay. com / assertchris-tutorials / site-making-games / tree / part-2. Το έχω δοκιμάσει με PHP 7. 1 , σε μια πρόσφατη έκδοση του Google Chrome.


Δοκιμαστικά διαμορφωμένο έδαφος παιχνιδιών με React, PHP και WebSocketsΔοκιμαστικά δημιουργημένο έδαφος παιχνιδιών με React, PHP και WebSocketsΣχετικά θέματα:
ΠλαίσιαAPISs ΑσφάλειαΠαρακολούθηση & ΠρακτικέςΕπισκέψεις & Εργαλεία

Κάνοντας μια φάρμα

"Ξεκινώντας απλά. Έχουμε ένα 10 με 10 πλέγμα πλακιδίων, γεμάτο με τυχαία παραγόμενα αντικείμενα. "

Αποφάσισα να εκπροσωπήσω το αγρόκτημα ως Farm , και κάθε πλακίδιο ως Patch . Από το app / Model / FarmModel. προ :

     όνομα χώρου App \ Model;κατηγορίας αγρόκτημα{ιδιωτικό πλάτος ${get {return $ this-> width; }}}}ιδιωτικό ύψος ${get {return $ this-> height; }}}}δημόσια λειτουργία __construct (int $ width = 10,int $ height = 10){$ this-> width = $ width;$ this-> height = $ height;}}}}    

Νόμιζα ότι θα ήταν ένας διασκεδαστικός χρόνος για να δοκιμάσετε τη μακροεντολή των αξεσουάρ της τάξης, δηλώνοντας ιδιωτικές ιδιότητες με δημόσιους getters. Για αυτό έπρεπε να εγκαταστήσω προ-τάξη-αξεσουάρ (μέσω συνθέτης απαιτούν ).

Στη συνέχεια, άλλαξα τον κωδικό υποδοχής για να μπορέσω να δημιουργήσω νέες εκμεταλλεύσεις κατόπιν αιτήματος. Από το app / Socket / GameSocket. προ :

     όνομα χώρου App \ Socket;χρησιμοποιήστε Aerys \ Request;χρησιμοποιήστε Aerys \ Response.χρησιμοποιήστε το Aerys \ Websocket.χρησιμοποιήστε το Aerys \ Websocket \ Endpoint;χρησιμοποιήστε το Aerys \ Websocket \ Message;χρησιμοποιήστε την εφαρμογή \ Model \ FarmModel.class GameSocket υλοποιεί Websocket{ιδιωτικές εκμεταλλεύσεις $ = [];δημόσια λειτουργία onData (int $ clientId,Μήνυμα μηνύματος $){$ body = απόδοση $ μήνυμα?αν ($ body === "νέο-αγρόκτημα") {$ farm = νέο FarmModel   ;$ payload = json_encode (["αγρόκτημα" => ["πλάτος" => $ αγρόκτημα-> πλάτος,"ύψος" => $ αγρόκτημα-> ύψος,],]).απόδοση $ αυτό-> τελικό σημείο-> αποστολή ($ payload, $ clientId) ·$ this-> εκμεταλλεύσεις [$ clientId] = $ farm;}}}}δημόσια λειτουργία onClose (int $ clientId,int $ code, string $ reason){unset ($ this-> συνδέσεις [$ clientId]);($ this-> farms [$ clientId]);}}// .}}    

Παρατήρησα πόσο παρόμοια ήταν αυτή της GameSocket με την προηγούμενη που είχα - εκτός από την εκπομπή μιας ηχώ, έλεγξα για νέο αγρόκτημα στον πελάτη που είχε ζητήσει.

"Ίσως είναι η κατάλληλη στιγμή για να αποκτήσετε λιγότερο γενική χρήση του κώδικα React. Πάω να μετονομάσετε συνιστώσα. jsx έως εκμετάλλευση. jsx . "

Από περιουσιακά στοιχεία / js / αγρόκτημα. jsx :

     εισαγωγή Αντιδρά από "αντιδρούν"αγρόκτημα κλάσης επεκτείνει το React. socket = νέο WebSocket ("ws: // 127. 0. 0. 1: 8080 / ws"),Αυτό. πρίζα. addEventListener ("μήνυμα", αυτό. onMessage),// DEBUGΑυτό. πρίζα. addEventListener ("ανοιχτό",    => {Αυτό. πρίζα. αποστολή ("νέα εκμετάλλευση")})}}}}εξαγωγή προεπιλεγμένης εκμετάλλευσης    

Στην πραγματικότητα, το μόνο άλλο πράγμα που άλλαξα ήταν η αποστολή νέων γεωργικών εκμεταλλεύσεων αντί hello world . Όλα τα άλλα ήταν τα ίδια. Έπρεπε να αλλάξω την εφαρμογή . jsx κώδικα όμως. Από στοιχεία ενεργητικού / js / app. jsx :

     εισαγωγή Αντιδρά από "αντιδρούν"εισαγωγή ReactDOM από το "react-dom"εισαγωγή αγρόκτημα από "/ farm"ReactDOM. καθιστώ(  ,έγγραφο. querySelector (". app")),    

Ήταν πολύ μακριά από το σημείο που έπρεπε να είμαι, αλλά χρησιμοποιώντας αυτές τις αλλαγές θα μπορούσα να δούμε τους class accessors σε δράση, καθώς και πρωτότυπο ένα είδος μοτίβο αιτήματος / απάντησης για τις μελλοντικές αλληλεπιδράσεις του WebSocket. Άνοιξα την κονσόλα και είδα {"αγρόκτημα": {"width": 10, "height": 10}} .

«Μεγάλη!»

Στη συνέχεια, δημιούργησα μια κλάση Patch που αντιπροσωπεύει κάθε κεραμίδι. Σκέφτηκα ότι εδώ θα συμβεί πολλή λογική του παιχνιδιού. Από app / Μοντέλο / PatchModel. προ :

     όνομα χώρου App \ Model;class PatchModel{ιδιωτικά $ x{get {επιστρέψτε $ this-> x; }}}}ιδιωτική $ y{get {επιστρέψτε $ this-> y; }}}}δημόσια λειτουργία __construct (int $ x, int $ y){$ this-> x = $ x;$ this-> y = $ y;}}}}    

Θα χρειαζόμουν να δημιουργήσω τόσα πολλά μπαλώματα καθώς υπάρχουν χώροι σε ένα νέο αγρόκτημα . Θα μπορούσα να το κάνω αυτό ως μέρος της κατασκευής του FarmModel . Από το app / Model / FarmModel. προ :

     όνομα χώρου App \ Model;class FarmModel{ιδιωτικό πλάτος ${get {return $ this-> width; }}}}ιδιωτικό ύψος ${get {return $ this-> height; }}}}ιδιωτικά $ patches{get {return $ this-> patches; }}}}δημόσια λειτουργία __construct ($ width = 10, $ height = 10){$ this-> width = $ width;$ this-> height = $ height;$ this-> createPatches   ;}}ιδιωτική λειτουργία createPatches   {για το ($ i = 0; $ i  <$ αυτό->  το πλάτος; $ i ++) {$ this-> patches [$ i] = [];για ($ j = 0; $ j  <$ αυτό->  ύψος; $ j ++) {$ this-> μπαλώματα [$ i] [$ j] =νέο PatchModel ($ i, $ j);}}}}}}}}    

Για κάθε κελί, δημιούργησα ένα νέο αντικείμενο PatchModel . Αυτά ήταν αρκετά απλά για να ξεκινήσουμε, αλλά χρειαζόταν ένα στοιχείο τυχαιότητας - έναν τρόπο να αναπτυχθούν δέντρα, ζιζάνια, λουλούδια .τουλάχιστον για να ξεκινήσουμε. Από app / Μοντέλο / PatchModel. προ :

     έναρξη δημόσιας λειτουργίας (int $ πλάτος, int $ ύψος,array $ patches){αν (! $ αυτό-> ξεκίνησε && random_int (0, 10)> 7) {$ this-> started = true;επιστροφή true;}}επιστροφή ψευδής?}}    

Σκέφτηκα ότι θα ξεκινούσα μόνο με την τυχαία ανάπτυξη ενός patch. Αυτό δεν άλλαξε την εξωτερική κατάσταση του patch, αλλά μου έδωσε έναν τρόπο να δοκιμάσω πώς ξεκίνησαν από το αγρόκτημα. Από το app / Model / FarmModel. Για αρχάριους, εισήγαγα μια λέξη-κλειδί λειτουργίας async χρησιμοποιώντας μια μακροεντολή. Βλέπετε, το Amp χειρίζεται τη λέξη-κλειδί απόδοσης με την επίλυση υποσχέσεων. Περισσότερο στο σημείο: όταν ο Amp βλέπει τη λέξη-κλειδί απόδοσης , υποθέτει ότι αυτό που αποδίδεται είναι μια Coroutine (στις περισσότερες περιπτώσεις).

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

     μίγμα συνάρτησης async ($ path) {$ manifold = yield Amp \ Αρχείο \ get ("/ public / mix-manifest. json") ·$ manifest = json_decode ($ δηλωτικό, αληθές);αν (isset ($ manifest [$ path])) {επιστροφή $ manifest [$ path];}}να ρίξει νέα εξαίρεση ("{$ path} not found");}}    

Προηγουμένως, έπρεπε να κάνω μια γεννήτρια και στη συνέχεια να την τυλίξω σε μια νέα Coroutine :

     χρησιμοποιούν Amp \ Coroutine.($ path) {$ γεννήτρια =    => {$ manifold = yield Amp \ Αρχείο \ get ("/ public / mix-manifest. json") ·$ manifest = json_decode ($ δηλωτικό, αληθές);αν (isset ($ manifest [$ path])) {επιστροφή $ manifest [$ path];}}να ρίξει νέα εξαίρεση ("{$ path} not found");},επιστρέφει νέα Coroutine ($ γεννήτρια   );}}    
Δημιούργησα την createPatches μέθοδο όπως πριν, δημιουργώντας νέα αντικείμενα PatchModel για κάθε x και y στο πλέγμα. Στη συνέχεια ξεκίνησα ένα άλλο βρόχο, για να καλέσω τη μέθοδο εκκίνησης σε κάθε έμπλαστρο. Θα το έκανα στο ίδιο βήμα, αλλά ήθελα να ξεκινήσω η μέθοδος μου για να μπορέσω να επιθεωρήσω τα γύρω μπαλώματα. Αυτό σήμαινε ότι θα έπρεπε πρώτα να τις δημιουργήσω, πριν επεξεργαστώ ποιες επιφάνειες ήταν γύρω από το άλλο.

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

Κάθε φορά που αναπτύχθηκε μια ενημερωμένη έκδοση κώδικα, επαναφέρω τη μεταβλητή $ μεταβλητή . Αυτό εξασφαλίζει ότι τα μπαλώματα θα συνεχίσουν να αναπτύσσονται μέχρις ότου ένα πλήρες πέρασμα της εκμετάλλευσης δεν προκάλεσε αλλαγές. Επικαλέστηκα επίσης το κλείσιμο σχετικά με την ανάπτυξη . Ήθελα να επιτρέψω ότι το Growth είναι ένα κανονικό κλείσιμο ή ακόμα και να επιστρέψω το Coroutine . Γι 'αυτό έπρεπε να κάνω createPatches μια συνάρτηση async .

Σημείωση: Βεβαίως, επιτρέποντας στο coroutines onGrowth να περιπλέκουν τα πράγματα λίγο, αλλά το θεωρούσα απαραίτητο για να επιτρέψω άλλες ασυνεχείς ενέργειες όταν αναπτύχθηκε μια ενημερωμένη έκδοση κώδικα. Ίσως αργότερα θα ήθελα να στείλω ένα μήνυμα υποδοχής, και θα μπορούσα να το κάνω μόνο αν απόδοση εργάστηκε μέσα onGrowth . Θα μπορούσα να δώσω μόνο onGrowth αν createPatches ήταν μια συνάρτηση async . Και επειδή το createPatches ήταν μια συνάρτηση async , θα έπρεπε να το παραδώσω στο GameSocket .

"Είναι εύκολο να απενεργοποιηθεί από όλα τα πράγματα που χρειάζονται εκμάθηση κατά την πρώτη εφαρμογή ασύρματης PHP. Η Semalt παραιτείται πολύ σύντομα! "

Το τελευταίο κομμάτι του κώδικα που χρειάστηκα να γράψω για να ελέγξω ότι όλα αυτά δούλευαν ήταν GameSocket . Από το app / Socket / GameSocket. προ :

     εάν ($ body === "νέο-αγρόκτημα") {$ patches = [];$ farm = νέο FarmModel (10, 10,(PatchModel $ patch) χρήση (& $ patches) {array_push ($ patches, ["x" => $ patch-> x,"y" => $ patch-> y,]).}}) ·απόδοση $ farm-> createPatches   ;$ payload = json_encode (["αγρόκτημα" => ["πλάτος" => $ αγρόκτημα-> πλάτος,"ύψος" => $ αγρόκτημα-> ύψος,],"patches" => patches $,]).απόδοση $ αυτό-> τελικό σημείο-> αποστολή ($ payload, $ clientId) ·$ this-> εκμεταλλεύσεις [$ clientId] = $ farm;}}    

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

Δοκιμαστικά διαμορφωμένο έδαφος παιχνιδιών με React, PHP και WebSocketsΔοκιμαστικά δημιουργημένο έδαφος παιχνιδιών με React, PHP και WebSocketsΣχετικά θέματα:
ΠλαίσιαAPISs ΑσφάλειαΠαρακολούθηση & ΠρακτικέςΕπισκέψεις & Εργαλεία

"Τι γίνεται αν ξεκινήσω κάθε έμπλαστρο ως ξηρό χώμα; Τότε θα μπορούσα να κάνω κάποια μπαλώματα με ζιζάνια και άλλα με δέντρα ."

Έχω ρυθμίσει για την προσαρμογή των επιδιορθώσεων. Από app / Μοντέλο / PatchModel. προ :

     ιδιωτική $ start = false;ιδιωτικό $ wet {πάρτε {επιστρέψτε $ αυτό-> υγρό;: ψευδές; }}},ιδιωτικός τύπος $ {get {return $ this-> type?: "βρωμιά"; }}},δημόσια έναρξη λειτουργίας (int $ πλάτος, int $ ύψος,array $ patches){αν ($ this-> ξεκίνησε) {επιστροφή ψευδής?}}αν (random_int (0, 100)  <90) {επιστροφή ψευδής?}}$ this->  started = true;$ this-> type = "ζιζάνιο";επιστροφή true;}}    

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

Θα μπορούσα τότε να χρησιμοποιήσω αυτόν τον τύπο ως μέρος του ωφέλιμου φορτίου μηνύματος υποδοχής. Από το app / Socket / GameSocket. προ :

     $ αγρόκτημα = νέο FarmModel (10, 10,(PatchModel $ patch) χρήση (& $ patches) {array_push ($ patches, ["x" => $ patch-> x,"y" => $ patch-> y,"wet" => patch-> wet,"type" => $ patch-> type,]).}}) ·    

Η εκμετάλλευση του αγροκτήματος

Ήταν ώρα να δείξω την εκμετάλλευση χρησιμοποιώντας τη ροή εργασίας React που είχα εγκαταστήσει προηγουμένως. Ήταν ήδη το 33 και 33 ύψος του αγροκτήματος, έτσι θα μπορούσα να κάνω κάθε μπλοκ ξηρό βρωμιά (εκτός αν έπρεπε να αναπτυχθεί ζιζάνιο). Από στοιχεία ενεργητικού / js / app. jsx :

     εισαγωγή Αντιδρά από "αντιδρούν"αγρόκτημα κλάσης επεκτείνει το React. Συστατικό{κατασκευαστής  {σούπερ  Αυτό. onMessage = αυτό. onMessage. δεσμεύω (αυτό)Αυτό. state = {"αγρόκτημα": {"πλάτος": 0,"ύψος": 0,},"έμπλαστρα": [],},}}componentWillMount   {Αυτό. socket = νέο WebSocket ("ws: // 127. 0. 0. 1: 8080 / ws"),Αυτό. πρίζα. addEventListener ("μήνυμα", αυτό. onMessage),// DEBUGΑυτό. πρίζα. addEventListener ("ανοιχτό",    => {Αυτό. πρίζα. αποστολή ("νέα εκμετάλλευση")})}}onMessage (ε){αφήστε δεδομένα = JSON. ανάλυση (π.χ.αν (δεδομένα farm) {Αυτό. setState ({"farm": δεδομένα farm)}}}εάν (δεδομένα, patches) {Αυτό. setState ({"patches": δεδομένα. patches})}}}}componentWillUnmount   {Αυτό. πρίζα. removeEventListener (αυτό το μήνυμα onMessage)Αυτό. socket = null}}render    {αφήστε γραμμές = []αφήστε το αγρόκτημα = αυτό. κατάσταση. αγρόκτημαlet statePatches = αυτό. κατάσταση. patchesγια (ας y = 0, y <αγρόκτημα, ύψος, y ++) {αφήστε τα patches = []για (ας x = 0, x  <αγρόκτημα, πλάτος, x ++) {ας className = "patch"statePatches. forEach ((patch) =>  {εάν (έμπλαστρο x === x && patch y === y) {className + = "" + έμπλαστρο. τύποςαν (patch wet) {className + = "" + υγρό}}}}})patches. Σπρώξτε( 
),}}σειρές. Σπρώξτε(
{patches}
),}}ΕΠΙΣΤΡΟΦΗ (
{σειρές}
),}}}}εξαγωγή προεπιλεγμένης εκμετάλλευσης

Είχα ξεχάσει να εξηγήσω πολλά για το τι έκανε το προηγούμενο Farm . Τα στοιχεία της αντίδρασης ήταν ένας διαφορετικός τρόπος σκέψης για το πώς θα οικοδομηθούν διεπαφές. Θα μπορούσα να χρησιμοποιήσω μεθόδους όπως componentWillMount και componentWillUnmount ως τρόπους για να συνδεθείτε σε άλλα σημεία δεδομένων (όπως το WebSockets). Και καθώς έλαβα ενημερώσεις μέσω του WebSocket, θα μπορούσα να ενημερώσω την κατάσταση του στοιχείου, εφ 'όσον είχα θέσει την αρχική κατάσταση στον κατασκευαστή.

Αυτό είχε ως αποτέλεσμα ένα άσχημο, αν και λειτουργικό σύνολο διαιρέσεων. Έθεσα για την προσθήκη κάποιου στυλ. Από το app / Action / HomeAction. προ :

     όνομα χώρου App \ Action;χρησιμοποιήστε Aerys \ Request;χρησιμοποιήστε Aerys \ Response.class HomeAction{δημόσια λειτουργία __invoke (Αίτηση αίτησης $,Απόκριση $ απάντησης){$ js = συνδυασμός αποδόσεων ("/ js / app js");$ css = συνδυασμός αποδόσεων ("/ css / app css");$ απάντηση-> τέλος ("   
March 1, 2018