< Volver 

Blocks Simulator

2021-04-11

Sandbox de construcción en Roblox que empecé en 2021, a los 12 años, con herramientas para poner, escalar, mover, rotar, pintar y borrar bloques. Esta página explica cómo llega la petición de una herramienta del cliente al servidor y enlaza al artículo técnico de cada una.

LuauRobloxGameplayUINetworking
Ir a vista rápida

Herramientas de construcción que escribí a los doce.

Nota

Es código de 2021 y 2022, y no lo voy a mejorar. Los artículos lo describen tal como está, con bugs incluidos, porque la gracia es leerlo con lo que sé ahora.

Resumen. Blocks Simulator es un sandbox de construcción en Roblox. Entras, pones bloques y les das forma con seis herramientas, cada una en su tecla: Q construye, E borra, Z escala, X mueve, C rota y F pinta. Lo empecé en 2021, cuando tenía 12 años, y seguí reescribiendo las herramientas hasta 2022. Las primeras versiones eran Tools de Roblox que firmé como juank. Las que quedaron en el juego son ventanas que se abren desde la barra de arriba, firmadas como SolarDev, y dos de ellas tienen una nota fechada en mayo de 2022 que dice que reemplazaron a las viejas. Por debajo hay un solo script de servidor que recibe la petición de cada herramienta, la revisa, la aplica y contesta cada rechazo con un código de error en hexadecimal. Esta página es un índice. Describe cómo está organizado el juego y cómo viaja una petición desde una herramienta hasta el servidor, y enlaza a un artículo por herramienta donde explico la matemática, que fue lo que más me costó en su momento.

Una demostración rapida de todas las herramientas de Blocks Simulator

1. Mi rol

Lo hice solo: las herramientas, sus ventanas, la parte del servidor y un framework pequeño mío, SolarFramework, con un módulo de matemática (Tena) y otro de permisos por bloque (Solar3). Los primeros scripts empiezan con “script hecho por juank” y los más nuevos con “Made by SolarDev”. Los dos soy yo, con los nombres que usaba en esa época.

No todo lo que hay en el place es mío. Los íconos de la barra de arriba usan TopbarPlus, las ventanas se arrastran con el DraggableObject de Spynaz, y los comandos de admin, el reproductor de música y casi todos los gear salieron de la comunidad. Nada de eso se cubre aquí.

2. Armado como un sistema operativo

En algún momento decidí que el juego tenía que parecer un computador. Cada herramienta es un ScreenGui con nombre de ejecutable (new.exe, Transform.exe, Color.exe, Delete.exe), con un Main.lua como punto de entrada, y cada sección de una ventana es un frame cuyo nombre termina en .dll. Un BindableFunction es un .cmd, un BindableEvent es Signal.js, y cada jugador tiene en el Workspace una carpeta llamada NombreDelJugador.reg, como un registro, donde quedan sus bloques y algunos ajustes. Si new.exe arranca sin su ventana, lanza “This program cannot be run on free mode”, un guiño al “This program cannot be run in DOS mode” que traen los ejecutables de Windows para quien los corra en DOS. Transform.exe incluso trae el texto completo de la licencia Apache 2.0 dentro de un ModuleScript.

La metáfora tuvo una consecuencia útil. Cada programa tiene un ED.lua (“Enable/Disable Manager”) que lo prende y lo apaga desde su ícono o su tecla, y prender o apagar una herramienta es activar o desactivar su Main.lua. Desactivar un LocalScript que está corriendo lo detiene y suelta todas sus conexiones, y activarlo lo corre otra vez desde arriba, así que una herramienta se comporta de verdad como un proceso que arranca y muere. Transform.exe hace esto con tres herramientas a la vez y las mantiene excluyentes: abrir Scale detiene Move y Rotate y limpia sus cajas de selección. TopbarPlus dispara su evento selected incluso cuando es el propio código el que selecciona un ícono, así que cada ED.lua protege sus handlers con una bandera para que el toggle no se llame a sí mismo.

3. De la herramienta al servidor

3.1 Vista previa en el cliente, un solo envío

Las herramientas de transformación trabajan primero en el cliente. Mientras arrastras un handle, la herramienta cambia el Size y el CFrame del bloque localmente, así que el bloque sigue al mouse sin ningún retraso de red. Solo cuando sueltas el botón manda un mensaje con el estado final. El servidor lo aplica, y luego la replicación sobrescribe la copia local del cliente con el valor del servidor, que es el mismo. Las primeras versiones lo hacían al revés y disparaban el remote en cada evento de arrastre, que mientras el mouse se mueve es más o menos una vez por frame.

3.2 Una tabla, siete remotes

En el servidor, todo pasa por un solo script, ReplicateMgr.lua. Su núcleo es una tabla que asocia un nombre con un remote:

local Runtimes = {
	["Print"]	=	RemoteControllers.PrintToClient;
	["Build"]	=	RemoteControllers.PlaceRep;
	["Color"]	=	RemoteControllers.ColorRep;
	["Delete"]	=	RemoteControllers.DestroyRep;
	["Scale"]	=	RemoteControllers.ScaleRep;
	["Move"]	=	RemoteControllers.MoveRep;
	["Rotate"]	=	RemoteControllers.RotateRep;
}

y cada handler de ahí para abajo cuelga de Runtimes.Build, Runtimes.Scale y así. Build y Delete son RemoteFunctions porque el cliente espera una respuesta. Delete, por ejemplo, destruye el bloque localmente cuando el servidor dice que allá no existe, lo que limpia partes que solo vivían en el cliente. Los otros cuatro son RemoteEvents: se disparan y ya.

