< Volver 

Sword Masters: Fireball

2024-09-15

Fireball crece en tu mano, te deja elegir un punto a menos de 75 studs y doblar el arco arrastrando el mouse, y después vuela por ese arco un punto por frame hasta explotar contra el mapa con 80 de daño.

LuauRobloxGameplayPhysicsMath
Ir a vista rápida

Un arco que doblas con el mouse.

Advertencia

Sword Masters quedó abandonado en octubre de 2024, y la mayoría de sus animaciones, sonidos e imágenes son del grupo dueño del juego, así que no se puede correr como antes y este sistema no se puede mostrar en video.

Resumen. Fireball es la habilidad Epic de Sword Masters. Al apretar la E, una bola de fuego crece en la mano izquierda del personaje. Un disco verde sigue al mouse por el suelo para marcar el objetivo. Haces clic y mantienes para fijarlo, arrastras el mouse hacia arriba o hacia abajo para que el arco sea más alto o más plano, y sueltas para lanzar. La bola vuela por el arco y explota al tocar el mapa, con 80 de daño en una esfera. Es una habilidad larga en el sentido del sistema de habilidades: el servidor la corre de principio a fin dentro de Load y, a la mitad, le pide la trayectoria al cliente con un RemoteFunction. El cliente calcula el arco como una lista de puntos separados por más o menos un stud, y el servidor mueve la bola por esa lista con un AlignPosition, un punto por frame. Este artículo recorre la secuencia, la forma del área alcanzable, la fórmula del arco y cómo se compara con una parábola, el vuelo, la explosión y la confianza que el servidor pone en el cliente.

1. El problema

Un proyectil lanzado necesita una trayectoria que el jugador pueda ver antes de lanzar y pueda moldear. La versión obvia es la física: darle una velocidad a la bola y dejar que la gravedad dibuje la parábola. Eso le da al jugador poco control sobre dónde cae, y predecir el punto de caída en el cliente para dibujar una guía obliga a simular la misma física en los dos lados. Preferí definir la trayectoria como una curva explícita entre la mano y el objetivo. El jugador elige el objetivo y la altura, la guía que se dibuja en pantalla es la curva misma, y la bola sigue exactamente lo que el jugador vio.

2. La secuencia

El servidor hace esto, en orden:

  1. Revisa el atributo Busy del personaje y se niega a empezar si está puesto, y si no, lo pone.
  2. Clona la bola de la plantilla del módulo, la suelda a la mano izquierda y la hace crecer de 0.1 studs a su tamaño completo en un segundo, mientras reproduce las animaciones de invocación y de espera.
  3. Conecta Bring. Cuando el cliente avisa que el jugador hizo clic, el servidor reproduce la animación de apuntar y la congela en 4.81 s, sosteniendo el brazo atrás con AdjustSpeed(0).
  4. Llama a Callback:InvokeClient(plr, "Fireball", true) y espera la lista de puntos.
  5. Reproduce la animación de lanzar, y cuando llega a 0.35 s destruye la soldadura y suelta la bola.
  6. Mueve la bola por los puntos, la hace explotar al contacto, limpia Busy y vuelve.

El cliente hace su parte mientras el servidor espera en el paso 4. Espera a que el personaje esté en el suelo, apunta, espera el clic, dispara Bring, gira al personaje hacia el objetivo y lo ancla, dibuja el arco, deja que el jugador lo doble y devuelve los puntos cuando se suelta el botón o a los 3 segundos, lo que pase primero.

3. Apuntar

En cada frame, el cliente lanza un rayo desde la cámara a través del mouse y pone el disco donde pega, limitando cada coordenada a 75 studs (MaxRadius) de la raíz del personaje:

Dk=clamp⁡(rk, hk−75, hk+75),k∈{x,y,z}

El límite es por eje, así que el área alcanzable no es una esfera de radio 75 sino un cubo de 150 studs de lado centrado en el jugador. A lo largo de los ejes el alcance es de 75 studs; en las diagonales del suelo llega a 752≈106, y en las esquinas del cubo a 753≈130. Un jugador que apunta en diagonal a los ejes del mundo lanza más lejos que uno que apunta justo a lo largo de uno. Limitar la distancia, D=h+min⁡(1,75/‖r−h‖)(r−h), daría un círculo del mismo radio en todas las direcciones.

El objetivo real, D, está 3 studs encima del disco, así que la bola llega a la altura del torso de un personaje y no al suelo.

4. El arco

4.1 La fórmula

Cuando el jugador hace clic, el cliente cuenta n=⌈‖D−B‖⌉, donde B es la posición de la bola en la mano, y crea n esferas pequeñas, alternando entre visibles e invisibles para dibujar una línea punteada. La esfera i va en

pi=(1−si)B+siD+Hsin⁡(πsi)yˆ,si=in

una línea recta entre la mano y el objetivo con una joroba vertical que vale cero en los dos extremos y H en el medio. La separación entre puntos sobre la recta es ‖D−B‖/n≤1 stud.

4.2 Doblarlo

Mientras se mantiene el botón, la altura sigue el movimiento vertical del mouse desde el clic, Δy en píxeles (positivo hacia abajo):

H(Δy)=clamp⁡(|6−Δy9|, 0, 36)

