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

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

Γεια, είμαι ο Nikita Brizhak, προγραμματιστής διακομιστή από την Pixonic. Σήμερα θα ήθελα να μιλήσω για την αντιστάθμιση της καθυστέρησης στο multiplayer για κινητά.

Πολλά άρθρα έχουν γραφτεί σχετικά με την αντιστάθμιση καθυστέρησης διακομιστή, μεταξύ άλλων στα ρωσικά. Αυτό δεν προκαλεί έκπληξη, καθώς αυτή η τεχνολογία έχει χρησιμοποιηθεί ενεργά στη δημιουργία FPS για πολλούς παίκτες από τα τέλη της δεκαετίας του '90. Για παράδειγμα, μπορείτε να θυμηθείτε το QuakeWorld mod, το οποίο ήταν ένα από τα πρώτα που το χρησιμοποίησαν.

Το χρησιμοποιούμε επίσης στο Dino Squad για σκοπευτές για πολλούς παίκτες για κινητά.

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

Λίγα λόγια για τον φλοιό και την τεχνολογία μας.

Το Dino Squad είναι ένα πρόγραμμα σκοποβολής PvP για φορητές συσκευές δικτύου. Οι παίκτες ελέγχουν δεινόσαυρους εξοπλισμένους με διάφορα όπλα και πολεμούν μεταξύ τους σε ομάδες 6v6.

Τόσο ο πελάτης όσο και ο διακομιστής βασίζονται στο Unity. Η αρχιτεκτονική είναι αρκετά κλασική για τους σκοπευτές: ο διακομιστής είναι αυταρχικός και η πρόβλεψη πελάτη λειτουργεί στους πελάτες. Η προσομοίωση παιχνιδιού είναι γραμμένη με χρήση εσωτερικού ECS και χρησιμοποιείται τόσο στον διακομιστή όσο και στον πελάτη.

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

Στα παιχνίδια FPS για πολλούς παίκτες, ο αγώνας προσομοιώνεται συνήθως σε έναν απομακρυσμένο διακομιστή. Οι παίκτες στέλνουν τις πληροφορίες τους (πληροφορίες σχετικά με τα πλήκτρα που έχουν πατηθεί) στον διακομιστή και σε απάντηση ο διακομιστής τους στέλνει μια ενημερωμένη κατάσταση παιχνιδιού λαμβάνοντας υπόψη τα δεδομένα που λαμβάνονται. Με αυτό το σχήμα αλληλεπίδρασης, η καθυστέρηση μεταξύ του πατήματος του πλήκτρου εμπρός και της στιγμής που ο χαρακτήρας του παίκτη μετακινείται στην οθόνη θα είναι πάντα μεγαλύτερη από το ping.

Ενώ στα τοπικά δίκτυα αυτή η καθυστέρηση (γνωστή ως καθυστέρηση εισόδου) μπορεί να είναι απαρατήρητη, όταν παίζετε μέσω Διαδικτύου δημιουργείται μια αίσθηση «γλιστρήματος στον πάγο» όταν ελέγχετε έναν χαρακτήρα. Αυτό το πρόβλημα είναι διπλά σημαντικό για τα δίκτυα κινητής τηλεφωνίας, όπου η περίπτωση όταν το ping ενός παίκτη είναι 200 ​​ms εξακολουθεί να θεωρείται εξαιρετική σύνδεση. Συχνά το ping μπορεί να είναι 350, 500 ή 1000 ms. Τότε είναι σχεδόν αδύνατο να παίξετε ένα γρήγορο σουτέρ με καθυστέρηση εισόδου.

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

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

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

