Uma zona é uma área do mapa (representada por um dispositivo) onde o jogador pode pegar ou entregar itens. Ao concluir esta etapa no tutorial Prova de tempo: Em busca da pizza, você aprenderá a criar essas zonas de coleta e entrega e ativá-las/desativá-las para o jogador.
Como usar abstração para criar uma classe de zona
Abstração é um princípio de programação em que detalhes desnecessários são ocultados de um usuário quando o usuário não precisa entender as complexidades ocultas. A abstração descreve o que algo é, sem saber como funciona. Por exemplo, você pode colocar dinheiro em uma máquina de venda automática e receber uma guloseima sem entender como a mecânica funciona.
Em Prova de tempo: Em busca da pizza, existem dois tipos de zonas: zonas de retirada, que usam o dispositivo Gerador de Itens, e zonas de entrega, que usam o dispositivo Área de Captura. Como essas zonas se comportarão da mesma maneira, mesmo sendo dispositivos diferentes — o que significa que tanto podem ser ativadas quanto desativadas — você pode criar uma classe para abstrair esse comportamento em um objeto de zona genérico que manipula as interações específicas do dispositivo.
Abstrair esse comportamento em uma classe significa que você só tem um lugar para alterar o tipo de dispositivo que utiliza. Essa implementação também significa que você pode alterar suas especificidades sem alterar nenhum outro código, porque qualquer código que usa essa classe conhece apenas as funções de ativação/desativação.
Siga estas etapas para criar essa classe de zona:
Crie um novo arquivo do Verse vazio chamado pickup_delivery_zone.verse e abra-o no Visual Studio Code.
No arquivo do Verse, crie uma nova classe chamada
base_zonecom o especificadorpublice adicione:Uma constante
creative_object_interfacechamadaActivatorDevicecom o especificadorpublic, para armazenar o dispositivo usado na zona.Um evento chamado
ZoneCompletedEventque tenha o especificadorpublicpara sinalizar quando o jogador interage com essa zona, como pegar itens ou entregá-los.Uma função chamada
ActivateZone()que tem o tipo de retornovoide o especificadorpublic, para habilitar o dispositivo usado para a zona.Uma função chamada
DeactivateZone()que tem o tipo de retornovoide o especificadorpublic, para desabilitar o dispositivo usado para a 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 uma classe e seus membros têm o especificador
public, eles são universalmente acessíveis de outro código. Para obter mais detalhes, consulte Especificadores e atributos.
Na função
ActivateZone(), verifique se oActivatorDeviceé um dispositivo de Área de Captura ou um dispositivo Gerador de Itens, convertendo o tipo deActivatorDevicenos diferentes tipos e chame a funçãoEnable()no dispositivo convertido. Faça o mesmo com a funçãoDeactivateZone(), agora chamando a funçãoDisable().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()Crie uma função chamada
WaitForZoneCompleted()que tenha o especificadorprivatee o especificadorsuspends. Essa função sinalizaráZoneCompletedEventquando o evento específico do dispositivo ocorrer. Essa configuração significa que outro código só precisa esperar peloZoneCompletedEvente não deve se importar com o tipo de evento que o dispositivo subjacente usa.VerseWaitForZoneCompleted<private>(ZoneDeviceCompletionEventOpt : ?awaitable(agent))<suspends> : void = if (DeviceEvent := ZoneDeviceCompletionEventOpt?): DeviceEvent.Await() ZoneCompletedEvent.Signal(Self)Essa função deve ter o efeito
suspendspara poder chamarDeviceEvent.Await().Atualize
ActivateZone()com uma expressãospawnque chameWaitForZoneCompleted()com o evento de dispositivo apropriado para o jogador que interage com o dispositivo:AgentEntersEventpara o dispositivo Área de Captura eItemPickedUpEventpara o dispositivo Gerador de Itens.WaitForZoneCompletedespera um parâmetrooption(indicado por?) do tipoawaitable(agent), para que possamos passar qualquer tipo que implemente a interfaceawaitable, com seu tipo paramétrico igual aagent. TantoCaptureArea.AgentEntersEventquantoItemSpawner.ItemPickedUpEventrespeitam essa condição, então podemos usá-los como parâmetro. Este é outro exemplo de abstração.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}) }Adicione outro evento chamado
ZoneDeactivatedEventque tenha o especificadorprotected. Esse evento é necessário para encerrar a funçãoWaitForZoneCompleted()se a zona for desativada antes que o jogador a complete. Sinalize esse evento na funçãoDeactivateZone().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()Atualize
WaitForZoneCompleted()com uma expressãoracepara que a função espere que o jogador complete a zona ou a zona seja desativada. Com a expressãorace, a chamada da função assíncronaZoneDeactivatedEvent.Await()e a expressãoblockcom o evento do dispositivo e o sinalZoneCompletedEventserão executadas ao mesmo tempo, mas a expressão que não terminar primeiro será cancelada.VerseWaitForZoneCompleted<private>(ZoneDeviceCompletionEventOpt : ?awaitable(agent))<suspends> : void = if (DeviceEvent := ZoneDeviceCompletionEventOpt?): race: block: DeviceEvent.Await() ZoneCompletedEvent.Signal(Self) ZoneDeactivatedEvent.Await()Finalmente, crie um construtor para a classe
base_zoneque inicializará o campoActivatorDevice.VerseMakeBaseZone<constructor><public>(InActivatorDevice : creative_object_interface) := base_zone: ActivatorDevice := InActivatorDeviceA seguir está o código completo da 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()
Como encontrar zonas em tempo de execução com tags de jogabilidade
Agora que você tem uma maneira de criar zonas, ativá-las e desativá-las, vamos adicionar uma forma de inicializar todas as zonas que você marcou no nível e de selecionar a próxima a ser ativada.
Este exemplo mostra como fazer isso com uma classe responsável por criar as zonas e selecionar a próxima zona a ser ativada.
Siga estas etapas para criar a classe de criação e seleção de zonas:
Crie uma nova classe chamada
tagged_zone_selectorno arquivo pickup_delivery_zone.verse. Adicione uma matriz variável para armazenar todas as zonas no nível.tagged_zone_selector<public> := class: var Zones<protected> : []base_zone = array{}Adicione um método chamado
InitZones()que tenha o especificadorpublice um parâmetrotagpara encontrar todas as zonas associadas a essa tag de jogabilidade e armazená-las em 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)Adicione um método chamado
SelectNext()que tenha os especificadoresdecidesetransactspara que o método encontre outra zona ou falhe. Selecione a zona em um índice aleatório na matriz usandoGetRandomInt(0, Zones.Length - 1)para o índice.VerseSelectNext<public>()<transacts><decides> : base_zone = Zones[GetRandomInt(0, Zones.Length - 1)]O código completo do arquivo pickup_delivery_zone.verse agora deve ter a seguinte aparência:
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.
Como testar zonas de retirada e entrega
Agora que você criou duas classes, é uma boa ideia testar seu código e garantir que sua seleção de zona funcione conforme esperado.
Siga estas etapas para atualizar o arquivo game_coordinator_device.verse:
Adicione uma constante para o seletor de zona de entrega e uma matriz de variável para os seletores de zona de retirada ao
game_coordinator_device. Como o jogo aumentará posteriormente o nível de retirada após cada retirada de pizza, você precisará de umtagged_zone_selectorpara cada nível de retirada que desejar no jogo, daí a matrizPickupZoneSelectors. Cada seletor de zona contém todas as zonas de retirada de um determinado nível. Ele precisa ser uma variável, pois sua configuração é determinada pelo número de elementospickup_zone_tagemPickupZoneLevelTags.Use essa configuração para estender o número de níveis de retirada com o mínimo de alterações no código: você só precisa atualizar
PickupZoneLevelTagscom outros derivados depickup_zone_tage, em seguida, marcar os dispositivos no editor.Versegame_coordinator_device<public> := class<concrete>(creative_device): DeliveryZoneSelector<private> : tagged_zone_selector = tagged_zone_selector{} var PickupZoneSelectors<private> : []tagged_zone_selector = array{}Adicione um método chamado
SetupZones()e chame o método emOnBegin ():Defina o método para ter o especificador
privatee um tipo de retornovoid.Inicialize o seletor de zona de entrega com
delivery_zone_tag.Crie as etiquetas de nível da zona de retirada e inicialize os seletores da zona de retirada.
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{}
Crie um loop em
OnBegin()que irá selecionar a próxima zona de retirada, ativá-la, esperar que o jogador complete a zona e, em seguida, desativar a zona.VerseOnBegin<override>()<suspends> : void = SetupZones() var PickupLevel : int = 0 loop: if (PickupZone : base_zone = PickupZoneSelectors[PickupLevel].SelectNext[]): PickupZone.ActivateZone() PickupZone.ZoneCompletedEvent.Await() PickupZone.DeactivateZone()Salve seus arquivos do Verse, compile seu código e teste seu nível.
Quando você testar seu nível, um dos dispositivos Gerador de Itens será ativado no início do jogo. Depois de retirar o item, o dispositivo Gerador de Itens será desativado, e um dispositivo Área de Captura será ativado. Isso continuará até você terminar o jogo manualmente.