Empieza en 6 studs. Subir el mouse 270 píxeles la lleva al techo de 36 studs, y bajarlo 54 píxeles la aplana a 0. Más abajo, el valor absoluto la hace subir otra vez, así que la función es una V y no un deslizador: arrastrar muy abajo termina haciendo el arco alto de nuevo. No es lo que diseñaría hoy.

4.3 No es una parábola

La joroba es medio seno, no la parábola que dibujaría la gravedad. Con el mismo vértice, la parábola es 4Hs(1−s), y las dos curvas se comparan así:

sin⁡(πs)≤4s(1−s)para s∈[0,1]

con igualdad solo en los extremos y en el medio. El seno sale de la mano con una pendiente de πH contra los 4H de la parábola, un 21% menos, y se queda un poco por debajo de la parábola en todo el recorrido. Su área es 2H/π≈0.637H contra 2H/3≈0.667H. A la vista son casi iguales, pero el seno hace que la aceleración vertical de la bola cambie a lo largo del camino (−π2Hsin⁡(πs) por unidad de s al cuadrado, cero en los extremos) en vez de ser constante como la de la gravedad. Para una bola de fuego mágica se ve bien. Para una piedra se vería raro.

5. El vuelo

WaypointMove es un AlignPosition enganchado a la bola. El servidor le pone como objetivo el primer punto y después avanza un punto por frame:

while true do
	if not fireball then break; end;
	pcall(function() fireball:SetNetworkOwner(plr) end)
	position = math.clamp(position + 1, 1, #positions)
	if position%2 then runSvc.Heartbeat:Wait(); end
	ao.Position = positions[position];
	if position == #positions then break; end;
end

El objetivo se mueve, entonces, a más o menos un stud por frame, 60 studs por segundo a 60 Hz, y el lanzamiento dura n/60 segundos: un poco más de un segundo para un tiro de 75 studs. La bola sigue al objetivo con el retraso propio del constraint. Darle la propiedad de red al que lanza hace que su cliente simule la bola, así que la ve volar suave.

position%2 parece un intento de esperar solo en los puntos impares, lo que habría duplicado la velocidad. En Luau el 0 es verdadero, así que la condición siempre se cumple y el bucle espera en cada punto.

6. La explosión

Mientras vuela, una conexión en Heartbeat revisa con qué partes del mapa se superpone la bola, con GetPartsInPart. Cuando una de ellas pertenece a la geometría destructible (dentro de una carpeta Destroyables o con esa etiqueta o atributo), la bola explota donde está. Si el camino termina sin tocar nada, el servidor destruye el AlignPosition y la bola cae por la gravedad hasta tocar el mapa y explotar ahí.

La explosión crea una parte con las partículas del efecto, que Debris borra a los 20 s, y una parte esférica que sirve como volumen de daño. GetPartsInPart con una esfera usa la forma real, así que el área de daño es exactamente una esfera. Su tamaño sale de DamageAreaRadius, 33, pero Size es un diámetro, así que el radio real es de 16.5 studs. Cada personaje dentro recibe 80×MoreDamage una sola vez. En PvP eso incluye a otros jugadores. Fuera del PvP solo incluye NPCs. Una etiqueta con un sufijo al azar (Burned_ más un número del 0 al 3690) marca a los personajes que ya recibieron el golpe, para que la misma explosión no dañe a nadie dos veces. Una décima de segundo después de cada golpe, el servidor revisa si el objetivo murió y, si es así, acredita la muerte.

7. Limitaciones

La más grande es la confianza. El servidor toma la lista de puntos que devuelve el cliente y hace volar la bola por ella sin revisar nada: ni la cantidad de puntos, ni la separación, ni que el objetivo esté a menos de 75 studs. Un cliente modificado puede devolver un camino que termine en cualquier parte del mapa. El arreglo es barato, porque el arco queda determinado por tres cosas, B, D y H. El cliente debería mandar solo D y H, y el servidor debería limitarlos y recalcular los puntos. Lo mismo vale para todo lo que el artículo del sistema de habilidades dice de InvokeClient: si el cliente no contesta, Load espera para siempre con la bola en la mano.

La revisión de la explosión espera dentro de un callback de Heartbeat (espera un frame antes de emitir las partículas y una décima de segundo por golpe), y cada frame arranca una llamada nueva a ese callback. Hasta que la primera explosión termina y destruye la bola, los frames siguientes pueden detectar el mismo contacto y explotar de nuevo. La etiqueta evita que el daño se repita, pero la ráfaga de partículas sí se repite. Las etiquetas tampoco se borran nunca, y con 3 691 sufijos posibles, dos bolas de fuego pueden compartir uno.

Algunos nombres no dicen lo que hacen: MaxRadius es el medio lado de un cubo, DamageAreaRadius es un diámetro, y los 3 segundos de MaxChargeTime en los datos no se leen, porque el cliente tiene el 3 escrito a mano.

8. Lo que me dejó

Definir la trayectoria como una curva explícita fue la decisión correcta para esta habilidad: lo que ves es lo que pasa, y la fórmula cabe en una línea. Lo que cambiaría es dónde vive la curva. Si el servidor la calcula a partir de un objetivo y una altura, el cliente solo tiene que proponer esos dos números, el servidor puede validarlos con una línea cada uno, y InvokeClient desaparece. El bug de position%2 me enseñó a revisar qué es verdadero con las reglas del lenguaje y no con las que tengo en la cabeza de otros lenguajes.

Este texto de Mastedore. Todos los derechos reservados.