Οπλισμένοι με αυτή τη γνώση, αρχίσαμε να εφαρμόζουμε αντιστάθμιση καθυστέρησης διακομιστή στο Dino Squad. Πρώτα απ 'όλα, έπρεπε να καταλάβουμε πώς να επαναφέρουμε στον διακομιστή αυτό που είδε ο πελάτης; Και τι ακριβώς χρειάζεται να αποκατασταθεί; Στο παιχνίδι μας, τα χτυπήματα από όπλα και ικανότητες υπολογίζονται μέσω εκπομπών ακτίνων και επικαλύψεων - δηλαδή μέσω αλληλεπιδράσεων με τους φυσικούς επιταχυντές του εχθρού. Αντίστοιχα, έπρεπε να αναπαράγουμε τη θέση αυτών των επιταχυντών, που ο παίκτης «είδε» τοπικά, στον διακομιστή. Εκείνη την εποχή χρησιμοποιούσαμε Unity έκδοση 2018.x. Το API της φυσικής εκεί είναι στατικό, ο φυσικός κόσμος υπάρχει σε ένα μόνο αντίγραφο. Δεν υπάρχει τρόπος να αποθηκεύσετε την κατάστασή του και μετά να την επαναφέρετε από το κουτί. Τι να κάνουμε λοιπόν;

Η λύση ήταν στην επιφάνεια· όλα τα στοιχεία της είχαν ήδη χρησιμοποιηθεί από εμάς για την επίλυση άλλων προβλημάτων:

  1. Για κάθε πελάτη, πρέπει να γνωρίζουμε πότε είδε αντιπάλους όταν πάτησε τα πλήκτρα. Έχουμε ήδη γράψει αυτές τις πληροφορίες στο πακέτο εισόδου και τις χρησιμοποιήσαμε για να προσαρμόσουμε την πρόβλεψη του πελάτη.
  2. Πρέπει να μπορούμε να αποθηκεύουμε την ιστορία των καταστάσεων παιχνιδιού. Σε αυτό θα κρατήσουμε τις θέσεις των αντιπάλων μας (και επομένως των συγκρουόμενων τους). Είχαμε ήδη ένα ιστορικό κατάστασης στον διακομιστή, το χρησιμοποιήσαμε για τη δημιουργία δέλτα. Γνωρίζοντας την κατάλληλη στιγμή, θα μπορούσαμε εύκολα να βρούμε την κατάλληλη κατάσταση στην ιστορία.
  3. Τώρα που έχουμε την κατάσταση του παιχνιδιού από την ιστορία στο χέρι, πρέπει να είμαστε σε θέση να συγχρονίζουμε τα δεδομένα των παικτών με την κατάσταση του φυσικού κόσμου. Υπάρχοντες συγκρουόμενοι - μετακινήστε, λείπουν - δημιουργήστε, περιττοί - καταστρέψτε. Αυτή η λογική ήταν επίσης ήδη γραμμένη και αποτελούνταν από πολλά συστήματα ECS. Το χρησιμοποιήσαμε για να κρατήσουμε πολλές αίθουσες παιχνιδιών σε μία διαδικασία Unity. Και επειδή ο φυσικός κόσμος είναι ένας ανά διαδικασία, έπρεπε να επαναχρησιμοποιηθεί μεταξύ των δωματίων. Πριν από κάθε tick της προσομοίωσης, «επαναφέραμε» την κατάσταση του φυσικού κόσμου και τον αρχικοποιήσαμε ξανά με δεδομένα για το τρέχον δωμάτιο, προσπαθώντας να επαναχρησιμοποιήσουμε αντικείμενα παιχνιδιού Unity όσο το δυνατόν περισσότερο μέσω ενός έξυπνου συστήματος συγκέντρωσης. Το μόνο που έμενε ήταν να επικαλεστεί την ίδια λογική για την κατάσταση του παιχνιδιού από το παρελθόν.

Συνδυάζοντας όλα αυτά τα στοιχεία μαζί, αποκτήσαμε μια «μηχανή του χρόνου» που θα μπορούσε να επαναφέρει την κατάσταση του φυσικού κόσμου στην κατάλληλη στιγμή. Ο κώδικας αποδείχθηκε απλός:

public class TimeMachine : ITimeMachine
{
     //История игровых состояний
     private readonly IGameStateHistory _history;

     //Текущее игровое состояние на сервере
     private readonly ExecutableSystem[] _systems;

