Zuvor schrieb ich über meine Erfahrungen bei der Entwicklung eines mobilen
Wortspiels für Android und iOS, das sehr beliebt ist, und entschied mich, den Mehrspielermodus daran zu binden, wenn zwei Spieler untereinander gegeneinander antreten und nacheinander Wörter schreiben, als letzte Runde der Fernsehsendung von Sergey Suponev Stunde. "

Ich habe anderthalb Monate gebraucht, um den Multiplayer zu studieren und zu implementieren. In dem Artikel werde ich versuchen, das Konzept ohne Beispiele für den Quellcode zu beschreiben, um die Menge der geleisteten Arbeit zu reduzieren.
Ein bisschen Geschichte
Die Anwendung wurde in C ++ mit dem Marmalade SDK geschrieben. Seitdem hat der Anbieter die Unterstützung dieser Plattform eingestellt und die Sorten an die Japaner verkauft, und die Zukunft dieser Entwicklungsumgebung ist sehr vage geworden.

Es stellte sich die Frage, was aktuelle Projekte für ihre weitere Unterstützung portieren sollen.
Warum nicht cocos2d-x
Cocos2d-x ist eine der häufigsten plattformübergreifenden C ++ - Entwicklungsmaschinen für mobile Spiele. Anscheinend aufgrund seines kostenlosen und Open Source Codes. Der Motor ist schlecht dokumentiert. Die Beschreibung deckt den mageren Teil des Motors ab und der größte Teil des Materials ist längst veraltet.
Basierend auf den Ergebnissen einer Periode gelang es mir immer noch, einen Prototyp meiner Anwendung zu erstellen. Aber die Eindrücke waren sehr schlecht: Es fühlt sich an, als wäre cocos2d-x auf dem Knie montiert. Die Abstraktionsebenen Szene, Sprite, Anwendungsdelegierter schienen mir sehr unangenehm, und die Notwendigkeit, im Kokosnussforum nach Antworten auf Fragen zu suchen, führt Sie zunehmend zu der Idee, dass Sie etwas falsch machen. Wahrscheinlich wachsen meine Hände an der falschen Stelle.
Meine Wahl fiel auf SDL
SDL ist wie Marmalade SDL keine Engine, sondern eine Plattform. Es bietet eine Low-Level-API, aus der ich dann für mich geeignete Abstraktionsebenen erstelle. All dies ist in C geschrieben, der Quellcode ist offen.
Kurz gesagt, die SDL ist eine kostenlose plattformübergreifende Bibliothek für die Arbeit mit Grafiken, Sound und die Verarbeitung von Nachrichten des Betriebssystems. Es ist sehr praktisch, ein Win32-Build zu erstellen und die Logik des Spiels unter Windows zu debuggen, sodass mobile Emulatoren und physische Geräte nur betriebssystemspezifische Funktionen debuggen.
Glücklicherweise oder unglücklicherweise bietet die SDL keine Tools für eine so enge Aufgabe wie die Entwicklung eines Multiplayers für iOS und Android, sodass ich mich selbst in die entsprechenden Dienste integrieren musste.
Multithread-Anwendungsarchitektur
Die Logik der Anwendung und alle Arbeiten mit Grafiken sind im Hauptthread implementiert, der ein Nachrichtenverarbeitungszyklus ist und in der Hauptfunktion beginnt. Nennen Sie diesen Stream SDL Thread. Andere Threads wiederum werfen Ereignisse (SDL_PushEvent) zur Verarbeitung in die Warteschlange und lesen sie mit SDL_WaitEvent und SDL_PollEvent daraus. Dies sind entweder vom System ausgelöste Systemereignisse, deren Unterstützung bereits in SDL implementiert ist, oder Aufrufe von Rückrufen und Listenern, die wir bereits über die SDL-Funktionalität hinaus implementieren.

Die gesamte Spielelogik ist in C ++ geschrieben. Das Projektverzeichnis enthält eine Reihe von * .cpp-Dateien, die in drei Gruppen unterteilt werden können:
- plattformübergreifend - die Dateien, die in der Zusammenstellung aller Plattformen enthalten sind (Spielelogik);
- Monoplattform, d.h. sind in der Anwendung einer Plattform enthalten, um deren Funktionen zu implementieren.
Dementsprechend gibt es drei separate Verzeichnisse für das Design jeder Plattform:
- proj.win32 - Projekt VS2017 Community Edition;
- proj.android - Android-Projekt mit Gradle;
- proj.ios - Xcode-Projekt für iOS.
Integration mit Multiplayer-Diensten
Jetzt müssen wir eine separate Schicht kleben, die für folgende Funktionen verantwortlich ist:
- Suche nach einem Gegner, Verbindung zum Spiel;
- Nachrichten zwischen Rivalen;
- Verlassen Sie das Spielzimmer.
- Festlegen von Spielerpunkten in Bestenlisten.
Sowohl iOS- als auch Android-Plattformen unterstützen Echtzeit-Multiplayer (RTMP). Bei Android integrieren wir Google Play Services (GPS), bei iOS Game Center. Zuvor unterstützte Google die Integration mit iOS, entschied sich jedoch in diesem Jahr, die Integration aufzugeben.
In diesem Artikel werde ich nicht die Aktionen beschreiben, die Sie in der Google Play Console und in AppStoreConnect ausführen müssen, um den Multiplayer zu konfigurieren. Ich werde nicht die Spezifikation von Klassen und Integrationsmethoden beschreiben - all dies wird auf Websites von Anbietern beschrieben.
Als nächstes werde ich kurz beschreiben, welche Änderungen im Projekt für jede der Plattformen vorgenommen werden müssen.
Android
Wie? Ich habe das noch nicht gesagt?
Android NDK wird zum Kompilieren von C ++ - Code verwendet. Wenn Sie ein Android-Entwickler sind, wissen Sie es bereits.
Die allgemeinen Anweisungen zum Integrieren von Google Play Services in ein Android-Projekt sind auf der Website für Android-Entwickler beschrieben. In meinem Projekt verwende ich die folgenden Abhängigkeiten:
implementation 'com.google.android.gms:play-services-games:16.0.0' implementation 'com.google.android.gms:play-services-nearby:16.0.0' implementation 'com.google.android.gms:play-services-auth:16.0.1'
Ursprünglich bestand die Idee darin, eine
C ++ - API zu verwenden , die in Form kompilierter statischer Bibliotheken ohne Quellen vorliegt. Aufgrund der Tatsache, dass die Liste der Bibliotheken keine Assembly für die x86_64-Plattform enthält, entschied ich, dass die Jungs von Google die Relevanz dieses SDK nicht wirklich überwachen, und beschloss,
ihr Fahrrad zu
erfinden , um diese Ebene in Java zu schreiben, und es mit
JNI- Wrappern zu verpacken. Und warum brauche ich dann eine zusätzliche Abhängigkeit in Form von Bibliotheken ohne Quellcode, die in Java immer noch Java ziehen? Neben der Relevanz von Java-Klassen müssen Sie auch die Relevanz dieser Bibliotheken überwachen.
Als Leitfaden habe ich ein gutes Beispiel aus
Google Samples verwendet . Vielen Dank an Google dafür. Apple, nimm ein Beispiel von Google!
iOS
Um sich in Game Center zu integrieren, müssen Sie das GameKit-Framework verbinden. Wir beschreiben die gesamte Integrationsschicht mit Game Center in einer * .m-Datei und stellen die Schnittstelle dazu über eine separate * .h-Datei bereit. Da C ++ eine Teilmenge der Ziel-C-Sprache ist, gibt es keine Probleme bei der Zusammenstellung von * .cpp- und * .m-Dateien in einem Projekt.
Neben der
offiziellen Dokumentation wurde er von diesem Projekt geleitet:
GameCenterManager . Es stimmt, einige Dinge aus dem Beispiel sind bereits veraltet. Xcode 10 wird Ihnen dies mitteilen und Sie werden die veraltete Funktionalität durch die neue ersetzen.
Das Prinzip der Arbeit mit der Multiplayer-Ebene
Einzelner Einstiegspunkt
Nachdem ich die Funktionen der Arbeit mit Multiplayer auf beiden Plattformen untersucht hatte, erstellte ich eine einzelne C ++ - Zusammenfassung für meine Anwendung, und zum Zeitpunkt der Kompilierung „passt“ die entsprechende Implementierung je nach Plattform darauf. Das heißt, meine Anwendung kennt keine Google Play-Dienste, kein Game Center und deren Funktionen. Es kennt nur die ihm zur Verfügung gestellte C ++ - API, wobei es beispielsweise Methoden gibt wie:
SignIn()
Suche nach einem Gegner
Der Spieler kann einen Freund aus der Liste seiner Kontakte einladen oder das Spiel mit einem zufälligen Gegner beginnen. Der Spieler, der die Einladung erhalten hat, kann sie annehmen oder ablehnen. Für all diese Szenarien verwende ich die Standardschnittstelle des verwendeten Dienstes. Ich möchte darauf hinweisen, dass Google-Maulkörbe viel besser aussehen iOS iOS. Vielleicht werden meine Hände eines Tages dort ankommen und ich werde meine Schnittstelle mit Dominosteinen und jungen Damen schreiben.
Verbindung zum Spielzimmer
Wenn zwei Spieler eine Verbindung zum virtuellen Spielzimmer herstellen, erhalten sie die entsprechenden Rückrufe. Jetzt müssen Sie auswählen, wer der Gastgeber sein soll.
Hostauswahl
Unter den Spielern müssen Sie einen Host auswählen, damit er den Ausgangszustand des Spiels bestimmt.
Überlegen Sie im allgemeinen Fall, wie Sie Nachrichten zwischen Spielern weiterleiten können. Bitte beachten Sie, dass in der zweiten Ausführungsform dem Host auch die Rolle eines Routers zugewiesen wird.

Da ich immer nur zwei Spieler im Spiel habe, stellt sich heraus, dass ich einen Sonderfall der Peer-to-Peer-Verbindung habe. Und deshalb fällt nur die Definition des Anfangszustands auf die Wirtsrolle, nämlich die Wahl des Wortes, aus dem die Wörter zusammengesetzt werden.
Nachdem die Spieler mit dem Spielzimmer verbunden sind, kennt jeder der Spieler die Liste der Kennungen der Teilnehmer des begonnenen Spiels. Wir nennen es eine Liste der
Teilnehmer-ID . Die Teilnehmer-ID ist eine bestimmte eindeutige Zeichenfolgen-ID des Spielteilnehmers, die vom Dienst zugewiesen wird. Sie müssen auswählen, welcher von ihnen der Host sein soll, dies zum Host selbst bringen und dem anderen mitteilen, dass sein Gegner als Host ausgewählt wurde. Wie kann man das machen?
Android Host Auswahl
Ich habe keinen Rat zur Auswahl eines Hosts in Google Dock gefunden. Sie schweigen, Partisanen. Aber die guten Leute auf
stackoverflow.com haben einen Link zum
Video geworfen , der das folgende Prinzip im Detail erklärt:
- Jeder Teilnehmer sortiert die Teilnehmer-ID-Liste (aufsteigend oder absteigend - es spielt keine Rolle, die Hauptsache ist, dass jeder in der gleichen Reihenfolge arbeitet).
- Jeder Teilnehmer vergleicht seine Teilnehmer-ID mit der ersten Teilnehmer-ID aus der Liste.
- Wenn sie übereinstimmen, hat der aktuelle Spieler das Recht zu wählen, wer der Gastgeber sein wird. Er
wirft eine Münze, zieht zufällig (), wählt dabei einen Host aus vorhandenen Teilnehmern aus und teilt jedem mit, wer der Host ist.
Hostauswahl unter iOS
Für iOS gibt es eine
ChooseBestHostPlayerWithCompletionHandler- Methode, die das
Hostauswahlszenario im Vergleich zu dem, was ich für Android beschrieben habe, erheblich vereinfacht. Anhand der spürbaren Verzögerungen beim Aufruf dieser Methode werden jedoch die Antwortparameter des Netzwerks geschätzt, der Ping gemessen und anhand dieser Statistiken entschieden, wer der Host sein soll. Dies ist wahrscheinlicher für die oben genannte Client-Server-Architektur, bei der der Host als Router fungiert. In meiner Version einer privaten Peer-to-Peer-Verbindung ist dies nicht sinnvoll. Um Zeit zu sparen, verwende ich ein ähnliches Prinzip wie für Android.
Nachrichten zwischen Spielern
Was ist eine Nachricht? Eine Nachricht ist ein Array von Bytes.
- In Java ist dies ein Typ:
byte[]
- in Ziel-C ist dies:
NSData *
- In C ++ ordne ich alle oben genannten Elemente zu
std::vector<Uint8>
Es gibt zwei Arten des Sendens von Nachrichten:
- Zuverlässig - garantierte Lieferung durch die Warteschlange. Wird verwendet, um kritische Nachrichten zu übermitteln.
- Unzuverlässig - nicht garantierte Lieferung. Verwendete Nachrichten, deren Zustellungserfolg vernachlässigt werden kann.
Unzuverlässig wird normalerweise schneller geliefert als Zuverlässig. Weitere Informationen finden Sie auf der Website des Anbieters:
Wie werden wir dieses Array verwenden? Sehr einfach:
- Im ersten Byte schreiben wir den Nachrichtentyp.
- Wenn die Nachricht Parameter enthält, werden diese in die folgenden Bytes eingefügt. Für jeden Nachrichtentyp, der hinzugefügt wurde. Parameter implementieren wir unsere Funktion der Serialisierung und Deserialisierung.
- Am Ende der Nachricht zur Überprüfung der Integrität wird eine Prüfsumme eingefügt.
Wir definieren also eine
Aufzählung mit den Arten von Nachrichten, die die Spieler während des Spiels untereinander austauschen:
- Ich werde vom Gastgeber ausgewählt. Ich übermittle den Ausgangszustand. Jetzt bin ich an der Reihe (Parameter: Versionsnummer des Messaging-Protokolls, Quellwort);
- Sie werden vom Host ausgewählt. Ich freue mich darauf, von Ihnen zu hören.
- Ich öffne (rufe) das Wort. Jetzt sind Sie dran (Parameter: benanntes Wort);
- Ich gebe auf. Du hast gewonnen;
- Ich konnte während des Umzugs kein Wort sagen. Du hast gewonnen;
- Ich bin damit einverstanden, mich zu rächen.
- Ich beende das Spiel.
- Fehler beim Parsen der Nachricht. Getrennt;
- Ihre Version des Messaging-Protokolls ist veraltet. Überprüfen Sie das Anwendungsupdate. Getrennt;
- Meine Version des Messaging-Protokolls ist veraltet. Müssen Sie das Update überprüfen. Getrennt;
- Ping (Systemnachricht);
Wenn die Anwendung eine eingehende Nachricht von einem Gegner empfängt, wird der entsprechende Rückruf aufgerufen, der sie wiederum zur Verarbeitung an den Haupt-SDL-Thread weiterleitet.
Verbindungsüberwachung
Spieledienste (die von Google, die von Apple) verfügen über Listener-Funktionen, die in der einen oder anderen Form dazu dienen, uns über eine Trennung von einem Gegner zu informieren. Ich habe jedoch festgestellt, dass, wenn einer der Spieler vom Internet getrennt ist, der zweite nicht sofort weiß, dass der erste die Verbindung getrennt hat und niemand zum Spielen da ist. Rückrufe werden in solchen Fällen nicht oder nach längerer Zeit aufgerufen. Damit in diesem Fall der zweite Spieler nicht darauf wartet, dass der Krebs auf dem Berg pfeift, musste ich die Verbindung selbst überwachen und dabei nach dem Prinzip arbeiten:
- Jeder Spieler sendet jede Sekunde eine Ping-Nachricht an den Gegner.
- Jeder Spieler prüft: Wenn der Gegner länger als 5 Sekunden keine Nachricht erhalten hat, ist die Verbindung unterbrochen und wir beenden das Spiel.
Ergebnis
Als Ergebnis der geleisteten Arbeit bekam ich ein Spiel, das ich selbst mit meinen Freunden und meiner Familie spiele. Ich spiele sowohl auf iOS als auch auf Android.
Es stimmt, es gibt eine Nuance unter iOS - aus irgendeinem Grund sind Brillen nicht in Bestenlisten festgelegt, über die ich derzeit mit dem Apple-Support korrespondiere.
Ich hoffe, dieser Artikel ist sowohl für Mitglieder meines Teams als auch für diejenigen nützlich, die an der Entwicklung mobiler Anwendungen interessiert sind. Vielen Dank für Ihre Aufmerksamkeit.