Τοπικό multiplayer στο Unity χρησιμοποιώντας το Unet. Πύλη πληροφοριών ασφαλείας

Προβολές: 1044

Γεια σε όλους! Σήμερα θα ήθελα να μιλήσω για έναν από τους τρόπους με τους οποίους μπορείτε να δημιουργήσετε τοπικό multiplayer στο Unity. Αυτή η λύση είναι κατάλληλη για βιτρίνες, δοκιμές χαρακτηριστικών ή τοπικό multiplayer. Για παράδειγμα, αν θέλετε να δείτε τι κάνει η συσκευή αναπαραγωγής, αλλά δεν θέλετε να ξοδέψετε επιπλέον πόρους στο Android και να κάνετε το screencast χρησιμοποιώντας ADB, τότε μπορείτε απλά να ρυθμίσετε έναν διακομιστή σε κάποιο μηχάνημα με τη μορφή αντιγράφου του την εφαρμογή που τρέχει στο τηλέφωνο και στείλτε εκεί πληροφορίες για τις ενέργειες του παίκτη.

Θα περιγράψω εν συντομία πώς μπορεί να γίνει αυτό χρησιμοποιώντας το HLAPI στο Unet, αλλά όχι μέσω του NetworkingManager, αλλά σε ελαφρώς χαμηλότερο επίπεδο. Όπως είναι καλή παράδοση, θα επισυνάψω ένα παράδειγμα εφαρμογής αυτής της αλληλεπίδρασης πελάτη-διακομιστή. Παρακαλώ μην κρίνετε πολύ σκληρά αυτό το παράδειγμα, καθώς καταλαβαίνω πολύ καλά ότι η αρχιτεκτονική αυτής της λύσης δεν είναι καλή και δημιουργεί πολλά προβλήματα στο μέλλον. Στόχος μου ήταν να γράψω ένα σύστημα όσο το δυνατόν γρηγορότερα (το Σαββατοκύριακο) στο οποίο θα μπορούσα να δείξω την αρχή της συνεργασίας με το δίκτυο. Θα σας πω επίσης τι προβλήματα αντιμετώπισα. Αυτό το παράδειγμα υλοποίησης προϋποθέτει ότι ο διακομιστής είναι επίσης μια εφαρμογή Unity.

Στο Διαδίκτυο, το πιο συνηθισμένο παράδειγμα για το πώς να δημιουργήσετε πολλούς παίκτες είναι η συνομιλία, αλλά μου αρέσουν τα παιχνίδια και η δημιουργία συνομιλίας μου φάνηκε βαρετή. Ως εκ τούτου, αποφάσισα να εξετάσω πώς να φτιάξω πολλούς παίκτες χρησιμοποιώντας το παράδειγμα του tic-tac-toe. Ας υποθέσουμε ότι έχει γραφτεί όλη η λογική του παιχνιδιού, ο καθορισμός του νικητή, η αλλαγή κινήσεων κ.λπ., και το μόνο που έχουμε να κάνουμε είναι να εγκαταστήσουμε τον διακομιστή. Στην απλούστερη περίπτωση, πρέπει να επεξεργαστούμε 2 μηνύματα σε tic-tac-toe. Καθορισμός της σειράς μετακίνησης (διανομή αναγνωριστικών) και σύλληψη του κελιού.

Συνολικά, το παιχνίδι σε αυτό το παράδειγμα λειτουργεί πολύ απλά. Υπάρχουν 2 σκηνές. Φόρτωση και παιχνίδι. Κατά τη φόρτωση μιας σκηνής παιχνιδιού, δημιουργούμε ένα πεδίο στο οποίο παίζουν οι παίκτες. Η συνθήκη νίκης ελέγχεται επίσης εκεί, υπάρχουν κατηγορίες που είναι υπεύθυνες για τη λειτουργία του UI, καθώς και η σειρά των κινήσεων και η συνολική λογική του παιχνιδιού, αλλά αυτό δεν μας ενδιαφέρει ιδιαίτερα. Οι κύριες κλάσεις που είναι υπεύθυνες για το πλέγμα είναι οι Server, Client, NetManager και ένα ξεχωριστό αρχείο για τα μηνύματα NetMessages και τον αριθμό MyMsgType που ορίζεται σε αυτό. Είναι ένα περιτύλιγμα πάνω από τα εργαλεία Unet. Από την άποψη του Unet, οι κύριες κλάσεις που θα χρησιμοποιήσουμε είναι NetworkClient, NetworkServer, MessageBase και MsgType. Τι είναι αυτές οι τάξεις;