     //Набор систем, расставляющих коллайдеры в физическом мире 
     //по данным из игрового состояния
     private readonly GameState _presentState;

     public TimeMachine(IGameStateHistory history, GameState presentState, ExecutableSystem[] timeInitSystems)
     {
         _history = history; 
         _presentState = presentState;
         _systems = timeInitSystems;  
     }

     public GameState TravelToTime(int tick)
     {
         var pastState = tick == _presentState.Time ? _presentState : _history.Get(tick);
         foreach (var system in _systems)
         {
             system.Execute(pastState);
         }
         return pastState;
     }
}

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

Στην απλούστερη περίπτωση, όταν οι μηχανισμοί βασίζονται σε ένα μόνο hitscan, όλα φαίνεται να είναι ξεκάθαρα: πριν ο παίκτης σουτάρει, πρέπει να επαναφέρει τον φυσικό κόσμο στην επιθυμητή κατάσταση, να κάνει ένα raycast, να μετρήσει το χτύπημα ή το χάσιμο και επιστρέψτε τον κόσμο στην αρχική κατάσταση.

Αλλά υπάρχουν πολύ λίγοι τέτοιοι μηχανικοί στο Dino Squad! Τα περισσότερα από τα όπλα του παιχνιδιού δημιουργούν βλήματα - σφαίρες μεγάλης διάρκειας που πετούν για πολλά τσιμπούρια προσομοίωσης (σε ορισμένες περιπτώσεις, δεκάδες κρότωνες). Τι να τα κάνουμε, τι ώρα πρέπει να πετάξουν;

В αρχαίο άρθρο σχετικά με τη στοίβα δικτύου Half-Life, τα παιδιά από τη Valve έκαναν την ίδια ερώτηση και η απάντησή τους ήταν η εξής: η αντιστάθμιση υστέρησης βλήματος είναι προβληματική και είναι καλύτερα να την αποφύγετε.

Δεν είχαμε αυτήν την επιλογή: τα όπλα που βασίζονταν σε βλήμα ήταν ένα βασικό χαρακτηριστικό του σχεδιασμού του παιχνιδιού. Έπρεπε λοιπόν να καταλήξουμε σε κάτι. Μετά από κάποιο καταιγισμό ιδεών, διατυπώσαμε δύο επιλογές που φάνηκαν να λειτουργούν:

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

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

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

  • Η σφαίρα έχει λήξει. Αυτό σημαίνει ότι οι υπολογισμοί έχουν τελειώσει, μπορούμε να μετρήσουμε μια αστοχία ή ένα χτύπημα. Και αυτό είναι στο ίδιο τσιμπούρι στο οποίο έγινε η βολή! Για εμάς αυτό ήταν και ένα συν και ένα μείον. Ένα πλεονέκτημα - γιατί για τον παίκτη σκοποβολής αυτό μείωσε σημαντικά την καθυστέρηση μεταξύ του χτυπήματος και τη μείωση της υγείας του εχθρού. Το μειονέκτημα είναι ότι το ίδιο αποτέλεσμα παρατηρήθηκε όταν οι αντίπαλοι πυροβόλησαν στον παίκτη: ο εχθρός, όπως φαίνεται, εκτόξευσε μόνο έναν αργό πύραυλο και η ζημιά είχε ήδη μετρηθεί.
  • Η κουκκίδα έφτασε στην ώρα του διακομιστή. Σε αυτήν την περίπτωση, η προσομοίωση του θα συνεχιστεί στο επόμενο τικ στο διακομιστή χωρίς καμία αντιστάθμιση καθυστέρησης. Για αργά βλήματα, αυτό θα μπορούσε θεωρητικά να μειώσει τον αριθμό των ανατροπών της φυσικής σε σύγκριση με την πρώτη επιλογή. Ταυτόχρονα, το ανομοιόμορφο φορτίο στην προσομοίωση αυξήθηκε: ο διακομιστής είτε ήταν αδρανής, είτε σε ένα τικ του διακομιστή υπολόγιζε μια ντουζίνα τικ προσομοίωσης για πολλές κουκκίδες.

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

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

