이 섹션에서는 플레이어의 팀과 클래스를 결정하고 커스터마이징하는 방법을 보여줍니다.
사용한 장치:
-
2 x 팀 설정 및 인벤토리 장치
-
2 x 클래스 설계 장치
-
2 x 클래스 선택 장치
팀 설정 및 인벤토리 장치
팀 설정 및 인벤토리 장치를 사용하여 점수판에 표시할 팀 이름과 색상을 설정합니다.
플레이어에게 보이지 않는 영역에서 각 팀에 하나의 장치를 배치합니다. 사물 팀을 구성하려면 사용자 옵션(User Options) 을 아래 표와 같이 환경설정합니다.
| 옵션 | 값 | 설명 |
|---|---|---|
| 팀 이름(Team Name) | 사물(Props) | 점수판과 HUD 구성요소에서 팀을 식별하는 데 사용할 텍스트 스트링을 설정합니다. |
| 팀 색상(Team Color) | 하늘색(Sky Blue) | 점수판, HUD, 특정 장치에 사용되는 색상을 선택한 팀에 할당합니다. |
| 팀(Team) | 팀 인덱스(Team Index): 1 | 이 장치의 설정을 적용할 팀을 지정합니다. |
헌터 팀을 구성하려면 다른 장치의 사용자 옵션 을 아래 표와 같이 환경설정합니다.
| 옵션 | 값 | 설명 |
|---|---|---|
| 팀 이름(Team Name) | 헌터(Hunters) | 점수판과 HUD 구성요소에서 팀을 식별하는 데 사용할 텍스트 스트링을 설정합니다. |
| 팀 색상(Team Color) | 주황색(Orange) | 점수판, HUD, 특정 장치에 사용되는 색상을 선택한 팀에 할당합니다. |
| 팀(Team) | 팀 인덱스(Team Index): 2 | 이 장치의 설정을 적용할 팀을 지정합니다. |
클래스 설계 장치
클래스 설계 장치를 사용하여 방금 생성한 팀을 수정합니다.
플레이어에게 보이지 않는 영역에서 각 팀에 하나씩 총 2개의 클래스 설계 장치를 배치합니다. 사물 팀을 커스터마이징하려면 사용자 옵션 을 아래 표와 같이 환경설정합니다.
| 옵션 | 값 | 설명 |
|---|---|---|
| 클래스 이름(Class Name) | 사물(Prop) | 이 클래스의 이름을 결정합니다. |
| 클래스 설명(Class Description) | 헌터로부터 숨고 생존하세요.(Hide from hunters. Survive.) | 이 클래스의 설명을 설정합니다. |
| 클래스 식별자 | 클래스 슬롯: 1 | 이 클래스의 고유 식별자를 설정합니다. |
| 최대 체력(Max Health) | 1 | 게임 중에 플레이어가 도달할 수 있는 최대 체력 값을 결정합니다. 사물은 1회 공격으로 처치됩니다. |
| 아이템 목록(Item List) | 사물 복제기(Prop-O-Matic) | 이 클래스가 가질 아이템의 목록을 설정합니다. |
| 지급된 아이템 장착(Equip Granted Item) | 1번째 아이템(First Item) | 목록에서 장착할 아이템을 결정합니다. |
헌터 팀을 커스터마이징하려면 다른 장치의 사용자 옵션 을 아래 표와 같이 환경설정합니다.
| 옵션 | 값 | 설명 |
|---|---|---|
| 클래스 이름(Class Name) | 헌터(Hunter) | 이 클래스의 이름을 결정합니다. |
| 클래스 설명(Class Description) | 사물을 찾아 처치하세요.(Find props. Eliminate them.) | 이 클래스의 설명을 설정합니다. |
| 클래스 식별자 | 클래스 슬롯: 2 | 이 클래스의 고유 식별자를 설정합니다. |
| 아이템 목록(Item List) | 손전등 권총(Flashlight Pistol) | 이 클래스가 가질 아이템의 목록을 설정합니다. |
| 지급된 아이템 장착(Equip Granted Item) | 1번째 아이템(First Item) | 목록에서 장착할 아이템을 결정합니다. |
클래스 선택 장치
클래스 설계 장치를 클래스 선택 장치와 조합하여, 직접 생성 및 커스터마이징한 클래스와 팀을 관리합니다.
이 장치의 설정은 Verse와 함께 플레이어 부활 시 클래스 슬롯 1의 플레이어를 클래스 슬롯 2로 옮깁니다.
플레이어에게 보이지 않는 영역에서 각 팀에 하나씩 총 2개의 클래스 선택 장치를 배치합니다. 사물 팀을 관리하려면 아래 표의 세팅을 사용하여 이 장치의 사용자 옵션 을 환경설정합니다.
| 옵션 | 값 | 설명 |
|---|---|---|
| 다음 클래스로 변경(Class to Switch to) | 클래스 슬롯(Class Slot): 1 | 플레이어를 어떤 클래스로 변경할지 결정합니다. |
| 게임 중에 표시(Visible During Game) | False | 이 장치가 게임 내에 표시되지 않습니다. |
| 구역 오디오(Zone Audio) | False | 플레이어가 구역에 들어올 때 클래스 선택 장치의 오디오 효과 재생 여부를 결정합니다. |
| 다음 팀으로 변경(Team to Switch to) | 팀 인덱스(Team Index): 1 | 플레이어를 어떤 팀으로 변경할지 결정합니다. |
| 변경 시 아이템 지우기(Clear Items on Switch) | True | 변경될 때 플레이어의 인벤토리에서 아이템을 제거할지 여부를 결정합니다. |
| 게임 내 볼륨 표시(Volume Visible in Game) | False | 게임 중에 장치의 볼륨을 표시할지 여부를 결정합니다. |
| 작동 시 VFX 표시(Display VFX on Activation) | False | 플레이어의 클래스 또는 팀을 변경할 때 장치에서 VFX 이펙트를 생성할지 여부를 결정합니다. |
헌터 팀을 관리하려면 아래 표의 세팅을 사용하여 이 장치의 사용자 옵션 을 환경설정합니다.
| 옵션 | 값 | 설명 |
|---|---|---|
| 다음 클래스로 변경(Class to Switch to) | 클래스 슬롯(Class Slot): 2 | 플레이어를 어떤 클래스로 변경할지 결정합니다. |
| 다음 팀으로 변경(Team to Switch to) | 팀 인덱스(Team Index): 2 | 플레이어를 어떤 팀으로 변경할지 결정합니다. |
Verse로 팀 기능 생성하기
이 숨바꼭질 게임에는 헌터 팀과 사물 팀이라는 두 팀이 있습니다. 게임이 작동하려면 두 팀에서 일부 작업을 동일하게 수행해야 합니다. 예시:
-
팀에 플레이어 추가
-
팀에서 플레이어 제거
-
플레이어에게 팀에 대한 정보 표시
코드 복제 없이 이 기능을 두 팀 모두에 생성하기 위해 <abstract> 지정자로 클래스를 생성합니다. abstract 지정자가 있는 클래스는 부분적인 함수 기능을 지니게 되는데, 해당 서브클래스가 이를 상속하여 빌드됩니다. 먼저 base_team 라는 추상 클래스를 생성하고, 사물 팀과 헌터 팀이 공유할 함수 기능을 부여합니다.
이 문서에는 이 게임플레이에 필요한 게임플레이 메커니즘의 실행법을 보여주는 Verse 스니펫이 포함되어 있습니다. 아래 단계를 따라 이 튜토리얼의 6단계에 있는 전체 스크립트를 복사합니다.
base_team.verse 라는 프로젝트에서 새 Verse 파일을 생성합니다. 이 파일은 Verse 장치가 되지 않으므로 빈 Verse 파일로 생성해도 됩니다.
using { /Fortnite.com/Characters }
using { /Fortnite.com/Devices }
using { /Fortnite.com/UI }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /UnrealEngine.com/Temporary/UI }
using { /Verse.org/Colors }
using { /Verse.org/Simulation }
log_team := class(log_channel){}
# 이 클래스는 경험 내 여러 팀에 필요한 장치를 정의합니다.
# 이 클래스는 추상이므로 자체적으로는 사용할 수 없으며, 다른 클래스에서 상속받아야 합니다.
base_team := class<abstract>:
Logger:log = log{Channel:=log_team}
@editable # 플레이어를 팀에 설정하는 데 사용됩니다.
ClassSelector:class_and_team_selector_device = class_and_team_selector_device{}
@editable # 팀의 에이전트에게 점수를 부여하는 데 사용됩니다.
ScoreManager:score_manager_device = score_manager_device{}
@editable # 팀 할당 제목을 표시하는 데 사용됩니다.
TeamTitle:hud_message_device = hud_message_device{}
@editable # 팀 할당 설명을 표시하는 데 사용됩니다.
TeamDescription:hud_message_device = hud_message_device{}
@editable # 팀원(사물 팀) 또는 적(헌터 팀) 처치됨 이벤트를 구독하는 데 사용됩니다.
TeamManager:team_settings_and_inventory_device = team_settings_and_inventory_device{}
# 팀의 에이전트 배열입니다.
var TeamAgents<private>:[]agent = array{}
# 이 이벤트는 TeamAgents 배열이 빈 상태가 될 때 라운드 종료 신호를 전송받습니다.
TeamEmptyEvent:event() = event(){}
# 현재 TeamAgents 배열을 반환합니다.
# TeamAgents 배열이 프라이빗이므로 다른 클래스에서 직접 액세스할 수 없기 때문에 필요합니다.
GetAgents()<decides><transacts>:[]agent =
TeamAgents
# TeamAgents 배열의 크기를 반환합니다.
# TeamAgents 배열이 프라이빗이므로 다른 클래스에서 직접 액세스할 수 없기 때문에 함수가 필요합니다.
Count()<transacts>:int =
TeamAgents.Length
# 에이전트의 TeamAgents 배열에 있는 인덱스를 반환하고, 그렇지 않으면 실패합니다.
FindOnTeam(Agent:agent)<decides><transacts>: int =
Index := TeamAgents.Find[Agent]
# 에이전트를 팀에 설정하고 플레이어에게 알립니다.
InitializeAgent(Agent:agent):void =
AddAgentToTeam(Agent)
ClassSelector.ChangeTeamAndClass(Agent)
DisplayTeamInformation(Agent)
# TeamAgents에 에이전트를 추가합니다.
AddAgentToTeam(AgentToAdd:agent):void =
if (not FindOnTeam[AgentToAdd]):
Logger.Print("Adding agent to team.")
set TeamAgents += array{AgentToAdd}
# HUD 메시지 장치를 활성화하여 플레이어에게 소속 팀을 표시합니다.
DisplayTeamInformation(Agent:agent):void =
TeamTitle.Show(Agent)
TeamDescription.Show(Agent)
# 에이전트가 매치에서 나갈 때 TeamAgents 배열에서 에이전트를 제거하고 라운드 종료를 확인합니다.
EliminateAgent(Agent:agent)<suspends>:void =
Sleep(0.0) # 게임 틱 1틱을 딜레이하여 진행 전에 플레이어가 부활되도록 합니다.
RemoveAgentFromTeam(Agent)
# TeamAgents에서 에이전트를 제거합니다.
# 제거된 에이전트가 마지막 남은 에이전트였다면 TeamEmptyEvent에 신호를 전송합니다.
RemoveAgentFromTeam(AgentToRemove:agent):void =
set TeamAgents = TeamAgents.RemoveAllElements(AgentToRemove)
Logger.Print("{Count()} agent(s) on team remaining.")
if (Count() < 1):
Logger.Print("No agents on team remaining. Ending the round.")
TeamEmptyEvent.Signal()
이제 이 클래스가 있으므로, 사물 팀과 헌터 팀을 위한 클래스를 생성할 수 있습니다. 각 클래스는 base_team 에서 상속되므로 다음과 같은 몇 가지 이점이 있습니다.
-
이미
base_team에 공통 함수 및 데이터가 정의되어 있기 때문에 각 팀을 구현하는 코드가 훨씬 더 짧습니다. -
사물 팀과 헌터 팀이 공통 코드로 섞여 있는 것이 아니라 자체 클래스 내에 있기 때문에 두 팀의 전용 코드가 무엇인지 더 쉽게 파악할 수 있습니다.
-
게임 모드에 더 많은 팀을 훨씬 손쉽게 추가할 수 있습니다. 새 팀은 모두
base_team에서 상속받으며, 새 팀을 구별하는 코드는 자체 클래스 내에 있습니다.
<abstract> 지정자로는 클래스의 인스턴스를 생성할 수 없습니다. 추상 클래스에서 상속하는 클래스를 생성하여 해당 클래스를 인스턴스화해야 합니다.
헌터 팀
먼저, 헌터 팀을 위한 클래스를 생성합니다. hunter_team.verse 라는 프로젝트에서 새 Verse 파일을 생성합니다. 이 파일은 Verse 장치가 되지 않으므로 빈 Verse 파일로 생성해도 됩니다.
hunter_team 으로 명명된 클래스를 선언합니다. 클래스는 <concrete> 여야 하며, 마찬가지로 base_team 에서 상속해야 합니다.
hunter_team := class<concrete>(base_team):
클래스를 <concrete> 로 만든다는 것은 클래스의 모든 필드에 기본값이 있어야 한다는 것을 뜻합니다. 자세한 내용은 지정자와 어트리뷰트를 참조하세요.
아래는 hunter_team.verse 스크립트의 전체 코드입니다.
hunter_team 클래스에는 base_team 클래스 내 함수와 이름이 동일한 2개의 함수가 있습니다. 이는 두 함수에 <override> 지정자가 있기 때문에 허용됩니다. 즉, 이러한 함수가 hunter_team 의 인스턴스에서 호출될 때 hunter_team 클래스 내 버전이 사용됩니다.
예를 들어, 다음 코드에서 hunter_team 에 정의된 InitializeAgent() 버전이 사용됩니다. base_team 에 있는 동일한 이름의 함수를 오버라이드하기 때문입니다. 오버라이드 함수가 없어 base_team 에서 정의된 버전을 사용하는 Count() 를 호출하는 경우와 비교해 보세요.
HunterTeam:hunter_team = hunter_team{}
# hunter_team의 함수를 사용합니다.
HunterTeam.InitializeAgent(StartingHunterAgent)
# base_team의 함수를 사용합니다.
HunterTeam.Count()
오버라이드된 두 함수는 (super:) 도 사용합니다. 이는 base_team 이 hunter_team 의 수퍼클래스이므로 base_team 에 정의된 함수 버전 호출을 허용합니다. InitializeAgent() 및 EliminateAgent() 의 경우 둘 다 Logger.Print() 를 사용하여 로그에 무언가를 출력합니다. 그런 다음 base_team 에서 각 함수를 호출합니다. 즉, 함수는 Logger.Print() 에 대한 호출을 제외하면 base_team 내 버전과 완전히 동일하게 작동합니다.
<override> 및 (super:) 에 대한 자세한 내용은 서브클래스를 참조하세요.
사물 팀
이제 사물 팀을 위한 클래스를 생성합니다. prop_team.verse 라는 프로젝트에서 새 Verse 파일을 생성합니다. 이 파일은 Verse 장치가 되지 않으므로 빈 Verse 파일로 생성해도 됩니다.
사물 팀 팀원의 경우 관리할 것이 더 많습니다. 사물 팀에는 타이머 및 이동 거리를 기반으로 시작 및 중지해야 하는 심장 박동 이펙트가 있으며, 또한 처치되면 헌터 팀으로 이동되어야 하기 때문입니다.
사물 팀 팀원을 관리하려면 RunPropGameLoop() 메서드를 사용합니다. 이 메서드는 게임에서 사물의 전체 여정을 위한 관리 장치로 간주해도 됩니다. 생성되는 시점부터 처치되거나 게임에서 나가는 시점까지, 이 메서드는 모든 사물 팀 팀원을 대상으로 실행됩니다.
# 사물 에이전트가 이동을 멈추면, 사물 에이전트가 MinimumMoveDistance를 초과하여 이동하는지, 심장 박동 타이머가 완료되는지 또는 사물 에이전트가 처치되는지 확인하기 위해 경합합니다.
RunPropGameLoop(PropAgent:agent)<suspends>:void =
Logger.Print("Starting prop agent game loop.")
# 사물 에이전트가 처치되거나 플레이어가 세션에서 나갈 때까지 사물 비헤이비어를 무한 루프합니다.
race:
PropAgent.AwaitNoLongerAProp()
loop:
# 사물 에이전트가 최소 거리 미만으로 이동할 때까지 대기한 다음 진행합니다.
PropAgent.AwaitStopMoving(MinimumMoveDistance)
# 사물 에이전트가 최소 거리를 초과하여 이동할 때까지 심장 박동을 카운트다운한 다음 심장 박동을 무한 재생합니다.
race:
PropAgent.AwaitStartMoving(MinimumMoveDistance)
block:
CountdownTimer(PropAgent)
PropAgent.StartHeartbeat()
Sleep(0.0) # race가 완료되면(사물 에이전트가 이동하면) 루프가 다시 시작됩니다.
RunPropGameLoop() 에는 PropAgent 라는 하나의 파라미터가 있습니다. 이는 사물 팀의 플레이어를 나타내는 상수입니다. 또한 완료하는 데 걸리는 시간을 뜻하는 <suspends> 지정자가 있습니다. 이 경우, 전달된 PropAgent 가 더 이상 사물 팀에 소속되지 않을 때까지 완료되지 않습니다.
이 메서드의 모든 함수 기능은 race 표현식 내에 포함되어 있습니다. 즉, 메서드는 이 경합 내 표현식 중 하나가 완료될 때까지는 완료되지 않습니다. 해당 표현식은 다음과 같습니다.
-
PropAgent.AwaitNoLongerAProp() -
loop
이 경합 내 loop 표현식은 종료되지 않으며, 의도적으로 무한히 루프합니다. 즉, AwaitNoLongerAProp() 메서드가 경합에서 항상 승리하여 메서드를 완료합니다. 이런 방식으로 race를 사용하는 것은 어떤 일이 일어날 때까지 특정 코드 세트를 반복해서 실행하도록 프로그램에 전달하는 것과 같습니다. 이 강력한 표현식에 대한 자세한 내용은 race를 참조하세요.
이 코드를 사용하면 AwaitNoLongerAProp() 이 경합에서 승리합니다.
# 사물 에이전트가 더 이상 PropAgents 배열의 일부가 아닐 때까지 루프합니다. 사물 에이전트가 처치되어 헌터로 바뀌거나 플레이어가 세션에서 나가면 제거가 일어납니다.
(PropAgent:agent).AwaitNoLongerAProp()<suspends>:void =
loop:
if (not FindOnTeam[PropAgent]):
Logger.Print("Cancelling prop agent behavior.")
break
Sleep(0.0) # 다음 게임 틱으로 진행합니다.
이 메서드는 사물 팀에 PropAgent 가 있는지 끊임없이 확인합니다. not FindOnTeam[PropAgent] 가 성공할 때까지 실행되는 루프로 시작된 다음, 루프가 중단되고 메서드가 완료됩니다. 자세한 내용은 loop와 break를 참조하세요.
FindOnTeam[] 은 base_team 에서 선언된 실패 가능 함수로, 사물 팀에서 PropAgent 가 발견되면 성공합니다. 하지만 사물 팀에서 PropAgent 가 발견되지 않는 경우에만 루프를 중단하고자 하므로, not 연산자를 사용해야 합니다. not 에 대한 자세한 내용은 연산자를 참조하세요.
마지막으로, Sleep(0.0) 를 루프 끝에 추가해야 합니다. 이는 루프가 한 번 실행된 후 다음 시뮬레이션 업데이트로 진행되도록 합니다. 이 확인을 더 자주 실행할 필요는 없으므로, Sleep(0.0) 이 추가되어 퍼포먼스에 도움을 줍니다. 자세한 내용은 Sleep의 Verse API 레퍼런스 페이지를 참조하세요.
이제 AwaitNoLongerAProp() 의 작동 방식을 알았으니, 이와 경합하는 무한 루프를 RunPropGameLoop() 에 작성할 수 있습니다.