조명 태그 퍼즐 튜토리얼의 마지막 단계에서는 퍼즐의 완성된 스크립트를 확인하고 예시를 확장할 수 있는 아이디어에는 무엇이 있는지 알아볼 것입니다.
전체 스크립트
다음 코드는 플레이어가 버튼을 사용해 조명을 켜고 끄며 올바른 조합을 찾아야 하는 재사용 가능한 퍼즐의 완성된 스크립트입니다.
using { /Fortnite.com/Devices }
using { /Verse.org/Native }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Verse.org/Simulation/Tags }
using { /Verse.org/Simulation }
# Verse.org/Simulation/Tags 모듈의 `tag` 클래스를 상속받아 새 게임플레이 태그를 생성합니다.
puzzle_light := class(tag){}
log_tagged_lights_puzzle := class(log_channel){}
<#
이 클래스의 인스턴스를 사용해
button_device의 InteractedWithEvent에 등록하고 이벤트 핸들러의 이벤트별 프로퍼티인 Indices와 PuzzleDevice에 액세스할 수 있습니다.
모든 버튼이 각자의 스테이트와 이벤트 핸들러를 가지는 것으로 생각할 수 있습니다.
클래스에 <concrete> 지정자가 없으므로 필드의 기본값을 명시할 필요가 없습니다.
#>
button_event_handler := class():
# 이 버튼이 제어하는 조명들에 액세스하기 위해 사용하는 위치.
Indices : []int
# button_event_handler를 생성한 tagged_lights_puzzle. 해당 퍼즐의 함수를 호출하기 위해 필요합니다.
PuzzleDevice : tagged_lights_puzzle
OnButtonPressed(InPlayer : agent) : void =
# 이 버튼이 제어하는 위치에 있는 조명들을 PuzzleDevice가 토글하도록 만듭니다.
PuzzleDevice.ToggleLights(Indices)
tagged_lights_puzzle := class<concrete>(creative_device):
Logger : log = log{Channel := log_tagged_lights_puzzle}
# 플레이어가 퍼즐과 상호작용하기 위해 사용하는 버튼.
@editable
Buttons : []button_device = array{}
<#
ButtonsToLights의 엘리먼트 숫자와 Buttons의 엘리먼트 숫자가 일치해야 합니다.
‘x’가 버튼의 인덱스일 때, Buttons[x]의 버튼이 토글하는 조명들을 인덱스가 0부터 시작하는 각 인덱스 배열로 명시합니다.
예를 들어, Buttons[2]는 첫 번째와 두 번째 조명을 토글하며 이를 array{0,1}로 명시합니다.
#>
ButtonsToLights : [][]int = array{array{0, 3}, array{0, 1, 2}, array{0, 1}, array{1}}
<#
LightsState 로직 배열을 사용해 모든 조명의 켜짐과 꺼짐 상태를 트래킹합니다.
또한 조명의 초기 상태를 설정하는 데에도 사용하므로
엘리먼트의 숫자가 puzzle_light 태그가 달린 조명의 숫자와 일치해야 합니다.
#>
@editable
var LightsState : []logic = array{false, false, false, false}
<#
LightsState와 SolvedLightsState가 일치하면 퍼즐이 풀린 것입니다.
따라서 SolvedLightsState의 엘리먼트 숫자와 LightsState의 엘리먼트 숫자가 일치해야 합니다.
#>
@editable
SolvedLightsState : []logic = array{true, true, true, true}
@editable
# 퍼즐을 풀었을 때 활성화되는 ItemSpawner입니다.
ItemSpawner:item_spawner_device = item_spawner_device{}
# 게임플레이 태그로 찾은 조명들을 저장합니다.
var Lights : []customizable_light_device = array{}
# 퍼즐을 풀었을 때 이벤트 등록 해제를 위해 각 버튼의 InteractedWithEvent 핸들러를 저장합니다.
var ButtonSubscriptions : []cancelable = array{} []cancelable = array{}
OnBegin<override>()<suspends> : void =
SetupPuzzleLights()
<#
각 버튼과 그 인덱스를 가지고 해당 버튼이 유효한 LightsIndices 값을 가지면
해당 버튼의 Indices와 Self로 표현되는 이 tagged_light_puzzle의 레퍼런스를 가지고 새 button_event_handler를 생성합니다.
핸들러의 OnButtonPressed 함수를 사용해 버튼의 InteractedWithEvent에 등록합니다.
나중에 퍼즐이 풀리면 플레이어가 상호작용하지 못하도록 등록을 해제하기 위해 이 등록을 ButtonSubscriptions 배열에 저장합니다.
#>
set ButtonSubscriptions = for:
ButtonIndex -> Button : 버튼
LightIndices := ButtonsToLights[ButtonIndex]
do:
Button.InteractedWithEvent.Subscribe(button_event_handler{Indices := LightIndices, PuzzleDevice := Self}.OnButtonPressed)
SetupPuzzleLights() : void =
# 게임플레이 태그를 사용해 가져온 장치의 순서는 일정하게 보장되지 않는다는 점에 주의하세요.
TaggedActors := GetCreativeObjectsWithTag(puzzle_light{})
<#
puzzle_light 태그가 달린 각 액터를 customizable_light_device로 타입 형변환 시도하여 설정 가능한 조명 장치인지 확인합니다.
설정 가능한 조명 장치라면 초기 LightState를 가져와 LightDevice를 TurnOn()하거나 TurnOff()합니다.
태그가 달린 모든 customizable_light_device를 Lights 배열에 저장합니다.
#>
set Lights = for:
ActorIndex -> TaggedActor : TaggedActors
LightDevice := customizable_light_device[TaggedActor]
ShouldLightBeOn := LightsState[ActorIndex]
do:
Logger.Print("Adding Light at index {ActorIndex} with State:{if (ShouldLightBeOn?) then "On" else "Off"}")
if (ShouldLightBeOn?) then LightDevice.TurnOn() else LightDevice.TurnOff()
LightDevice # 표현식의 마지막 명령문은 결과 값이 됩니다. `for` 표현식은 모든 값을 배열에 담아 반환합니다.
ToggleLights(LightIndices : []int) : void =
<#
각 인덱스별로 해당하는 LightState와 조명 장치의 레퍼런스를 가져옵니다.
둘 다 유효하다면 현재 LightState를 받아 IsLightOn 값을 결정하고
켜져 있다면 끄고 꺼져 있다면 켜기 위해 IsLightOn 값을 반전해 해당 LightsState[Index]를 업데이트합니다.
#>
for:
LightIndex : LightIndices
IsLightOn := LightsState[LightIndex]
Light := Lights[LightIndex]
do:
<#
Verse에서 부울 값을 반전하려면 다음 구문을 사용합니다. if (MyLogic?) {false} else {true}
여기에서는 또한 값을 반전해 반환하기 전에 Light를 TurnOff()하거나 TurnOn()합니다.
#>
Logger.Print("Turning light at index {LightIndex} {if (IsLightOn?) then "Off" else "On"}")
NewLightState :=
if (IsLightOn?):
Light.TurnOff()
False
else:
Light.TurnOn()
True
if (set LightsState[LightIndex] = NewLightState): # 배열 인덱싱이 실패할 수 있으므로 실패 컨텍스트로 감싸야 합니다.
Logger.Print("Updated the state for light at {LightIndex}")
# LightsState가 변경되었을 수 있으므로 퍼즐이 풀렸는지 확인합니다.
if (IsPuzzleSolved[]):
Logger.Print("Puzzle solved!")
ItemSpawner.Enable()
# 플레이어가 더 이상 조명을 켜거나 끌 수 없도록 모든 이벤트 핸들러를 등록 해제하고 취소합니다.
for (ButtonSubscription : ButtonSubscriptions):
ButtonSubscription.Cancel()
<#
<decides> 이펙트가 달린 함수는 `if (IsPuzzleSolved[])` 같은 실패 컨텍스트에서만 사용할 수 있습니다.
실패 가능한 함수 호출을 위해 () 대신 []를 사용한다는 점에 주의하세요.
#>
IsPuzzleSolved()<decides><transacts> : void =
<#
각 LightsState를 반복작업해 해당하는 SolvedLightsState와 일치하는지 확인합니다.
처음으로 `for` 내부 블록이 실패하면 함수가 실패하고
호출자에게 실패가 반환됩니다.
그렇지 않다면 함수가 성공합니다.
#>
for:
LightIndex -> IsLightOn : LightsState
IsLightOnInSolution := SolvedLightsState[LightIndex]
do:
IsLightOn = IsLightOnInSolution
직접 해보기
이 튜토리얼에서는 Verse를 사용해 플레이어가 버튼으로 조명 상태를 토글하며 올바른 조명 조합을 찾아내야 하는 재사용 가능한 퍼즐을 만드는 방법을 알아보았습니다.
학습한 내용을 활용하여 다음과 같은 작업을 해 보세요.
- 태그를 더 생성하고 그것을 사용해 결정론적인 시각적 순서로 조명을 제어하기
- 동일한 세팅에서 여러 초기 조건과 해답을 설정하고 버튼과 조명을 추가해 다양한 퍼즐 만들기
- 사용자가 상호작용할 수 있는 다양한 장치를 사용해 여러 장치 유형 제어하기