Implementar el comportamiento del blaster es el proceso de programar una mecánica de explosión en juegos de disparos en primera persona. Mientras que los jugadores pueden disparar con un solo clic o pulsación de un botón, crear un comportamiento de explosión satisfactorio y preciso es importante porque mejora el disfrute de los jugadores en el juego en general.
Usando el juego de láser tag de muestra como referencia, esta sección del tutorial te enseña sobre los scripts detrás de la implementación del comportamiento del blaster para dos tipos diferentes de blasters, incluyendo orientación sobre:
- Detectar cuándo los jugadores presionan el botón de explosión.
- Verificar si el jugador puede usar su blaster si recientemente presionó el botón de explosión.
- Generar datos de explosión que le dicen al servidor quién inició la explosión, de dónde provino y cuál fue el destino final de cada rayo láser.
- Notificar al servidor sobre los datos de explosión para que pueda realizar las acciones apropiadas si la explosión colisionó con otro jugador.
- Reiniciar el blaster entre cada explosión para darle al blaster suficiente tiempo para enfriarse antes de que pueda disparar nuevamente.
Después de completar esta sección, aprenderás sobre los scripts que permiten al blaster detectar cuándo sus explosiones colisionan con otros jugadores, y luego deducir la cantidad correspondiente de salud según cada tipo de blaster.
Detectar la entrada del jugador
El primer paso para implementar el comportamiento del blaster es escuchar cuándo un jugador presiona el botón de explosión. El tipo de entrada que los jugadores utilizan para presionar el botón de explosión depende del dispositivo que están usando para acceder al juego. Por ejemplo, el juego de láser tag de muestra admite controles de mouse y teclado, gamepads y controles táctiles. Puedes ver cada uno de estos tipos de entrada en ReplicatedStorage ⟩ UserInputHandler.
Este script del cliente utiliza ContextActionService para vincular MouseButton1 y ButtonR2 a la acción de explosión. Esto significa que cada vez que un jugador presiona el botón izquierdo del mouse o el botón R2 de un gamepad, se activa un rayo láser que sale del blaster. Ten en cuenta que el HUDGui contiene un botón para disparar en dispositivos móviles, que se conecta más adelante en el script.
ContextActionService:BindAction("_", onBlasterActivated, false,
Enum.UserInputType.MouseButton1,
Enum.KeyCode.ButtonR2
)Otra nota importante es el uso de Enum.UserInputState.Begin en la definición de onBlasterActivated(). Muchas interacciones de la interfaz de usuario, como elegir un blaster en este ejemplo, no ocurren hasta que el botón del mouse se suelta (Enum.UserInputState.End), lo que le da a los usuarios una última oportunidad para evitar la interacción. Sin embargo, una mecánica de explosión no se siente receptiva a menos que ocurra en el instante en que se presiona el botón.
Para demostrar, puedes cambiar Enum.UserInputState.Begin a Enum.UserInputState.End, luego probar el juego para ver cómo la capacidad de respuesta de la explosión impacta en la jugabilidad del juego. Por ejemplo, si los jugadores pueden mantener presionado el botón sin activar la explosión, ¿cómo podría eso cambiar su experiencia al etiquetar a otros jugadores?
local function onBlasterActivated(_actionName: string,
inputState: Enum.UserInputState, _inputObject: InputObject)
if inputState == Enum.UserInputState.End then -- línea actualizada, asegúrate de cambiar de nuevo
attemptBlastClient()
end
endVerificar si el jugador puede disparar
Después de que UserInputHandler detecta una pulsación de botón o un toque en la pantalla, llama a ReplicatedStorage ⟩ Blaster ⟩ attemptBlastClient para verificar si el jugador puede disparar o no. Al igual que la mayoría de las verificaciones en el juego de láser tag de muestra, ocurre dos veces: primero en el cliente, y luego más tarde en el servidor. attemptBlastClient luego llama a ReplicatedStorage ⟩ Blaster ⟩ canLocalPlayerBlast para realizar una verificación simple del atributo del jugador blasterStateClient:
local function canLocalPlayerBlast(): boolean
return localPlayer:GetAttribute(PlayerAttribute.blasterStateClient) == BlasterState.Ready
endSi examinas ReplicatedStorage ⟩ Blaster ⟩ BlasterState, puedes ver que el juego tiene tres estados de blaster: Ready, Blasting, y Disabled. Para ver el efecto de cada uno de estos estados, puedes probar el juego, seleccionar tu jugador en el servicio Players, y luego observar el atributo blasterStateClient en la ventana de Properties. Nota cómo muestra Disabled mientras eliges tu blaster, Ready la mayor parte del tiempo, y Blasting durante menos de un segundo después de presionar el botón.
Esta ligera pausa te impide disparar tan rápido como puedes hacer clic. Por ejemplo, si cambias la función para que siempre devuelva verdadero, puedes disparar rápidamente tu blaster sin ningún retraso, lo cual es poco realista para la jugabilidad de láser tag.
local function canLocalPlayerBlast(): boolean
return true -- línea actualizada, asegúrate de cambiar de nuevo
endGenerar datos de explosión
Después de verificar que el blaster del jugador está en el estado Ready, attemptBlastClient llama a ReplicatedStorage ⟩ attemptBlastClient ⟩ blastClient. El primer paso que toma blastClient es establecer el atributo del jugador blasterStateClient a Blasting, lo que evita el mismo caso de disparo rápido de antes.
El siguiente paso es generar los datos de explosión. Si revisas ReplicatedStorage ⟩ Blaster ⟩ BlastData, puedes ver que cada explosión consiste en tres piezas de información:
- El jugador que inicia la explosión.
- Un DataType.CFrame que representa el punto de origen de la explosión.
- Una tabla RayResult que contiene el destino final de cada rayo láser y el jugador golpeado, si golpeó a otro jugador.
Para generar estos datos, blastClient llama a ReplicatedStorage ⟩ attemptBlastClient ⟩ blastClient ⟩ generateBlastData, que puedes revisar a continuación.
local function generateBlastData(): BlastData.Type
local blasterConfig = getBlasterConfig()
local rayDirections = getDirectionsForBlast(
currentCamera.CFrame, blasterConfig)
local rayResults = castLaserRay(
localPlayer, currentCamera.CFrame.Position, rayDirections)
local blastData: BlastData.Type = {
player = localPlayer,
originCFrame = currentCamera.CFrame,
rayResults = rayResults,
}
return blastData
endEsta función comienza utilizando getBlasterConfig para recuperar el tipo de blaster del jugador. La muestra proporciona dos tipos de blasters: uno que produce varios rayos con una dispersión horizontal amplia, y otro que produce un solo rayo. Puedes encontrar sus configuraciones en ReplicatedStorage ⟩ Instances ⟩ LaserBlastersFolder.
La función luego utiliza currentCamera.CFrame como el punto de origen para la explosión, pasándolo a getDirectionsForBlast. En este punto, el código ya no se trata del blaster, se trata del rayo láser, sobre el cual aprenderás más en la sección de detectar impactos del tutorial. Finalmente, después de crear la tabla rayResults, generateBlastData tiene toda la información que necesita para devolver los datos de explosión a blastClient.
Notificar al servidor
Una vez que blastClient tiene datos completos para la explosión, dispara dos eventos:
local laserBlastedBindableEvent = ReplicatedStorage.Instances.LaserBlastedBindableEvent
local laserBlastedEvent = ReplicatedStorage.Instances.LaserBlastedEvent
laserBlastedBindableEvent:Fire(blastData)
laserBlastedEvent:FireServer(blastData)El BindableEvent notifica a otros scripts del cliente sobre la explosión. Por ejemplo, ReplicatedStorage ⟩ FirstPersonBlasterVisuals utiliza este evento para saber cuándo mostrar efectos visuales, como la animación de explosión y la barra de enfriamiento. De manera similar, el RemoteEvent notifica a los scripts del servidor sobre la explosión, que comienza a procesar la explosión en ServerScriptService ⟩ LaserBlastHandler.
local function onLaserBlastedEvent(playerBlasted: Player, blastData: BlastData.Type)
local validatedBlastData = getValidatedBlastData(playerBlasted, blastData)
if not validatedBlastData then
return
end
if not canPlayerBlast(playerBlasted) then
return
end
blastServer(playerBlasted)
processTaggedPlayers(playerBlasted, blastData)
for _, replicateToPlayer in Players:GetPlayers() do
if playerBlasted == replicateToPlayer then
continue
end
replicateBlastEvent:FireClient(replicateToPlayer, playerBlasted, blastData)
end
endPara ayudar a prevenir trampas, el servidor debe verificar todos los datos que cada cliente envía. Estas verificaciones incluyen:
- ¿Es BlastData una tabla? ¿Contiene un Class.CFrame y otra tabla llamada rayResults?
- ¿El jugador tiene un blaster equipado?
- ¿El jugador tiene un personaje y una ubicación dentro del mundo?
- Después de enviar los datos de explosión, ¿se ha movido el jugador a una distancia excesiva de donde disparó el rayo láser?
Esta última verificación implica un juicio, y de acuerdo con la latencia del servidor y la velocidad de movimiento del jugador, podrías decidir que diferentes valores son excesivos para tu propio juego. Para demostrar cómo hacer este juicio, puedes tener una idea de la magnitud típica del cambio posicional añadiendo una declaración de impresión en getValidatedBlastData y probando el juego.
local distanceFromCharacterToOrigin = blastData.originCFrame.Position - rootPartCFrame.Position
print(distanceFromCharacterToOrigin.Magnitude) -- línea actualizada, asegúrate de eliminar
if distanceFromCharacterToOrigin.Magnitude > ToleranceValues.DISTANCE_SANITY_CHECK_TOLERANCE_STUDS then
warn(`Player {player.Name} failed an origin sanity check while blasting`)
return
endA medida que te mueves y disparas, nota la salida. Podría verse algo así:
1.9019629955291748
3.1549558639526367
2.5742883682250977
4.8044586181640625
2.6434271335601807Si aumentas la velocidad de movimiento para los jugadores en ReplicatedStorage ⟩ PlayerStateHandler ⟩ togglePlayerMovement, entonces prueba el juego nuevamente, probablemente encontrarás muchas verificaciones fallidas debido a un movimiento excesivo entre explosiones.
local ENABLED_WALK_SPEED = 60 -- línea actualizada, asegúrate de cambiar de nuevoEl servidor luego hace lo siguiente:
- Valida rayResults.
- Verifica si el jugador puede disparar.
- Reinicia el estado del blaster.
- Reduce la salud de cualquier jugador etiquetado.
- Replica la explosión a todos los demás jugadores para que puedan ver efectos visuales en tercera persona.
Para más información sobre estas operaciones del servidor, consulta la sección de detectar impactos del tutorial.
Reiniciar el blaster
En el juego de láser tag de muestra, los blasters utilizan una mecánica de calor. En lugar de recargar después de un número establecido de explosiones, necesitan tiempo para "enfriarse" entre cada explosión. Este mismo retraso de enfriamiento ocurre tanto en el cliente (blastClient) como en el servidor (blastServer), siendo el servidor la fuente de verdad.
local blasterConfig = getBlasterConfig(player)
local secondsBetweenBlasts = blasterConfig:GetAttribute("secondsBetweenBlasts")
task.delay(secondsBetweenBlasts, function()
local currentState = player:GetAttribute(PlayerAttribute.blasterStateServer)
if currentState == BlasterState.Blasting then
player:SetAttribute(PlayerAttribute.blasterStateServer, BlasterState.Ready)
end
end)El atributo secondsBetweenBlasts es parte de la configuración del blaster en ReplicatedStorage ⟩ Instances ⟩ LaserBlastersFolder. Después de que pasa el retraso de secondsBetweenBlasts, el jugador puede disparar nuevamente, y todo el proceso se repite. Para ayudar al jugador a entender cuándo puede disparar nuevamente, el juego incluye una barra de enfriamiento.
En este punto, los jugadores pueden aparecer y reaparecer, apuntar y disparar, pero el juego aún tiene que determinar los resultados de cada explosión. En la siguiente sección del tutorial, aprenderás cómo programar la capacidad del blaster para detectar cuándo la explosión golpea a otro jugador, y luego reducir la cantidad apropiada de salud del jugador según la configuración del blaster.