구역은 플레이어가 아이템을 픽업하거나 배달할 수 있는 맵의 영역이며, 장치로 나타냅니다. 타임 트라이얼: 피자 배달 게임 튜토리얼의 이번 단계를 완료하면 이러한 픽업 및 배달 구역을 생성하고 플레이어를 위해 활성화/비활성화하는 방법을 배우게 됩니다.
구역 클래스를 생성하기 위해 추상화 사용하기
추상화는 사용자가 이면의 복잡한 내용을 이해할 필요가 없는 경우 불필요한 세부 내용을 숨긴다는 프로그래밍 원칙입니다. 추상화는 어떤 것의 작동 원리를 드러내지 않고 그것이 무엇인지 설명합니다. 예를 들어 자동판매기에 돈을 넣고 간식을 사 먹는데 자동판매기의 작동 원리까지 이해할 필요는 없다는 것입니다.
타임 트라이얼 피자 배달 게임에는 아이템 생성 장치(Item Spawner)를 사용하는 픽업 구역과 회수 영역(Capture Area) 장치를 사용하는 배달 구역, 이렇게 두 구역이 있습니다. 두 구역은 장치가 다르지만 같은 방식으로 작동하므로, 두 구역 모두 활성화 및 비활성화가 가능합니다. 클래스를 생성하여 특정 장치 상호작용을 처리하는 일반 구역 오브젝트로 이러한 작동 방식을 추상화할 수 있습니다.
이 행동을 클래스로 추상화한다는 것은 한 부분만 변경하여 사용할 장치의 종류를 변경할 수 있다는 뜻입니다. 또한 이 구현에서는 코드를 변경하지 않고도 세부 사항을 변경할 수 있습니다. 이 클래스를 사용하는 코드는 활성화/비활성화 함수밖에 모르기 때문입니다.
다음 단계에 따라 이 구역 클래스를 생성합니다.
새로운 빈 Verse 파일을 생성하여 pickup_delivery_zone.verse라고 명명한 뒤 Visual Studio Code로 엽니다.
이 Verse 파일에서
base_zone이라는 새 클래스를public지정자와 함께 생성하고 다음을 추가합니다.public지정자를 갖는creative_object_interface상수ActivatorDevice: 구역에서 사용되는 장치를 저장합니다.public지정자를 갖는 이벤트ZoneCompletedEvent: 아이템 픽업 또는 배달 등 이 구역에서 플레이어가 상호작용하는 경우를 알립니다.void반환 타입과public지정자를 갖는 함수ActivateZone(): 구역에 사용되는 장치를 활성화합니다.void반환 타입과public지정자를 갖는 함수DeactivateZone(): 구역에 사용되는 장치를 비활성화합니다.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")클래스와 멤버에
public지정자가 있는 경우 다른 코드에서 모두 액세스 가능합니다. 자세한 내용은 지정자와 어트리뷰트를 참고하세요.
ActivateZone()함수에서ActivatorDevice를 다른 타입으로 타입 형변환하고 변환된 장치에서Enable()함수를 호출하여ActivatorDevice가 회수 영역 장치인지 아이템 생성 장치인지 확인합니다.DeactivateZone()함수에서도 똑같이 하되Disable()함수를 호출합니다.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()private지정자와suspends지정자가 있고WaitForZoneCompleted()로 명명된 함수를 생성합니다. 이 함수는 장치별 이벤트가 발생할 때ZoneCompletedEvent에 신호를 보냅니다. 이렇게 구성하면 다른 코드는ZoneCompletedEvent를 기다리기만 하면 되며, 기반 장치가 어떤 유형의 이벤트를 사용하는지 신경 쓰지 않아도 됩니다.VerseWaitForZoneCompleted<private>(ZoneDeviceCompletionEventOpt : ?awaitable(agent))<suspends> : void = if (DeviceEvent := ZoneDeviceCompletionEventOpt?): DeviceEvent.Await() ZoneCompletedEvent.Signal(Self)이 함수는
suspends이펙트를 가져야DeviceEvent.Await()를 호출할 수 있습니다.장치와 상호작용하는 플레이어에게 적절한 장치 이벤트와 함께
WaitForZoneCompleted()를 호출하는spawn표현식으로ActivateZone()을 업데이트합니다. 회수 영역 장치는AgentEntersEvent, 아이템 생성 장치는ItemPickedUpEvent입니다.WaitForZoneCompleted는awaitable(agent)타입의option(?로 표시) 파라미터를 예상하므로,awaitable인터페이스를 구현하는 어떤 타입이든agent와 동일한 파라미터 타입과 함께 전달할 수 있습니다.CaptureArea.AgentEntersEvent및ItemSpawner.ItemPickedUpEvent는 모두 이 조건을 충족하므로 파라미터로 사용할 수 있습니다. 다음은 추상화의 또 한 가지 예시입니다.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}) }protected지정자가 있고ZoneDeactivatedEvent라고 명명된 이벤트를 추가합니다. 이 이벤트는 플레이어가 완료하기 전에 구역이 비활성화되는 경우WaitForZoneCompleted()함수를 종료하는 데 필요합니다.DeactivateZone()함수에서 이 이벤트로 신호를 보냅니다.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()WaitForZoneCompleted()를race표현식으로 업데이트하여 함수가 플레이어의 구역 완료 또는 구역 비활성화를 기다리게 합니다.race표현식을 사용하면ZoneDeactivatedEvent.Await()비동기 함수 호출, 그리고 장치 이벤트와ZoneCompletedEvent신호가 포함된block표현식이 동시에 실행되지만, 먼저 완료되지 않는 표현식은 취소됩니다.VerseWaitForZoneCompleted<private>(ZoneDeviceCompletionEventOpt : ?awaitable(agent))<suspends> : void = if (DeviceEvent := ZoneDeviceCompletionEventOpt?): race: block: DeviceEvent.Await() ZoneCompletedEvent.Signal(Self) ZoneDeactivatedEvent.Await()마지막으로
ActivatorDevice필드를 초기화할base_zone클래스의 생성자를 만듭니다.VerseMakeBaseZone<constructor><public>(InActivatorDevice : creative_object_interface) := base_zone: ActivatorDevice := InActivatorDevicebase_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()
게임플레이 태그로 런타임에 구역 찾기
이제 구역을 생성하고 활성화/비활성화할 방법을 마련했으니 레벨에서 태그 지정한 모든 구역을 초기화하고 다음으로 활성화할 구역을 선택하는 방법을 추가하겠습니다.
이 예시에서는 구역 생성 및 다음으로 활성화할 구역 선택을 담당하는 클래스를 사용하여 이를 수행하는 방법을 보여줍니다.
구역을 생성하고 선택하는 클래스를 생성하려면 다음 단계를 따릅니다.
pickup_delivery_zone.verse 파일에 새 클래스
tagged_zone_selector를 생성합니다. 레벨 내 모든 구역을 저장할 변수 배열을 추가합니다.tagged_zone_selector<public> := class: var Zones<protected> : []base_zone = array{}게임플레이 태그와 관련된 모든 구역을 찾고 캐시하기 위해
public지정자와tag파라미터가 있는 메서드InitZones()를 추가합니다.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)decides및transacts지정자가 있는 메서드SelectNext()를 추가하여 메서드가 다른 구역을 찾거나 실패하게 합니다. 인덱스에GetRandomInt(0, Zones.Length - 1)를 사용하여 배열의 랜덤 인덱스에서 구역을 선택합니다.VerseSelectNext<public>()<transacts><decides> : base_zone = Zones[GetRandomInt(0, Zones.Length - 1)]pickup_delivery_zone.verse 파일의 완성된 코드는 다음과 같은 모습이어야 합니다.
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.
픽업 및 배달 구역 테스트하기
두 클래스를 생성했으니 이제 코드를 테스트하고 구역 선택이 예상대로 작동하는지 확인해야 합니다.
다음 단계를 따라 game_coordinator_device.verse 파일을 업데이트합니다.
game_coordinator_device에 배달 구역 선택기를 위한 상수 하나와 픽업 구역 선택기를 위한 변수 배열 하나를 추가합니다. 게임은 피자가 픽업될 때마다 픽업 레벨을 높여야 하므로 게임에서 원하는 픽업 레벨마다 하나의tagged_zone_selector가 필요하며, 따라서PickupZoneSelectors배열이 있어야 합니다. 각 구역 선택기는 특정 레벨의 모든 픽업 구역을 포함합니다.PickupZoneLevelTags의pickup_zone_tag수에 따라 구성이 결정되기 때문에 이는 변수여야 합니다.이 구성을 활용하면 코드를 최소한으로 변경하면서 픽업 레벨 수를 확장할 수 있습니다.
pickup_zone_tag에서 파생된 추가 태그로PickupZoneLevelTags를 업데이트한 다음 에디터에서 장치를 태그 지정하면 됩니다.Versegame_coordinator_device<public> := class<concrete>(creative_device): DeliveryZoneSelector<private> : tagged_zone_selector = tagged_zone_selector{} var PickupZoneSelectors<private> : []tagged_zone_selector = array{}SetupZones()라고 명명된 메서드를 추가하고OnBegin()에서 메서드를 호출합니다.메서드가
private지정자 및void반환 타입을 갖도록 설정합니다.delivery_zone_tag로 배달 구역 선택기를 초기화합니다.픽업 구역 레벨 태그를 생성하고 픽업 구역 선택기를 초기화합니다.
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{}
OnBegin()에 다음 픽업 구역을 선택하는 루프를 생성하고 활성화한 다음 플레이어가 구역을 완료할 때까지 기다렸다가 구역을 비활성화합니다.VerseOnBegin<override>()<suspends> : void = SetupZones() var PickupLevel : int = 0 loop: if (PickupZone : base_zone = PickupZoneSelectors[PickupLevel].SelectNext[]): PickupZone.ActivateZone() PickupZone.ZoneCompletedEvent.Await() PickupZone.DeactivateZone()Verse 파일을 저장하고 코드를 컴파일한 뒤 레벨을 플레이테스트합니다.
레벨을 플레이테스트하면 게임 시작 시 아이템 생성 장치 중 하나가 활성화됩니다. 아이템을 픽업하면 아이템 생성 장치가 비활성화되고 회수 영역 장치가 활성화됩니다. 이는 게임을 수동으로 종료할 때까지 계속됩니다.