Juegos con Golang. Box2d y Phaser

Golang es un lenguaje perfecto para trabajar en el mundo del backend, gracias a su velocidad de ejecución, sus librerías estáticas y su capacidad para la concurrencia.

Sin embargo, es poco considerado en el mundo del gaming, probablemente por su poco acceso a interfaces gráficas, la dificultad de crear aplicaciones mobile y la mala fama que tienen los garbage collectors en el mundo del gaming.

Tampoco vamos a obviar que entornos como Godot o Unity, donde gran parte del boilerplate se hace a golpe de click y te permiten tenerlo todo bajo control en el mismo entorno, ayudan poco a que la gente se lie con go en el mundo del gaming.

El escenario.

Existen un tipo de juegos que pueden beneficiarse mucho de un lenguaje tan rápido como Golang. Los juegos de tipo multijugador masivo que tan de moda se han puesto.

Este tipo de juegos mantienen gran parte del estado del juego en el servidor. La idea es que todo el juego se mantiene en un server centralizado que se comunica con cada uno de los clientes pasando el estado apropiado. El cliente es quien realiza la magia de hacer que todo vaya fluido y perfecto, pero siguiendo siempre las ordenes que recibe desde el server.

El ejercicio que propongo aquí se basa en un juego tipo slither.io donde unas peonzas controladas por los usuarios rebotan en un mundo que vive en el servidor. Para el motor de física hemos propongo usar el port de box2d que hicieron los amigos de ByteArena

Para renderizar, cambiaremos la foto completamente y usaremos el framework phaser y javascript.

En todo caso, como yo soy un programador de backend, estoy enfocando el problema como un server que proporciona un API a un cliente frontend. Si el rendimiento de Phaser no es adecuado, o necesitamos un cliente no html, siempre podríamos programarlo en Unity, Godot o cualquier otra cosa y seguir usando el mismo backend.

Box2D

Box2D es un motor de física en 2D que funciona verdaderamente bien. Tiene ya unos años, se programó entre el 2006 y 2008 en C++ por Erin Catto. Muchos juegos importantes la han usado y está incluido como motor de física en la mayoria de frameworks. Ha sido portada a prácticamente todos los lenguajes usados en el gaming. La versión de go no está documentada pero no es difícil moverse usando la documentación original escrita para C++. Es un producto tan bueno que apenas ha cambiado con los años.

Phaser

Phaser es un framework javascript fantástico para hacer juegos en html5 accediendo directamente a webgl, aunque también es capaz de hacer fallback a canvas.

La capacidad de acceso a WebGL permite a los navegadores usar las funciones de renderizado de la tarjeta gráfica. A pesar de que webGL no nos va a dar lo mismo que Open GL o DirectX, os aseguro que el rendimiento y la cantidad de cosas que pueden hacerse es muy alto, llegando a calidades que uno no imaginaría en un browser y javascript.

Phaser proporciona varios motores de física, y uno de ellos es, Box2D. O sea que tenemos la posibilidad de pensar en 2 mundos diferentes con el mismo motor físico.

De todos modos, apenas usaremos el motor de físicas de phaser. Las colisiones, creación o destrucción de objetos y toda la lógica del juego se realizarán en el server. Phaser se encargará sólo de dibujar. Crearemos objetos que en el server sólo son círculos, pero que en phaser se decorarán ricamente. El server irá actualizando velocidad y posición y phaser se encargará de mover los objetos “aproximadamente” al mismo lugar que dice el server.

A primera vista, usar un framework web puede parecer que nos limite mercado sólo a los navegadores. Pero siempre podemos encapsular esa “web” en un electron, por ejemplo, para tener una aplicación standalone o usar Phonegap para crear una aplicación mobile.

¿Y las comunicaciones? Websockets al rescate.

Ya tenemos el mundo virtual moviéndose en el server y un fantástico framework como Phaser para dibujarlo en el cliente. ¿Pero, como nos comunicamos?

Existe muchísima documentación en el mundo del gaming sobre como implementar comunicaciones. El gaming necesita de mucho ancho de banda y poca latencia, por lo que el consenso general es que TCP es lento y que toda implementación que se precie debe pasar por UDP y técnicas muy esotéticas de gestión de paquetes.

Yo no estoy especialmente de acuerdo en eso. Como el escenario que estamos planteando usa javascript y correrá en un browser, no nos quedan muchas alternativas de comunicación.

