Esta sección incluye el código completo para añadirlo a los archivos de Verse que creaste.
Código completo
En este proyecto, hay múltiples archivos de Verse.
-
heartbeat.verse: consulta, abajo, el código completo del archivo.
-
base_team.verse: consulta, abajo, el código completo del archivo.
-
hunter_team.verse: consulta, abajo, el código completo del archivo.
-
prop_team.verse: consulta, abajo, el código completo del archivo.
-
round_timer.verse: consulta, abajo, el código completo del archivo.
-
waiting_for_more_players.verse: consulta, abajo, el código completo del archivo.
heartbeat.verse
using { /Fortnite.com/Characters }
using { /Fortnite.com/Devices }
using { /Fortnite.com/UI }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/UI }
using { /Verse.org/Colors }
using { /Verse.org/Simulation }
log_heart_beat := class(log_channel){}
# Estos mensajes se utilizan para notificar a un agente de utilería con un mensaje (u ocultarlo) cuando deben moverse para evitar que su latido se haga visible.
HeartBeatWarningMessage<localizes>(Time:int):mensaje = "Latido en {Time} segundos. ¡Muévete!"
HeartBeatWarningClear<localizes>:message = ""
# Esta clase expuso las propiedades editables para el latido al dispositivo prop_hunt.
heart_beat := class<concrete>():
Logger:log = log{Channel:=log_heart_beat}
@editable # La cantidad de segundos antes de que un agente de utilería deba moverse para que el latido revele su posición.
MoveTime:float = 15.0
@editable # Los segundos restantes antes de que aparezca el aviso de latido. No debe ser > que HeartBeatTimer.
WarningTime:float = 5.0
@editable # Una matriz de dispositivos de efectos visuales de latido. Existe uno por jugador.
AgentVFX:[]heartbeat_vfx = array{}
@editable # El dispositivo reproductor de sonido que se utiliza para reproducir los efectos de sonido (SFX) de los latidos.
SFXPlayer:radio_device = radio_device{}
# Este mapa asocia una IU para mostrar el aviso de latido a cada agente de utilería.
var WarningUI:[agent]heartbeat_warning_ui = map{}
# Realiza un seguimiento de cuántos jugadores tienen un latido activo para poder gestionar el dispositivo de SFX.
var NumberOfHeartBeats:int = 0
# Establece una IU de latido para este agente.
SetUpUI(PropAgent:agent):void =
if:
not WarningUI[PropAgent]
AsPlayer := player[PropAgent]
PlayerUI := GetPlayerUI[AsPlayer]
then:
UIData:heartbeat_warning_ui = heartbeat_warning_ui{}
UIData.CreateCanvas()
PlayerUI.AddWidget(UIData.Canvas, player_ui_slot{ZOrder := 1})
if (set WarningUI[PropAgent] = UIData) {}
# Activa los efectos visuales y los efectos de sonido de latido para el reproductor especificado.
Enable(PropAgent:agent, HeartBeatVFXData:heartbeat_vfx):void =
if:
# Obtén el personaje, que se utiliza para encontrar la posición del agente de utilería en la escena.
Character := PropAgent.GetFortCharacter[]
then:
# Establece la posición del efecto visual del latido del corazón a la posición del agente de utilería.
HeartBeatVFXData.Activate(Character.GetTransform())
# Aumenta la cuenta de latidos, y si este es el primer latido que se reproduce, necesitamos reproducir el audio para que comience.
set NumberOfHeartBeats += 1
if (NumberOfHeartBeats = 1) then SFXPlayer.Play()
# Registra el agente de utilería al dispositivo reproductor de audio para que el audio del latido se reproduzca desde esa posición.
SFXPlayer.Register(PropAgent)
else:
Logger.Print("No se encontró el personaje, índice o HeartBeatVFXData. No se puede empezar a reproducir el latido.")
# Borra los VFX y SFX de latido para el agente de utilería especificado.
Disable(PropAgent:agent, HeartBeatVFXData:heartbeat_vfx):void =
Logger.Print("Se está deshabilitando el latido.")
# Desactiva los VFX.
HeartBeatVFXData.Deactivate()
# Cancela el registro del agente de utilería del dispositivo Reproductor de audio para que el audio del latido se detenga.
SFXPlayer.Unregister(PropAgent)
# Disminuye el contador de latidos. Este contador nunca debe ser inferior a 0.
set NumberOfHeartBeats -= 1
if (NumberOfHeartBeats < 0) then set NumberOfHeartBeats = 0
# Borra todos los efectos visuales y los efectos de sonido de latido para todos los agentes de utilería.
DisableAll():void =
Logger.Print("Se están deshabilitando todos los latidos.")
# Itera a través de todos los efectos visuales y muévelos a 0,0,0.
for (HeartBeatVFXDevice : AgentVFX):
HeartBeatVFXDevice.Deactivate()
# Cancela el registro de todos los reproductores de audio de latido.
SFXPlayer.UnregisterAll()
# Reinicia el contador de latidos a 0
set NumberOfHeartBeats = 0
# La clase heartbeat_warning_ui contiene una estructura de datos para rastrear el lienzo de IU y un text_block por jugador, así como la función para crear un nuevo lienzo de IU de aviso de latido.
heartbeat_warning_ui := class:
var Canvas:canvas = canvas{}
var Text:text_block = text_block{}
# Crea el lienzo de la IU para el mensaje de aviso.
CreateCanvas():void =
set Text = text_block{DefaultTextColor := NamedColors.White, DefaultShadowColor := NamedColors.Black}
set Canvas = canvas:
Slots := array:
canvas_slot:
Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.75}, Maximum := vector2{X := 0.5, Y := 0.75}}
Offsets := margin{Top := 0.0, Left := 0.0, Right := 0.0, Bottom := 0.0}
Alignment := vector2{X := 0.5, Y := 1.0}
SizeToContent := true
Widget := Text
# La clase heartbeat_vfx contiene una estructura de datos para realizar un seguimiento de la raíz del VFX y de los objetos vfx_spawner_device por jugador, así como las funciones para establecer el VFX en una posición o restablecerlo.
heartbeat_vfx := class<concrete>:
@editable # El dispositivo de efectos visuales para cada latido.
VFXDevice:vfx_spawner_device = vfx_spawner_device{}
# Esta compensación se utiliza para posicionar el latido por encima de la cabeza de un agente de utilería.
HeartBeatVFXOffset:vector3 = vector3{X := 0.0, Y := 0.0, Z := 110.0}
# Establece la posición del efecto visual del latido del corazón y, luego, habilita el efecto visual.
Activate(Transform:transform):void =
VFXPosition := Transform.Translation + HeartBeatVFXOffset
if (VFXDevice.TeleportTo[VFXPosition, Transform.Rotation]):
VFXDevice.Enable()
# Deshabilita los efectos visuales y oculta los visuales de latidos del corazón.
Deactivate():void =
VFXDevice.Disable()
base_team.verse
using { /Fortnite.com/Characters }
using { /Fortnite.com/Devices }
using { /Fortnite.com/UI }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /UnrealEngine.com/Temporary/UI }
using { /Verse.org/Colors }
using { /Verse.org/Simulation }
log_team := class(log_channel){}
# Esta clase define los dispositivos necesarios para los distintos equipos en esta experiencia.
# Esta clase es abstracta, así que no puede utilizarse por sí sola. Tiene que heredarse de otra clase.
base_team := class<abstract>:
Logger:log = log{Channel:=log_team}
@editable # Usado para establecer un jugador para el equipo.
ClassSelector:class_and_team_selector_device = class_and_team_selector_device{}
@editable # Usado para otorgar puntaje a los agentes en el equipo.
ScoreManager:score_manager_device = score_manager_device{}
@editable # Usado para mostrar el título de asignación del equipo.
TeamTitle:hud_message_device = hud_message_device{}
@editable # Usado para mostrar la descripción de asignación del equipo.
TeamDescription:hud_message_device = hud_message_device{}
@editable # Usado para suscribirse a los eventos eliminados del equipo miembro (equipo utilería) o enemigo (equipo cazador).
TeamManager:team_settings_and_inventory_device = team_settings_and_inventory_device{}
# Esta es una matriz de agentes en el equipo.
var TeamAgents<private>:[]agent = array{}
# Este evento se señala cuando la matriz TeamAgents se vacía (lo que marca el fin de la ronda).
TeamEmptyEvent:event() = event(){}
# Devuelve la matriz TeamAgents actual.
# Esto es obligatorio porque la matriz TeamAgents es privada, por eso las otras clases no pueden acceder a ella directamente.
GetAgents()<decides><transacts>:[]agent =
TeamAgents
# Devuelve el tamaño de la matriz TeamAgents
# Esto requiere una función porque la matriz TeamAgents es privada, por eso las otras clases no pueden acceder a ella directamente.
Count()<transacts>:int =
TeamAgents.Length
# Devuelve un índice en la matriz TeamAgents de un agente; en caso contrario, falla.
FindOnTeam(Agent:agent)<decides><transacts>: int =
Index := TeamAgents.Find[Agent]
# Establece el agente para el equipo y notifica al jugador.
InitializeAgent(Agent:agent):void =
AddAgentToTeam(Agent)
ClassSelector.ChangeTeamAndClass(Agent)
DisplayTeamInformation(Agent)
# Añade un agente al TeamAgents.
AddAgentToTeam(AgentToAdd:agent):void =
if (not FindOnTeam[AgentToAdd]):
Logger.Print("Se está añadiendo al agente al equipo.")
set TeamAgents += array{AgentToAdd}
# Activa los dispositivos de mensaje del HUD para mostrarle al jugador en qué equipo está
DisplayTeamInformation(Agent:agent):void =
TeamTitle.Show(Agent)
TeamDescription.Show(Agent)
# Cuando un agente deja una partida, elimínalo de la matriz TeamAgents y corrobora el fin de la ronda.
EliminateAgent(Agent:agent)<suspends>:void =
Sleep(0.0) # Retraso 1 tic de juego para asegurarte de que el jugador reaparezca antes de continuar.
RemoveAgentFromTeam(Agent)
# Elimina un agente de TeamAgents.
# Si el agente eliminado fue el último, marca TeamEmptyEvent.
RemoveAgentFromTeam(AgentToRemove:agent):void =
set TeamAgents = TeamAgents.RemoveAllElements(AgentToRemove)
Logger.Print("Agentes restantes en el equipo: {Count()}.")
if (Count() < 1):
Logger.Print("No quedan agentes en el equipo. Fin de la ronda.")
TeamEmptyEvent.Signal()
hunter_team.verse
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Verse.org/Simulation }
# Se hereda de la clase base_team. La clase hunter_team contiene las definiciones de dispositivos y funciones relacionadas con el equipo cazador y sus agentes.
hunter_team := class<concrete>(base_team):
@editable # En cada ronda, se crea un agente cazador para cada jugador. Ejemplo: HunterTeamPerNumberOfPlayers = 5.0 es 1 por cada 5 jugadores. Si jugadores = 6, se crean 2 agentes cazadores.
HunterAgentPerNumberOfPlayers:float = 5.0 # Se impone un mínimo de 1.1 para garantizar que se cree al menos 1 agente de utilería.
@editable # La cantidad de segundos antes de que los agentes cazadores aparezcan y le den a los agentes de utilería una ventaja para esconderse.
SpawnDelay:float = 15.0
@editable # El máximo de puntos base que un agente cazador obtiene por eliminar a un agente de utilería. Estos puntos se dividen por la cantidad de agentes de utilería restantes.
MaxEliminationScore:int = 5000
@editable # El cronómetro se utiliza para dar a la utilería un período de gracia para esconderse.
WaitTimer:timer_device = timer_device{}
# Establece el agente como agente cazador.
InitializeAgent<override>(NewHunterAgent:agent):void =
Logger.Print("Se está estableciendo un nuevo agente cazador.")
(super:)InitializeAgent(NewHunterAgent)
# Cuando un agente cazador abandona la partida, se extrae de la matriz HunterAgents y se comprueba el fin de la ronda.
# Observa que estamos sobrescribiendo esta función porque no necesitamos pasar datos extra aquí como lo hacemos para el equipo de utilería.
EliminateAgent<override>(HunterAgent:agent)<suspends>:void =
Logger.Print("Agente cazador eliminado.")
(super:)EliminateAgent(HunterAgent)
prop_team.verse
using { /Fortnite.com/Characters }
using { /Fortnite.com/Devices }
using { /Fortnite.com/UI }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /UnrealEngine.com/Temporary/UI }
using { /Verse.org/Colors }
using { /Verse.org/Simulation }
# Este mensaje se utiliza para imprimir la cantidad de utilería restante a todos los jugadores durante una ronda.
PropAgentsRemainingMessage<localizes>(Count:int):message = "{Count} Prop(s) Remaining"
# Se hereda de la clase base_team. La clase prop_team contiene las definiciones de dispositivos y funciones relacionadas con el equipo utilería y sus agentes.
# Notablemente, el comportamiento del latido de un agente de utilería se puede encontrar en esta clase.
prop_team := class<concrete>(base_team):
@editable # El puntaje que un agente de utilería recibe por segundo.
ScorePerSecond:int = 10
@editable # La distancia mínima en la que debe moverse un agente de utilería para reiniciar el cronómetro de latidos.
MinimumMoveDistance:float = 100.0
@editable # El cronómetro que se utiliza para otorgar puntaje a la utilería.
ScoreTimer:timer_device = timer_device{}
@editable # Este dispositivo de seguimiento se utiliza para mostrar la utilería restante en la pantalla.
PropsRemainingTracker:tracker_device = tracker_device{}
@editable # Obtén las propiedades de la clase heart_beat.
HeartBeat:heart_beat = heart_beat{}
# Establece el agente como agente de utilería y asigna la IU de aviso de latidos.
InitializeAgent<override>(NewPropAgent:agent):void =
Logger.Print("Se está estableciendo un nuevo agente de utilería.")
(super:)InitializeAgent(NewPropAgent)
# Cuando el PropScoreTimer se complete, otorga puntajes a todos los agentes de utilería. PropInstigator es necesario para la suscripción al evento, pero no se utiliza.
OnPropsScore(PropInstigator:?agent):void =
if (PropAgents := GetAgents[]):
for (PropAgent : PropAgents):
ScoreManager.Activate(PropAgent)
# Cuando un agente cazador se elimina o abandona la partida, se extrae de la matriz HunterAgents y se comprueba el fin de la ronda.
# Observa que esto no es una sobreescritura porque estamos pasando todos los jugadores a la función para actualizar el mensaje restante de la utilería.
EliminateAgent<override>(PropAgent:agent)<suspends>:void =
Logger.Print("Agente de utilería eliminado.")
(super:)EliminateAgent(PropAgent)
# Actualiza la cantidad de utilería restante.
UpdatePropsRemainingTracker()
# Actualiza el valor del dispositivo de seguimiento que muestra la cantidad de utilería restante.
UpdatePropsRemainingTracker():void =
PropsRemainingTracker.SetValue(Count())
# Si el agente de utilería se deja de mover, entonces corre a ver si el agente de utilería se mueve más allá de MinimumMoveDistance, el cronómetro de latido de corazón se completa o el agente de utilería se elimina.
RunPropGameLoop(PropAgent:agent)<suspends>:void =
Logger.Print("Se está iniciando el bucle de juego de agente de utilería.")
# Muévete en bucle a través del comportamiento de utilería hasta que el agente de utilería se elimine o hasta que el jugador abandone la sesión.
race:
PropAgent.AwaitNoLongerAProp()
loop:
# Espera hasta que el agente de utilería se mueva menos que la distancia mínima; luego, avanza.
PropAgent.AwaitStopMoving(MinimumMoveDistance)
# Hasta que el agente de utilería no se mueva más allá que la distancia mínima, realiza la cuenta regresiva del latido y, luego, reprodúcelo de forma indefinida.
race:
PropAgent.AwaitStartMoving(MinimumMoveDistance)
block:
CountdownTimer(PropAgent)
PropAgent.StartHeartbeat()
Sleep(0.0) # Una vez que la carrera se completa (el agente de utilería se mueve), inicia el bucle nuevamente.
# Haz un bucle hasta que el agente de utilería no forme parte de la matriz PropAgents. La extracción sucede si el agente de utilería se elimina y se convierte en un cazador o si el jugador abandona la sesión.
(PropAgent:agent).AwaitNoLongerAProp()<suspends>:void =
loop:
if (not FindOnTeam[AgentToAdd]):
Logger.Print("Se cancela el comportamiento del agente de utilería.")
break
Sleep(0.0) # Avanza al siguiente tic de juego.
# Hace un bucle hasta que el agente se mueva menos que la MinimumDistance.
(PropAgent:agent).AwaitStopMoving(MinimumDistance:float)<suspends>:void =
Logger.Print("Se está comprobando si el agente se ha movido menos de la distancia mínima.")
# Obtén la posición inicial del agente por medio de la posición del personaje del agente en la escena.
if (Tracked := PropAgent.GetFortCharacter[]):
var StartPosition:vector3 = Tracked.GetTransform().Translation
loop:
Sleep(0.0) # Obtén la posición del agente en el siguiente tic de juego.
NewPosition := Tracked.GetTransform().Translation
# Si la distancia de la nueva posición a la posición inicial es menor que MinimumDistance, el agente no se movió, y rompemos el bucle.
if (Distance(StartPosition, NewPosition) < MinimumDistance):
Logger.Print("El agente se ha movido menos de la distancia mínima.")
break
# En caso contrario, reiniciamos StartPosition para asegurarnos de que el jugador se mueva desde la nueva posición.
else:
set StartPosition = NewPosition
# Hace un bucle hasta que el agente se mueva más que la MinimumDistance.
(PropAgent:agent).AwaitStartMoving(MinimumDistance:float)<suspends>:void =
Logger.Print("Se está comprobando si el agente se mueve más lejos que la distancia mínima.")
# Obtén la posición inicial del agente por medio de la posición del personaje del agente en la escena.
if (Tracked := PropAgent.GetFortCharacter[]):
StartPosition:vector3 = Tracked.GetTransform().Translation
loop:
Sleep(0.0) # Obtén la posición del agente en el siguiente tic de juego.
NewPosition := Tracked.GetTransform().Translation
# Si la distancia de la nueva posición a la posición inicial es mayor o igual que MinimumDistance, el agente se movió, y rompemos el bucle.
if (Distance(StartPosition, NewPosition) >= MinimumDistance):
Logger.Print("El agente se movido una distancia mayor o equivalente a la distancia mínima.")
break
# Retrasa hasta que HeartBeatWarningTime debe comenzar. A continuación, realiza una cuenta regresiva según HeartBeatWarningTime y establece el texto de la cuenta regresiva. Borra el texto cuando se difiere.
CountdownTimer(PropAgent:agent)<suspends>:void =
Logger.Print("Se está iniciando la cuenta regresiva.")
if (UIData := HeartBeat.WarningUI[PropAgent]):
Sleep(HeartBeat.MoveTime - HeartBeat.WarningTime) # Duerme el tiempo necesario antes de que aparezca el aviso.
Logger.Print("Aviso de latido iniciado.")
var WarningTimeRemaining:int = 0
if (set WarningTimeRemaining = Ceil[HeartBeat.WarningTime]) {}
# Diferir ocurre cuando la función se completa o se cancela, por ejemplo, si pierde una carrera.
# Así que, en este caso, el texto de aviso se borra cuando termina la cuenta regresiva o si el agente de utilería se mueve antes de que esta termine.
defer:
UIData.Text.SetText(HeartBeatWarningClear)
# Establece el texto de aviso en el tiempo restante, espera un segundo y, luego, descuenta el tiempo restante. Si se completa la cuenta regresiva, rompe el bucle.
loop:
Logger.Print("Latido en {WarningTimeRemaining} segundos.")
UIData.Text.SetText(HeartBeatWarningMessage(WarningTimeRemaining))
Sleep(1.0)
set WarningTimeRemaining -= 1
if (WarningTimeRemaining <= 0):
break
else:
Logger.Print("No se encontró UIData.")
# Enciende los VFX y SFX de los latidos. Espera infinitamente hasta que se difiere y, luego, desactiva los VFX y SFX de los latidos.
(PropAgent:agent).StartHeartbeat()<suspends>:void =
Logger.Print("Se está generando un latido.")
# Guarda los datos de los latidos para que podamos pasarlos en el deferido más tarde, después de que el PropAgent se destruya o abandone el juego.
var HeartBeatVFXData:heartbeat_vfx = heartbeat_vfx{}
if:
# Obtén el índice del agente de utilería en la matriz PropAgents para luego acceder al actor VFX de latido correspondiente.
Index := FindOnTeam[PropAgent]
set HeartBeatVFXData = HeartBeat.AgentVFX[Index]
then:
HeartBeat.Enable(PropAgent, HeartBeatVFXData)
# Cuando esta función se cancela porque el agente de utilería se mueve, se elimina o el jugador abandona la sesión, desactiva el latido.
defer:
HeartBeat.Disable(PropAgent, HeartBeatVFXData)
Sleep(Inf) # No dejes de dormir hasta que se complete la carrera.
round_timer.verse
using { /Fortnite.com/Devices }
using { /Fortnite.com/UI }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /UnrealEngine.com/Temporary/UI }
using { /Verse.org/Colors }
using { /Verse.org/Simulation }
log_round_timer_device := class(log_channel){}
# Un int que permite valores entre los rangos especificados. Este tipo es necesario para player_ui_slot.ZOrder.
round_int_clamped := type{_X:int where 0 <= _X, _X <= 2147483647}
# Este mensaje se utiliza para imprimir el tiempo restante antes de que una ronda finalice.
TimeRemainingMessage<localizes>(Minutes:string, Seconds:string):message = "{Minutes}:{Seconds}"
<#
Esta clase contiene toda la lógica para administrar el tiempo de la ronda y mostrarlo en la pantalla.
Puedes utilizar este dispositivo con un round_settings_device para finalizar la ronda.
Este dispositivo administra el tiempo sin necesidad de utilizar un cronómetro.
Para usar esta clase debes realizar lo siguiente:
1) Añade el archivo a tu proyecto.
2) Compila el código Verse desde el menú en la barra de herramientas.
3) Arrastra el dispositivo a tu isla desde la carpeta Contenido/Dispositivos de modo Creativo en el explorador de contenido.
4) Incluye la clase waiting_for_more_players en otra secuencia de comandos de Verse con lo siguiente:
@editable
RoundTimer:round_timer = round_timer{}
5) Compila el código Verse desde el menú en la barra de herramientas.
6) Conecta el dispositivo que preparaste en el paso 3 al dispositivo de Verse.
7) Inicia el cronómetro de la ronda con el siguiente Verse:
RoundTimer.Start()
8) Reinicia o detiene el cronómetro con las funciones equivalentes.
9) Espera a que se inicie el cronómetro con lo siguiente:
RoundTimer.AwaitStart()
10) Espera a que se complete el cronómetro con lo siguiente:
RoundTimer.AwaitEnd()
Llama a la función EndRound en un round_settings_device para finalizar la ronda de juego.
#>
round_timer := class(creative_device):
Logger:log = log{Channel:=log_prop_hunt_device}
@editable # El tiempo, en minutos, que dura una ronda.
RoundTimeInMinutes:float = 5.0
@editable # La posición horizontal y vertical de la IU del cronómetro en la pantalla. X 0-1 es de izquierda a derecha, e Y 0-1 es de arriba hacia abajo.
UIPosition:vector2 = vector2{X:= 0.98, Y:=0.13}
@editable # La posición horizontal y vertical de la IU del cronómetro en la pantalla. X 0-1 es de izquierda a derecha, e Y 0-1 es de arriba hacia abajo.
UIAlignment:vector2 = vector2{X := 1.0, Y := 0.0}
@editable # El ZOrder de la IU comparado con otros elementos de la IU.
UIZOrder:round_int_clamped = 4
# Se señala cuando se inicia la ronda.
RoundStarted:event() = event(){}
# Se señala cuando la ronda está a punto de finalizar.
RoundEndedEvent:event() = event(){}
# Este mapa incluye un cuadro de texto para mostrar el tiempo a cada jugador.
var TimeRemainingTextBlocks:[player]text_block = map{}
# El tiempo restante antes de que la ronda se complete, como número entero.
var TimeRemainingInSeconds:int = 0
# Espera hasta que se inicie el cronómetro de la ronda.
AwaitStart()<suspends>:void =
RoundStarted.Await()
Logger.Print("Cronómetro de ronda iniciado.")
# Se utiliza para iniciar el cronómetro de la ronda.
Start():void =
Logger.Print("Se está iniciando el cronómetro de ronda.")
RoundStarted.Signal()
set TimeRemainingInSeconds = GetRoundTimeInSeconds()
spawn{ Running() }
# Reinicia el cronómetro a RoundTime
Restart():void =
Logger.Print("Se está reiniciando el cronómetro de ronda.")
set TimeRemainingInSeconds = GetRoundTimeInSeconds()
# Ejecuta la lógica del cronómetro.
Running()<suspends>:void =
Logger.Print("Cronómetro de ronda en curso.")
loop:
UpdateTimeUI()
Sleep(1.0)
# Reduce el TimeRemaining en 1 segundo y comprueba si se acabó el tiempo. Si es así, finaliza la ronda.
set TimeRemainingInSeconds -= 1
if (TimeRemainingInSeconds < 0):
Stop()
break
# Se detiene el cronómetro y finaliza la ronda.
Stop():void =
Logger.Print("Se está deteniendo el cronómetro de ronda.")
# Obtenemos un jugador de los que quedan en la escena para finalizar la ronda.
Players:[]player = GetPlayspace().GetPlayers()
if (Instigator := Players[0]):
RoundEndedEvent.Signal()
# Espera hasta que el cronómetro de la ronda esté a punto de finalizar.
AwaitEnd()<suspends>:void =
RoundEndedEvent.Await()
Logger.Print("Cronómetro de ronda detenido.")
# Acepta un valor de tiempo en minutos y devuelve el valor en segundos.
GetRoundTimeInSeconds():int =
var InSeconds:int = 0
if (set InSeconds = Round[RoundTimeInMinutes * 60.0]) {}
InSeconds
# Cuando el cronómetro finalice, actualiza el tiempo restante y comprueba si el tiempo se acabó.
UpdateTimeUI():void =
# Establece los minutos en TimeRemainingInSeconds/60 sin el tiempo restante.
var Minutes:int = 0
if (set Minutes = Floor(TimeRemainingInSeconds / 60)) {}
# Establece los segundos en el tiempo restante de TimeRemainingInSeconds/60.
var Seconds:int = 0
if (set Seconds = Mod[TimeRemainingInSeconds, 60]) {}
# Convierte minutos y segundos en cadenas.
MinutesAsString := string("{Minutes}")
# Si segundos < 10, entonces necesitamos añadir un 0 delante del valor para que se muestre como :0# en lugar de :#
SecondsAsString := if (Seconds < 10) then Join(array{string("{0}"),string("{Seconds}")},string()) else string("{Seconds}")
# Itera a través de todos los jugadores y comprueba si tienen un TimeRemainingTextBlock; si no, dales uno. Luego, actualiza el texto.
Players:[]player = GetPlayspace().GetPlayers()
for (Player : Players):
var TextBlock:text_block = text_block{}
if (set TextBlock = TimeRemainingTextBlocks[Player]) {}
else:
set TextBlock = SetUpTimeRemainingUI(Player)
TextBlock.SetText(TimeRemainingMessage(MinutesAsString, SecondsAsString))
# Acepta un jugador y, luego, añade un lienzo de la IU para el cronómetro de la ronda a su pantalla y almacena su TimeRemainingTextBlock para actualizarlo más tarde.
SetUpTimeRemainingUI(Player:player):text_block =
Logger.Print("Se añadió una IU de cronómetro de ronda a un jugador.")
# Este es el text_block que imprime el texto del tiempo restante en la pantalla.
TextBlock:text_block = text_block:
DefaultTextColor := NamedColors.White
DefaultShadowColor := NamedColors.Black
if (PlayerUI := GetPlayerUI[Player]):
if (set TimeRemainingTextBlocks[Player] = TextBlock) {}
# Este es el lienzo que sostiene y posiciona el text_block en la pantalla.
Canvas := canvas:
Slots := array:
canvas_slot:
Anchors := anchors{Minimum := UIPosition, Maximum := UIPosition}
Offsets := margin{Top := 0.0, Left := 0.0, Right := 0.0, Bottom := 0.0}
Alignment := UIAlignment
SizeToContent := true
Widget := TextBlock
# El lienzo se asigna al jugador.
PlayerUI.AddWidget(Canvas, player_ui_slot{ZOrder := UIZOrder})
# El text_block se devuelve para que pueda guardarse en el mapa y actualizarse más tarde a medida que pasa el tiempo.
return TextBlock
waiting_for_more_players.verse
using { /Fortnite.com/Devices }
using { /Fortnite.com/UI }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /UnrealEngine.com/Temporary/UI }
using { /Verse.org/Colors }
using { /Verse.org/Simulation }
log_waiting_for_more_players_device := class(log_channel){}
# Un int que permite valores entre los rangos especificados. Este tipo es necesario para player_ui_slot.ZOrder.
waiting_int_clamped := type{_X:int where 0 <= _X, _X <= 2147483647}
# Este mensaje se utiliza para imprimir la cantidad necesaria de jugadores antes de que una ronda pueda comenzar.
WaitingForMorePlayersMessage<localizes>(Count:int):message = "Esperando a {Count} jugador(es) más"
# Esta clase sirve para mostrar cuántos jugadores se necesitan para empezar la ronda.
waiting_for_more_players_ui := class:
var Canvas:canvas
var TextBlock:text_block
<#
Esta clase contiene toda la lógica para establecer una cantidad mínima de jugadores y comprobar si hay suficientes para empezar la ronda.
Para usar esta clase debes realizar lo siguiente:
1) Añade el archivo a tu proyecto.
2) Compila el código Verse desde el menú en la barra de herramientas.
3) Arrastra el dispositivo a tu isla desde la carpeta Contenido/Dispositivos de modo Creativo en el explorador de contenido.
4) Conecta un cronómetro a la propiedad "WaitingForMorePlayersTimer" de este dispositivo.
5) Incluye la clase waiting_for_more_players en otra secuencia de comandos de Verse con lo siguiente:
@editable
WaitingForMorePlayers:waiting_for_more_players = waiting_for_more_players{}
6) Compila el código Verse desde el menú en la barra de herramientas.
7) Conecta el dispositivo que preparaste en el paso 3 al dispositivo Verse y a la propiedad que expusiste en el paso 6.
8) Espera la función CheckForMinimumNumberOfPlayers al pasársela a un jugador. Por ejemplo:
Players = GetPlayspace().GetPlayers()
CheckForMinimumNumberOfPlayers(Players)
9) En IslandSettings, establece la cuenta regresiva de inicio de juego en 0.0.
#>
waiting_for_more_players := class(creative_device):
Logger:log = log{Channel:=log_waiting_for_more_players_device}
@editable # La cantidad mínima de jugadores necesarios en la partida para que comience una ronda.
MinimumNumberOfPlayers:int = 2
@editable # La posición horizontal y vertical de la IU del cronómetro en la pantalla. X 0-1 es de izquierda a derecha, e Y 0-1 es de arriba hacia abajo.
UIPosition:vector2 = vector2{X:= 0.5, Y:=0.4}
@editable # La posición horizontal y vertical de la IU del cronómetro en la pantalla. X 0-1 es de izquierda a derecha, e Y 0-1 es de arriba hacia abajo.
UIAlignment:vector2 = vector2{X := 0.5, Y := 0.5}
@editable # El ZOrder de la IU comparado con otros elementos de la IU.
UIZOrder:waiting_int_clamped = 3
@editable # Este cronómetro se utiliza para la cuenta regresiva hasta el inicio de la ronda después de esperar a que los jugadores se unan a la partida.
WaitingForMorePlayersTimer:timer_device = timer_device{}
# Este mapa incluye un lienzo de la IU para mostrar a cada jugador la cantidad necesaria de jugadores para comenzar una ronda.
var WaitingForMorePlayersUI:[player]?waiting_for_more_players_ui = map{}
# Comprueba si hay suficientes jugadores para comenzar la ronda. Si no, espera hasta que la cantidad de jugadores sea >= MinimumNumberOfPlayers.
WaitForMinimumNumberOfPlayers(Players:[]player)<suspends>:[]player =
Logger.Print("Se está esperando a que haya suficientes jugadores para iniciar la ronda.")
# Crea una nueva variable para que puedas modificarla a medida que se unan más jugadores. Se inicializa con una matriz de jugadores que se pasa a la función.
var PlayersWaiting:[]player = Players
# Si la cantidad de jugadores es inferior al mínimo necesario para comenzar la ronda…
if (PlayersWaiting.Length < MinimumNumberOfPlayers):
loop: # Haz un bucle hasta que la cantidad de jugadores sea mayor o igual que el mínimo necesario.
Logger.Print("Faltan {PlayersWaiting.Length}/{MinimumNumberOfPlayers} jugadores para que inicie la ronda.")
# Actualiza la IU de espera de más jugadores.
DisplayWaitingForMorePlayers(PlayersWaiting)
Sleep(2.0) # Espera a ver si más jugadores se unen a la partida y, luego, comprueba si hay suficientes jugadores para empezar la ronda.
set PlayersWaiting = GetPlayspace().GetPlayers()
if (PlayersWaiting.Length >= MinimumNumberOfPlayers):
# Si ya hay suficientes jugadores, borra la IU de espera de más jugadores,
Logger.Print("{PlayersWaiting.Length}/{MinimumNumberOfPlayers} jugadores en la ronda. Se está preparando el inicio de la ronda.")
ClearWaitingForMorePlayers()
# Entonces, interrumpe el bucle.
break
# inicia la cuenta regresiva de inicio de ronda y espera hasta que se complete.
WaitingForMorePlayersTimer.Start()
WaitingForMorePlayersTimer.SuccessEvent.Await()
Logger.Print("Se está iniciando la ronda.")
# Devuelve la lista de jugadores de la sesión.
return PlayersWaiting
# Muestra el mensaje de IU "Esperando a más jugadores" para cada jugador si aún no tiene uno. Actualiza el contador de jugadores de todos los jugadores.
DisplayWaitingForMorePlayers(Players:[]player):void =
PlayersNeededCount := MinimumNumberOfPlayers - Players.Length
Logger.Print("{Players.Length} jugadores en la ronda. Esperando a que {PlayersNeededCount} jugadores más se unan.")
for (Player : Players):
# Si el jugador tiene una WaitingForMorePlayersUI, obtén el TextBlock y refresca el texto para que muestre la cantidad correcta de jugadores necesarios para empezar la ronda.
if (UIData := WaitingForMorePlayersUI[Player]?):
UIData.TextBlock.SetText(WaitingForMorePlayersMessage(PlayersNeededCount))
# Si no, crea una WaitingForMorePlayersUI para el jugador.
else:
SetUpWaitingForMorePlayersUI(Player, PlayersNeededCount)
# Acepta un jugador y un player_ui, y añade un lienzo de la IU de espera de más jugadores a sus pantallas.
SetUpWaitingForMorePlayersUI(Player:player, PlayersNeededCount:int):void =
Logger.Print("Se está creando un IU de espera de más jugadores.")
if (PlayerUI := GetPlayerUI[Player]):
# Este es el text_block que imprime en pantalla el texto de espera de más jugadores.
TextBlock:text_block = text_block:
DefaultText := WaitingForMorePlayersMessage(PlayersNeededCount)
DefaultTextColor := NamedColors.White
DefaultShadowColor := NamedColors.Black
# Este es el lienzo que sostiene y posiciona el text_block en la pantalla.
Canvas := canvas:
Slots := array:
canvas_slot:
Anchors := anchors{Minimum := UIPosition, Maximum := UIPosition}
Offsets := margin{Top := 0.0, Left := 0.0, Right := 0.0, Bottom := 0.0}
Alignment := UIAlignment
SizeToContent := true
Widget := TextBlock
# El lienzo se asigna al jugador.
PlayerUI.AddWidget(Canvas, player_ui_slot{ZOrder := UIZOrder})
# El text_block se guarda en el mapa para que podamos actualizar el texto más tarde a medida que más jugadores se unen al juego.
if (set WaitingForMorePlayersUI[Player] = option{ waiting_for_more_players_ui{Canvas := Canvas, TextBlock := TextBlock} }) {}
# Elimina el mensaje de IU "Esperando a más jugadores" para cada jugador que tenga uno.
ClearWaitingForMorePlayers():void =
Logger.Print("Se está borrando la IU de espera de más jugadores.")
for (Player -> UIData : WaitingForMorePlayersUI):
if:
PlayerUI := GetPlayerUI[Player]
Canvas := UIData?.Canvas
set WaitingForMorePlayersUI[Player] = false
then:
PlayerUI.RemoveWidget(Canvas)