Οι απλούστερες κλάσεις είναι το MessageBase και το MsgType. Η πρώτη είναι μια αφηρημένη κλάση από την οποία πρέπει να κληρονομηθούν όλα τα μηνύματά μας για να σταλούν μεταξύ του πελάτη και του διακομιστή. Το MsgType είναι απλώς μια κλάση που αποθηκεύει σταθερές που είναι υπεύθυνες για ένα συγκεκριμένο σύνολο μηνυμάτων που είναι ενσωματωμένα στο Unet.

Ο NetworkServer είναι ένας singleton που παρέχει τη δυνατότητα χειρισμού επικοινωνιών με απομακρυσμένους πελάτες. Κάτω από την κουκούλα χρησιμοποιεί ένα παράδειγμα NetworkServerSimple και είναι ουσιαστικά ένα βολικό περιτύλιγμα γύρω του. Αρχικά, πρέπει να ξεκινήσουμε τον διακομιστή σε μια συγκεκριμένη θύρα. Για να το κάνετε αυτό, πρέπει να καλέσετε τη μέθοδο Listen (int serverPort)- αυτή η μέθοδος ξεκινά τον διακομιστή στη θύρα serverPort. (Επιτρέψτε μου να σας υπενθυμίσω ότι όλες οι θύρες στην περιοχή από 0 έως 1023 είναι θύρες συστήματος και δεν πρέπει να χρησιμοποιούνται ως παράμετρος αυτής της μεθόδου)

Ο διακομιστής λειτουργεί καλά και ακούει κάποια θύρα. Τώρα το χρειαζόμαστε για να απαντάμε σε μηνύματα. Για να το κάνετε αυτό, πρέπει να καταχωρίσετε έναν χειριστή χρησιμοποιώντας τη μέθοδο RegisterHandler (σύντομο msgType, Networking.NetworkMessageDelegate handler). Αυτή η μέθοδος λαμβάνει ως παραμέτρους έναν τύπο μηνύματος και έναν πληρεξούσιο. Ο πληρεξούσιος, με τη σειρά του, πρέπει να λάβει ένα NetworkMessage ως παράμετρο εισόδου. Ας υποθέσουμε ότι θέλουμε να ξεκινήσει η φόρτωση της σκηνής του παιχνιδιού στον διακομιστή τη στιγμή που ένας παίκτης συμμετέχει και τα αναγνωριστικά των παικτών διανέμονται επίσης. Στη συνέχεια πρέπει να καταχωρήσουμε έναν χειριστή για το αντίστοιχο μήνυμα, καθώς και να εφαρμόσουμε μια μέθοδο που θα περάσουμε ως εκπρόσωπος για εγγραφή.

Μοιάζει κάπως έτσι:

NetworkServer.RegisterHandler(MsgType.Connect, OnConnect); private void OnConnect(NetworkMessagemsg) ( Debug.Log(string.Concat("Connected: ", msg.conn.address)); var connId = msg.conn.connectionId; if (NetworkServer.connections.Count > Constants.PLAYERS_COUNT) ( SendPlayerID(connId, -1); ) other ( int index = Random.Range(0, Constants.PLAYERS_IDS.Length); SendPlayerID(connId, Constants.PLAYERS_IDS); _CurrentUser.PlayerID = Constants.PLAYERS_IDS % Constants.PLAYERS_COUNT], SceneManager.LoadScene(1) )


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

Υπάρχει ένας απλός διακομιστής. Τώρα οι πελάτες δεν θα επενέβαιναν. Για να γίνει αυτό, θα χρησιμοποιήσουμε την κλάση NetworkClient. Για να εγγραφείτε στον διακομιστή, πρέπει απλώς να καλέσετε τη μέθοδο Σύνδεση (string serverIp, int serverPort). Ορίζουμε τη θύρα που ακούει ο διακομιστής μας, ορίζουμε ως IP τον localhost εάν δοκιμάζουμε την εφαρμογή μας σε ένα μηχάνημα ή την IP ενός υπολογιστή στο τοπικό δίκτυο που χρησιμοποιούμε ως διακομιστή (Μπορείτε να το βρείτε είτε στο ρυθμίσεις δικτύου ή στην κονσόλα χρησιμοποιώντας την εντολή ipconfig στον υπολογιστή που θα λειτουργεί ως διακομιστής).

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

δημόσια κλάση PlayerIDMessage: MessageBase ( public int PlayerID; ) public enum MyMsgType: σύντομο ( Αναγνωριστικό παίκτη = MsgType.Highest + 1, )


