Una zona es un área del mapa (representada por un dispositivo) donde el jugador puede recoger y entregar objetos. Al completar este paso del tutorial Prueba contrarreloj: Reparto de pizzas, tendrás más información sobre cómo crear estas zonas de recogida y entrega, y cómo activarlas/desactivarlas para el jugador.
Cómo utilizar la abstracción para crear una clase de zona
La abstracción es un principio de programación en el que se ocultan al usuario los detalles innecesarios, cuando este no necesita comprender las complejidades ocultas. La abstracción describe lo que algo es, sin saber cómo funciona. Por ejemplo, puedes meter dinero en una máquina expendedora y sacar una bolsa de patatas sin necesidad de entender cómo funciona la mecánica.
En Prueba contrarreloj: Reparto de pizzas, hay dos tipos de zonas: zonas de recogida, que utilizan el dispositivo Generador de elementos y zonas de entrega, que utilizan el dispositivo Zona de captura. Como estas zonas se comportarán de la misma manera aunque sean dispositivos diferentes (es decir, que puedan activarse y desactivarse) puedes crear una clase para abstraer este comportamiento en un objeto de zona genérico que se encargue de las interacciones específicas del dispositivo.
Abstraer este comportamiento en una clase significa que solo tienes un lugar para cambiar el tipo de dispositivos que utilizas. Esta implementación también significa que puedes cambiar sus características específicas sin cambiar nada de tu otro código, porque cualquier código que utilice esta clase solo conoce las funciones activar/desactivar.
Sigue estos pasos para crear esta clase de zona:
Crea un nuevo archivo de Verse vacío nombrado pickup_delivery_zone.verse y ábrelo en Visual Studio Code.
En el archivo de Verse, crea una nueva clase nombrada
base_zonecon el especificadorpublicy añade:Una constante
creative_object_interfacenombradaActivatorDevicecon el especificadorpublic, para almacenar el dispositivo utilizado en la zona.Un evento nombrado
ZoneCompletedEventque tiene el especificadorpublic, para señalar cuando el jugador interactúa con esta zona (por ejemplo, recogiendo o entregando objetos).Una función nombrada
ActivateZone()que tiene el tipo de retornovoidy el especificadorpublic, para activar el dispositivo utilizado para la zona.Una función nombrada
DeactivateZone()que tiene el tipo de retornovoidy el especificadorpublic, para desactivar 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 universalmente accesibles desde otro código. Para obtener más información, consulta Especificadores y atributos.
En la función
ActivateZone(), comprueba siActivatorDevicees un dispositivo Zona de captura o un dispositivo Generador de objetos mediante casting de tipo deActivatorDevicea los distintos tipos y llama a la funciónEnable()en el dispositivo convertido. Haz lo mismo con la funciónDeactivateZone(), pero aquí llama 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 nombrada
WaitForZoneCompleted()que tenga el especificadorprivatey el especificadorsuspends. Esta función señalaráZoneCompletedEventcuando se produzca el evento específico del dispositivo. Esta configuración significa que otro código solo tiene que esperar aZoneCompletedEventy no preocuparse de qué tipo de evento 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 apropiado del dispositivo para el jugador que interactúa con el dispositivo:AgentEntersEventpara el dispositivo Zona de captura yItemPickedUpEventpara dispositivo Generador de elementos.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 lo que podemos utilizarlos 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 llamado
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 la función asíncronaZoneDeactivatedEvent.Await()y la expresiónblockcon el evento del dispositivo y la señalZoneCompletedEventse ejecutarán al mismo tiempo, pero la expresión que no termine primero se cancelará.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 inicialice el campoActivatorDevice.VerseMakeBaseZone<constructor><public>(InActivatorDevice : creative_object_interface) := base_zone: ActivatorDevice := InActivatorDeviceA continuación se muestra 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 jugabilidad
Ahora que ya tienes una forma de crear zonas y activarlas/desactivarlas, vamos a añadir una forma de inicializar todas las zonas que hayas etiquetado en el nivel y cómo seleccionar la siguiente para activarla.
Este ejemplo muestra cómo hacerlo con una clase que se encarga de crear las zonas y de seleccionar la siguiente zona para activar.
Sigue estos pasos para crear la clase de creación y selección de zonas:
Crea una clase nueva nombrada
tagged_zone_selectoren el archivo pickup_delivery_zone.verse. Añade una matriz de variables para almacenar todas las zonas del nivel.tagged_zone_selector<public> := class: var Zones<protected> : []base_zone = array{}Añade un método llamado
InitZones()que tenga el especificadorpublicy un parámetrotagpara encontrar todas las zonas asociadas a 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 llamado
SelectNext()que tenga los especificadoresdecidesytransactspara que el método encuentre otra zona o falle. Selecciona la zona en un índice aleatorio de la matriz utilizandoGetRandomInt(0, Zonas.Longitud - 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 debería tener ahora el siguiente aspecto:
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 recogida y entrega
Ahora que has creado dos clases, conviene probar tu código y asegurarte de que la selección de zonas funciona como esperas.
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 de variables para los selectores de zona de
game_coordinator_device. Como el juego aumentará posteriormente el nivel de recogida después de cada recogida de pizza, necesitarás untagged_zone_selectorpor cada nivel de recogida que quieras en el juego, de ahí la matrizPickupZoneSelectors. Cada selector de zona contiene todas las zonas de recogida de un determinado nivel. Tiene que ser una variable porque su configuración viene determinada por el número depickup_zone_tagenPickupZoneLevelTags.Utiliza esta configuración para ampliar el número de niveles de recogida con cambios mínimos en el código: solo tienes que actualizar las etiquetas
PickupZoneLevelTagscon otras derivadas depickup_zone_tag, y luego 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{}Añade un método nombrado
SetupZones()y llama al método enOnBegin():Configura el método para que tenga el especificador
privatey un tipo de retornovoid.Inicializa el selector de zona de entrega con
delivery_zone_tag.Crea las etiquetas de nivel de zona de recogida e inicializa los selectores de zona de recogida.
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 recogida, la active, espere a que el jugador complete la zona y 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 tus archivos de Verse, compila el código y prueba tu nivel.
Cuando pruebes tu nivel, uno de los dispositivos Generador de objetos se activará al inicio de la partida. Cuando recojas el objeto, se desactivará el dispositivo Generador de objetos y se activará el dispositivo Zona de captura. Esto continúa hasta que termines manualmente el juego.