Une zone est un endroit sur la carte (représenté par un appareil) où le joueur peut collecter ou livrer des objets. Dans cette étape du tutoriel
Utiliser l'abstraction pour créer une classe de zone
L'abstraction est un principe de programmation qui consiste à masquer les détails inutiles à l'utilisateur lorsque celui-ci n'a pas besoin de comprendre les complexités. L'abstraction décrit ce qu'est une chose, sans savoir comment elle fonctionne. Par exemple, vous pouvez mettre de l'argent dans un distributeur automatique et en retirer une friandise sans comprendre le fonctionnement de la mécanique.
Dans le jeu Contre-la-montre : Pizza Pursuit, il existe deux types de zones : les zones de collecte, qui font appel à l'appareil Générateur d'objets, et les zones de livraison, qui font appel à l'appareil Zone de capture. Dans la mesure où ces zones se comportent de la même manière (c'est-à-dire qu'il est possible de les activer et de les désactiver) bien qu'elles recourent à des appareils différents, vous pouvez créer une classe pour abstraire ce comportement dans un objet de zone générique qui gère les interactions spécifiques de l'appareil.
L'abstraction de ce comportement dans une classe signifie que vous ne pouvez modifier le type d'appareils que vous utilisez qu’à un seul emplacement. Cette implémentation signifie en outre que vous pouvez modifier ses spécificités sans changer vos autres codes, car tout code qui utilise cette classe ne connaît que les fonctions d'activation et de désactivation.
Procédez comme suit pour créer cette classe de zone :
Créez un nouveau fichier Verse vide nommé pickup_delivery_zone.verse et ouvrez-le dans Visual Studio Code.
Dans le fichier Verse, créez une nouvelle classe nommée
base_zoneavec le spécificateurpublicet ajoutez ce qui suit :Une constante
creative_object_interfacenomméeActivatorDeviceavec le spécificateurpublic, pour stocker l'appareil utilisé dans la zone.Un événement nommé
ZoneCompletedEventavec le spécificateurpublic, pour signaler quand le joueur interagit avec cette zone, par exemple en collectant des objets ou en les livrant.Une fonction nommée
ActivateZone()avec le type de retourvoidet le spécificateurpublic, pour activer l'appareil utilisé pour la zone.Une fonction nommée
DeactivateZone()avec le type de retourvoidet le spécificateurpublic, pour désactiver l'appareil utilisé pour la zone.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")Lorsqu'une classe et ses membres ont le spécificateur
public,ils sont universellement accessibles depuis d'autres codes. Pour en savoir plus, consultez la rubrique Spécificateurs et attributs.
Dans la fonction
ActivateZone(), vérifiez si l'appareilActivatorDeviceest un appareil Zone de capture ou un appareil Générateur d'objets en convertissant l'appareilActivatorDeviceen différents types et en appelant la fonctionEnable()sur l'appareil converti. Procédez de la même façon avec la fonctionDeactivateZone(), mais en appelant la fonctionDisable().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()Créez une fonction nommée
WaitForZoneCompleted()avec les spécificateursprivateetsuspends. Cette fonction signale l'événementZoneCompletedEventlorsque l'événement propre à l'appareil se produit. Cela signifie que les autres codes doivent se contenter d'attendreZoneCompletedEventsans se soucier du type d'événement que l'appareil sous-jacent utilise.VerseWaitForZoneCompleted<private>(ZoneDeviceCompletionEventOpt : ?awaitable(agent))<suspends> : void = if (DeviceEvent := ZoneDeviceCompletionEventOpt?): DeviceEvent.Await() ZoneCompletedEvent.Signal(Self)Cette fonction doit avoir l'effet
suspendspour pouvoir appelerDeviceEvent.Await().Mettez à jour
ActivateZone()avec une expressionspawnqui appelleWaitForZoneCompleted()avec l'événement approprié pour le joueur interagissant avec l'appareil :AgentEntersEventpour la zone de capture etItemPickedUpEventpour le générateur d'objets.WaitForZoneCompletedattend un paramètreoption(indiqué par?) de typeawaitable(agent); vous pouvez transmettre n'importe quel type qui implémente l'interfaceawaitable, avec son type paramétrique égal àagent. TantCaptureArea.AgentEntersEventqueItemSpawner.ItemPickedUpEventrespectent cette condition. Il est donc possible de les utiliser comme paramètre. Voici un autre exemple d'abstraction.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}) }Ajoutez un autre événement nommé
ZoneDeactivatedEventavec le spécificateurprotected. Cet événement est nécessaire pour mettre fin à la fonctionWaitForZoneCompleted()si la zone est désactivée avant que le joueur l'ait terminée. Signalez cet événement dans la fonctionDeactivateZone().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()Mettez à jour
WaitForZoneCompleted()avec une expressionracepour que la fonction attende soit que le joueur termine la zone, soit que la zone soit désactivée. Avec l'expressionrace, l'appel de fonction asynchroneZoneDeactivatedEvent.Await()et l'expressionblockavec l'événement d'appareil et le signalZoneCompletedEvents'exécutent simultanément, mais l'expression qui ne se termine pas en premier est annulée.VerseWaitForZoneCompleted<private>(ZoneDeviceCompletionEventOpt : ?awaitable(agent))<suspends> : void = if (DeviceEvent := ZoneDeviceCompletionEventOpt?): race: block: DeviceEvent.Await() ZoneCompletedEvent.Signal(Self) ZoneDeactivatedEvent.Await()Enfin, créez un constructeur pour la classe
base_zonequi initialisera le champActivatorDevice.VerseMakeBaseZone<constructor><public>(InActivatorDevice : creative_object_interface) := base_zone: ActivatorDevice := InActivatorDeviceVoici le code complet de la classe
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()
Rechercher des zones au moment de l'exécution avec des étiquettes de jeu
Maintenant que vous savez comment créer des zones et les activer/désactiver, découvrez comment initialiser toutes les zones que vous avez étiquetées dans le niveau et comment sélectionner la prochaine zone à activer.
Dans cet exemple, nous vous expliquons comment y parvenir avec une classe chargée de créer des zones et de sélectionner la prochaine zone à activer.
Procédez comme suit pour créer la classe permettant de créer et de sélectionner des zones :
Créez une nouvelle classe nommée
tagged_zone_selectordans le fichier pickup_delivery_zone.verse. Ajoutez une matrice de variable pour stocker toutes les zones du niveau.tagged_zone_selector<public> := class: var Zones<protected> : []base_zone = array{}Ajoutez une méthode nommée
InitZones()avec le spécificateurpublicet un paramètretagpour rechercher toutes les zones associées à cette étiquette de jeu et les mettre en cache.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)Ajoutez une méthode nommée
SelectNext()avec les spécificateursdecidesettransactsafin que la méthode recherche une autre zone ou échoue. Sélectionnez la zone à un index aléatoire dans la matrice en utilisantGetRandomInt(0, Zones.Length - 1)pour l'index.VerseSelectNext<public>()<transacts><decides> : base_zone = Zones[GetRandomInt(0, Zones.Length - 1)]Le code complet du fichier pickup_delivery_zone.verse doit maintenant être similaire à celui-ci :
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.
Tester les zones de collecte et de livraison
Maintenant que vous avez créé deux classes, nous vous conseillons de tester votre code et de vous assurer que votre sélection de zone fonctionne comme prévu.
Procédez comme suit pour mettre à jour votre fichier game_coordinator_device.verse :
Ajoutez une constante pour le sélecteur de zone de livraison et une matrice de variable pour les sélecteurs de zone de collecte à l'appareil
game_coordinator_device. Dans la mesure où le jeu augmente le niveau de collecte après chaque collecte de pizza, vous devez disposer d'un sélecteurtagged_zone_selectorpour chaque niveau de collecte que vous souhaitez intégrer au jeu, d'où la matricePickupZoneSelectors. Chaque sélecteur de zone contient toutes les zones de collecte d'un certain niveau. Il doit s'agit d'une variable, car sa configuration est déterminée par le nombre de balisespickup_zone_tagdansPickupZoneLevelTags.Utilisez cette configuration pour augmenter le nombre de niveaux de collecte avec un minimum de modifications du code : il vous suffit en effet de mettre à jour les balises
PickupZoneLevelTagsavec les autres balises dérivées depickup_zone_tag, puis de baliser les appareils dans l'éditeur.Versegame_coordinator_device<public> := class<concrete>(creative_device): DeliveryZoneSelector<private> : tagged_zone_selector = tagged_zone_selector{} var PickupZoneSelectors<private> : []tagged_zone_selector = array{}Ajoutez une méthode nommée
SetupZones()et appelez la méthode dansOnBegin():Définissez la méthode pour obtenir le spécificateur
privateet un type de retourvoid.Initialisez le sélecteur de zone de livraison avec
delivery_zone_tag.Créez les balises de niveau de zone de collecte et initialisez les sélecteurs de zone de collecte.
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{}
Créez une boucle dans
OnBegin()qui sélectionne la prochaine zone de collecte, l'active, attend que le joueur termine la zone, puis désactive la zone.VerseOnBegin<override>()<suspends> : void = SetupZones() var PickupLevel : int = 0 loop: if (PickupZone : base_zone = PickupZoneSelectors[PickupLevel].SelectNext[]): PickupZone.ActivateZone() PickupZone.ZoneCompletedEvent.Await() PickupZone.DeactivateZone()Enregistrez vos fichiers Verse, compilez votre code et testez votre niveau.
Lorsque vous testez votre niveau, l'un des générateurs d'objets est activé au début du jeu. Après avoir collecté l'objet, le générateur d'objets est désactivé et un appareil de capture de zone est activé. Cette séquence se poursuit jusqu'à ce que vous mettiez fin manuellement à la partie.