< Volver 

Blocks Simulator: Move

2021-12-22

La herramienta de mover desliza un bloque sobre sus propios ejes en pasos ajustados de hasta 255 studs. Este artículo cubre la única línea que lo hace, por qué redondear hacia arriba hace asimétricos el avance y el retroceso, un bug en los campos de posición y lo que cambió la reescritura de mayo de 2022.

LuauRobloxGameplayMath
Ir a vista rápida

Una línea que mueve un bloque sobre sus propios ejes.

Resumen. La herramienta de mover (X) pone seis flechas en el bloque seleccionado, una por cara, sobre los ejes del propio bloque. Arrastrar una desliza el bloque en esa dirección en pasos de un incremento configurable, de 0.01 a 255 studs. Toda la matemática es una línea: toma el CFrame del bloque del momento en que se presionó el botón, pasa la normal de la cara arrastrada al espacio del mundo y la suma, escalada por la distancia ajustada. Este artículo explica esa línea, la asimetría que sale de ajustar con math.ceil, los tres campos de posición y un bug que mandaba los bloques rotados a otra parte cuando escribías una coordenada, lo que va al servidor y lo que cambió en la reescritura fechada el 22 de mayo de 2022, que conservó la línea y reemplazó casi todo lo demás.

1. El problema

La herramienta usa Handles en modo de movimiento. Igual que en la herramienta de escalar, reportan la cara que se arrastra y cuánto se ha movido el mouse a lo largo de su normal desde que se presionó el botón. El bloque tiene que moverse esa distancia en esa dirección, ajustada al incremento, y la dirección tiene que ser la del bloque y no la del mundo, porque una pared rotada 30 grados debería deslizarse a lo largo de sí misma.

2. La línea

bloque.CFrame = cframe + cframe:VectorToWorldSpace(Vector3.FromNormalId(lado) * (math.ceil(posicion/incrementar) * incrementar))

cframe es el CFrame del bloque guardado cuando se presionó el botón, lado la cara y posicion la distancia. Con 𝐩0 y R0 la posición y la rotación del bloque en ese momento, nˆ la normal de la cara en los ejes del propio bloque, d la distancia e i el incremento, la línea es

𝐩′=𝐩0+R0nˆi⌈di⌉

Vector3.FromNormalId da nˆ, un vector unitario sobre uno de los ejes del bloque, por ejemplo (0,1,0) para la cara de arriba. VectorToWorldSpace lo multiplica solo por la rotación, sin la traslación, y eso convierte una dirección en los ejes del bloque en la misma dirección en el mundo. Y sumarle un Vector3 a un CFrame lo mueve sin tocar su rotación, así que el bloque se desliza sobre su propio eje y sigue mirando hacia el mismo lado.

Calcular desde el estado del momento de presionar, en vez de sumar un poquito en cada evento, le da dos propiedades a la línea. El resultado depende solo de qué tan lejos está el mouse en ese instante, no de cómo llegó ahí, así que un arrastre rápido y uno lento terminan en el mismo lugar, que es justo lo que le falta a la herramienta de escalar. Y no se acumula error a lo largo de un arrastre largo.

La línea es la misma en la primera versión, la que escribí como juank, y en la reescritura.

3. Redondear hacia arriba

El ajuste usa math.ceil, así que la distancia ajustada es

q(d)=i⌈di⌉,q(d)−d∈[0,i)

Nunca va detrás del mouse y puede ir hasta un incremento entero por delante. Eso tiene un efecto visible. Apenas arrastras una flecha hacia afuera, aunque sea una centésima de stud, el bloque salta un incremento entero, porque ⌈0.01/i⌉=1. Arrastrar de vuelta hacia el bloque hace lo contrario: para d∈(−i,0] el techo es 0, así que tienes que devolverte un incremento entero antes de que el bloque se mueva. Con el 0.5 por defecto casi no se nota, pero con 50 studs el bloque salta apenas tocas la flecha.

Redondear al más cercano deja el error dentro de medio incremento hacia los dos lados:

q(d)=i⌊di+12⌋,q(d)−d∈[−i2,i2)

