El paso final es unir todas las piezas a través de un administrador de juego. El administrador de juego controla la asignación de objetos a los jugadores y el flujo del bucle. Específicamente, el administrador de juego:
Asigna objetos de juego a los jugadores, como el tablero y el minitablero.
Controla la lógica del bucle de juego, incluido lo que sucede cuando se produce un movimiento.
Determina cuándo un jugador gana y el juego termina.
Definición de 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 de movimiento diferentes:
Ataque: intenta destruir el peón en la ubicación dada.
Revelación: revela todos los peones dentro de cierto radio de una ubicación determinada.
En el módulo DataTypes, añade la 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 está abierto, siempre puedes añadir más tipos de movimiento en el futuro.
Creación del administrador del juego
A continuación, crea un nuevo archivo de Verse llamado game_manager.verse y añade un nuevo dispositivo del modo Creativo llamado game_manager. Este será un único objeto que vivirá en el entorno del juego para controlar el flujo del juego.
Definición de objetos por jugador
Cada jugador tiene varios objetos asociados, incluidos un tablero de juego y un minitablero, pero también qué cuadros ha atacado, un evento para indicar que se realizó un movimiento y un evento para indicar que se cambió la coordenada elegida. Define una nueva clase llamada 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){}Definición del administración del juego
La clase de administrador de juego necesita asociar jugadores a sus objetos de jugador. Una forma ideal de asociar un jugador a un objeto en Verse es a través de un weak_map. Añade los siguientes campos a tu clase de administrador de juegos:
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){}
Asignación de objetos de jugador
A medida que los jugadores se unan, asigna objetos de jugador a cada jugador desde el PerPlayerObjects al objeto PerPlayerManagement. Primero, reúne a todos los jugadores del juego y, luego, asigna objetos de jugador a cada jugador. Si no hay suficientes objetos de jugador, resuelve 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:
Definición de condición de victoria
Lo siguiente que hay que hacer es determinar cuándo un jugador cumplió la condición de victoria. Un jugador gana si no tiene más peones que encontrar y destruir en el tablero. Esto se hace consultando directamente la longitud de la matriz de peones en el tablero. Añade la siguiente función WinConditionMet al administrador del 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 que encontrar y eliminar para el jugador de entrada.
Al moverse para atacar
Ahora que sabes cómo asignar los objetos a cada jugador y determinar si un jugador ganó o no, el siguiente paso es definir qué sucede durante un movimiento de ataque. Cuando un jugador realiza un ataque a uno de los cuadros de su oponente, ocurre lo siguiente:
Determina si hay un peón en el tablero en la coordenada de ataque.
En caso afirmativo:
Elimina el peón del tablero del jugador.
Define un marcador de golpe en el minitablero del rival.
Si no:
Define un marcador de fallo en el minitablero del rival.
Añade una función llamada 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}")
Al mostrar movimiento
El otro tipo de movimiento es el movimiento de revelación. Esto requiere una configuración adicional para trabajar sobre el movimiento de ataque. Primero, añade tres nuevas funciones a tu módulo UtilityFunctions:
operator'-': define la operación binaria de resta para dos objetostile_coordinate.Abs: obtiene el valor absoluto por componentes de unatile_coordinate.ManhattanDistance: obtén la distancia de Manhattan o Taxicab entre dos objetostile_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 de Manhattan calcula la distancia entre dos objetos tile_coordinate al navegar a lo largo de las direcciones cardinales en la cuadrícula de cuadros. Consulta https://en.wikipedia.org/wiki/Taxicab_geometry para obtener más información.
Ahora que las utilidades están definidas, define el comportamiento de la función OnReveal. Cuando un jugador elige revelar peones en un radio determinado de una tile_coordinate para su enemigo, se producen los siguientes pasos:
Busca todos los peones en el tablero del jugador que se encuentren dentro de una
RevealDistanceestablecida desde la coordenada de entrada según laManhattanDistance.Por cada peón dentro de esa distancia, reproduce un efecto de revelación.
Añade una función llamada 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 del jugador
A continuación, decide cómo es el turno de un jugador. Cuando sea el turno de un jugador, espera uno de los dos eventos diferentes que podrían señalarse: MoveEvent o CoordinateChangeEvent. Cada vez que se señale uno de estos eventos, abandona el otro evento; coloca ambos eventos en paralelo dentro de una condición de carrera. 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 pasa al siguiente jugador cuando se selecciona ataque 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)
Definición del bucle de juego
Por fin puedes construir el bucle de juego principal ahora que el turno del jugador está definido. Dentro del bucle de juego, ocurren los siguientes pasos:
Obtén todos los jugadores.
Asigna a un jugador para que tenga su turno y al otro para que espere a que el primer jugador se mueva.
Repite el bucle hasta que uno de los jugadores gane, haciendo que cada uno tome 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]):
Jugabilidad inicial
Lo último que queda por hacer es asignar objetos jugador a cada jugador y comenzar el bucle de juego. Haz esto automáticamente añadiendo llamadas a AssignPlayerObjects y GameLoop a 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 llevó a través de 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 hay muchas cosas que puedes hacer para que esta experiencia sea única, entre ellas, las siguientes:
Diseñar e implementar una interfaz de usuario.
Conectar cuándo y cómo se señalan los movimientos del jugador al administrador del juego.
Diseñar el entorno y la ambientación del juego.
Crear efectos para ataques y revelaciones.
Añadir diseño musical y definir la escenografía.
Siéntete libre de construir a partir de estas clases base de jugabilidad, reconstruirlas, usar partes de ellas y darles tu toque personal.
Archivos
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