Der letzte Schritt besteht darin, alle Stücke mit einem Game Manager zusammenzufügen. Der Game Manager steuert die Zuweisung der Gameplay-Objekte zu den Spielern und den Ablauf der Spielschleife. Dies sind die einzelnen Aufgaben des Game Managers:
Weist den Spielern Gameplay-Objekte wie Brett und Miniboard zu.
Steuert die Logik der Spielschleife, einschließlich dessen, was passiert, wenn ein Zug stattfindet.
Bestimmt, wann ein Spieler gewonnen hat und das Gameplay endet.
Zugtypen definieren
Während ein Spieler an der Reihe ist, wählt er eine Koordinate auf dem Spielbrett. Sobald eine Koordinate gewählt ist, gibt es zwei verschiedene Zugtypen:
Angriff: Versucht, die Spielfigur an der angegebenen Position zu zerstören.
Anzeigen: Zeigt alle Spielfiguren in einem bestimmten Radius um eine bestimmte Position auf.
Füge im Modul DataTypes die folgende Enum hinzu, die Zugtypen definiert:
using{/Verse.org/Simulation}
using{/Verse.org/Random}
using{/UnrealEngine.com/Temporary/SpatialMath}
DataTypes<public> := module:
...
move_type<public> := enum<open>:
Attack
Da diese Enum offen ist, kannst du in Zukunft jederzeit mehr Zugtypen hinzufügen.
Game Manager erstellen
Erstelle als Nächstes eine neue Verse-Datei mit dem Namen game_manager.verse und füge ein neues Kreativmodus-Gerät mit dem Namen game_manager hinzu. Dies ist ein einzelnes Objekt, das in der Spielwelt existiert, um den Ablauf des Spiels zu steuern.
Definiere Pro-Spieler-Objekte
Jeder Spieler hat mehrere Objekte, die ihm zugeordnet sind, darunter ein Spielbrett und ein Miniboard,aber auch, welche Felder er angegriffen hat, ein Event, um anzuzeigen, dass ein Zug gemacht wurde, und ein Event, um anzuzeigen, dass sich die gewählte Koordinate geändert hat. Definiere eine neue Klasse namens 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){}Definiere den Game Manager
Die Game-Manager-Klasse muss Spieler ihren Spielerobjekten zuordnen. Ein idealer Weg, einen Spieler einem Objekt in Verse zuzuordnen, ist eine weak_map. Füge die folgenden Felder zu deiner Game-Manager-Klasse hinzu:
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){}
Spielerobjekte zuweisen
Wenn Spieler beitreten, weise jedem Spieler Objekte von den PerPlayerObjects bis zum PerPlayerManagement zu. Hole zuerst alle Spieler im Spiel und weise dann jedem Spieler Objekte zu. Wenn nicht genügend Spielerobjekte vorhanden sind, behandle den Fehler. Füge die folgende AssignPlayerObjects-Funktion zu deinem Game Manager hinzu:
using { /Verse.org/Simulation }
using { /Fortnite.com/Devices }
...
game_manager := class(creative_device):
AssignPlayerObjects():void =
for (Index -> Player : GetPlayspace().GetPlayers()):
if:
Siegbedingung definieren
Als nächstes musst du bestimmen, wann ein Spieler die Siegbedingung erfüllt hat. Ein Spieler hat gewonnen, wenn es keine Spielfiguren mehr gibt, die er auf dem Brett finden und zerstören kann. Dies geschieht durch direkte Abfrage der Länge des Arrays Pawns im Spielbrett. Füge dem Game Manager die folgende WinConditionMet-Funktion hinzu:
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}")
Diese Funktion ist genau dann erfolgreich, wenn es keine Spielfiguren mehr gibt, die der Input-Spieler finden und zerstören muss.
Beim Angriffszug
Nachdem du nun weißt, wie du die Spielobjekte den einzelnen Spielern zuweist und wie du bestimmst, ob ein Spieler gewonnen hat oder nicht, musst du als Nächstes definieren, was während eines Angriffszugs passiert. Wenn ein Spieler eine der Kacheln seines Gegners angreift, laufen die folgenden Schritte ab:
Bestimme, ob sich an der Angriffskoordinate eine Spielfigur auf dem Brett befindet.
Wenn ja:
Entferne die Spielfigur vom Brett des Spielers.
Setze eine Treffermarkierung auf das Miniboard des Gegners.
Wenn nein:
Setzt eine Fehlschlag-Markierung auf das Miniboard des Gegners.
Füge eine Funktion namens OnAttack zu deiner game_manager-Klasse hinzu, mit der folgenden Definition:
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}")
Beim Anzeigezug
Der andere Zugtyp ist der Anzeigezug. Dies erfordert einige zusätzliche Einstellungen, um den Angriffszug zu überarbeiten. Füge zunächst drei neue Funktionen zu deinem Modul Hilfsfunktionen hinzu:
operator'-': Definiert die binäre Subtraktionsoperation für zweitile_coordinate-Objekte.Abs: Ermittelt den komponentenweisen Absolutwert einertile_coordinate.ManhattanDistance: Ermittelt die Manhattan- oder Taxi-Distanz zwischen zweitile_coordinate-Objekten.
UtilityFunctions<public> := module:
using{DataTypes}
...
Abs(TileCoordinate:tile_coordinate)<transacts>:tile_coordinate =
tile_coordinate:
Left := Abs(TileCoordinate.Left)
Forward := Abs(TileCoordinate.Forward)
Die Manhattan-Distanz berechnet die Entfernung zwischen zwei tile_coordinate-Objekten, indem sie entlang der Kardinalrichtungen auf dem Kachelraster navigiert. Weitere Informationen findest du unter https://en.wikipedia.org/wiki/Taxicab_geometry.
Nachdem die Hilfsprogramme nun definiert sind, definiere das Verhalten der OnReveal-Funktion. When ein Spieler Spielfiguren in einem bestimmten Radius einer tile_coordinate für seinen Feind aufdeckt, laufen die folgenden Schritte ab:
Finde alle Spielfiguren auf dem Brett des Spielers, die sich gemäß der
ManhattanDistanceinnerhalb einer bestimmtenRevealDistancevon der Input-Koordinate befinden.Für jede Spielfigur in dieser Entfernung wird ein Enthüllen-Effekt gespielt.
Füge eine Funktion namens OnReveal zu deiner Klasse game_manager hinzu, mit der folgenden Definition:
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:
Zug des Spielers
Als Nächstes setze zusammen, wie der Zug eines Spielers aussieht. Wenn ein Spieler an der Reihe ist, warte auf eines der beiden verschiedenen Events, die signalisiert werden können: MoveEvent oder CoordinateChangeEvent. Immer wenn eines dieser Events signalisiert wird, brich das andere Event ab, stelle die Events nebeneinander in eine race-Bedingung. Wenn eine Koordinatenänderung signalisiert wird, sollte derselbe Spieler weiterspielen, bis er einen Zugtyp wählt. Deshalb darf der nächste Spieler erst dann an der Reihe sein, wenn Angriff oder Anzeigen gewählt wird.
Füge die Funktion OnTurn zu deiner Klasse game_manager hinzu, mit der folgenden Definition:
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)
Die Spielschleife definieren
Jetzt, wo der Zug des Spielers definiert ist, kannst du endlich die primäre Spielschleife konstruieren. Innerhalb der Spielschleife laufen die folgenden Schritte ab:
Rufe alle Spieler ab.
Weise einen Spieler zu, der an der Reihe ist, und den anderen, der darauf wartet, dass der erste Spieler seinen Zug macht.
Schleife, bis einer der Spieler gewinnt, wobei jeder abwechselnd dran ist.
Füge dazu die folgende GameLoop -Funktion zu deiner game_manager-Klasse hinzu:
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 starten
Als letztes musst du jedem Spieler Spielerobjekte zuweisen und die Spielschleife beginnen. Dies geschieht automatisch, indem du Aufrufe von AssignPlayerObjects und GameLoop zur OnBegin-Funktion hinzufügst:
# Runs when the device is started in a running game
OnBegin<override>()<suspends>:void=
AssignPlayerObjects()
GameLoop()Überblick
Auf dieser Seite haben wir dich durch die folgenden Schritte geführt:
Die Spielzüge definieren.
Die Spielschleife konstruieren.
Bestimmen, wann eine Gewinnbedingung erfüllt ist.
Es gibt immer noch viele Dinge, die du tun kannst, um dieses Erlebnis zu etwas ganz Besonderem zu machen, darunter:
Benutzeroberfläche entwerfen und implementieren.
Variationen entwerfen, wann und wie die Spielerzüge an den Game Manager gemeldet werden.
Die Spielwelt und die Einstellungen gestalten.
Effekte für Angriff- und Anzeigezüge entwerfen.
Musikdesign und Set-Einrichtung hinzufügen.
Du kannst auf diesen grundlegenden Gameplay-Klassen aufbauen, sie umbauen, Teile von ihnen verwenden und sie zu etwas ganz Eigenem machen.
Dateien
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