y es lo que hace la herramienta de rotar con su ángulo. La herramienta de construir ajusta su cuadrícula con este mismo incremento y el mismo ceil, y justo al lado tiene un i * math.round(integer) comentado, así que en algún momento sí lo consideré.

El campo de incremento acepta hasta 255 studs. Dejarlo vacío equivale a 0.5, y escribir “0” equivale a 0.01.

4. Los campos de posición

La ventana muestra la posición del bloque en tres campos, y escribir en uno debería mover el bloque a esa coordenada. Para el campo X el código hace esto:

obj.Position = Vector3.new(tonumber(x.Text), obj.CFrame.Y, obj.CFrame.Z)
local cfX = Vector3.new(tonumber(x.Text), obj.CFrame.Y, obj.CFrame.Z)
obj.Position = obj.CFrame:VectorToWorldSpace(cfX)
evento:FireServer(flechas.Adornee,flechas.Adornee.CFrame)

La primera línea está bien. La tercera toma esa posición, que es un punto en el espacio del mundo, y la pasa por VectorToWorldSpace, que la rota con la rotación del bloque alrededor del origen del mundo. En un bloque sin rotación, la rotación es la identidad y no pasa nada, que seguramente es por lo que no me di cuenta. En un bloque rotado 90° alrededor de Y en (10,5,0), escribir X = 10 lo manda a

Ry(90°)(10,5,0)=(0,5,−10)

a más de 14 studs de donde estaba. Los campos Y y Z tienen las mismas líneas. El arreglo es borrar la segunda y la tercera.

El botón de borrar (la x) que hay al lado de cada campo tiene su propio problema. Le manda al servidor CFrame.new(0, y, z), que tiene esa posición y ninguna rotación, así que borrar una coordenada también endereza el bloque en el servidor. Y como solo dispara el remote sin cambiar nada en local, el jugador no ve nada hasta que la replicación le devuelve la versión del servidor.

5. Lo que llega al servidor

Al soltar, la herramienta dispara MoveRep una vez con el bloque y su CFrame nuevo, y el servidor lo asigna después de las revisiones que describe el resumen general. Viaja el CFrame completo, doce números, cuando solo cambió la posición. Eso cuesta unos bytes, pero la rotación va con él, así que el servidor no puede terminar con otra. El servidor no revisa cuánto se movió el bloque.

6. Lo que cambió la reescritura

La nota de la reescritura dice que reemplazó la versión vieja por una “con menos errores y parcialmente hecha desde 0”. La línea de la sección 2 sobrevivió. Alrededor de ella, la primera versión era un Tool de Roblox. Equiparlo conectaba otra vez los eventos Move e Idle del mouse cada vez, y desequiparlo nunca los desconectaba, así que se iban acumulando. Disparaba el remote en cada evento de arrastre. Y tenía algo que parece un pedazo de la herramienta de construir pegado ahí: cada clic en cualquier parte conectaba un listener a la coordenada X del mouse que ponía el Position del último bloque seleccionado en la celda de la cuadrícula bajo el cursor. Después de hacer clic en cualquier lado, mover el mouse arrastraba ese bloque con él, y cada clic sumaba otro listener. La reescritura quitó todo eso, manda un solo mensaje al soltar y vive en una ventana que Transform.exe arranca y detiene.

7. Limitaciones

  • ceil en vez de redondear, de la sección 3.
  • Los campos de posición rotan la coordenada que escribes, y los botones de borrar le quitan la rotación al bloque.
  • El servidor acepta cualquier CFrame, así que un cliente modificado puede mandar un bloque a cualquier parte, con rotación y todo.

8. Lo que me dejó

La línea de la sección 2 es el mejor código de esta herramienta, y lo que la hace buena es que es absoluta: todo se calcula desde donde estaba el bloque cuando presionaste. El bug de los campos salió de usar una función que había visto funcionar sin saber qué hacía. VectorToWorldSpace rota sin trasladar, que es lo que necesita una dirección. Una posición que el jugador escribió en el espacio del mundo no necesitaba ni lo uno ni lo otro; solo había que asignarla.

Este texto de Mastedore. Todos los derechos reservados.