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 zona
Astrazione è un principio di programmazione in cui i dettagli non necessari vengono 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: le zone di raccolta, che utilizzano il dispositivo Generatore oggetti, e le zone di consegna, che utilizzano il dispositivo Area di cattura . Poiché queste zone si comporteranno 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 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 vuoto di Verse denominato pickup_delivery_zone.verse e aprilo in Visual Studio Code.
- Nel file Verse, crea una nuova classe chiamata
base_zone
con lo specificatorepublic
e aggiungi:- Una costante
creative_object_interface
denominataActivatorDevice
con lo specificatorepublic
, per memorizzare il dispositivo utilizzato nella zona. - Un evento chiamato
ZoneCompletedEvent
con lo specificatorepublic
, per segnalare quando il giocatore interagisce con la zona, ad esempio raccogliendo oggetti o consegnandoli. - Una funzione denominata
ActivateZone()
con tipo restituitovoid
e specificatorepublic
, per attivare il dispositivo utilizzato per la zona. - Una funzione denominata
DeactivateZone()
con tipo restituitovoid
e specificatorepublic
, per disattivare il dispositivo utilizzato per la zona.
base_zone<public> := class: ActivatorDevice<public> : creative_object_interface ZoneCompletedEvent<public> : event(base_zone) = event(base_zone){} ActivateZone<public>() : void = Print("Zona attivata") DeactivateZone<public>() : void = Print("Zona disattivata")
Quando una classe e i suoi membri contengono lo specificatore
public
, risultano accessibili universalmente da altro codice. Per maggiori dettagli, vedi Specificatori e attributi. - Una costante
- Nella funzione
ActivateZone()
, verifica se il dispositivoActivatorDevice
è un dispositivo Area di cattura o Generatore oggetti, eseguendo il cast di tipo del dispositivoActivatorDevice
nei diversi tipi e chiamando la funzioneEnable()
sul dispositivo convertito. Esegui la stessa operazione con la funzioneDeactivateZone()
, ma richiama la funzioneDisable()
.base_zone<public> := class: ActivatorDevice<public> : creative_object_interface ActivateZone<public>() : void = Print("Zona attivata") if (CaptureArea := capture_area_device[ActivatorDevice]): CaptureArea.Enable() else if (ItemSpawner := item_spawner_device[ActivatorDevice]): ItemSpawner.Enable() DeactivateZone<public>() : void = Print("Zona disattivata") if (CaptureArea := capture_area_device[ActivatorDevice]): CaptureArea.Disable() else if (ItemSpawner := item_spawner_device[ActivatorDevice]): ItemSpawner.Disable()
- Crea una funzione denominata
WaitForZoneCompleted()
con gli specificatoriprivate
esuspends
. Questa funzione segnala ilZoneCompletedEvent
, quando si verifica l'evento specifico del dispositivo. Questa impostazione significa che l'altro codice deve solo attendere ilZoneCompletedEvent
e non considerare il tipo di evento utilizzato dal dispositivo sottostante.WaitForZoneCompleted<private>(ZoneDeviceCompletionEventOpt : ?awaitable(agent))<suspends> : void = if (DeviceEvent := ZoneDeviceCompletionEventOpt?): DeviceEvent.Await() ZoneCompletedEvent.Signal(Self)
Questa funzione deve avere l'effetto
suspends
per poter chiamareDeviceEvent.Await()
. - Aggiorna
ActivateZone()
con un'espressionespawn
che chiamaWaitForZoneCompleted()
con l'evento appropriato per il giocatore che interagisce con il dispositivo:AgentEntersEvent
per il dispositivo Area di cattura eItemPickedUpEvent
per il dispositivo Generatore oggetti.WaitForZoneCompleted
si aspetta un parametrooption
(indicato da?
) di tipoawaitable(agent)
, quindi possiamo passare qualsiasi tipo che implementi l'interfacciaawaitable
, con il suo tipo parametrico uguale adagent
. WaitForZoneCompletedsi aspetta un parametro
option(indicato da
?) di tipo
awaitable(agent), quindi possiamo passare qualsiasi tipo che implementi l'interfaccia
awaitable, con il suo tipo parametrico uguale ad
agent. Sia
CaptureArea.AgentEntersEventche
ItemSpawner.ItemPickedUpEvent` rispettano questa condizione, quindi possiamo utilizzarli come parametro. Questo è un altro esempio di astrazione.ActivateZone<public>() : void = Print("Zona attivata") 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
ZoneDeactivatedEvent
con 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()
.ZoneDeactivatedEvent<protected> : event() = event(){} DeactivateZone<public>() : void = Print("Zona disattivata") if (CaptureArea := capture_area_device[ActivatorDevice]): CaptureArea.Disable() else if (ItemSpawner := item_spawner_device[ActivatorDevice]): ItemSpawner.Disable() ZoneDeactivatedEvent.Signal()
- Aggiorna
WaitForZoneCompleted()
con un'espressionerace
in modo che la funzione attenda che il giocatore completi la zona o che la zona venga disattivata. Con l'espressionerace
, la chiamata di funzione asincronaZoneDeactivatedEvent.Await()
e l'espressioneblock
con l'evento del dispositivo e il segnaleZoneCompletedEvent
vengono eseguiti contemporaneamente, ma l'espressione che non termina per prima viene annullata.WaitForZoneCompleted<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_zone
che inizializzerà il campoActivatorDevice
.MakeBaseZone<constructor><public>(InActivatorDevice : creative_object_interface) := base_zone: ActivatorDevice := InActivatorDevice
- Di seguito è riportato il codice completo della classe
base_zone
.<# Una zona è un'area della mappa (rappresentata da un dispositivo) che può essere attivata/disattivata e che fornisce eventi per segnalare quando la zona è stata "Completata" (non può più essere completata fino alla prossima attivazione). La zona "Completata" dipende dal tipo di dispositivo (ActivatorDevice) della zona. Utilizzo consigliato: 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() <# Attiva la zona. Devi abilitare i dispositivi e tutti gli indicatori visivi della zona. #> ActivateZone<public>() : void = # La zona base può gestire le zone definite come generatori oggetti o aree di cattura. # Prova a eseguire il cast di ogni tipo per vedere di che zona si tratta. 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}) } <# Disattiva la zona. Devi disabilitare i dispositivi e tutti gli indicatori visivi della zona. #> DeactivateZone<public>() : void = if (CaptureArea := capture_area_device[ActivatorDevice]): CaptureArea.Disable() else if (ItemSpawner := item_spawner_device[ActivatorDevice]): ItemSpawner.Disable() ZoneDeactivatedEvent.Signal() <# Questo evento è necessario per terminare la coroutine WaitForZoneCompleted, se la zona viene disattivata senza essere completata. #> ZoneDeactivatedEvent<protected> : event() = event(){} WaitForZoneCompleted<private>(ZoneDeviceCompletionEventOpt : ?awaitable(agent))<suspends> : void = if (DeviceEvent := ZoneDeviceCompletionEventOpt?): race: block: DeviceEvent.Await() ZoneCompletedEvent.Signal(Self) ZoneDeactivatedEvent.Await() MakeBaseZone<constructor><public>(InActivatorDevice : creative_object_interface) := base_zone: ActivatorDevice := InActivatorDevice
Trovare le zone nel runtime con i tag 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_selector
nel 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()
con lo specificatorepublic
e un parametrotag
per individuare tutte le zone associate a quel tag gameplay e memorizzarle nella cache.InitZones<public>(ZoneTag : tag) : void = <# Al momento della creazione di un selettore di zona, individua tutte le zone disponibili e memorizzale nella cache per non sprecare tempo nella ricerca di dispositivi taggati a ogni selezione della zona successiva. #> ZoneDevices := GetCreativeObjectsWithTag(ZoneTag) set Zones = for (ZoneDevice : ZoneDevices): MakeBaseZone(ZoneDevice)
- Aggiungi un metodo denominato
SelectNext()
che contenga gli specificatoridecides
etransacts
, in modo che il metodo individui un'altra zona o non riesca. Seleziona la zona a indice casuale nell'array utilizzandoGetRandomInt(0, Zones.Length - 1)
per l'indice.SelectNext<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:
using { /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 } <# Una zona è un'area della mappa (rappresentata da un dispositivo) che può essere attivata/disattivata e che fornisce eventi per segnalare quando la zona è stata "Completata" (non può più essere completata fino all'attivazione successiva). La zona "Completata" dipende dal tipo di dispositivo (ActivatorDevice) della zona. Utilizzo consigliato: 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() <# Attiva la zona. Devi abilitare i dispositivi e tutti gli indicatori visivi della zona. #> ActivateZone<public>() : void = # La zona base può gestire le zone definite come generatori oggetti o aree di cattura. # Prova a eseguire il cast di ogni tipo per vedere di che zona si tratta. 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}) } <# Disattiva la zona. Devi disabilitare i dispositivi e tutti gli indicatori visivi della zona. #> DeactivateZone<public>() : void = if (CaptureArea := capture_area_device[ActivatorDevice]): CaptureArea.Disable() else if (ItemSpawner := item_spawner_device[ActivatorDevice]): ItemSpawner.Disable() ZoneDeactivatedEvent.Signal() <# Questo evento è necessario per terminare la coroutine WaitForZoneCompleted, se la zona viene disattivata senza essere completata. #> ZoneDeactivatedEvent<protected> : event() = event(){} WaitForZoneCompleted<private>(ZoneDeviceCompletionEventOpt : ?awaitable(agent))<suspends> : void = if (DeviceEvent := ZoneDeviceCompletionEventOpt?): race: block: DeviceEvent.Await() ZoneCompletedEvent.Signal(Self) ZoneDeactivatedEvent.Await() MakeBaseZone<constructor><public>(InActivatorDevice : creative_object_interface) := base_zone: ActivatorDevice := InActivatorDevice # Il selettore tagged_zone crea zone basate su trigger taggati con il tag passato a InitZones. tagged_zone_selector<public> := class: var Zones<protected> : []base_zone = array{} InitZones<public>(ZoneTag : tag) : void = <# Al momento della creazione di un selettore di zona, individua tutte le zone disponibili e memorizzale nella cache per non sprecare tempo nella ricerca di dispositivi taggati a ogni selezione della zona successiva. #> ZoneDevices := GetCreativeObjectsWithTag(ZoneTag) set Zones = for (ZoneDevice : ZoneDevices): MakeBaseZone(ZoneDevice) SelectNext<public>()<transacts><decides> : base_zone = Zones[GetRandomInt(0, Zones.Length - 1)]
Eseguire il 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.
Per aggiornare il file game_coordinator_device.verse, attieniti ai seguenti passaggi:
- Aggiungi una costante per il selettore della zona di consegna e un array di variabili per i selettori della zona di raccolta al dispositivo
game_coordinator_device
. Poiché in seguito il gioco aumenterà il livello di raccolta dopo ogni raccolta di pizza, sarà necessario untagged_zone_selector
per ogni livello di raccolta desiderato nel gioco, da qui l'arrayPickupZoneSelectors
. Ogni selettore di zona contiene tutte le zone di raccolta di un determinato livello. Deve essere una variabile perché la sua impostazione è determinata dal numero dipickup_zone_tag
inPickupZoneLevelTags
. - Utilizza questa configurazione per estendere il numero di livelli di raccolta eseguendo modifiche minime al codice: devi solo aggiornare i
PickupZoneLevelTags
con altri che derivano dapickup_zone_tag
, quindi taggare i dispositivi nell'editor.game_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 nominaloOnBegin()
:- Imposta il metodo in modo che contenga lo specificatore
private
e un tipo restituitovoid
. - Inizializza il selettore della zona di consegna con il tag
delivery_zone_tag
. - Crea i tag di livello della zona di raccolta e inizializza i selettori della zona di raccolta.
OnBegin<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{} PickupZone.InitZones(PickupZoneTag) PickupZone
- Imposta il metodo in modo che contenga lo specificatore
- Crea un loop in
OnBegin()
che selezioni la zona di raccolta successiva, la attivi, attenda che il giocatore completi la zona e quindi la disattivi.OnBegin<override>()<suspends> : void = SetupZones() var PickupLevel : int = 0 loop: if (PickupZone : base_zone = PickupZoneSelectors[PickupLevel].SelectNext[]): PickupZone.ActivateZone() PickupZone.ZoneCompletedEvent.Await() PickupZone.DeactivateZone() else: Print("PickupZone successiva da selezionare non trovata") return if (DeliveryZone := DeliveryZoneSelector.SelectNext[]): DeliveryZone.ActivateZone() DeliveryZone.ZoneCompletedEvent.Await() DeliveryZone.DeactivateZone() else: Print("DeliveryZone successiva da selezionare non trovata") return
- 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.