Ocho segundos de rayo, apuntados con la cámara.
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. Laser beam es la habilidad Legendary de Sword Masters y la más larga. Al apretar la E, la espada se encoge hasta desaparecer, el personaje saca un aparato láser de su brazo y lo monta en el pecho, y el juego espera un clic. Después el rayo se dispara durante 8 segundos. Mientras está encendido, el jugador se mueve lento, puede saltar una vez cada 3 segundos y apunta girando la cámara, porque la habilidad fuerza el shift lock y el personaje gira con ella. En cada frame, un blockcast contra el mapa mide hasta dónde llega el rayo, como mucho 135 studs, y una consulta con una caja alineada al rayo encuentra a quién toca. Después el rayo se retrae, el aparato vuelve al brazo y la espada vuelve a crecer. Es una habilidad larga en el sentido del sistema de habilidades: toda la secuencia es una sola función Load en el servidor. Este artículo recorre la secuencia fase por fase, la puntería, cómo se mide el rayo y cómo se encuentran sus golpes, el daño por segundo (que no es lo que sugiere el número configurado) y los restos.
1. El problema
Un rayo continuo tiene dos problemas que un proyectil no tiene. El primero es la puntería: el jugador tiene que poder barrer el rayo sobre un grupo de enemigos, y el servidor tiene que saber hacia dónde apunta en cada frame sin preguntarle al cliente. El segundo es que lo que se ve tiene que coincidir con lo que pega. Si en pantalla el rayo atraviesa una pared pero el daño se corta en la pared, o al revés, la habilidad se siente rota. Resolví los dos derivando todo, lo visual y el daño, de la misma pieza: el aparato en el pecho del personaje.
2. La secuencia
Load corre estas fases, revisando en cada espera si el personaje murió, en cuyo caso limpia y se va:
- Revisa y pone
Busy, bajaMoreSpeeda 0.25 y suelda el mango de la espada al brazo derecho. - Cambia las animaciones de caminar, correr, esperar y saltar del script
Animatedel personaje por las versiones del láser, guardando las originales. - Reproduce la animación de sacar el aparato y encoge la espada con
ScaleToen 20 frames, de 0.95 hasta casi 0. - Espera a que la animación llegue a 0.95 s y monta el aparato en el brazo derecho, y después espera a 1.95 s y lo pasa al pecho.
- Le pide al cliente que espere un clic (
Callback:InvokeClient(plr, "Laser beam", "getch")). - Reproduce la animación de activación, fuerza el shift lock en el cliente, baja la velocidad de caminar a 4 y, cuando la animación termina, dispara el rayo.
- Mantiene el rayo durante 8 segundos (secciones 4 y 5).
- Retrae el rayo en un segundo y restaura la velocidad, el salto y el shift lock.
- Reproduce la animación de cierre, devuelve el aparato al brazo a los 2.111 s, lo destruye a los 3.666 s y hace crecer la espada otra vez en 20 frames.
- Restaura las animaciones originales, limpia
Busyy vuelve.
Los tiempos de los pasos 4 y 9 son los puntos donde las animaciones tienen marcadores, y los comentarios todavía los nombran (LaserSwitch, LaserInTorso, LaserRips, SwordbackLaser). En vez de escuchar los marcadores, el código consulta el TimePosition de la pista en cada frame. Funciona, pero ata el código al largo exacto de cada animación: si un animador mueve un marcador, el código no se entera.
El bucle que encoge la espada tiene un detalle pequeño:
for i = 95,0,-5 do
runSvc.Heartbeat:Wait();
i += 0.01
sword:ScaleTo(i/100);
end
El 0.01 extra hace que el último paso escale la espada a 0.0001 y no a exactamente 0, así que el modelo nunca llega a una escala cero.
3. Apuntar con la cámara
El rayo sale del aparato en el pecho, y el aparato está soldado al torso, así que el rayo apunta hacia donde mira el personaje. El shift lock hace que el personaje mire en la dirección horizontal de la cámara. Forzarlo mientras dura la habilidad convierte a la cámara en el control de puntería: el jugador mueve el mouse y el rayo barre con él. El servidor no necesita preguntar nada, porque la rotación del personaje se replica sola.
El rayo se queda horizontal. El torso no se inclina hacia arriba ni hacia abajo con la cámara, así que se apunta en el giro horizontal y no en la inclinación.
4. Medir el rayo
En cada frame, el servidor lanza una caja de 1×1×1 stud [1] desde el aparato en la dirección en que mira, hasta la distancia máxima:
local ray = workspace:Blockcast(chest.CFrame, Vector3.one,
chest.CFrame.LookVector * Settings[self].MaxDistance, lib.TheParams2)
ray = ray and ray.Distance or Settings[self].MaxDistance;
laser.Out.Position = (laser.CFrame:ToWorldSpace(CFrame.new(0,0,-ray))).Position;
El blockcast solo ve el mapa (el lobby y el mapa de la ronda, con RespectCanCollide), así que los personajes no cortan el rayo. El largo del rayo es
y la pieza del extremo del rayo, Out, se coloca a studs sobre el del aparato. Usé una caja en vez de un rayo porque el rayo tiene volumen. Un raycast se colaría por rendijas de menos de un stud por las que un rayo de un stud de grosor no debería pasar, y con una caja el lanzamiento tiene el mismo grosor que el volumen de golpe de la sección 5.
5. Encontrar los golpes
Con el rayo medido, el servidor busca lo que hay dentro con una consulta de caja:
local parts = workspace:GetPartBoundsInBox(
laser.CFrame:Lerp(laser.Out.CFrame, 0.5),
Vector3.new(1, 1, (laser.Position - laser.Out.Position).Magnitude),
lib.TheParams
)
La caja está centrada a mitad de camino entre el inicio y el final, con la rotación del rayo, y mide . Es el rayo mismo como volumen: una caja orientada [2] cuyo eje largo es el rayo. Esta consulta excluye el mapa, así que solo devuelve personajes y objetos sueltos. GetPartBoundsInBox compara la caja contra la caja envolvente de cada parte y no contra su forma exacta, lo que es más barato y, para las partes cuadradas de un personaje R6, casi lo mismo.
Como las dos consultas salen del CFrame del aparato en el mismo frame, el rayo que se dibuja y el área que daña siempre coinciden: si el rayo se corta en una pared, el daño también.
6. Daño por segundo
El daño configurado es 0.4. Parece poco, y no es por segundo. El bucle recorre cada parte que devolvió la consulta y, por cada parte que pertenece a un personaje con Humanoid, aplica . Como corre en cada frame y cuenta partes y no personajes, el daño por segundo a un personaje con partes dentro del rayo es
donde es la tasa de frames del servidor. A 60 Hz:
| Partes en el rayo () | DPS | Total en 8 s |
|---|---|---|
| 1 | 24 | 192 |
| 2 | 48 | 384 |
| 3 | 72 | 576 |
| 4 | 96 | 768 |
Un rayo de un stud de grosor a la altura del pecho puede cruzar dos o tres partes de un personaje (torso, brazos, raíz). Con , el rayo mata a cualquier zombie normal (de 20 a 75 de vida) en menos de dos segundos y tumba al Crystal Guard (250) en poco más de cinco. El Victorious Zomdiator (450) sobrevive a un rayo que solo le toca dos partes. En PvP lo mismo vale para los jugadores, y fuera del PvP los jugadores se ignoran.
Hay un detalle en cómo llega el daño. El bucle espera una décima de segundo antes de cada golpe y otra después, dentro del callback de Heartbeat. Cada frame arranca una llamada nueva al callback en su propio hilo, así que las esperas no frenan el ritmo del rayo, pero sí retrasan el daño: el golpe a la -ésima parte de un frame llega segundos después, y el daño sigue llegando un momento después de que el rayo se apaga. La muerte se acredita una sola vez por personaje, con una etiqueta DeadByLaser.
7. Moverse con el rayo encendido
Mientras el rayo está encendido, la velocidad de caminar baja de 16 a 4 y las animaciones de caminar, esperar y saltar son las del láser. El primer salto se permite, y justo después la fuerza de salto pasa a cero durante 3 segundos, así que un jugador puede saltar a lo sumo una vez cada 3 segundos con el rayo encendido.
La habilidad también suelda a la raíz un cubo de 20 studs, invisible y con colisiones, toques y consultas desactivados. Pero no es Massless, así que le suma masa al ensamblaje del personaje: unidades con la densidad del plástico por defecto, contra unas 13 de un cuerpo R6 estándar. Con el rayo encendido, el personaje pesa unos cientos de veces más, y las fuerzas que lo empujan casi no lo mueven.
8. Limitaciones
El daño depende de la tasa de frames y de la cantidad de partes, dos cosas que quien ajusta la habilidad no ve en el número 0.4. Debería ser un daño por segundo multiplicado por el deltaTime del frame, aplicado una vez por personaje por frame.
Esperar dentro del callback de Heartbeat funciona, pero crea un hilo por frame que vive unas décimas de segundo, y hace que el daño llegue tarde. Las fases atadas al TimePosition dependen del largo exacto de las animaciones. Y el clic pasa por InvokeClient, con el mismo riesgo que describe el artículo del sistema de habilidades: si el cliente nunca contesta, el servidor se queda esperando con el aparato en el pecho.
9. Lo que me dejó
Derivar lo visual y el daño del mismo CFrame en el mismo frame fue lo que hizo que la habilidad se sintiera bien, y es una regla que mantuve: si dos cosas tienen que coincidir en pantalla, se calculan de la misma fuente y no de dos que se supone que concuerdan. Apuntar con la cámara mediante el shift lock también me ahorró un remote, porque el servidor lee la puntería de la rotación del personaje, que ya se replica. Lo que cambiaría es el modelo de daño, que debería ser por segundo y por personaje, y los marcadores, que deberían escucharse y no consultarse por tiempo.
Referencias
- Roblox Creator Documentation. “Raycasting” y “Spatial queries” (
WorldRoot:Blockcast,WorldRoot:GetPartBoundsInBox). - C. Ericson. Real-Time Collision Detection. Morgan Kaufmann, 2004.
Este texto de Mastedore. Todos los derechos reservados.