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:
- Revisa el atributo
Busydel personaje y se niega a empezar si está puesto, y si no, lo pone. - 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.
- 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 conAdjustSpeed(0). - Llama a
Callback:InvokeClient(plr, "Fireball", true)y espera la lista de puntos. - Reproduce la animación de lanzar, y cuando llega a 0.35 s destruye la soldadura y suelta la bola.
- Mueve la bola por los puntos, la hace explotar al contacto, limpia
Busyy 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:
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 , y en las esquinas del cubo a . 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, , daría un círculo del mismo radio en todas las direcciones.
El objetivo real, , 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 , donde es la posición de la bola en la mano, y crea esferas pequeñas, alternando entre visibles e invisibles para dibujar una línea punteada. La esfera va en
una línea recta entre la mano y el objetivo con una joroba vertical que vale cero en los dos extremos y en el medio. La separación entre puntos sobre la recta es stud.
4.2 Doblarlo
Mientras se mantiene el botón, la altura sigue el movimiento vertical del mouse desde el clic, en píxeles (positivo hacia abajo):
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 , y las dos curvas se comparan así:
con igualdad solo en los extremos y en el medio. El seno sale de la mano con una pendiente de contra los 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 contra . A la vista son casi iguales, pero el seno hace que la aceleración vertical de la bola cambie a lo largo del camino ( por unidad de 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 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 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, , y . El cliente debería mandar solo y , 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.