Ξεχωριστά, αξίζει να τεθεί το θέμα της απόδοσης. Αν νομίζατε ότι όλο αυτό θα επιβραδύνει τα πράγματα, απαντώ: είναι. Το Unity είναι αρκετά αργό στην κίνηση των επιταχυντών και στην ενεργοποίηση και απενεργοποίηση τους. Στο Dino Squad, στη "χειρότερη" περίπτωση, μπορεί να υπάρχουν πολλές εκατοντάδες βλήματα ταυτόχρονα σε μάχη. Η κίνηση των επιταχυντών για να μετρήσει κάθε βλήμα ξεχωριστά είναι μια απρόσιτη πολυτέλεια. Ως εκ τούτου, ήταν απολύτως απαραίτητο για εμάς να ελαχιστοποιήσουμε τον αριθμό των «ανατροπών» της φυσικής. Για να γίνει αυτό, δημιουργήσαμε ένα ξεχωριστό στοιχείο στο ECS στο οποίο καταγράφουμε την ώρα του παίκτη. Το προσθέσαμε σε όλες τις οντότητες που απαιτούν αντιστάθμιση υστέρησης (βλήματα, ικανότητες κ.λπ.). Πριν ξεκινήσουμε την επεξεργασία τέτοιων οντοτήτων, τις συγκεντρώνουμε μέχρι αυτή τη στιγμή και τις επεξεργαζόμαστε μαζί, επαναφέροντας τον φυσικό κόσμο μία φορά για κάθε σύμπλεγμα.

Σε αυτό το στάδιο έχουμε ένα γενικά λειτουργικό σύστημα. Ο κώδικάς του σε κάπως απλοποιημένη μορφή:

public sealed class LagCompensationSystemGroup : ExecutableSystem
{
     //Машина времени
     private readonly ITimeMachine _timeMachine;

     //Набор систем лагкомпенсации
     private readonly LagCompensationSystem[] _systems;
     
     //Наша реализация кластеризатора
     private readonly TimeTravelMap _travelMap = new TimeTravelMap();

    public LagCompensationSystemGroup(ITimeMachine timeMachine, 
        LagCompensationSystem[] lagCompensationSystems)
     {
         _timeMachine = timeMachine;
         _systems = lagCompensationSystems;
     }

     public override void Execute(GameState gs)
     {
         //На вход кластеризатор принимает текущее игровое состояние,
         //а на выход выдает набор «корзин». В каждой корзине лежат энтити,
         //которым для лагкомпенсации нужно одно и то же время из истории.
         var buckets = _travelMap.RefillBuckets(gs);

         for (int bucketIndex = 0; bucketIndex < buckets.Count; bucketIndex++)
         {
             ProcessBucket(gs, buckets[bucketIndex]);
         }

         //В конце лагкомпенсации мы восстанавливаем физический мир 
         //в исходное состояние
         _timeMachine.TravelToTime(gs.Time);
     }

     private void ProcessBucket(GameState presentState, TimeTravelMap.Bucket bucket)
     {
         //Откатываем время один раз для каждой корзины
         var pastState = _timeMachine.TravelToTime(bucket.Time);

         foreach (var system in _systems)
         {
               system.PastState = pastState;
               system.PresentState = presentState;

               foreach (var entity in bucket)
               {
                   system.Execute(entity);
               }
          }
     }
}

Το μόνο που έμεινε ήταν να διαμορφωθούν οι λεπτομέρειες:

1. Κατανοήστε πόσο να περιορίσετε τη μέγιστη απόσταση κίνησης στο χρόνο.

Ήταν σημαντικό για εμάς να κάνουμε το παιχνίδι όσο το δυνατόν πιο προσιτό σε συνθήκες φτωχών δικτύων κινητής τηλεφωνίας, επομένως περιορίσαμε την ιστορία με ένα περιθώριο 30 τικ (με ρυθμό κρότου 20 Hz). Αυτό επιτρέπει στους παίκτες να χτυπούν τους αντιπάλους ακόμη και σε πολύ ψηλά ping.

