Eine Zone ist ein Bereich auf der Karte (dargestellt durch ein Gerät), in dem der Spieler Gegenstände abholen oder abgeben kann. Wenn du diesen Schritt im Tutorial Time Trial: Pizza Pursuit abgeschlossen hast, weißt du, wie du diese Abhol- und Lieferzonen erstellst und sie für den Spieler aktivierst bzw. deaktivierst.
Verwendung der Abstraktion zur Erstellung einer Zonenklasse
Abstraktion ist ein Programmierprinzip, bei dem unnötige Details vor einem Benutzer verborgen werden, wobei der Benutzer die verborgenen Komplexitäten nicht verstehen muss. Abstraktion beschreibt, was etwas ist, ohne zu wissen, wie es funktioniert. Du kannst beispielsweise Geld in einen Verkaufsautomaten stecken und ein Leckerli herausholen, ohne zu verstehen, wie die Mechanik funktioniert.
Pizza PursuitIn Time Trial: Pizza Pursuit gibt es zwei Arten von Zonen: Abholpunktzonen, die das Gegenstands-Spawnpunkt-Gerät verwenden, und Lieferzonen, die das Eroberungsgebiet-Gerät verwenden. Da sich diese Zonen gleich verhalten, obwohl es sich um unterschiedliche Geräte handelt, d. h. beide können aktiviert und deaktiviert werden, kannst du eine class erstellen, die dieses Verhalten in ein generisches Zonenobjekt abstrahiert, das die spezifischen Geräteinteraktionen verarbeitet.
Wenn du dieses Verhalten in eine Klasse abstrahierst, hast du nur eine Stelle, an der du ändern kannst, welche Art von Geräten du verwendest. Diese Implementierung bedeutet auch, dass du ihre Besonderheiten ändern kannst, ohne deinen anderen Code zu ändern, da jeder Code, der diese Klasse verwendet, nur die Aktivierungs-/Deaktivierungsfunktionen kennt.
Befolge diese Schritte, um diese Zonenklasse zu erstellen:
Erstelle eine neue leere Verse-Datei mit dem Namen pickup_delivery_zone.verse und öffne sie in Visual Studio Code.
Erstelle in der Verse-Datei eine neue Klasse mit dem Namen
base_zonemit dem Bezeichnerpublicund füge hinzu:Eine
creative_object_interface-Konstante namensActivatorDevicemit dem Bezeichnerpublic, um das in der Zone verwendete Gerät zu speichern.Ein Event mit dem Namen
ZoneCompletedEventmit dem Bezeichnerpublic, um zu signalisieren, wenn der Spieler mit dieser Zone interagiert, z.B. Gegenstände aufhebt oder abliefert.Eine Funktion mit dem Namen
ActivateZone(), die den Rückgabetypvoidund den Bezeichnerpublichat, um das für die Zone verwendete Gerät zu aktivieren.Eine Funktion mit dem Namen
DeactivateZone(), die den Rückgabetypvoidund den Bezeichnerpublichat, um das für die Zone verwendete Gerät zu deaktivieren.Versebase_zone<public> := class: ActivatorDevice<public> : creative_object_interface ZoneCompletedEvent<public> : event(base_zone) = event(base_zone){} ActivateZone<public>() : void = Print("Zone activated") DeactivateZone<public>() : void = Print("Zone deactivated")Wenn eine Klasse und ihre Mitglieder den Bezeichner
publichaben, dann sind sie von anderem Code aus allgemein zugänglich. Weitere Einzelheiten findest du hier: Specifiers and Attributes.
Überprüfe in der Funktion
ActivateZone(), ob es sich bei demActivatorDeviceum ein Eroberungsgebiet-Gerät oder ein Gegenstands-Spawnpunkt-Gerät handelt, indem du dasActivatorDevicein die verschiedenen Typen umwandelst und die FunktionEnable()für das umgewandelte Gerät aufrufst. Mach dasselbe mit der FunktionDeactivateZone(), außer dass du die FunktionDisable()aufrufst.Versebase_zone<public> := class: ActivatorDevice<public> : creative_object_interface ActivateZone<public>() : void = Print("Zone activated") if (CaptureArea := capture_area_device[ActivatorDevice]): CaptureArea.Enable() else if (ItemSpawner := item_spawner_device[ActivatorDevice]): ItemSpawner.Enable()Erstelle eine Funktion mit dem Namen
WaitForZoneCompleted(), die den Bezeichnerprivateund den Bezeichnersuspendshat. Diese Funktion signalisiert dasZoneCompletedEvent, wenn das gerätespezifische Ereignis eintritt. Diese Konfiguration bedeutet, dass anderer Code nur auf dasZoneCompletedEventwarten muss und sich nicht darum kümmert, welche Art von Event das zugrunde liegende Gerät verwendet.VerseWaitForZoneCompleted<private>(ZoneDeviceCompletionEventOpt : ?awaitable(agent))<suspends> : void = if (DeviceEvent := ZoneDeviceCompletionEventOpt?): DeviceEvent.Await() ZoneCompletedEvent.Signal(Self)Diese Funktion muss den Effekt
suspendshaben, umDeviceEvent.Await()aufrufen zu können.Aktualisiere
ActivateZone()mit einemspawn-Ausdruck, derWaitForZoneCompleted()mit dem entsprechenden Geräteevent für den Spieler aufruft, der mit dem Gerät interagiert:AgentEntersEventfür das Eroberungsgebiet-Gerät undItemPickedUpEventfür das Gegenstands-Spawnpunkt-Gerät.WaitForZoneCompletederwartet einenoption-Parameter (gekennzeichnet durch?) des Typsawaitable(agent), so dass jeder Typ, der die Schnittstelleawaitableimplementiert und dessen Parametertyp gleichagentist, übergeben werden kann. SowohlCaptureArea.AgentEntersEventals auchItemSpawner.ItemPickedUpEventerfüllen diese Bedingung, so dass wir sie als Parameter verwenden können. Dies ist ein weiteres Beispiel für Abstraktion.VerseActivateZone<public>() : void = Print("Zone activated") if (CaptureArea := capture_area_device[ActivatorDevice]): CaptureArea.Enable() spawn { WaitForZoneCompleted(option{CaptureArea.AgentEntersEvent})} else if (ItemSpawner := item_spawner_device[ActivatorDevice]): ItemSpawner.Enable() spawn { WaitForZoneCompleted(option{ItemSpawner.ItemPickedUpEvent}) }Füge ein weiteres Ereignis mit dem Namen
ZoneDeactivatedEventmit dem Bezeichnerprotectedhinzu. Dieses Ereignis ist notwendig, um die FunktionWaitForZoneCompleted()zu beenden, wenn die Zone deaktiviert wird, bevor der Spieler sie beendet hat. Melde dieses Ereignis in der FunktionDeactivateZone().VerseZoneDeactivatedEvent<protected> : event() = event(){} DeactivateZone<public>() : void = Print("Zone deactivated") if (CaptureArea := capture_area_device[ActivatorDevice]): CaptureArea.Disable() else if (ItemSpawner := item_spawner_device[ActivatorDevice]): ItemSpawner.Disable() ZoneDeactivatedEvent.Signal()Aktualisiere
WaitForZoneCompleted()mit einemrace-Ausdruck, so dass die Funktion entweder darauf wartet, dass der Spieler die Zone abschließt, oder dass die Zone deaktiviert wird. Mit demrace-Ausdruck werden der asynchrone FunktionsaufrufZoneDeactivatedEvent.Await()und derblock-Ausdruck mit dem Geräte-Event und dem SignalZoneCompletedEventgleichzeitig ausgeführt, aber der Ausdruck, der nicht zuerst beendet wird, wird abgebrochen.VerseWaitForZoneCompleted<private>(ZoneDeviceCompletionEventOpt : ?awaitable(agent))<suspends> : void = if (DeviceEvent := ZoneDeviceCompletionEventOpt?): race: block: DeviceEvent.Await() ZoneCompletedEvent.Signal(Self) ZoneDeactivatedEvent.Await()Erstelle schließlich einen Constructor für die Klasse
base_zone, der das FeldActivatorDeviceinitialisiert.VerseMakeBaseZone<constructor><public>(InActivatorDevice : creative_object_interface) := base_zone: ActivatorDevice := InActivatorDeviceEs folgt der vollständige Code für die Klasse
base_zone.Verse# A zone is an area of the map (represented by a device) that can be Activated/Deactivated and that provides events to signal when the zone has been "Completed" (can't be completed anymore until next activation). # Zone "Completed" depends on the device type (ActivatorDevice) for the zone. # Suggested usage: ActivateZone() -> ZoneCompletedEvent.Await() -> DeactivateZone() # base_zone<public> := class: ActivatorDevice<public> : creative_object_interface ZoneCompletedEvent<public> : event(base_zone) = event(base_zone){} GetTransform<public>() : transform = ActivatorDevice.GetTransform()
Zonen zur Laufzeit mit Gameplay-Tags finden
Da du nun eine Möglichkeit hast, Zonen zu erstellen und sie zu aktivieren/deaktivieren, lass uns eine Möglichkeit hinzufügen, alle Zonen, die du im Level markiert hast, zu initialisieren und lernen, wie du die nächste zur Aktivierung auswählst.
Dieses Beispiel zeigt, wie man dies mit einer Klasse macht, die für die Erstellung der Zonen und die Auswahl der nächsten zu aktivierenden Zone verantwortlich ist.
Befolge diese Schritte, um die Klasse für die Erstellung und Auswahl von Zonen zu erstellen:
Erstelle eine neue Klasse mit dem Namen
tagged_zone_selectorin der Datei pickup_delivery_zone.verse. Füge ein Variablen-Array hinzu, um alle Zonen des Levels zu speichern.tagged_zone_selector<public> := class: var Zones<protected> : []base_zone = array{}Füge eine Methode mit dem Namen
InitZones()hinzu, die denpublic-Bezeichner und einentag-Parameter hat, um alle Zonen zu finden, die mit diesem Gameplay-Tag verbunden sind, und sie zwischenzuspeichern.VerseInitZones<public>(ZoneTag : tag) : void = # On creation of a zone selector, find all available zones and cache them so we don't consume time searching for tagged devices every time the next zone is selected. ZoneDevices := GetCreativeObjectsWithTag(ZoneTag) set Zones = for (ZoneDevice : ZoneDevices): MakeBaseZone(ZoneDevice)Füge eine Methode mit dem Namen
SelectNext()hinzu, die die Bezeichnerdecidesundtransactsenthält, so dass die Methode entweder eine andere Zone findet oder fehlschlägt. Wähle die Zone an einem zufälligen Index im Array mitGetRandomInt(0, Zones.Length - 1)für den Index.VerseSelectNext<public>()<transacts><decides> : base_zone = Zones[GetRandomInt(0, Zones.Length - 1)]Der vollständige Code der Datei pickup_delivery_zone.verse sollte nun wie folgt aussehen:
Verseusing { /Verse.org/Simulation } using { /Verse.org/Random } using { /Verse.org/Concurrency } using { /Verse.org/Simulation/Tags } using { /UnrealEngine.com/Temporary/SpatialMath } using { /Fortnite.com/Devices } <# A zone is an area of the map (represented by a device) that can be Activated/Deactivated and that provides events to signal when the zone has been "Completed" (can't be completed anymore until next activation). Zone "Completed" depends on the device type (ActivatorDevice) for the zone.
Testen von Abhol- und Lieferzonen
Nachdem du nun zwei Klassen erstellt hast, ist es eine gute Idee, deinen Code zu testen und sicherzustellen, dass deine Zonenauswahl so funktioniert, wie du es erwartest.
Befolge diese Schritte, um deine Datei game_coordinator_device.verse zu aktualisieren:
Füge eine Konstante für den Lieferzonen-Selektor und ein Variablen-Array für die Abholzonen-Selektoren zum
game_coordinator_devicehinzu. Da das Spiel später den Abholpunkt-Level nach jedem Pizzasammeln erhöht, brauchst du einentagged_zone_selectorfür jeden Abholpunkt-Level, den du im Spiel haben willst, daher dasPickupZoneSelectors-Array. Jeder Zonen-Selektor enthält alle Abholpunkt-Zonen einer bestimmten Ebene. Er muss eine Variable sein, weil seine Einrichtung durch die Anzahl derpickup_zone_tags inPickupZoneLevelTagsbestimmt wird.Benutze dieses Setup, um die Anzahl der Abholpunkt-Ebenen mit minimalen Änderungen am Code zu erweitern: Du musst nur die
PickupZoneLevelTagsmit zusätzlichen, vonpickup_zone_tagabgeleiteten, aktualisieren und dann die Geräte im Editor taggen.Versegame_coordinator_device<public> := class<concrete>(creative_device): DeliveryZoneSelector<private> : tagged_zone_selector = tagged_zone_selector{} var PickupZoneSelectors<private> : []tagged_zone_selector = array{}Füge eine Methode namens
SetupZones()hinzu und rufe die Methode inOnBegin()auf:Richte die Methode so ein, dass sie den Bezeichner
privateund einen Rückgabetypvoidhat.Initialisiere den Lieferzonen-Selektor mit dem
delivery_zone_tag.Erstelle die Abhol-Zonen-Leveltags und initialisiere die Abhol-Zonen-Selektoren.
VerseOnBegin<override>()<suspends> : void = SetupZones() SetupZones<private>() : void = DeliveryZoneSelector.InitZones(delivery_zone_tag{}) PickupZoneLevelTags : []pickup_zone_tag = array{pickup_zone_level_1_tag{}, pickup_zone_level_2_tag{}, pickup_zone_level_3_tag{}} set PickupZoneSelectors = for(PickupZoneTag : PickupZoneLevelTags): PickupZone := tagged_zone_selector{}
Erstelle eine Schleife in
OnBegin(), die die nächste Abholzone auswählt, sie aktiviert, darauf wartet, dass der Spieler die Zone abschließt, und dann die Zone wieder deaktiviert.VerseOnBegin<override>()<suspends> : void = SetupZones() var PickupLevel : int = 0 loop: if (PickupZone : base_zone = PickupZoneSelectors[PickupLevel].SelectNext[]): PickupZone.ActivateZone() PickupZone.ZoneCompletedEvent.Await() PickupZone.DeactivateZone()Speichere deine Verse-Dateien, kompiliere deinen Code und teste dein Level.
Wenn du dein Level testest, wird eines der Gegenstands-Spawnpunkt-Geräte zu Beginn des Spiels aktiviert. Sobald du den Gegenstand aufgesammelt hast, wird das Gegenstands-Spawnpunkt-Gerät deaktiviert und ein Gerät für das Eroberungsgebiet aktiviert. Das geht so lange, bis du das Spiel manuell beendest.