Código completo
Lo que sigue es el código completo de un juego de infiltración de tres equipos que equilibra asimétricamente a los jugadores para crear experiencias de juego dinámicas.
triad_infiltration_game.verse
using { /Fortnite.com/Devices }
using { /Fortnite.com/FortPlayerUtilities }
using { /Verse.org/Simulation }
using { /Verse.org/Random }
using { /UnrealEngine.com/Temporary/Diagnostics }
triad_infiltration_log_channel := class(log_channel){}
triad_infiltration := class(creative_device):
Logger:log = log{Channel := triad_infiltration_log_channel}
# Para evitar que los jugadores no puedan unirse a un equipo, debes establecer el número máximo
# de jugadores en la configuración de la isla para la suma de todas las variables Maximum(Team).
# Número máximo de jugadores en el equipo de infiltrados.
@editable
MaximumInfiltrators:int = 2
# Número máximo de jugadores en el equipo de atacantes.
@editable
MaximumAttackers:int = 4
# Número máximo de jugadores en el equipo de defensores.
@editable
MaximumDefenders:int = 4
# Matriz de teletransportadores que teletransportan a los jugadores al punto de aparición de su equipo cuando se inicia la partida.
@editable
Teleporters:[]teleporter_device = array{}
# Referencia a la secuencia de comandos invisibility_manager que controla la invisibilidad del infiltrado.
@editable
InvisibilityManager:invisibility_manager = invisibility_manager{}
# Matriz de repartidores de armas para cada equipo.
@editable
var WeaponGranters:[]item_granter_device = array{}
# Matriz de generadores de jugadores para cada equipo.
@editable
PlayersSpawners:[]player_spawner_device = array{}
# Referencia al equipo de infiltrados.
var MaybeInfiltrators:?team = false
# Referencia al equipo de atacantes.
var MaybeAttackers:?team = false
# Referencia al equipo de defensores.
var MaybeDefenders:?team = false
# Matriz de todos los equipos en el juego.
var AllTeams:[]team = array{}
# Mapa de equipos con su número máximo de jugadores.
var TeamsAndTotals:[team]int = map{}
OnBegin<override>()<suspends>:void =
# Obtiene todos los equipos.
set AllTeams = GetPlayspace().GetTeamCollection().GetTeams()
var AllPlayers:[]player = GetPlayspace().GetPlayers()
# Guarda los equipos para hacer referencia a ellos más tarde.
set MaybeInfiltrators = option{AllTeams[0]}
set MaybeAttackers = option{AllTeams[1]}
set MaybeDefenders = option{AllTeams[2]}
if:
Infiltrators := MaybeInfiltrators?
Attackers := MaybeAttackers?
Defenders := MaybeDefenders?
Logger.Print("Encontrados los tres equipos")
set TeamsAndTotals[Infiltrators] = MaximumInfiltrators
set TeamsAndTotals[Attackers] = MaximumAttackers
set TeamsAndTotals[Defenders] = MaximumDefenders
Logger.Print("Configura los tres equipos en TeamsAndTotals")
then:
#Se suscribe a PlayerAddedEvent para permitir el reequilibrio del equipo cuando un nuevo jugador se une a la partida.
GetPlayspace().PlayerAddedEvent().Subscribe(OnPlayerAdded)
for(PlayerSpawner:PlayersSpawners):
PlayerSpawner.SpawnedEvent.Subscribe(OnPlayerSpawn)
BalanceTeams()
Logger.Print("Equipos equilibrados, llamando a la secuencia de comandos de invisibilidad")
InvisibilityManager.StartInvisibilityManager(AllTeams, AllPlayers, Infiltrators)
Sleep(0.25)
TeleportPlayersToStartLocations()
else:
Logger.Print("No se han encontrado todos los equipos, asegúrate de asignar los equipos correctos en la configuración de tu isla.")
# Concede a los jugadores un arma en función del índice de su equipo en la matriz Teams
# indexando en la matriz WeaponGranters.
GrantTeamWeapon(InPlayer:player):void=
if(CurrentTeam := GetPlayspace().GetTeamCollection().GetTeam[InPlayer]):
for(TeamIndex -> PlayerTeam:AllTeams, PlayerTeam = CurrentTeam):
if(WeaponGranter := WeaponGranters[TeamIndex]):
WeaponGranter.GrantItem(InPlayer)
Logger.Print("Se ha concedido un arma al jugador del equipo {TeamIndex + 1}")
# Se ejecuta cuando un jugador aparece en una plataforma de aparición.
# Llama a GrantTeamWeapon utilizando el SpawnedAgent proporcionado.
OnPlayerSpawn(SpawnedAgent:agent):void=
if(SpawnedPlayer := player[SpawnedAgent]):
Logger.Print("Intentando conceder un arma al jugador generado")
GrantTeamWeapon(SpawnedPlayer)
# Gestiona a un nuevo jugador que se une al juego.
OnPlayerAdded(InPlayer:player):void=
Logger.Print("Se ha unido un nuevo jugador; asignándolo a un equipo")
FortTeamCollection := GetPlayspace().GetTeamCollection()
# Asigna al nuevo jugador al equipo más pequeño, de forma asimétrica.
BalancePlayer(InPlayer)
for:
TeamIndex -> PlayerTeam:AllTeams
PlayerTeam = FortTeamCollection.GetTeam[InPlayer]
TeamTeleporter := Teleporters[TeamIndex]
Transform := TeamTeleporter.GetTransform()
do:
InPlayer.Respawn(Transform.Translation, Transform.Rotation)
Logger.Print("Se ha teletransportado al jugador que ha aparecido a su ubicación inicial.")
# Si el jugador era un infiltrado, se llama a OnInfiltratorJoined en
# InvisibilityManager.
if(PlayerTeam = MaybeInfiltrators?):
InvisibilityManager.OnInfiltratorJoined(InPlayer)
# Equilibra a todos los jugadores de todos los equipos del juego.
BalanceTeams():void=
Logger.Print("Empezando a equilibrar los equipos")
var AllPlayers:[]player := GetPlayspace().GetPlayers()
set AllPlayers = Shuffle(AllPlayers)
Logger.Print("La longitud de AllPlayers es {AllPlayers.Length}")
for (TeamPlayer:AllPlayers):
BalancePlayer(TeamPlayer)
# Para cada jugador, se itera por la lista de equipos y se asignan al
# equipo con menos jugadores o al equipo inicial en caso de empate.
BalancePlayer(InPlayer:player):void=
Logger.Print("Empezando a equilibrar los jugadores.")
var TeamToAssign:?team = false
set TeamToAssign = FindTeamWithLargestDifference()
if (AssignedTeam := TeamToAssign?, GetPlayspace().GetTeamCollection().AddToTeam[InPlayer, AssignedTeam]):
Logger.Print("Jugador asignado a un nuevo equipo")
else:
Logger.Print("Este jugador ya estaba en el equipo más pequeño")
# Encuentra el equipo con la mayor diferencia en el número de jugadores de su
# número máximo de jugadores.
FindTeamWithLargestDifference():?team =
Logger.Print("Intentando encontrar el equipo más pequeño")
var TeamToAssign:?team = false
var LargestDifference:int = 0
for:
CandidateTeamIndex -> CandidateTeam:AllTeams
CurrentTeamSize := GetPlayspace().GetTeamCollection().GetAgents[CandidateTeam].Length
MaximumTeamSize := TeamsAndTotals[CandidateTeam]
do:
Logger.Print("Comprobando un equipo...")
Logger.Print("El tamaño máximo del equipo {CandidateTeamIndex + 1} es {MaximumTeamSize}")
DifferenceFromMaximum := MaximumTeamSize - CurrentTeamSize
Logger.Print("La diferencia con el máximo es {DifferenceFromMaximum}")
if(LargestDifference < DifferenceFromMaximum):
set LargestDifference = DifferenceFromMaximum
set TeamToAssign = option{CandidateTeam}
Logger.Print("Se ha encontrado el equipo {CandidateTeamIndex + 1} con la diferencia {DifferenceFromMaximum}")
return TeamToAssign
# Teletransporta a los jugadores al punto de aparición de su equipo tras equilibrar los equipos.
TeleportPlayersToStartLocations():void=
Logger.Print("Teletransportando a los jugadores a las ubicaciones iniciales")
for:
TeamIndex -> PlayerTeam:AllTeams
TeamPlayers := GetPlayspace().GetTeamCollection().GetAgents[PlayerTeam]
TeamTeleporter := Teleporters[TeamIndex]
do:
for(TeamPlayer:TeamPlayers):
TeamTeleporter.Teleport(TeamPlayer)
Logger.Print("Se ha teletransportado a este jugador a su ubicación inicial")
invisibility.verse
using { /Fortnite.com/Devices }
using { /Fortnite.com/Characters }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
triad_invisibility_log_channel := class(log_channel){}
invisibility_manager := class(creative_device):
Logger:log = log{Channel := triad_invisibility_log_channel}
# Matriz de generadores de jugadores para el equipo de infiltrados
@editable
PlayersSpawners:[]player_spawner_device = array{}
# Si la visibilidad de los infiltrados se comparte con los compañeros de equipo.
@editable
IsVisibilityShared:logic = true
# Cuánto tiempo permanecen visibles los infiltrados tras ser dañados.
@editable
VulnerableSeconds:float = 3.0
# Indica la rapidez con la que los infiltrados parpadean tras recibir daño.
@editable
FlickerRateSeconds:float = 0.4
# Matriz de todos los equipos en el juego.
var AllTeams:[]team = array{}
# Mapa de jugadores a la cantidad de segundos que les quedan para seguir parpadeando.
var PlayerVisibilitySeconds:[agent]float = map{}
OnBegin<override>()<suspends>:void=
# Espera a que los equipos estén equilibrados antes de suscribirte a eventos que hagan invisibles a los jugadores.
Logger.Print("Esperando a que se equilibren los equipos...")
# Inicia la lógica del gestor de invisibilidad. Se llama desde la clase triad_infiltration tras equilibrar los equipos.
StartInvisibilityManager<public>(GameTeams:[]team, AllPlayers:[]player, Infiltrators:team):void=
Logger.Print("¡Secuencia de comandos de invisibilidad iniciada!")
set AllTeams = GameTeams
for(PlayerSpawner:PlayersSpawners):
PlayerSpawner.SpawnedEvent.Subscribe(OnPlayerSpawn)
# Para cada jugador, si ha aparecido en el equipo de infiltrados, crea una función OnInfiltratorDamaged para dicho
# jugador. Luego haz que su personaje sea invisible.
for(TeamPlayer:AllPlayers):
if:
FortCharacter:fort_character = TeamPlayer.GetFortCharacter[]
CurrentTeam:team := GetPlayspace().GetTeamCollection().GetTeam[TeamPlayer]
Logger.Print("Tengo el equipo actual de este jugador")
Infiltrators = CurrentTeam
set PlayerVisibilitySeconds[TeamPlayer] = 0.0
Logger.Print("Jugador añadido a PlayerVisibilitySeconds")
then:
spawn{OnInfiltratorDamaged(TeamPlayer)}
Logger.Print("El jugador aparece como infiltrado, haciéndolo invisible")
FortCharacter.Hide()
else:
Logger.Print("Este jugador no es un infiltrado")
# Parpadea la visibilidad de un agente ocultando y mostrando repetidamente su fort_character.
FlickerCharacter(InCharacter:fort_character)<suspends>:void=
Logger.Print("FlickerCharacter() invocado")
# Haz un bucle ocultando y mostrando el personaje para crear un efecto de parpadeo.
loop:
InCharacter.Hide()
Sleep(FlickerRateSeconds)
InCharacter.Show()
Sleep(FlickerRateSeconds)
# En cada bucle, disminuye la cantidad de tiempo que el personaje parpadea en FlickerRateSeconds.
# Si el tiempo restante llega a 0, sal del bucle.
if:
TimeRemaining := set PlayerVisibilitySeconds[InCharacter.GetAgent[]] -= FlickerRateSeconds * 2
TimeRemaining <= 0.0
then:
InCharacter.Hide()
break
# Parpadea la visibilidad de un agente cada vez que recibe daño.
OnInfiltratorDamaged(InAgent:agent)<suspends>:void=
Logger.Print("Intentando iniciar el parpadeo de este personaje")
TeamCollection := GetPlayspace().GetTeamCollection()
if (FortCharacter := InAgent.GetFortCharacter[]):
loop:
if(IsVisibilityShared?, CurrentTeam := TeamCollection.GetTeam[InAgent], TeamAgents := TeamCollection.GetAgents[CurrentTeam]):
# Para cada compañero de equipo, configúralo en segundos de PlayerVisibility y genera un FlickerEvent.
for(Teammate:TeamAgents):
Logger.Print("Llamando a StartOrResetFlickering en un Teammate")
StartOrResetFlickering(Teammate)
else:
# Solo parpadea el personaje dañado.
Logger.Print("Llamando a StartOrResetFlickering en InAgent")
StartOrResetFlickering(InAgent)
FortCharacter.DamagedEvent().Await()
# Inicia un nuevo evento de parpadeo si el agente era invisible, en caso contrario
# restablece el parpadeo en curso.
StartOrResetFlickering(InAgent:agent):void=
if (not IsFlickering[InAgent], FortCharacter := InAgent.GetFortCharacter[]):
Logger.Print("Intentando iniciar un nuevo FlickerEvent para este personaje")
# Nuevo parpadeo iniciado.
if (set PlayerVisibilitySeconds[InAgent] = VulnerableSeconds):
spawn{FlickerCharacter(FortCharacter)}
Logger.Print("Se ha generado un FlickerEvent para este personaje")
else:
# Restablece el parpadeo en curso.
if (set PlayerVisibilitySeconds[InAgent] = VulnerableSeconds):
Logger.Print("Restablece el FlickerTimer del personaje a VulnerableSeconds")
# Devuelve si al jugador le queda tiempo de parpadeo.
IsFlickering(InAgent:agent)<decides><transacts>:void=
PlayerVisibilitySeconds[InAgent] > 0.0
# Genera una función OnInfiltratorDamaged cuando un nuevo infiltrado se une al juego.
OnInfiltratorJoined<public>(InAgent:agent):void=
spawn{OnInfiltratorDamaged(InAgent)}
# Gestiona la aparición de un jugador desde la plataforma de aparición de infiltrados.
OnPlayerSpawn(SpawnedAgent:agent):void=
Logger.Print("Acaba de aparecer un jugador de una plataforma de aparición de infiltrados.")
if:
FortCharacter:fort_character = SpawnedAgent.GetFortCharacter[]
CurrentTeam := GetPlayspace().GetTeamCollection().GetTeam[SpawnedAgent]
AllTeams[0] = CurrentTeam
Logger.Print("El jugador aparece como infiltrado, haciéndolo invisible")
then:
FortCharacter.Hide()
item_capture_manager.verse
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Fortnite.com/Characters }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/SpatialMath }
triad_item_capture_log_channel := class(log_channel){}
item_capture_manager := class(creative_device):
Logger:log = log{Channel := triad_item_capture_log_channel}
# Captura el generador de objetos que genera el objeto a capturar.
@editable
CaptureItemSpawner:capture_item_spawner_device = capture_item_spawner_device{}
# Elemento que flota sobre la cabeza de un jugador cuando lleva un objeto consigo del
# CaptureItemSpawner.
@editable
CaptureItemIndicator:creative_prop = creative_prop{}
# Indicador que muestra en el mapa dónde están los objetivos de cada equipo.
@editable
MapIndicator:map_indicator_device = map_indicator_device{}
# Frecuencia con la que el CaptureItemIndicator actualiza su posición.
@editable
UpdateRateSeconds:float = 0.15
# Indica a qué altura sobre la cabeza del jugador flota el CaptureItemIndicator.
@editable
VerticalOffset:float = 180.0
# Muestra un mensaje cuando un jugador consigue el objeto de captura.
@editable
ItemGrabbedMessageDevice:hud_message_device = hud_message_device{}
# Cantidad de tiempo que hay que esperar antes de devolver los indicadores de CaptureItem y Map.
# Un tiempo negativo indica que los indicadores nunca se devolverán a menos que el objetivo se
# recoja de nuevo.
@editable
ReturnTime:float = 10.0
# Otorga puntuación cuando un jugador captura el objeto de captura.
@editable
ScoreManagerDevice:score_manager_device = score_manager_device{}
OnBegin<override>()<suspends>:void=
CaptureItemSpawner.ItemPickedUpEvent.Subscribe(OnItemPickedUp)
CaptureItemSpawner.ItemCapturedEvent.Subscribe(OnItemCaptured)
CaptureItemSpawner.ItemDroppedEvent.Subscribe(OnItemDropped)
SpawnerTransform := CaptureItemSpawner.GetTransform()
# Teletransporta de vuelta al generador y oculta el CaptureItemIndicator debajo del mapa.
CaptureItemIndicator.MoveTo(SpawnerTransform.Translation + vector3{Z := VerticalOffset * 10.0}, SpawnerTransform.Rotation, UpdateRateSeconds)
MapIndicator.MoveTo(SpawnerTransform.Translation + vector3{Z := VerticalOffset * 10.0}, SpawnerTransform.Rotation, UpdateRateSeconds)
Logger.Print("Baliza devuelta para capturar el generador")
# Avisa a cada jugador cuando un jugador consigue el objetivo.
OnItemPickedUp(InAgent:agent):void=
Logger.Print("Objetivo conseguido")
if(FortCharacter := InAgent.GetFortCharacter[]):
ItemGrabbedMessageDevice.Show()
spawn{FollowCharacter(FortCharacter)}
# Cuando un jugador suelta un objeto, genera una función WaitForReturn()
# si el ReturnTime es mayor que 0.
OnItemDropped(InAgent:agent):void=
Logger.Print("Objetivo soltado")
if(ReturnTime >= 0.0):
spawn{WaitForReturn()}
else:
Logger.Print("El objetivo soltado no se devuelve.")
# Cuando se captura el objeto, otorga puntuación al equipo de captura y devuelve los indicadores.
OnItemCaptured(CapturingAgent:agent):void=
Logger.Print("Objetivo capturado")
ScoreManagerDevice.Activate()
ReturnIndicators()
# Espera una cantidad de tiempo ReturnTime y, a continuación, devuelve los indicadores.
WaitForReturn()<suspends>:void=
Logger.Print("Esperando a que se devuelvan los indicadores…")
# Devuelve los indicadores CaptureItem y Map si el objeto de captura
# no se recoge antes de que acabe el tiempo.
ShouldReturn:logic := race:
block:
Sleep(ReturnTime)
true
block:
CaptureItemSpawner.ItemPickedUpEvent.Await()
false
if(ShouldReturn?):
ReturnIndicators()
# Hace que el CaptureItemIndicator siga continuamente a un jugador por encima de su cabeza.
# Compite entre el bucle de actualización del CaptureItemIndictator, y si el jugador
# captura el objeto, lo suelta o es eliminado.
FollowCharacter(FortCharacter:fort_character)<suspends>:void=
Logger.Print("Se ha generado la función FollowCharacter")
race:
loop:
Transform := FortCharacter.GetTransform()
spawn{CaptureItemIndicator.MoveTo(Transform.Translation + vector3{Z := VerticalOffset}, Transform.Rotation, UpdateRateSeconds)}
spawn{MapIndicator.MoveTo(Transform.Translation + vector3{Z := VerticalOffset}, Transform.Rotation, UpdateRateSeconds)}
# Debemos asegurarnos de que este bucle se ejecuta solo una vez por cada actualización de la simulación, así que permanecemos inactivos durante un tic de juego.
Sleep(0.0)
CaptureItemSpawner.ItemCapturedEvent.Await()
CaptureItemSpawner.ItemDroppedEvent.Await()
FortCharacter.EliminatedEvent().Await()
Logger.Print("Objetivo soltado o capturado")
# Devuelve los indicadores del mapa y del objeto de captura a sus posiciones iniciales sobre los generadores.
ReturnIndicators():void=
SpawnerTransform := CaptureItemSpawner.GetTransform()
# Teletransporta de vuelta al generador y oculta el CaptureItemIndicator y el MapIndicator por encima del mapa.
spawn{CaptureItemIndicator.MoveTo(SpawnerTransform.Translation + vector3{Z := VerticalOffset * 10.0}, SpawnerTransform.Rotation, UpdateRateSeconds)}
spawn{MapIndicator.MoveTo(SpawnerTransform.Translation + vector3{Z := VerticalOffset * 10.0}, SpawnerTransform.Rotation, UpdateRateSeconds)}
Logger.Print("Indicadores devueltos para capturar el generador.")
Por tu cuenta
Al completar esta guía, habrás aprendido a utilizar Verse para crear un juego que equilibre equipos de jugadores de forma asimétrica.
Con lo que has aprendido, intenta hacer lo siguiente:
- Prueba a jugar con distintos parámetros para los infiltrados, atacantes y defensores para crear tu experiencia de juego ideal. ¿Y si los infiltrados tuvieran armas cuerpo a cuerpo? ¿Y si los defensores también fueran invisibles?
- ¿Pueden los infiltrados y los atacantes luchar por el mismo objetivo? ¿Puedes cambiar la condición de victoria?