Herramienta Remote Qué manda el cliente Códigos de error
Build PlaceRep, función posición, anclado, protegido, material, forma, tipo, transparencia, reflectancia ninguno
Delete DestroyRep, función la parte 0xBA, 0xBB
Scale ScaleRep, evento la parte, su tamaño y su CFrame 0xCA a 0xCD
Move MoveRep, evento la parte y su CFrame 0xDA a 0xDC
Rotate RotateRep, evento la parte y su orientación 0xEA a 0xEC
Color ColorRep, evento la parte y un Color3 0xFA, 0xFB

Cada handler sube la misma escalera de revisiones. Primero, que llegaron los argumentos. Después, que el objetivo es un BasePart, que no está Locked y que no es parte de un personaje (Delete además rechaza la baseplate). Por último, que el valor tiene el tipo correcto, con typeof. Solo entonces asigna la propiedad. Cada escalón que falla le contesta al cliente por PrintToClient con un nivel de gravedad (0 una advertencia, 1 una excepción no manejada, 2 una fatal), un código y un mensaje. Los códigos siguen una regla que dejé en un comentario del cliente, 0xQW, donde Q es el índice del programa y W el del error. 0xCB es Scale (C) rechazando su objetivo (B), y 0xDA es Move (D) con un argumento faltante (A).

Lo que todavía me gusta es que es uniforme. Las herramientas hablan un mismo protocolo, cada rechazo se puede rastrear hasta una línea de código por su código, y agregar una herramienta es agregar una fila a la tabla y un handler que suba la misma escalera.

3.3 Lo que cuesta

También es ineficiente y confía demasiado, y las dos cosas se ven fácil ahora.

  • Los errores viajan por InvokeClient, un RemoteFunction que el servidor llama en el cliente. El hilo del servidor espera hasta que el cliente responda, así que un cliente que nunca contesta deja ese hilo esperando para siempre, y un cliente que tira un error lo tira dentro del handler del servidor. Todo eso para imprimir una línea, que un RemoteEvent habría impreso sin esperar a nadie.
  • El servidor revisa tipos, no valores. Scale acepta cualquier tamaño y cualquier CFrame, así que un cliente modificado puede estirar un bloque hasta el límite de tamaño o mandarlo al otro lado del mapa, y nada limita qué tan seguido dispara un cliente.
  • La protección nunca se aplica. La ventana de Build tiene una opción Protected, descrita como que tus construcciones queden “unalterable by other players”. Solar3 guarda el dueño en el bloque, pero ningún handler lo lee, así que cualquiera puede mover, pintar o borrar los bloques de cualquiera. Encima, Solar3.New pone todos los atributos de permiso del bloque en “permitido”.
  • Guardar ese dueño cuesta una petición web por bloque, porque Solar3 busca el UserId del jugador con GetUserIdFromNameAsync(player.Name) cuando player.UserId estaba ahí mismo.
  • Siete remotes y seis handlers, cuatro de ellos casi idénticos. Un solo remote que lleve el nombre de la herramienta, con la escalera escrita una vez, habría sido el mismo dispatcher con una fracción del código.

4. Las herramientas

  • Build. Una ventana de ajustes cuyo estado vive en los atributos de un BindableFunction, una vista previa en 3D que cambia la clase de su parte cuando cambias el tipo, y un bloque fantasma que se ajusta a la cuadrícula contra la cara que apuntas. El artículo explica por qué esa regla queda al ras en unas caras y se mete en otras cuando los bloques salen de la cuadrícula.
  • Scale. Arrastrar una cara hace crecer el bloque solo hacia esa cara, rotado o no, corriéndolo la mitad de lo que creció. Cubre los seis vectores de dirección, una fórmula de ajuste que mezcla unidades y solo se porta bien si arrastras despacio, y la primera versión, que encogía el bloque cuando jalabas tres de sus seis caras.
  • Move. Una línea mueve el bloque sobre sus propios ejes en pasos ajustados. El artículo cubre por qué redondear hacia arriba hace que avanzar y retroceder sean asimétricos, y un bug que mandaba los bloques rotados a otra parte cuando escribías una coordenada.
  • Rotate. Arcos que giran el bloque alrededor de sus propios ejes y lo ajustan al incremento más cercano. Al servidor solo llegan tres números, y los tres campos de ángulo de la ventana resultan ser tres tipos distintos de rotación.
  • Color. Una rueda de color hecha con un ángulo y una distancia, campos RGB, HSV y hexadecimal sincronizados, un cuentagotas, y un slider de valor implementado como un tinte sobre la imagen de la rueda, que es exacto por cómo está definido HSV.

Delete no tiene artículo propio. Resalta el bloque que está bajo el mouse, le pide al servidor que lo destruya y, si el servidor contesta que allá no existe, destruye la copia local.

5. Lo que me dejó

Lo que conservaría es la forma del sistema: herramientas que muestran el cambio en local y lo mandan una sola vez, y un servidor con un solo protocolo y códigos de error legibles. Lo que cambiaría es dónde está la confianza. Cada handler revisa que los datos tengan el tipo correcto y ninguno revisa que tengan sentido, y la única función que prometía protección nunca corrió. La matemática es la parte de la que sigo orgulloso. Hacer que un bloque creciera desde una cara y girara sobre sus propios ejes me tomó mucho tiempo, con hilos de foros abiertos, y leyéndolo ahora distingo qué partes entendí y cuáles ajusté hasta que funcionaron.

Este texto de Mastedore. Todos los derechos reservados.