Podemos usar llamadas HTTP vía ajax, pero eso nos va a complicar la vida terriblemente, dado lo ineficaz del protocolo para datos pequeños. Tampoco tendríamos una manera sencilla de implementar comunicaciones a demanda del servidor, o hacer broadcasting a todos los clientes.

La solución pasan por los websockets. Estos nos permiten abrir un canal de comunicación TCP a partir de una primera llamada HTTP. La comunicación es óptima, permitiendo enviar datos binarios, y además es bidireccional, es decir, el servidor puede enviar el dato que quiera cuando quiera.

El único problema es la latencia, especialmente en redes móviles o en WIFI. En mi opinión, este es un problema con el que tendremos que vivir. Deberemos diseñar el juego de manera que una latencia de 10 o 20ms no sea un problema para la experiencia de juego.

Juegos como slither.io o agar.io están usando websockets para sus comunicaciones y su experiencia de usuario es más que correcta en la mayoría de ocasiones.

Otra ventaja de los websockets es que tanto Golang como javascript manejan muy bien el protocolo, permitiendo usar unas comunicaciones estándar que luego nos van a dar mucha flexibilidad para implementar nuevos clientes.

En un principio usaremos jsons para codificar mensajes. No es lo más óptimo, dado su elevado peso en datos binarios, pero es legible y fácil de procesar por javascript. No soy muy amigo de las early optimizations, o sea que primero llevaremos json al límite y luego buscaremos una mensajería más compacta.

Pero.. ¿Funciona?

Y tanto que funciona. No sólo eso. Tengo una versión casi implementada que pronto os enseñaré y probablemente diseccionaré en este blog.

No considero que el código esté en absoluto presentable aún, por lo que no voy a enlazarlo. Aunque los más espabilados quizás son capaces de encontrarlo en mi github, mezclado en alguno de los proyectos.


Volviendo a los origenes

Recientemente leía un artículo en meneame en el que se trataba la poca calidad del código que escribían alumnos de últimos cursos y en cierta manera abogaban por volver a trabajar en entornos donde la memoria y el tiempo de proceso eran mucho más caros que hoy.

Reconozco que el artículo me tiró un poco para atrás al principio, pero luego recordé dos o tres experiencias en mi vida que me hicieron estar bastante de acuerdo.

El jodido VAX de la Salle

