Una zona è un'area della mappa (rappresentata da un dispositivo) in cui il giocatore può raccogliere o consegnare oggetti. Completando questo passaggio del tutorial Prova a tempo: A caccia di pizza, imparerai a creare queste zone di raccolta e consegna e ad attivarle/disattivarle per il giocatore.
Utilizzo dell'astrazione per la creazione di una classe di zona
L'Astrazione è un principio di programmazione in cui i dettagli non necessari sono nascosti all'utente che non ha bisogno di comprendere le complessità nascoste. L'astrazione descrive ciò che è qualcosa senza conoscerne il funzionamento. Analogamente, non ci serve sapere come funzionano i meccanismi di un distributore automatico per comprare una merendina.
In Prova a tempo: A caccia di pizza, ci sono due tipi di zone: zone di raccolta, che utilizzano il dispositivo Generatore oggetti, e zone di consegna, che utilizzano il dispositivo Area di cattura. Poiché queste zone si comportano allo stesso modo anche se si tratta di dispositivi diversi (cioè entrambi possono essere attivati e disattivati), è possibile creare una classe per astrarre questo comportamento in un oggetto di zona generico che gestisce le interazioni specifiche del dispositivo.
Astrarre questo comportamento in una classe significa che disporrai di un solo posto per modificare il tipo di dispositivo utilizzato. Questa implementazione significa anche che è possibile cambiarne le specifiche senza modificare il codice, perché il codice che utilizza questa classe conosce solo le funzioni di attivazione/disattivazione.
Per creare questa classe zona, attieniti ai seguenti passaggi:
Crea un nuovo file di Verse vuoto denominato pickup_delivery_zone.verse e aprilo in Visual Studio Code.
Nel file Verse, crea una nuova classe denominata
base_zonecon lo specificatorepublice aggiungi:Una costante
creative_object_interfacedenominataActivatorDevicecon lo specificatorepublic, per memorizzare il dispositivo utilizzato nella zona.Evento denominato
ZoneCompletedEventche ha lo specificatorepublic, per segnalare quando il giocatore interagisce con la zona, ad esempio raccogliendo oggetti o consegnandoli.Funzione denominata
ActivateZone()che ha il tipo di restituzionevoide lo specificatorepublic, per abilitare il dispositivo utilizzato per la zona.Funzione denominata
DeactivateZone()che ha il tipo di restituzionevoide lo specificatorepublic, per disabilitare il dispositivo utilizzato per la zona.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")Quando una classe e i suoi membri hanno lo specificatore
public, sono universalmente accessibili da altro codice. Per maggiori dettagli, vedi Specificatori e attributi.
Nella funzione
ActivateZone(), verifica se il dispositivoActivatorDeviceè un dispositivo Area di cattura o Generatore oggetti eseguendo il cast di tipo del dispositivoActivatorDevicenei diversi tipi e chiama la funzioneEnable()sul dispositivo convertito. Esegui la stessa operazione con la funzioneDeactivateZone(), ma chiama la funzioneDisable().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()Crea una funzione denominata
WaitForZoneCompleted()che abbia lo specificatoreprivateesuspends. Questa funzione segnala aZoneCompletedEventquando si verifica l'evento specifico del dispositivo. Questa configurazione implica che l'altro codice deve solo attendereZoneCompletedEvente non preoccuparsi del tipo di evento utilizzato dal dispositivo sottostante.VerseWaitForZoneCompleted<private>(ZoneDeviceCompletionEventOpt : ?awaitable(agent))<suspends> : void = if (DeviceEvent := ZoneDeviceCompletionEventOpt?): DeviceEvent.Await() ZoneCompletedEvent.Signal(Self)Questa funzione deve avere l'effetto
suspendsper poter chiamareDeviceEvent.Await().Aggiorna
ActivateZone()con un'espressionespawnche chiamaWaitForZoneCompleted()con l’evento appropriato per il giocatore che interagisce con il dispositivo:AgentEntersEventper il dispositivo Area di cattura eItemPickedUpEventper il dispositivo Generatore oggetti.WaitForZoneCompletedsi aspetta un parametrooption(indicato da?) di tipoawaitable(agent), quindi possiamo passare qualsiasi tipo che implementi l'interfacciaawaitable, con il suo tipo parametrico uguale adagent. SiaCaptureArea.AgentEntersEventcheItemSpawner.ItemPickedUpEventrispettano questa condizione, quindi possiamo utilizzarli come parametro. Questo è un altro esempio di astrazione.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}) }Aggiungi un altro evento denominato
ZoneDeactivatedEventche abbia lo specificatoreprotected. Questo evento è necessario per terminare la funzioneWaitForZoneCompleted()se la zona viene disattivata prima che il giocatore la completi. Segnala questo evento nella funzioneDeactivateZone().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()Aggiorna
WaitForZoneCompleted()con un'espressioneracein modo che la funzione attenda che il giocatore completi la zona o che la zona sia disattivata. Con l'espressionerace,la chiamata di funzione asincronaZoneDeactivatedEvent.Await()e l'espressioneblockcon l'evento del dispositivo e il segnaleZoneCompletedEventsono eseguiti contemporaneamente, ma l'espressione che non termina per prima è annullata.VerseWaitForZoneCompleted<private>(ZoneDeviceCompletionEventOpt : ?awaitable(agent))<suspends> : void = if (DeviceEvent := ZoneDeviceCompletionEventOpt?): race: block: DeviceEvent.Await() ZoneCompletedEvent.Signal(Self) ZoneDeactivatedEvent.Await()Infine, crea un costruttore per la classe
base_zoneche inizializza il campoActivatorDevice.VerseMakeBaseZone<constructor><public>(InActivatorDevice : creative_object_interface) := base_zone: ActivatorDevice := InActivatorDeviceDi seguito è riportato il codice completo per 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()
Individuazione delle zone nel runtime con i tag di gameplay
Ora che abbiamo un modo per creare zone e attivarle/disattivarle, aggiungiamo un modo per inizializzare tutte le zone che hai taggato nel livello e come selezionare la prossima da attivare.
Questo esempio mostra come eseguire questa operazione con una classe responsabile della creazione delle zone e della selezione della zona successiva da attivare.
Per creare la classe per la creazione e la selezione delle zone, attieniti ai seguenti passaggi:
Crea una nuova classe denominata
tagged_zone_selectornel file pickup_delivery_zone.verse. Aggiungi un array variabile per memorizzare tutte le zone del livello.tagged_zone_selector<public> := class: var Zones<protected> : []base_zone = array{}Aggiungi un metodo denominato
InitZones()che abbia lo specificatorepublice un parametrotagper trovare tutte le zone associate a quel Tag di gameplay e memorizzarle nella 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)Aggiungi un metodo denominato
SelectNext()che abbia gli specificatoridecidesetransactsin modo che il metodo trovi un'altra zona o non riesca. Seleziona la zona a indice casuale nell'array utilizzandoGetRandomInt(0, Zones.Length - 1)per l'indice.VerseSelectNext<public>()<transacts><decides> : base_zone = Zones[GetRandomInt(0, Zones.Length - 1)]Il codice completo del file pickup_delivery_zone.verse ora deve essere visualizzato come segue:
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.
Test delle zone di raccolta e di consegna
Ora che hai creato due classi, è bene eseguire il test del codice e assicurarti che la selezione delle zone funzioni nel modo previsto.
Segui questi passaggi per aggiornare il file game_coordinator_device.verse:
Aggiungi una costante per il selettore della zona di consegna e un array di variabili per i selettori della zona di raccolta a
game_coordinator_device. Poiché in seguito il gioco aumenterà il livello di raccolta dopo ogni raccolta di pizza, sarà necessario untagged_zone_selectorper ogni livello di raccolta desiderato nel gioco, quindi l'arrayPickupZoneSelectors. Ogni selettore di zona contiene tutte le zone di raccolta di un determinato livello. Deve essere una variabile perché la sua configurazione è determinata dal numero dipickup_zone_taginPickupZoneLevelTags.Utilizza questa configurazione per estendere il numero di livelli di raccolta con modifiche minime al codice: devi solo aggiornare i
PickupZoneLevelTagcon altri derivanti dapickup_zone_tag, quindi taggare i dispositivi nell'editor.Versegame_coordinator_device<public> := class<concrete>(creative_device): DeliveryZoneSelector<private> : tagged_zone_selector = tagged_zone_selector{} var PickupZoneSelectors<private> : []tagged_zone_selector = array{}Aggiungi un metodo denominato
SetupZones()e chiama il metodo inOnBegin():Imposta il metodo in modo che abbia lo specificatore
privatee un tipo di restituzionevoid.Inizializza il selettore della zona di consegna con il
delivery_zone_tag.Crea i tag di livello della zona di raccolta e inizializza i selettori della zona di raccolta.
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{}
Crea un loop in
OnBegin()che selezioni la prossima zona di raccolta, la attivi, attenda che il giocatore completi la zona e poi la disattivi.VerseOnBegin<override>()<suspends> : void = SetupZones() var PickupLevel : int = 0 loop: if (PickupZone : base_zone = PickupZoneSelectors[PickupLevel].SelectNext[]): PickupZone.ActivateZone() PickupZone.ZoneCompletedEvent.Await() PickupZone.DeactivateZone()Salva i file di Verse, compila il codice ed esegui il playtest del livello.
Durante il playtest del livello, uno dei dispositivi Generatore oggetti si attiverà all'inizio del gioco. Dopo aver raccolto l'oggetto, il dispositivo Generatore oggetti si disattiva e si attiva il dispositivo Area di cattura. Ciò continua fino a quando non termini manualmente il gioco.