El último paso es juntar todas las piezas mediante un administrador de juego. El administrador de juego controla la asignación de objetos de juego a los jugadores y el flujo del bucle de juego. En concreto, el administrador de juego:
Asigna objetos de juego, como el tablero y el minitablero, a los jugadores.
Controla la lógica del bucle del juego, incluyendo lo que ocurre cuando se realiza un movimiento.
Determina cuándo un jugador ha ganado y finaliza la partida.
Cómo definir los tipos de movimiento
Durante el turno de un jugador, este elige una coordenada en el tablero de juego. Una vez que se elige una coordenada, hay dos tipos diferentes de movimientos:
Ataque: intenta destruir el peón en la ubicación indicada.
Revelar: revela todos los peones dentro de un radio determinado de una ubicación dada.
En el módulo DataTypes, añada el siguiente enum que define los tipos de movimiento:
using{/Verse.org/Simulation}
using{/Verse.org/Random}
using{/UnrealEngine.com/Temporary/SpatialMath}
DataTypes<public> := module:
...
move_type<public> := enum<open>:
Attack
Como este enum es abierto, siempre puedes añadir más tipos de movimientos en el futuro.
Cómo crear el administrador de juego
A continuación, crea un nuevo archivo de Verse con el nombre game_manager.verse y añade un nuevo dispositivo del modo Creativo con el nombre game_manager. Este será un objeto único que residirá en el mundo del juego para controlar el flujo del mismo.
Cómo definir objetos por jugador
Cada jugador tiene varios objetos asociados, incluyendo un tablero de juego y un minitablero, pero también las casillas que ha atacado, un evento para indicar que se ha realizado un movimiento y un evento para indicar que la coordenada elegida ha cambiado. Define una nueva clase con el nombre per_player_objects:
using { /Verse.org/Simulation }
per_player_objects<public> := class:
@editable
Board<public>:board
@editable
Miniboard<public>:miniboard
var AttackedTiles<public>:[]tile_coordinate = array{}
MoveEvent<public>:event(tuple(tile_coordinate, move_type)) = event(tuple(tile_coordinate, move_type)){}
CoordinateChangeEvent<public>:event(tile_coordinate) = event(tile_coordinate){}Cómo definir el administrador de juego
La clase de administrador de juego necesita asociar a los jugadores con sus objetos de jugador. Una forma ideal de asociar un jugador a un objeto en Verse es mediante un weak_map. Añade los siguientes campos a tu clase de administrador de juego:
using { /Verse.org/Simulation }
per_player_objects<public> := class:
@editable
Board<public>:board
@editable
Miniboard<public>:miniboard
var AttackedTiles<public>:[]tile_coordinate = array{}
MoveEvent<public>:event(tuple(tile_coordinate, move_type)) = event(tuple(tile_coordinate, move_type)){}
CoordinateChangeEvent<public>:event(tile_coordinate) = event(tile_coordinate){}
Cómo asignar objetos de jugador
A medida que los jugadores se unen, asigna objetos de jugador a cada jugador desde PerPlayerObjects al objeto PerPlayerManagement. Primero, obtén todos los jugadores de la partida y, a continuación, asigna objetos de jugador a cada jugador. En caso de que no haya suficientes objetos de jugador, gestiona el error. Añade la siguiente función AssignPlayerObjects a tu administrador de juego:
using { /Verse.org/Simulation }
using { /Fortnite.com/Devices }
...
game_manager := class(creative_device):
AssignPlayerObjects():void =
for (Index -> Player : GetPlayspace().GetPlayers()):
if:
Cómo definir la condición de victoria
Lo siguiente que hay que hacer es determinar cuándo un jugador ha cumplido la condición de victoria. Un jugador gana si no quedan más peones por encontrar y destruir en el tablero. Esto se hace consultando directamente la longitud de la matriz Pawns en el tablero. Añade la siguiente función WinConditionMet al administrador de juego:
using { /Verse.org/Simulation }
using { /Fortnite.com/Devices }
...
game_manager := class(creative_device):
WinConditionMet(Player:player)<decides><transacts>:void =
# Player wins if no pawns remain
Print("Pawns remaining: {PerPlayerManagement[Player].Board.Pawns.Length}")
Esta función tiene éxito si y solo si no quedan más peones por encontrar y eliminar para el jugador de entrada.
Movimiento de ataque
Ahora que ya sabes cómo asignar los objetos de juego a cada jugador y determinar si un jugador ha ganado o no, el siguiente paso es definir qué ocurre durante un movimiento de ataque. Cuando un jugador ataca una de las casillas de su oponente, se producen los siguientes pasos:
Determinar si hay un peón en el tablero en la coordenada de ataque.
En caso afirmativo:
Eliminar el peón del tablero del jugador.
Colocar un marcador de impacto en el minitablero del oponente.
En caso negativo:
Colocar un marcador de fallo en el minitablero del oponente.
Añade una función con el nombre OnAttack a tu clase game_manager con la siguiente definición:
OnAttack(Instigator:player, Recipient:player, TileCoordinate:tile_coordinate):void =
if:
InstigatorObjects := PerPlayerManagement[Instigator]
RecipientObjects := PerPlayerManagement[Recipient]
then:
# Determine if the attack is a hit
var MarkerType:marker_type = marker_type.Miss
Print("Attack coordinate: Left: {TileCoordinate.Left}, Forward: {TileCoordinate.Forward}")
Movimiento de revelación
El otro tipo de movimiento es el movimiento de revelar. Esto requiere una configuración adicional para funcionar sobre el movimiento de ataque. Primero, añade tres nuevas funciones al módulo UtilityFunctions:
operator'-': define la operación binaria de resta para dos objetos detile_coordinate.Abs: obtiene el valor absoluto componente por componente de unatile_coordinate.ManhattanDistance: obtiene la distancia Manhattan o Taxicab entre dos objetos detile_coordinate.
UtilityFunctions<public> := module:
using{DataTypes}
...
Abs(TileCoordinate:tile_coordinate)<transacts>:tile_coordinate =
tile_coordinate:
Left := Abs(TileCoordinate.Left)
Forward := Abs(TileCoordinate.Forward)
La distancia Manhattan calcula la distancia entre dos objetos de tile_coordinate desplazándose por los puntos cardinales de la cuadrícula de casillas. Para obtener más información, consulta https://en.wikipedia.org/wiki/Taxicab_geometry.
Ahora que las utilidades están definidas, define el comportamiento de la función OnReveal. Cuando un jugador decide revelar peones en un radio determinado de una tile_coordinate de su enemigo, se producen los siguientes pasos:
Busca todos los peones del tablero del jugador que se encuentren a una
RevealDistanceestablecida desde la coordenada de entrada según laManhattanDistance.Por cada peón que se encuentre dentro de esa distancia, se reproduce un efecto de revelación.
Añade una función con el nombre OnReveal a tu clase game_manager con la siguiente definición:
OnReveal(Instigator:player, Recipient:player, TileCoordinate:tile_coordinate):void =
if:
InstigatorObjects := PerPlayerManagement[Instigator]
RecipientObjects := PerPlayerManagement[Recipient]
then:
for:
Pawn : InstigatorObjects.Board.Pawns
PawnTileCoordinate := InstigatorObjects.Board.GetTileCoordinate[Pawn]
ManhattanDistance(PawnTileCoordinate, TileCoordinate) < RevealDistance
do:
Turno de jugador
A continuación, organiza cómo es el turno de un jugador. Cuando sea el turno de un jugador, espera a que se produzca uno de los dos eventos diferentes que se pueden señalar: MoveEvent o CoordinateChangeEvent. Siempre que se señale uno de estos eventos, abandona el otro evento y coloca estos eventos uno al lado del otro dentro de una condición race. Cuando se señala un cambio de coordenadas, el mismo jugador debe seguir jugando hasta que elija un tipo de movimiento. Por lo tanto, solo se pasa al siguiente jugador cuando se selecciona atacar o revelar.
Añade la función OnTurn a tu clase game_manager con la siguiente definición:
OnTurn(Player:player, Opponent:player)<suspends>:void =
if (PlayerObjects := PerPlayerManagement[Player]):
loop:
var Continue:logic = false
race:
block:
# Listens for a call to PerPlayerManager[Player].CoordinateChangeEvent.Signal(:tile_coordinate)
TileCoordinate := PlayerObjects.CoordinateChangeEvent.Await()
block:
# Listens for a call to PerPlayerManager[Player].MoveEvent.Signal(:tile_coordinate,:move_type)
Cómo definir el bucle del juego
Ahora que ya está definido el turno del jugador, por fin puedes construir el bucle del juego principal. Dentro del bucle del juego, se producen los siguientes pasos:
Obtener todos los jugadores.
Asignar a un jugador para que juegue su turno y al otro para que espere a que el primer jugador realice su movimiento.
Repetir hasta que uno de los jugadores gane, jugando por turnos alternos.
Para ello, añade la siguiente función GameLoop a tu clase game_manager:
GameLoop()<suspends>:void =
Players := GetPlayspace().GetPlayers()
if :
Players.Length = 2
var TurnPlayer:player = Players[0]
var OtherPlayer:player = Players[1]
then:
loop:
OnTurn(TurnPlayer, OtherPlayer)
if (WinConditionMet[TurnPlayer]):
Inicio de la partida
Lo último que hay que hacer es asignar objetos de jugador a cada jugador e iniciar el bucle del juego. Haz que esto se haga automáticamente añadiendo llamadas a AssignPlayerObjects y GameLoop en la función OnBegin:
# Runs when the device is started in a running game
OnBegin<override>()<suspends>:void=
AssignPlayerObjects()
GameLoop()Resumen
En resumen, esta página te ha guiado por los siguientes pasos:
Definir los movimientos del juego.
Construir el bucle del juego.
Determinar cuándo se cumple una condición de victoria.
Todavía quedan muchas cosas que puedes hacer para que esta experiencia sea única y personal, entre ellas:
Diseñar e implementar una interfaz de usuario.
Conectar cuándo y cómo se señalan los movimientos del jugador al administrador de juego.
Diseñar el mundo y los ajustes del juego.
Crear efectos para los movimientos de ataque y revelación.
Añadir diseño de música y decoración del escenario.
No dudes en partir de estas clases básicas de juego, reconstruirlas, utilizar partes de ellas y personalizarlas a tu gusto.
.udatasmith
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { DataTypes }
using { UtilityFunctions }
per_player_objects<public> := class:
@editable
Board<public>:board
@editable