Cuanto cursaba el primer curso de Telecos en la Salle Bonanova hacíamos las prácticas de programación en C, en un supermaquinón host que llamaban el VAX (ahora se que aquello no era más que un juguete grande, aunque muy caro https://es.wikipedia.org/wiki/VAX).  El tema es que teníamos que usar el editor vi por narices, no había otro. Además, estaba mal configurado el terminal y no había manera de usar las flechas de teclado. Y lo más divertido, teníamos 60 compilaciones por práctica. A partir de ese número, el sistema te penalizaba con 60 segundos, más 10 segundos extra por cada compilación que pasase de 60.

Había prácticas donde fumabas un cigarro en cada compilación. Sobra decir que te mirabas muy bien el código antes de mandar a compilar.

Al año siguiente, cuando dejé Telecos y pasé a estudiar Informática Superior en la UAB, las prácticas de programación me parecían un juego de niños.

Un ejemplo de overkilling, o como matar moscas a cañonazos

La segunda experiencia relacionada es un proceso batch que metía datos en un mysql. Un compañero (y supuesto technical lead del equipo) se empeñó en programarlo con un comando de symfony usando doctrine. Doctrine no es especialmente bueno en ese tipo de tareas, ya que, salvo que lo ajustes adecuadamente, intenta guardar en memoria todas las entidades que ha creado.

Aquel proceso duraba casi una hora y solía romperse a la mitad por falta de memoria.

Siempre pensé, en realidad estoy convencido de que era así, que aquel tech lead no usó doctrine por que creyese que era lo más óptimo, sino porque no sabía trabajar bien a nivel de SQL. Le daba tanto terror (opinión mía) que prefería implementar un mostruo en lugar de bajar de nivel y hacer las cosas fáciles, sencillas y óptimas.

El cerebro acorchado

De eso no hablaba el artículo, es una reflexión personal. Muchas veces he tenido la sensación de que llega un día en que el cerebro empieza a acorcharse. Se acomoda y no admite nuevos conocimientos. empezamos a creer que solo existe una manera de hacer las cosas y somo impermeables a nuevas ideas.

Es por eso que siempre intento estar un poco fuera de mi zona de confort. Con 25 años de carrera por delante, es muy importante aprovecharse de la experiencia adquirida, pero mucho más aún seguir con el motor en marcha y mejorar.

Para probarme y ver si ya estoy en fase de acartonamiento, me he puesto a programar lo que sería una práctica de la universidad, una implementación de árboles binarios en Golang.  Y ¿Cómo no? La he programado en Go, que es mi juguete actual y por el que estoy apostando fuerte en mi carrera profesional.

Y he de decir que he sufrido. Creo recordar que en mi época de estudiante era más rápido implementando este tipo de cosas. O quizás era menos exigente y sólo aspiraba al aprobado justito.

En próximos articulos me meteré más en explicaros este proyecto y algunos patrones que he aplicado que me parecen muy interesantes por la manera en que se usan en Golang.

 

 

Probando Docker en Windows 10

Hoy quería ponerme con un pequeño proyecto para mejorar una de mis webs. He decidido montarlo como un microservicio separado, ya que se trata de una funcionalidad fácilmente separable y me ha aparecido un pequeño problema logístico. Tengo Windows 10 instalado en casa. Generalmente virtualizo máquinas con vmware player para estas cosas, pero como en Waynabox llevamos un tiempo usando Docker  y le he cogido el gustillo, he pensado que podría probar la nueva implementación de Docker para Windows 10.

Y aquí tenéis mis primeras experiencias en lo que sería una guía para novatos, escrita por un novato.

Lo primero, la documentación oficial

Por algún lado hay que empezar, y lo mejor es leerse la página oficial de Docker al respecto, Getting Started with Docker for Windows.

Aquí encontramos algunas advertencias sobre la versión de Windows necesaria y la necesidad de activar Microsoft Hyper-V. De momento voy a ignorar estos avisos, (mi windows está a la última), espero que no explote en la cara dentro de unos minutos.

Paso 1: Instalando Docker

Lo típico, Descargar docker del enlace oficial, botón siguiente, siguiente, etc… Docker instalado y un aviso de que de Hyper-V no está instalado en mi ordenador y la pregunta de si quiero que Docker lo active por mi… Hasta ahora muy limpio todo, si señor.

hyper-v-not-installed-by-docker

Reinicio el ordenador y vuelvo en un minutos…. ¡Fantástico! Docker está instalado y tengo un icono en la bandeja del sistema que me dice que está corriendo.

De momento el invento funciona muy bien.

Paso 2: Probando Docker en Windows

Seguimos con la guía de instalación, abro una consola de sistema (Recordatorio: He de probar bash para Windows,…) y ejecuto docker run hello-word.

¡Genial! Parece que todo funciona. Ha sido tan fácil que incluso me da vergüenza estar escribiendo este tutorial.

Probaremos algo más ambicioso, descargar un ubuntu y ejecutar bash en modo interactivo dentro de ese ubuntu.

Docker se descargará una imagen de ubuntu en pocos segundos y ejecutará bash dentro de el. Hacemos un ls y en efecto, funciona, exactamente igual que en linux. Estoy corriendo en un windows un container docker que tiene un bash corriendo en ubuntu perfectamente funcional. Para cerrarlo, simplemente ejecutamos exit, lo que cerrará el bash. Al estar en modo interactivo ( parámetro -it), el contenedor de docker se cerrará. Si volvemos a lanzarlo veremos que arranca infinitamente más rápido, ya que ahora no necesita descargar la imagen de ubuntu.

¡Docker mola mucho!

Paso 3: Montando contenedores docker

Bueno.. a partir de ahora habría que seguir por lo obvio, exactamente igual que en linux, crear un archivo de configuración con los contenedores que nos interesen, compartir una unidad de disco entre el contenedor y el host si queremos tocar código de manera fácil, o exportar los logs, mapear los puertos internos y externos, etc..

De todo eso se trata en la propia guía de docker Get Started with Docker y se escapa un poco de lo que pretendía ser este artículo.

Por lo que a mi respecta, me voy a buscar una buena imagen para correr apache con php7 y posiblemente otra para un mongodb o un redis (aún no tengo claro) y crearé un docker compose que me lance las 2 y las configure adecuadamente.