Una zona es un área del mapa (representada por un dispositivo) donde el jugador puede recoger o entregar objetos. Al completar este paso del tutorial Prueba contrarreloj: Persecución de pizza, aprenderás a crear estas zonas de recolección y entrega, y a activarlas o desactivarlas para el jugador.
Cómo usar la abstracción para crear una clase de zona
La abstracción es un principio de programación donde los detalles innecesarios se ocultan al usuario cuando este no necesita comprender las complejidades ocultas. La abstracción describe algo sin saber cómo funciona. Por ejemplo, puedes colocar dinero en una máquina expendedora y obtener un dulce sin comprender cómo funciona la mecánica.
En Prueba contrarreloj: Persecución de pizza, hay dos tipos de zonas: zonas de recolección, que usan el dispositivo generador de objetos, y zonas de entrega, que usan el dispositivo de zona de captura. Como estas zonas se comportarán de la misma manera a pesar de ser dispositivos distintos, es decir, que pueden activarse y desactivarse, puedes crear una clase para abstraer este comportamiento en un objeto de zona genérico que controla las interacciones específicas del dispositivo.
Abstraer este comportamiento en una clase significa que solo tienes un lugar para cambiar qué tipo de dispositivo usas. Esta implementación también significa que puedes cambiar sus detalles sin cambiar el resto del código, porque cualquier código que use esta clase solo conoce las funciones de activación y desactivación.
Sigue estos pasos para crear esta clase de zona:
Crea un nuevo archivo de Verse vacío llamado pickup_delivery_zone.verse y ábrelo en Visual Studio Code.
En el archivo de Verse, crea una nueva clase denominada
base_zonecon el especificadorpublicy añade lo siguiente:Una constante
creative_object_interfacedenominadaActivatorDevicecon el especificadorpublicpara almacenar el dispositivo utilizado en la zona.Un evento denominado
ZoneCompletedEventcon el especificadorpublicpara señalar cuando el jugador interactúa con esta zona, como cuando recoge objetos o los entrega.Una función denominada
ActivateZone()con el tipo de devoluciónvoidy el especificadorpublic, para habilitar el dispositivo utilizado para la zona.Una función denominada
DeactivateZone()con el tipo de devoluciónvoidy el especificadorpublicpara deshabilitar el dispositivo utilizado para 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")Cuando una clase y sus miembros tienen el especificador
public, son accesibles de forma universal desde otro código. Si deseas más información, consulta Especificadores y atributos.
En la función
ActivateZone(), comprueba siActivatorDevicees un dispositivo de zona de captura o generador de objetos. Para ello, convierteActivatorDevicea los diferentes tipos y llama a la funciónEnable()en el dispositivo convertido. Haz lo mismo con la funciónDeactivateZone(), excepto que debes llamar a la funciónDisable().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 función denominada
WaitForZoneCompleted()que tenga los especificadoresprivateysuspends. Esta función señalará elZoneCompletedEventcuando ocurra el evento específico del dispositivo. Esta configuración significa que el resto del código solo necesita esperar alZoneCompletedEventy no preocuparse por el tipo de evento que utiliza el dispositivo subyacente.VerseWaitForZoneCompleted<private>(ZoneDeviceCompletionEventOpt : ?awaitable(agent))<suspends> : void = if (DeviceEvent := ZoneDeviceCompletionEventOpt?): DeviceEvent.Await() ZoneCompletedEvent.Signal(Self)Esta función debe tener el efecto
suspendspara poder llamar aDeviceEvent.Await().Actualiza
ActivateZone()con una expresiónspawnque llame aWaitForZoneCompleted()con el evento de dispositivo adecuado para el jugador que interactúa con el dispositivo:AgentEntersEventpara el dispositivo de zona de captura yItemPickedUpEventpara el dispositivo generador de objetos.WaitForZoneCompletedespera un parámetrooption(indicado por?) de tipoawaitable(agent), por lo que podemos pasar cualquier tipo que implemente la interfazawaitable, con su tipo paramétrico igual aagent. TantoCaptureArea.AgentEntersEventcomoItemSpawner.ItemPickedUpEventrespetan esta condición; por ello, podemos usarlos como parámetro. Este es otro ejemplo de abstracción.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}) }Añade otro evento denominado
ZoneDeactivatedEventque tenga el especificadorprotected. Este evento es necesario para finalizar la funciónWaitForZoneCompleted()si la zona se desactiva antes de que el jugador la complete. Señala este evento en la funciónDeactivateZone().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()Actualiza
WaitForZoneCompleted()con una expresiónracepara que la función espere a que el jugador complete la zona o a que la zona se desactive. Con la expresiónrace, la llamada a función asíncronaZoneDeactivatedEvent.Await()y la expresiónblockcon el evento de dispositivo y la señalZoneCompletedEventse ejecutarán al mismo tiempo, pero se cancela la expresión que no finaliza primero.VerseWaitForZoneCompleted<private>(ZoneDeviceCompletionEventOpt : ?awaitable(agent))<suspends> : void = if (DeviceEvent := ZoneDeviceCompletionEventOpt?): race: block: DeviceEvent.Await() ZoneCompletedEvent.Signal(Self) ZoneDeactivatedEvent.Await()Por último, crea un constructor para la clase
base_zoneque inicializará el campoActivatorDevice.VerseMakeBaseZone<constructor><public>(InActivatorDevice : creative_object_interface) := base_zone: ActivatorDevice := InActivatorDeviceEl siguiente es el código completo de la clase
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()
Cómo encontrar zonas en tiempo de ejecución con etiquetas de juego
Ahora que tienes una forma de crear zonas y activarlas o desactivarlas, agreguemos una forma de inicializar todas las zonas que etiquetaste en el nivel y veamos cómo seleccionar la siguiente para activarla.
Este ejemplo muestra cómo hacer esto con una clase que se encarga de crear las zonas y seleccionar la siguiente zona que se activará.
Sigue estos pasos para crear la clase que te permitirá crear y seleccionar zonas:
Crea una nueva clase denominada
tagged_zone_selectoren el archivo pickup_delivery_zone.verse. Añade una variable de tipo matriz para almacenar todas las zonas en el nivel.tagged_zone_selector<public> := class: var Zones<protected> : []base_zone = array{}Agrega un método denominado
InitZones()que tenga el especificadorpublicy un parámetrotagpara buscar todas las zonas asociadas con esa etiqueta de juego y almacenarlas en caché.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)Añade un método denominado
SelectNext()que tenga los especificadoresdecidesytransacts, de modo que el método encuentre otra zona o, en su defecto, falle. Selecciona la zona en un índice aleatorio de la matriz conGetRandomInt(0, Zones.Length - 1)para el índice.VerseSelectNext<public>()<transacts><decides> : base_zone = Zones[GetRandomInt(0, Zones.Length - 1)]El código completo del archivo pickup_delivery_zone.verse ahora debería verse de la siguiente manera:
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.
Cómo probar las zonas de recolección y entrega
Ahora que creaste dos clases, es buena idea probar el código y asegurarte de que la selección de zonas funcione como esperabas.
Sigue estos pasos para actualizar tu archivo game_coordinator_device.verse:
Añade una constante para el selector de zona de entrega y una matriz variable para los selectores de zona de recolección a
game_coordinator_device. Dado que el juego aumentará el nivel de recolección después de cada recolección de pizza, necesitarás untagged_zone_selectorpara cada nivel de recolección que desees en el juego, de ahí la matrizPickupZoneSelectors. Cada selector de zona contiene todas las zonas de recolección de un nivel determinado. Tiene que ser una variable porque su configuración está determinada por la cantidad depickup_zone_tagenPickupZoneLevelTags.Usa esta configuración para ampliar el número de niveles de recolección con cambios mínimos al código: solo debes actualizar
PickupZoneLevelTagscon adicionales que derivan depickup_zone_tagy, a continuación, etiquetar los dispositivos en el editor.Versegame_coordinator_device<public> := class<concrete>(creative_device): DeliveryZoneSelector<private> : tagged_zone_selector = tagged_zone_selector{} var PickupZoneSelectors<private> : []tagged_zone_selector = array{}Agrega un método denominado
SetupZones()y llama al método enOnBegin():Establece el método para que tenga el especificador
privatey un tipo de devoluciónvoid.Inicializa el selector de zona de entrega con
delivery_zone_tag.Crea las etiquetas de nivel de zona de recolección e inicializa los selectores de zona de recolección.
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 bucle en
OnBegin()que seleccione la siguiente zona de recolección, la active, espere a que el jugador complete la zona y, luego, la desactive.VerseOnBegin<override>()<suspends> : void = SetupZones() var PickupLevel : int = 0 loop: if (PickupZone : base_zone = PickupZoneSelectors[PickupLevel].SelectNext[]): PickupZone.ActivateZone() PickupZone.ZoneCompletedEvent.Await() PickupZone.DeactivateZone()Guarda los archivos de Verse, compila el código y realiza una prueba de juego en tu nivel.
Cuando realices esta prueba de juego, uno de los dispositivos de generador de objetos se activará al comienzo de la partida. Después de recoger el objeto, el dispositivo generador de objetos se desactivará y, a continuación, se activará un dispositivo de zona de captura. Esto continúa hasta que finalizas manualmente el juego.