Il passaggio finale consiste nell'assemblare tutti i pezzi attraverso una Gestione gioco. La Gestione gioco controlla l'assegnazione degli oggetti di gameplay ai giocatori e il flusso del loop di gioco. Nello specifico, la Gestione gioco:
Assegna ai giocatori oggetti di gameplay, come tabellone e minitabellone.
Controlla la logica del loop di gioco, incluso ciò che accade quando si verifica una mossa.
Determina quando un giocatore ha vinto e il quando termina il gameplay.
Definire i tipi di movimento
Durante il proprio turno, il giocatore sceglie una coordinata sul tabellone. Una volta scelta la coordinata, sono disponibili due diversi tipi di movimento:
Attacco: tenta di distruggere la pedina in una determinata posizione.
Rivelazione: rivela tutte le pedine entro un certo raggio da una determinata posizione.
Nel modulo DataTypes, aggiungi l'enum seguente definendo i tipi di movimento:
using{/Verse.org/Simulation}
using{/Verse.org/Random}
using{/UnrealEngine.com/Temporary/SpatialMath}
DataTypes<public> := module:
...
move_type<public> := enum<open>:
Attack
Dato che questo enum è aperto, puoi sempre aggiungere altri tipi di mosse in futuro.
Creare la Gestione gioco
Crea un nuovo file Verse denominato game_manager.verse e aggiungi un nuovo dispositivo creativo denominato game_manager. Corrisponderà a un singolo oggetto individuabile nel mondo di gioco utile a controllare il flusso dell'esperienza.
Definire oggetti per ogni giocatore
I giocatori dispongono di diversi oggetti associati a loro, tra cui un tabellone di gioco e un minitabellone, i tile su cui hanno sferrato un attacco, un evento per indicare che è stata eseguita una mossa e un evento per indicare che la coordinata scelta è cambiata. Definisci una nuova classe denominata 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){}Definire la Gestione gioco
La classe Gestione gioco deve associare i giocatori ai propri oggetti giocatore. Il modo ideale per associare un giocatore a un oggetto in Verse è attraverso un sistema di weak_map. Aggiungi i seguenti campi alla classe Gestione gioco:
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){}
Assegnare oggetti giocatore
Quando i giocatori partecipano alla sessione, assegna gli oggetti giocatore a ogni giocatore da un oggetto PerPlayerObjects a un altro di tipo PerPlayerManagement. Innanzitutto, raggruppa tutti i giocatori in partita, quindi assegna gli oggetti giocatore a ognuno di essi. Se non sono presenti abbastanza oggetti giocatore, gestisci l'errore. Aggiungi la seguente funzione AssignPlayerObjects alla Gestione gioco:
using { /Verse.org/Simulation }
using { /Fortnite.com/Devices }
...
game_manager := class(creative_device):
AssignPlayerObjects():void =
for (Index -> Player : GetPlayspace().GetPlayers()):
if:
Definire la condizione di vittoria
La prossima operazione da svolgere è determinare quando un giocatore ha soddisfatto la condizione di vittoria. Un giocatore ha vinto se non ci sono più pedine da trovare e distruggere sul tabellone. Questo avviene interrogando direttamente la lunghezza dell'array Pawns sul tabellone. Aggiungi la seguente funzione WinConditionMet alla Gestione gioco:
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}")
Questa funzione ha esito positivo se e solo se non ci sono più pedine da trovare ed eliminare per il giocatore di input.
Mossa di attacco
Ora che sai come assegnare gli oggetti di gioco a ogni giocatore e determinarne la vittoria in base a dei criteri specifici, il passo successivo è definire cosa succede durante una mossa di attacco. Quando un giocatore attacca uno dei tile dell'avversario, si verificano i seguenti passaggi:
Determina se è presente una pedina sul tabellone in corrispondenza delle coordinate di attacco.
Se sì:
Rimuovi la pedina dal tabellone del giocatore.
Imposta un indicatore di colpo inflitto sul minitabellone dell'avversario.
Se no:
Imposta un indicatore di colpo mancato sul minitabellone dell'avversario.
Aggiungi una funzione denominata OnAttack alla classe game_manager con la seguente definizione:
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}")
Mossa di rivelazione
La rivelazione è un altro tipo di mossa. Richiede alcune configurazioni aggiuntive per lavorare sulla mossa di attacco. Per iniziare, aggiungi tre nuove funzioni al modulo UtilityFunctions:
operator'-': definisce l'operazione binaria di sottrazione per due oggettitile_coordinate.Abs: ottiene il valore assoluto per componenti di unatile_coordinate.ManhattanDistance: calcola la distanza Manhattan o geometria del taxi tra due oggettitile_coordinate.
UtilityFunctions<public> := module:
using{DataTypes}
...
Abs(TileCoordinate:tile_coordinate)<transacts>:tile_coordinate =
tile_coordinate:
Left := Abs(TileCoordinate.Left)
Forward := Abs(TileCoordinate.Forward)
Con "distanza Manhattan" si intende il calcolo della distanza tra due oggetti tile_coordinate che avviene navigando lungo le direzioni cardinali sulla griglia di tile. Per maggiori informazioni, vedi https://en.wikipedia.org/wiki/Taxicab_geometry.
Ora che le utilità sono definite, determina il comportamento della funzione OnReveal. Quando un giocatore sceglie di rivelare le pedine in un certo raggio di tile_coordinate per il nemico, opera i seguenti passaggi:
Cerca tutte le pedine sul tabellone del giocatore che si trovano all'interno di una
RevealDistanceimpostata dalle coordinate di input in base allaManhattanDistance.Per ogni pedina entro quella distanza, riproduci un effetto di rivelazione.
Aggiungi una funzione denominata OnReveal alla classe game_manager con la seguente definizione:
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 giocatore
Ora, ricrea l'esperienza di un gioco a turni. Quando è il turno di un giocatore, attendi che uno dei due eventi tra MoveEvent o CoordinateChangeEvent venga segnalato. Ogni volta che questo avviene, abbandona l'altro evento e affianca questi eventi all'interno di una condizione race. Quando viene segnalato un cambio di coordinate, lo stesso giocatore deve continuare a giocare fino a quando non sceglie un tipo di mossa. Passa dunque al giocatore successivo solo quando è selezionato attacco o rivelazione.
Aggiungi la funzione OnTurn alla tua classe game_manager con la seguente definizione:
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)
Definire il loop di gioco
La strutturazione del turno giocatore è completa, puoi finalmente costruire il loop principale. All'interno del loop di gioco, segui questi passaggi:
Raduna tutti i giocatori.
Assegna a un giocatore il proprio turno e comunica a un altro giocatore di aspettare che il primo giocatore si muova.
Esegui un loop fino a quando uno dei giocatori vince e in cui ognuno di essi muove a turni alternati.
Perché questo sia possibile, aggiungi la seguente funzione GameLoop alla classe 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]):
Gameplay iniziale
Ora non manca che assegnare gli oggetti giocatore a ogni giocatore per iniziare il loop di gioco. Puoi farlo automaticamente aggiungendo chiamate ad AssignPlayerObjects e GameLoop alla funzione OnBegin:
# Runs when the device is started in a running game
OnBegin<override>()<suspends>:void=
AssignPlayerObjects()
GameLoop()Riepilogo
In sintesi, questa pagina ti ha guidato attraverso i seguenti passaggi:
Definisci le mosse del gioco.
Costruisci il loop di gioco.
Determina quando viene soddisfatta una condizione di vittoria.
Per rendere questa esperienza unica, hai a disposizione ancora molte opzioni; ad esempio:
Progettazione e implementazione di un'interfaccia utente.
Quando e come le mosse dei giocatori vengono segnalate alla Gestione gioco.
Progettazione del mondo di gioco e dell'ambientazione.
Creazione di effetti per attacchi e rivelazioni.
Aggiunta di music design e preparazione del set.
Puoi tranquillamente sviluppare a partire da queste classi di gameplay principali, modificarle, riutilizzarne i componenti e personalizzarle come preferisci.
File
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