Για να στείλετε αυτό το μήνυμα, καλέστε τη μέθοδο στον υπολογιστή-πελάτη Αποστολή(σύντομο msgType, Networking.MessageBase msg), το οποίο θα στείλει ένα μήνυμα μηνύματος τύπου msgType στον διακομιστή ή στον διακομιστή μία από τις μεθόδους ανάλογα με το σκοπό SendTo All (σύντομο msgType, Networking.MessageBase msg)ή SendToClient (int connectionId, σύντομο msgType, Networking.MessageBase msg), όπου ConnectId είναι το αναγνωριστικό ενός συγκεκριμένου πελάτη.

private void OnPlayerID(NetworkMessagemsg) ( PlayerIDMessage message = msg.reader.ReadMessage ()


Αυτό είναι βασικά. Η περαιτέρω χρήση εξαρτάται από τις ιδιαιτερότητες. Δημιουργούμε τα μηνύματα που χρειαζόμαστε ανάλογα με το gameplay και τα στέλνουμε. Στο tic-tac-toe όρισα επίσης ένα άλλο μήνυμα που είναι υπεύθυνο για τη σύλληψη ενός σημείου. Μπορείτε να δείτε ολόκληρη την υλοποίηση του έργου.

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

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

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

Στην περίπτωση που μιλάμε για τοπικό δίκτυο και προγνωστικό υλικό, δεν υπάρχουν πολλά προβλήματα που προκύπτουν στην περίπτωση του «πραγματικού» multiplayer. Πρακτικά δεν χρειάζεται να σκεφτόμαστε καθυστερήσεις, διακυμάνσεις, απώλειες πακέτων κ.λπ. Επομένως, μια τέτοια λύση μπορεί να είναι χρήσιμη, αν και αυτά τα προβλήματα μπορούν να λυθούν με αυτήν την προσέγγιση σε κάποιο βαθμό. Σε σύγκριση με το υψηλότερο επίπεδο αφαίρεσης στο Unet, μέσω του NetworkManager, του NetworkBehavior, κ.λπ., παρέχει σαφέστερη και πιο προφανή ευελιξία (κατά τη γνώμη μου) εάν οι πελάτες πρέπει να φορτώσουν διαφορετικές σκηνές κ.λπ., και να πουν ότι ο διακομιστής χρησιμοποιείται , σαν streaming, και δείχνει τι εμφανίζεται στη συσκευή του παίκτη, λαμβάνοντας τη θέση και την περιστροφή του + αναπαράγοντας αυτό που συμβαίνει στο πλάι του. Σε άλλες περιπτώσεις, όταν απαιτείται ταχύτερη λύση και γνωρίζετε πώς να εργάζεστε με το δίκτυο, το Unet παρέχει τη δυνατότητα εγγραφής στο επίπεδο μεταφοράς.

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

  • Ενότητα
  • Γεια σε όλους! Σήμερα θα ήθελα να μιλήσω για έναν από τους τρόπους με τους οποίους μπορείτε να δημιουργήσετε τοπικό multiplayer στο Unity. Αυτή η λύση είναι κατάλληλη για βιτρίνες, δοκιμές χαρακτηριστικών ή τοπικό multiplayer. Για παράδειγμα, αν θέλετε να δείτε τι κάνει η συσκευή αναπαραγωγής, αλλά δεν θέλετε να ξοδέψετε επιπλέον πόρους στο Android και να κάνετε το screencast χρησιμοποιώντας ADB, τότε μπορείτε απλά να ρυθμίσετε έναν διακομιστή σε κάποιο μηχάνημα με τη μορφή αντιγράφου του την εφαρμογή που τρέχει στο τηλέφωνο και στείλτε εκεί πληροφορίες για τις ενέργειες του παίκτη.


    Θα περιγράψω εν συντομία πώς μπορεί να γίνει αυτό χρησιμοποιώντας το HLAPI στο Unet, αλλά όχι μέσω του NetworkingManager, αλλά σε ελαφρώς χαμηλότερο επίπεδο. Όπως είναι καλή παράδοση, θα επισυνάψω ένα παράδειγμα εφαρμογής αυτής της αλληλεπίδρασης πελάτη-διακομιστή. Παρακαλώ μην κρίνετε πολύ σκληρά αυτό το παράδειγμα, καθώς καταλαβαίνω πολύ καλά ότι η αρχιτεκτονική αυτής της λύσης δεν είναι καλή και δημιουργεί πολλά προβλήματα στο μέλλον. Στόχος μου ήταν να γράψω ένα σύστημα όσο το δυνατόν γρηγορότερα (το Σαββατοκύριακο) στο οποίο θα μπορούσα να δείξω την αρχή της συνεργασίας με το δίκτυο. Θα σας πω επίσης τι προβλήματα αντιμετώπισα. Αυτό το παράδειγμα υλοποίησης προϋποθέτει ότι ο διακομιστής είναι επίσης μια εφαρμογή Unity.

    Στο Διαδίκτυο, το πιο συνηθισμένο παράδειγμα για το πώς να δημιουργήσετε πολλούς παίκτες είναι η συνομιλία, αλλά μου αρέσουν τα παιχνίδια και η δημιουργία συνομιλίας μου φάνηκε βαρετή. Ως εκ τούτου, αποφάσισα να εξετάσω πώς να φτιάξω πολλούς παίκτες χρησιμοποιώντας το παράδειγμα του tic-tac-toe. Ας υποθέσουμε ότι έχει γραφτεί όλη η λογική του παιχνιδιού, ο καθορισμός του νικητή, η αλλαγή κινήσεων κ.λπ., και το μόνο που έχουμε να κάνουμε είναι να εγκαταστήσουμε τον διακομιστή. Στην απλούστερη περίπτωση, πρέπει να επεξεργαστούμε 2 μηνύματα σε tic-tac-toe. Καθορισμός της σειράς μετακίνησης (διανομή αναγνωριστικών) και σύλληψη του κελιού.

    Συνολικά, το παιχνίδι σε αυτό το παράδειγμα λειτουργεί πολύ απλά. Υπάρχουν 2 σκηνές. Φόρτωση και παιχνίδι. Κατά τη φόρτωση μιας σκηνής παιχνιδιού, δημιουργούμε ένα πεδίο στο οποίο παίζουν οι παίκτες. Η συνθήκη νίκης ελέγχεται επίσης εκεί, υπάρχουν κατηγορίες που είναι υπεύθυνες για τη λειτουργία του UI, καθώς και η σειρά των κινήσεων και η συνολική λογική του παιχνιδιού, αλλά αυτό δεν μας ενδιαφέρει ιδιαίτερα. Οι κύριες κλάσεις που είναι υπεύθυνες για το πλέγμα είναι οι Server, Client, NetManager και ένα ξεχωριστό αρχείο για τα μηνύματα NetMessages και τον αριθμό MyMsgType που ορίζεται σε αυτό. Είναι ένα περιτύλιγμα πάνω από τα εργαλεία Unet. Από την άποψη του Unet, οι κύριες κλάσεις που θα χρησιμοποιήσουμε είναι NetworkClient, NetworkServer, MessageBase και MsgType. Τι είναι αυτές οι τάξεις;

    Οι απλούστερες κλάσεις είναι το MessageBase και το MsgType. Η πρώτη είναι μια αφηρημένη κλάση από την οποία πρέπει να κληρονομηθούν όλα τα μηνύματά μας για να σταλούν μεταξύ του πελάτη και του διακομιστή. Το MsgType είναι απλώς μια κλάση που αποθηκεύει σταθερές που είναι υπεύθυνες για ένα συγκεκριμένο σύνολο μηνυμάτων που είναι ενσωματωμένα στο Unet.

    Ο NetworkServer είναι ένας singleton που παρέχει τη δυνατότητα χειρισμού επικοινωνιών με απομακρυσμένους πελάτες. Κάτω από την κουκούλα χρησιμοποιεί ένα παράδειγμα NetworkServerSimple και είναι ουσιαστικά ένα βολικό περιτύλιγμα γύρω του. Αρχικά, πρέπει να ξεκινήσουμε τον διακομιστή σε μια συγκεκριμένη θύρα. Για να το κάνετε αυτό, πρέπει να καλέσετε τη μέθοδο Listen (int serverPort)- αυτή η μέθοδος ξεκινά τον διακομιστή στη θύρα serverPort. (Επιτρέψτε μου να σας υπενθυμίσω ότι όλες οι θύρες στην περιοχή από 0 έως 1023 είναι θύρες συστήματος και δεν πρέπει να χρησιμοποιούνται ως παράμετρος αυτής της μεθόδου)

    Ο διακομιστής λειτουργεί καλά και ακούει κάποια θύρα. Τώρα το χρειαζόμαστε για να απαντάμε σε μηνύματα. Για να το κάνετε αυτό, πρέπει να καταχωρίσετε έναν χειριστή χρησιμοποιώντας τη μέθοδο RegisterHandler (σύντομο msgType, Networking.NetworkMessageDelegate handler). Αυτή η μέθοδος λαμβάνει ως παραμέτρους έναν τύπο μηνύματος και έναν πληρεξούσιο. Ο πληρεξούσιος, με τη σειρά του, πρέπει να λάβει ένα NetworkMessage ως παράμετρο εισόδου. Ας υποθέσουμε ότι θέλουμε να ξεκινήσει η φόρτωση της σκηνής του παιχνιδιού στον διακομιστή τη στιγμή που ένας παίκτης συμμετέχει και τα αναγνωριστικά των παικτών διανέμονται επίσης. Στη συνέχεια πρέπει να καταχωρήσουμε έναν χειριστή για το αντίστοιχο μήνυμα, καθώς και να εφαρμόσουμε μια μέθοδο που θα περάσουμε ως εκπρόσωπος για εγγραφή.

    Μοιάζει κάπως έτσι:

    Παράδειγμα εγγραφής χειριστή και αντιπροσώπου

    NetworkServer.RegisterHandler(MsgType.Connect, OnConnect); private void OnConnect(NetworkMessagemsg) ( Debug.Log(string.Concat("Connected: ", msg.conn.address)); var connId = msg.conn.connectionId; if (NetworkServer.connections.Count > Constants.PLAYERS_COUNT) ( SendPlayerID(connId, -1); ) other ( int index = Random.Range(0, Constants.PLAYERS_IDS.Length); SendPlayerID(connId, Constants.PLAYERS_IDS); _CurrentUser.PlayerID = Constants.PLAYERS_IDS % Constants.PLAYERS_COUNT], SceneManager.LoadScene(1) )


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

    Υπάρχει ένας απλός διακομιστής. Τώρα οι πελάτες δεν θα επενέβαιναν. Για να γίνει αυτό, θα χρησιμοποιήσουμε την κλάση NetworkClient. Για να εγγραφείτε στον διακομιστή, πρέπει απλώς να καλέσετε τη μέθοδο Σύνδεση (string serverIp, int serverPort). Ορίζουμε τη θύρα που ακούει ο διακομιστής μας, ορίζουμε ως IP τον localhost εάν δοκιμάζουμε την εφαρμογή μας σε ένα μηχάνημα ή την IP ενός υπολογιστή στο τοπικό δίκτυο που χρησιμοποιούμε ως διακομιστή (Μπορείτε να το βρείτε είτε στο ρυθμίσεις δικτύου ή στην κονσόλα χρησιμοποιώντας την εντολή ipconfig στον υπολογιστή που θα λειτουργεί ως διακομιστής).

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

    Το μήνυμα και το είδος του

    δημόσια κλάση PlayerIDMessage: MessageBase ( public int PlayerID; ) public enum MyMsgType: σύντομο ( Αναγνωριστικό παίκτη = MsgType.Highest + 1, )


    Για να στείλετε αυτό το μήνυμα, καλέστε τη μέθοδο στον υπολογιστή-πελάτη Αποστολή(σύντομο msgType, Networking.MessageBase msg), το οποίο θα στείλει ένα μήνυμα μηνύματος τύπου msgType στον διακομιστή ή στον διακομιστή μία από τις μεθόδους ανάλογα με το σκοπό SendTo All (σύντομο msgType, Networking.MessageBase msg)ή SendToClient (int connectionId, σύντομο msgType, Networking.MessageBase msg), όπου ConnectId είναι το αναγνωριστικό ενός συγκεκριμένου πελάτη.

    Επεξεργασία του εισερχόμενου ατόμου ταυτότητας στον πελάτη

    private void OnPlayerID(NetworkMessagemsg) ( PlayerIDMessage message = msg.reader.ReadMessage ()


    Αυτό είναι βασικά. Η περαιτέρω χρήση εξαρτάται από τις ιδιαιτερότητες. Δημιουργούμε τα μηνύματα που χρειαζόμαστε ανάλογα με το gameplay και τα στέλνουμε. Στο tic-tac-toe όρισα επίσης ένα άλλο μήνυμα που είναι υπεύθυνο για τη σύλληψη ενός σημείου. Μπορείτε να δείτε ολόκληρη την υλοποίηση του έργου.

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

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

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

    Στην περίπτωση που μιλάμε για τοπικό δίκτυο και προγνωστικό υλικό, δεν υπάρχουν πολλά προβλήματα που προκύπτουν στην περίπτωση του «πραγματικού» multiplayer. Πρακτικά δεν χρειάζεται να σκεφτόμαστε καθυστερήσεις, διακυμάνσεις, απώλειες πακέτων κ.λπ. Επομένως, μια τέτοια λύση μπορεί να είναι χρήσιμη, αν και αυτά τα προβλήματα μπορούν να λυθούν με αυτήν την προσέγγιση σε κάποιο βαθμό. Σε σύγκριση με το υψηλότερο επίπεδο αφαίρεσης στο Unet, μέσω του NetworkManager, του NetworkBehavior, κ.λπ., παρέχει σαφέστερη και πιο προφανή ευελιξία (κατά τη γνώμη μου) εάν οι πελάτες πρέπει να φορτώσουν διαφορετικές σκηνές κ.λπ., και να πουν ότι ο διακομιστής χρησιμοποιείται , σαν streaming, και δείχνει τι εμφανίζεται στη συσκευή του παίκτη, λαμβάνοντας τη θέση και την περιστροφή του + αναπαράγοντας αυτό που συμβαίνει στο πλάι του. Σε άλλες περιπτώσεις, όταν απαιτείται ταχύτερη λύση και γνωρίζετε πώς να εργάζεστε με το δίκτυο, το Unet παρέχει τη δυνατότητα εγγραφής στο επίπεδο μεταφοράς.

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

    Γεια σε όλους! Σήμερα θα ήθελα να μιλήσω για έναν από τους τρόπους με τους οποίους μπορείτε να δημιουργήσετε τοπικό multiplayer στο Unity. Αυτή η λύση είναι κατάλληλη για βιτρίνες, δοκιμές χαρακτηριστικών ή τοπικό multiplayer. Για παράδειγμα, αν θέλετε να δείτε τι κάνει η συσκευή αναπαραγωγής, αλλά δεν θέλετε να ξοδέψετε επιπλέον πόρους στο Android και να κάνετε το screencast χρησιμοποιώντας ADB, τότε μπορείτε απλά να ρυθμίσετε έναν διακομιστή σε κάποιο μηχάνημα με τη μορφή αντιγράφου του την εφαρμογή που τρέχει στο τηλέφωνο και στείλτε εκεί πληροφορίες για τις ενέργειες του παίκτη.

    Θα περιγράψω εν συντομία πώς μπορεί να γίνει αυτό χρησιμοποιώντας το HLAPI στο Unet, αλλά όχι μέσω του NetworkingManager, αλλά σε ελαφρώς χαμηλότερο επίπεδο. Όπως είναι καλή παράδοση, θα επισυνάψω ένα παράδειγμα εφαρμογής αυτής της αλληλεπίδρασης πελάτη-διακομιστή. Παρακαλώ μην κρίνετε πολύ σκληρά αυτό το παράδειγμα, καθώς καταλαβαίνω πολύ καλά ότι η αρχιτεκτονική αυτής της λύσης δεν είναι καλή και δημιουργεί πολλά προβλήματα στο μέλλον. Στόχος μου ήταν να γράψω ένα σύστημα όσο το δυνατόν γρηγορότερα (το Σαββατοκύριακο) στο οποίο θα μπορούσα να δείξω την αρχή της συνεργασίας με το δίκτυο. Θα σας πω επίσης τι προβλήματα αντιμετώπισα. Αυτό το παράδειγμα υλοποίησης προϋποθέτει ότι ο διακομιστής είναι επίσης μια εφαρμογή Unity.

    Στο Διαδίκτυο, το πιο συνηθισμένο παράδειγμα για το πώς να δημιουργήσετε πολλούς παίκτες είναι η συνομιλία, αλλά μου αρέσουν τα παιχνίδια και η δημιουργία συνομιλίας μου φάνηκε βαρετή. Ως εκ τούτου, αποφάσισα να εξετάσω πώς να φτιάξω πολλούς παίκτες χρησιμοποιώντας το παράδειγμα του tic-tac-toe. Ας υποθέσουμε ότι έχει γραφτεί όλη η λογική του παιχνιδιού, ο καθορισμός του νικητή, η αλλαγή κινήσεων κ.λπ., και το μόνο που έχουμε να κάνουμε είναι να εγκαταστήσουμε τον διακομιστή. Στην απλούστερη περίπτωση, πρέπει να επεξεργαστούμε 2 μηνύματα σε tic-tac-toe. Καθορισμός της σειράς μετακίνησης (διανομή αναγνωριστικών) και σύλληψη του κελιού.


    Συνολικά, το παιχνίδι σε αυτό το παράδειγμα λειτουργεί πολύ απλά. Υπάρχουν 2 σκηνές. Φόρτωση και παιχνίδι. Κατά τη φόρτωση μιας σκηνής παιχνιδιού, δημιουργούμε ένα πεδίο στο οποίο παίζουν οι παίκτες. Η συνθήκη νίκης ελέγχεται επίσης εκεί, υπάρχουν κατηγορίες που είναι υπεύθυνες για τη λειτουργία του UI, καθώς και η σειρά των κινήσεων και η συνολική λογική του παιχνιδιού, αλλά αυτό δεν μας ενδιαφέρει ιδιαίτερα. Οι κύριες κλάσεις που είναι υπεύθυνες για το πλέγμα είναι οι Server, Client, NetManager και ένα ξεχωριστό αρχείο για τα μηνύματα NetMessages και τον αριθμό MyMsgType που ορίζεται σε αυτό. Είναι ένα περιτύλιγμα πάνω από τα εργαλεία Unet. Από την άποψη του Unet, οι κύριες κλάσεις που θα χρησιμοποιήσουμε είναι NetworkClient, NetworkServer, MessageBase και MsgType. Τι είναι αυτές οι τάξεις;

    Οι απλούστερες κλάσεις είναι το MessageBase και το MsgType. Η πρώτη είναι μια αφηρημένη κλάση από την οποία πρέπει να κληρονομηθούν όλα τα μηνύματά μας για να σταλούν μεταξύ του πελάτη και του διακομιστή. Το MsgType είναι απλώς μια κλάση που αποθηκεύει σταθερές που είναι υπεύθυνες για ένα συγκεκριμένο σύνολο μηνυμάτων που είναι ενσωματωμένα στο Unet.


    Ο NetworkServer είναι ένας singleton που παρέχει τη δυνατότητα χειρισμού επικοινωνιών με απομακρυσμένους πελάτες. Κάτω από την κουκούλα χρησιμοποιεί ένα παράδειγμα NetworkServerSimple και είναι ουσιαστικά ένα βολικό περιτύλιγμα γύρω του. Αρχικά, πρέπει να ξεκινήσουμε τον διακομιστή σε μια συγκεκριμένη θύρα. Για να το κάνετε αυτό, πρέπει να καλέσετε τη μέθοδο Listen (int serverPort)- αυτή η μέθοδος ξεκινά τον διακομιστή στη θύρα serverPort. (Επιτρέψτε μου να σας υπενθυμίσω ότι όλες οι θύρες στην περιοχή από 0 έως 1023 είναι θύρες συστήματος και δεν πρέπει να χρησιμοποιούνται ως παράμετρος αυτής της μεθόδου)

    Ο διακομιστής λειτουργεί καλά και ακούει κάποια θύρα. Τώρα το χρειαζόμαστε για να απαντάμε σε μηνύματα. Για να το κάνετε αυτό, πρέπει να καταχωρίσετε έναν χειριστή χρησιμοποιώντας τη μέθοδο RegisterHandler (σύντομο msgType, Networking.NetworkMessageDelegate handler). Αυτή η μέθοδος λαμβάνει ως παραμέτρους έναν τύπο μηνύματος και έναν πληρεξούσιο. Ο πληρεξούσιος, με τη σειρά του, πρέπει να λάβει ένα NetworkMessage ως παράμετρο εισόδου. Ας υποθέσουμε ότι θέλουμε να ξεκινήσει η φόρτωση της σκηνής του παιχνιδιού στον διακομιστή τη στιγμή που ένας παίκτης συμμετέχει και τα αναγνωριστικά των παικτών διανέμονται επίσης. Στη συνέχεια πρέπει να καταχωρήσουμε έναν χειριστή για το αντίστοιχο μήνυμα, καθώς και να εφαρμόσουμε μια μέθοδο που θα περάσουμε ως εκπρόσωπος για εγγραφή.

    Μοιάζει κάπως έτσι:

    Παράδειγμα εγγραφής χειριστή και αντιπροσώπου

    NetworkServer.RegisterHandler(MsgType.Connect, OnConnect); private void OnConnect(NetworkMessagemsg) ( Debug.Log(string.Concat("Connected: ", msg.conn.address)); var connId = msg.conn.connectionId; if (NetworkServer.connections.Count > Constants.PLAYERS_COUNT) ( SendPlayerID(connId, -1); ) other ( int index = Random.Range(0, Constants.PLAYERS_IDS.Length); SendPlayerID(connId, Constants.PLAYERS_IDS); _CurrentUser.PlayerID = Constants.PLAYERS_IDS % Constants.PLAYERS_COUNT], SceneManager.LoadScene(1) )

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

    Υπάρχει ένας απλός διακομιστής. Τώρα οι πελάτες δεν θα επενέβαιναν. Για να γίνει αυτό, θα χρησιμοποιήσουμε την κλάση NetworkClient. Για να εγγραφείτε στον διακομιστή, πρέπει απλώς να καλέσετε τη μέθοδο Σύνδεση (string serverIp, int serverPort). Ορίζουμε τη θύρα που ακούει ο διακομιστής μας, ορίζουμε ως IP τον localhost εάν δοκιμάζουμε την εφαρμογή μας σε ένα μηχάνημα ή την IP ενός υπολογιστή στο τοπικό δίκτυο που χρησιμοποιούμε ως διακομιστή (Μπορείτε να το βρείτε είτε στο ρυθμίσεις δικτύου ή στην κονσόλα χρησιμοποιώντας την εντολή ipconfig στον υπολογιστή που θα λειτουργεί ως διακομιστής).

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

    Το μήνυμα και το είδος του

    Public class PlayerIDMessage: MessageBase ( public int PlayerID; ) public enum MyMsgType: σύντομο ( Αναγνωριστικό παίκτη = MsgType.Highest + 1, )

    Για να στείλετε αυτό το μήνυμα, καλέστε τη μέθοδο στον υπολογιστή-πελάτη Αποστολή(σύντομο msgType, Networking.MessageBase msg), το οποίο θα στείλει ένα μήνυμα μηνύματος τύπου msgType στον διακομιστή ή στον διακομιστή μία από τις μεθόδους ανάλογα με το σκοπό SendTo All (σύντομο msgType, Networking.MessageBase msg)ή SendToClient (int connectionId, σύντομο msgType, Networking.MessageBase msg), όπου ConnectId είναι το αναγνωριστικό ενός συγκεκριμένου πελάτη.

    Επεξεργασία του εισερχόμενου ατόμου ταυτότητας στον πελάτη

    Private void OnPlayerID(NetworkMessagemsg) ( PlayerIDMessage message = msg.reader.ReadMessage ()

    Αυτό είναι βασικά. Η περαιτέρω χρήση εξαρτάται από τις ιδιαιτερότητες. Δημιουργούμε τα μηνύματα που χρειαζόμαστε ανάλογα με το gameplay και τα στέλνουμε. Στο tic-tac-toe όρισα επίσης ένα άλλο μήνυμα που είναι υπεύθυνο για τη σύλληψη ενός σημείου. Μπορείτε να δείτε ολόκληρη την υλοποίηση του έργου.

    _CurrentUser.PlayerID = message.PlayerID;

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

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

    Στην περίπτωση που μιλάμε για τοπικό δίκτυο και προγνωστικό υλικό, δεν υπάρχουν πολλά προβλήματα που προκύπτουν στην περίπτωση του «πραγματικού» multiplayer. Πρακτικά δεν χρειάζεται να σκεφτόμαστε καθυστερήσεις, διακυμάνσεις, απώλειες πακέτων κ.λπ. Επομένως, μια τέτοια λύση μπορεί να είναι χρήσιμη, αν και αυτά τα προβλήματα μπορούν να λυθούν με αυτήν την προσέγγιση σε κάποιο βαθμό. Σε σύγκριση με το υψηλότερο επίπεδο αφαίρεσης στο Unet, μέσω του NetworkManager, του NetworkBehavior, κ.λπ., παρέχει σαφέστερη και πιο προφανή ευελιξία (κατά τη γνώμη μου) εάν οι πελάτες πρέπει να φορτώσουν διαφορετικές σκηνές κ.λπ., και να πουν ότι ο διακομιστής χρησιμοποιείται , σαν streaming, και δείχνει τι εμφανίζεται στη συσκευή του παίκτη, λαμβάνοντας τη θέση και την περιστροφή του + αναπαράγοντας αυτό που συμβαίνει στο πλάι του. Σε άλλες περιπτώσεις, όταν απαιτείται ταχύτερη λύση και γνωρίζετε πώς να εργάζεστε με το δίκτυο, το Unet παρέχει τη δυνατότητα εγγραφής στο επίπεδο μεταφοράς.

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

    Σας ευχαριστώ για την προσοχή σας!



    Ερωτήσεις;

    Αναφέρετε ένα τυπογραφικό λάθος

    Κείμενο που θα σταλεί στους συντάκτες μας: