Fijamos el modo multijugador al juego móvil "Hacer palabras a partir de palabras" en iOS y Android, escrito en C ++

Anteriormente, escribí sobre mi experiencia en el desarrollo de un juego de palabras para dispositivos móviles en Android e iOS, que es muy popular, y decidí aplicar el modo multijugador cuando dos jugadores compiten entre ellos, escribiendo palabras, como la ronda final de la transmisión de Sergey Suponev. hora ".



Me llevó un mes y medio estudiar e implementar el modo multijugador, en el artículo intentaré describir el concepto sin ejemplos del código fuente, haciendo un pequeño esfuerzo por la cantidad de trabajo realizado.

Un poco de historia


La aplicación fue escrita en C ++ usando el SDK Marmalade. Desde entonces, el proveedor ha dejado de admitir esta plataforma, vendiendo el tipo a los japoneses, y el futuro de este entorno de desarrollo se ha vuelto muy vago.



Surgió la pregunta sobre qué portar proyectos actuales para su mayor apoyo.

¿Por qué no cocos2d-x?




Cocos2d-x es uno de los motores de desarrollo de juegos móviles multiplataforma C ++ más comunes. Aparentemente, debido a su código fuente gratuito y abierto. El motor está mal documentado. La descripción cubre la escasa parte del motor y la mayor parte del material está desactualizado.

Basado en los resultados de un período, todavía logré crear un prototipo de mi aplicación. Pero las impresiones fueron muy malas: parece que cocos2d-x está montado en la rodilla. Los niveles de abstracción Scene, Sprite, Application Delegate me parecieron muy incómodos, y la necesidad de buscar respuestas a las preguntas en el foro de coco lo lleva cada vez más a la idea de que está haciendo algo mal. Probablemente mis manos están saliendo del lugar equivocado.

Mi elección recayó en SDL




SDL , como Marmalade SDL, no es un motor, es una plataforma. Proporciona una API de bajo nivel, a partir de la cual construyo niveles de abstracción que son convenientes para mí. Todo esto está escrito en C, el código fuente está abierto.

En pocas palabras, el SDL es una biblioteca multiplataforma gratuita para trabajar con gráficos, sonido y procesar mensajes del sistema operativo. Es muy conveniente hacer una compilación win32 y depurar la lógica del juego en Windows, dejando que los emuladores móviles y los dispositivos físicos solo depuren la funcionalidad específica del sistema operativo.

Afortunadamente o desafortunadamente, el SDL no proporciona herramientas para una tarea tan limitada como desarrollar un multijugador para iOS y Android, por lo que tuve que integrarme con los servicios correspondientes.

Arquitectura de aplicaciones multiproceso


La lógica de la aplicación y todo el trabajo con gráficos se implementa en el hilo principal, que es un ciclo de procesamiento de mensajes y comienza en la función principal. Llame a esta secuencia SDL Thread. Otros hilos, a su vez, lanzan eventos (SDL_PushEvent) para procesarlos en la cola, y él los lee usando SDL_WaitEvent y SDL_PollEvent. Estos son eventos del sistema lanzados por el sistema y cuyo soporte ya está implementado en el SDL, o llamadas de Callbacks y Listener-s, que implementamos más allá de la funcionalidad SDL.



Toda la lógica del juego está escrita en C ++. El directorio del proyecto contiene un conjunto de archivos * .cpp que se pueden dividir en tres grupos:

  • multiplataforma: aquellos archivos que se incluyen en el ensamblaje de todas las plataformas (lógica del juego);
  • monoplataforma, es decir están incluidos en la aplicación de cualquier plataforma para implementar sus características.

En consecuencia, hay tres directorios separados para el diseño de cada plataforma:

  • proj.win32 - proyecto VS2017 Community Edition;
  • proj.android: proyecto de Android con Gradle;
  • proj.ios - Proyecto Xcode para iOS.

Integración con servicios multijugador.


Ahora tenemos que pegar una capa separada, que será responsable de funcionalidades como:

  • buscar un oponente, conexión al juego;
  • mensajería entre rivales;
  • salir de la sala de juegos;
  • arreglando puntos de jugador en tablas de clasificación.

Tanto las plataformas iOS como Android admiten el modo multijugador en tiempo real (RTMP). En el caso de Android, nos integramos con Google Play Services (GPS), en el caso de iOS, Game Center. Anteriormente, Google apoyaba la integración con iOS, pero este año decidió abandonarla.

En este artículo, no describiré las acciones que debe realizar en Google Play Console y AppStoreConnect para configurar el modo multijugador, no describiré la especificación de clases y métodos de integración; todo esto se describe en los sitios de los proveedores.

A continuación, describiré brevemente qué cambios deben realizarse en el proyecto para cada una de las plataformas.

Android


Como? ¿Aún no he dicho esto? Android NDK se usa para compilar código C ++. Aunque, si eres un desarrollador de Android, ya lo sabes.

Las instrucciones generales para integrar los servicios de Google Play en un proyecto de Android se describen en el sitio para desarrolladores de Android. En mi proyecto utilizo las siguientes dependencias:

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' 

Inicialmente, la idea era utilizar una API de C ++ , que viene en forma de bibliotecas estáticas compiladas sin fuentes. Debido al hecho de que no hay ensamblaje para la plataforma x86_64 en la lista de bibliotecas, decidí que los chicos de Google realmente no monitorean la relevancia de este SDK y decidí inventar su bicicleta para escribir esta capa en Java, envolviéndola con envoltorios JNI . Y luego, ¿por qué necesito una dependencia adicional en forma de librerías sin códigos fuente que dentro de Java todavía extraen Java? Además de la relevancia de las clases Java, también necesitará monitorear la relevancia de estas bibliotecas.

Como guía, utilicé un buen ejemplo de Google Samples . Gracias a Google por esto. Apple, toma un ejemplo de Google.

iOS


Para integrarse con Game Center, debe conectar el marco de GameKit. Describimos la capa de integración completa con Game Center en un archivo * .m y le proporcionamos la interfaz a través de un archivo * .h separado. Dado que C ++ es un subconjunto del lenguaje objetivo-C, no habrá problemas con el ensamblaje de los archivos * .cpp y * .m en un proyecto.

Además de la documentación oficial, fue guiado por este proyecto: GameCenterManager . Es cierto que algunas cosas del ejemplo ya están desactualizadas, Xcode 10 le dirá esto y reemplazará la funcionalidad desactualizada con la nueva.

El principio de trabajar con la capa multijugador.


Punto de entrada único


Habiendo estudiado las características de trabajar con el modo multijugador en ambas plataformas, creé una única abstracción de C ++ para mi aplicación y, en el momento de la compilación, la implementación correspondiente se "ajusta", dependiendo de la plataforma en particular. Es decir, mi aplicación no conoce los Servicios de Google Play, Game Center y sus funciones. Solo conoce la API de C ++ que se le proporciona, donde, por ejemplo, existen métodos como:

 SignIn() //     SignOut() //     LeaveRoom() //    SendMessage(...) //    ShowLeaderboards() //    SubmitScore(...) //    ... 

Busca un oponente


El jugador puede invitar a un amigo de la lista de sus contactos, o comenzar el juego con un oponente aleatorio. El jugador que recibió la invitación puede aceptarla o rechazarla. Para todos estos escenarios, uso la interfaz estándar del servicio utilizado. Me gustaría señalar que los bozales de Google se ven mucho mejor iOS iOS. Quizás algún día mis manos lleguen allí y escriba mi interfaz con dominó y señoritas.

Conexión a la sala de juegos.


Cuando dos jugadores se conectan a la sala de juegos virtual, reciben los Callbacks correspondientes. Ahora debe elegir quién será el anfitrión.

Selección de anfitrión


Entre los jugadores debes elegir un anfitrión, para que él determine el estado inicial del juego.
Considere posibles formas de enrutar mensajes entre jugadores en el caso general. Tenga en cuenta que en la segunda realización, al host también se le asigna la función de enrutador.



Como siempre tengo solo dos jugadores en el juego, resulta que tengo un caso especial de conexión entre pares. Y, por lo tanto, solo la definición del estado inicial recae en el rol del anfitrión, es decir, la elección de la palabra a partir de la cual se compondrán las palabras.

Entonces, después de que los jugadores se conectan a la sala de juegos, cada uno de ellos conoce la lista de identificadores de los participantes del juego que ha comenzado. Lo llamaremos una lista de ID de participante . participantID es un cierto identificador de cadena único del participante del juego, que es asignado por el servicio. Debes elegir cuál de ellos será el anfitrión, llevar esto al propio anfitrión y decirle al otro que su oponente está seleccionado como anfitrión. Como hacerlo

Selección de host de Android

No encontré ningún consejo sobre cómo elegir un host en Google Dock. Son silenciosos, partidarios. Pero las buenas personas en stackoverflow.com lanzaron un enlace al video , que explica en detalle el siguiente principio:

  • cada participante clasifica la lista de ID de participante (ascendente o descendente; no importa, lo principal es que todos lo hacen en el mismo orden);
  • cada participante compara su ID de participante con el primer ID de participante de la lista;
  • si coinciden, el jugador actual tiene derecho a elegir quién será el anfitrión. Lanza una moneda, tira al azar (), eligiendo así un anfitrión de los participantes existentes, y le dice a todos quién es el anfitrión.

Selección de host en iOS

Para iOS, hay un método chooseBestHostPlayerWithCompletionHandler , que simplifica enormemente el escenario de selección de host en comparación con lo que describí para Android. Pero a juzgar por los retrasos notables durante la llamada a este método, estima los parámetros de respuesta de la red, mide el ping y, en función de estas estadísticas, decide quién debe ser el host. Esto es más probable para la arquitectura cliente-servidor anterior, donde el host actúa como un enrutador. En mi versión de una conexión privada de igual a igual, esto no tiene sentido, y para ahorrar tiempo, utilizo un principio similar al que hice para Android.

Mensajería entre jugadores


¿Qué es un mensaje? Un mensaje es una matriz de bytes.

  • en java, este es un tipo:
     byte[] 
  • en el objetivo-C, esto es:
     NSData * 
  • en C ++, asigno todo lo anterior a
     std::vector<Uint8> 

Existen 2 tipos de envío de mensajes:

  • Fiable: entrega garantizada a través de la cola. Se usa para entregar mensajes críticos.
  • No confiable - entrega no garantizada. Mensajes usados ​​cuyo éxito de entrega puede ser descuidado.

No confiable generalmente se entrega más rápido que confiable. Puede leer más en el sitio web de proveedores:


¿Cómo usaremos esta matriz? Muy simple:

  • en el primer byte escribiremos el tipo de mensaje.
  • Si el mensaje tendrá algún parámetro, los colocaremos en los siguientes bytes. Para cada tipo de mensaje que tiene agregar. parámetros, implementamos nuestra función de serialización y deserialización.
  • Al final del mensaje para verificar la integridad, colocaremos una suma de verificación.

Entonces, definimos enum con los tipos de mensajes que los jugadores intercambiarán entre ellos durante el juego:

  • Soy seleccionado por el anfitrión. Transmito el estado inicial. Ahora es mi turno (parámetros: número de versión del protocolo de mensajería, palabra fuente);
  • Usted es seleccionado por el anfitrión. Espero tener noticias suyas;
  • Abro (llamo) la palabra. Ahora es tu turno (parámetro: palabra nombrada);
  • Me rindo Has ganado;
  • No pude pronunciar una palabra durante el movimiento. Has ganado;
  • Estoy de acuerdo en vengarme;
  • Dejé el juego;
  • Error al analizar el mensaje. Desconectado;
  • Su versión del protocolo de mensajería está desactualizada. Verifique la actualización de la aplicación. Desconectado;
  • Mi versión del protocolo de mensajería está desactualizada. Necesito verificar la actualización. Desconectado;
  • Ping (mensaje del sistema);

Cuando la aplicación recibe un mensaje entrante de un oponente, se llama a la devolución de llamada correspondiente, que a su vez lo pasa al subproceso SDL principal para su procesamiento.

Monitoreo de la conexión


Los servicios de juego (el de Google, el de Apple) tienen una funcionalidad de escucha, que de una forma u otra está diseñada para notificarnos sobre la desconexión de un oponente. Pero noté que si uno de los jugadores está desconectado de Internet, el segundo no reconoce inmediatamente que el primero se ha desconectado y no hay nadie con quien jugar. Las devoluciones de llamada no se llaman en tales casos, o se llaman después de un tiempo bastante largo. Para que en este caso el segundo jugador no espere a que el cáncer silbe en la montaña, tuve que hacer mi propio monitoreo de la conexión, trabajando según el principio:

  • Cada uno de los jugadores envía un mensaje de ping al oponente cada segundo;
  • Cada jugador comprueba: si no hubo ningún mensaje del oponente durante más de 5 segundos, entonces se pierde la conexión, salimos del juego.

Resultado


Como resultado del trabajo realizado, obtuve un juego que juego con mis amigos y familiares. Juego tanto en iOS como en Android.

Es cierto que hay un matiz en iOS: por alguna razón, las gafas no se arreglan en las tablas de clasificación, sobre las cuales actualmente estoy en correspondencia con el soporte de Apple.

Espero que este artículo sea útil tanto para los miembros de mi equipo como para aquellos que estén interesados ​​en desarrollar aplicaciones móviles. Gracias por su atencion

Source: https://habr.com/ru/post/es433172/


All Articles