2. Προσδιορίστε ποια αντικείμενα μπορούν να μετακινηθούν στο χρόνο και ποια όχι.

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

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

4. Καθορίστε τι να κάνετε με τους συγκρουόμενους του παίκτη για τον οποίο εκτελείται αντιστάθμιση καθυστέρησης. Με την καλή έννοια, η θέση τους δεν πρέπει να μετατοπίζεται στο παρελθόν: ο παίκτης θα πρέπει να βλέπει τον εαυτό του την ίδια στιγμή που βρίσκεται τώρα στον διακομιστή. Ωστόσο, επαναφέρουμε και τα colliders του shooting player και υπάρχουν αρκετοί λόγοι για αυτό.

Πρώτον, βελτιώνει το clustering: μπορούμε να χρησιμοποιήσουμε την ίδια φυσική κατάσταση για όλους τους παίκτες με κοντινά ping.

Δεύτερον, σε όλες τις εκπομπές ακτίνων και τις επικαλύψεις αποκλείουμε πάντα τους συγκρουόμενους του παίκτη που κατέχει τις ικανότητες ή τα βλήματα. Στο Dino Squad, οι παίκτες ελέγχουν δεινόσαυρους, οι οποίοι έχουν μάλλον μη τυπική γεωμετρία για τα πρότυπα σκοπευτών. Ακόμα κι αν ο παίκτης πυροβολήσει σε ασυνήθιστη γωνία και η τροχιά της σφαίρας περάσει μέσα από τον επιταχυντή δεινοσαύρων του παίκτη, η σφαίρα θα το αγνοήσει.

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

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

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

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

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

Δώσαμε σε κάθε αίθουσα παιχνιδιών τη δική της φυσική σκηνή και έτσι εξαλείφαμε την ανάγκη να «καθαρίσουμε» τη σκηνή από τα δεδομένα της γειτονικής αίθουσας πριν υπολογίσουμε την προσομοίωση. Πρώτον, έδωσε σημαντική αύξηση στην παραγωγικότητα. Δεύτερον, έδωσε τη δυνατότητα να απαλλαγούμε από μια ολόκληρη κατηγορία σφαλμάτων που προέκυψαν εάν ο προγραμματιστής έκανε ένα σφάλμα στον κώδικα εκκαθάρισης σκηνής κατά την προσθήκη νέων στοιχείων παιχνιδιού. Τέτοια σφάλματα ήταν δύσκολο να εντοπιστούν και συχνά είχαν ως αποτέλεσμα την κατάσταση των φυσικών αντικειμένων στη σκηνή ενός δωματίου να "ρέουν" σε ένα άλλο δωμάτιο.

Επιπλέον, κάναμε κάποια έρευνα σχετικά με το εάν οι φυσικές σκηνές θα μπορούσαν να χρησιμοποιηθούν για την αποθήκευση της ιστορίας του φυσικού κόσμου. Δηλαδή, υπό όρους, διαθέστε όχι μία σκηνή σε κάθε δωμάτιο, αλλά 30 σκηνές και δημιουργήστε ένα κυκλικό buffer από αυτές, στο οποίο θα αποθηκεύσετε την ιστορία. Σε γενικές γραμμές, η επιλογή αποδείχθηκε ότι λειτουργούσε, αλλά δεν την εφαρμόσαμε: δεν έδειξε καμία τρελή αύξηση στην παραγωγικότητα, αλλά απαιτούσε μάλλον επικίνδυνες αλλαγές. Ήταν δύσκολο να προβλέψουμε πώς θα συμπεριφερόταν ο διακομιστής όταν εργαζόταν για μεγάλο χρονικό διάστημα με τόσες πολλές σκηνές. Επομένως, ακολουθήσαμε τον κανόνα: «Εάν δεν έχει σπάσει, μην το διορθώσετε».

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο