의무병은 많은 게임에서 흔히 볼 수 있는 캐릭터입니다. 의무병의 역할은 근처에 있는 캐릭터를 치유하고, 피해를 받은 동료가 회복할 수 있도록 도움을 주는 것입니다. 의무병은 게임에 따라 다양한 역할을 합니다. 병원에서 환자를 돌보는 의사, 치유와 동시에 팀원의 전투를 돕는 군의관, 누구든 치유하는 중립 치료소를 예로 들 수 있습니다.
이 예시에서 만들 의무병 캐릭터는 몇 가지 로직 규칙을 따릅니다.
- 유휴 상태(Idle)
- 에이전트 치유 시작(Begin Healing Agents)
- 치유 반복(Healing Loop)
- 에이전트로 이동(Navigate to Agent)
의무병은 유휴 상태로 시작해 에이전트가 치유 구역에 들어올 때까지 정찰합니다. 들어온 에이전트는 의무병의 치유 큐에 추가됩니다. 의무병은 다음으로 치유해야 하는 에이전트를 추적해야 하고, 큐는 선입선출 데이터 구조이므로 치유 목적에 맞게 유용하게 사용할 수 있습니다. 따라서 치유 구역에 먼저 들어오는 캐릭터를 먼저 치유하게 됩니다.
의무병이 다음으로 치유해야 하는 에이전트를 찾으면 먼저 에이전트의 체력이 치유 한계치보다 낮은지 확인합니다. 낮은 경우 에이전트의 체력이 한계치에 도달하거나 에이전트가 치유 구역을 나갈 때까지 특정 속도로 치유를 시작해 지속합니다. 치유 시 의무병은 지속적으로 에이전트를 향해 이동하여 에이전트 근처에 머물려고 합니다. 에이전트의 체력이 한계치까지 차면 의무병은 치유할 다음 에이전트를 찾아 이 프로세스를 반복합니다. 치유할 에이전트가 없으면 의무병은 다시 유휴 상태가 됩니다.
아래의 유한 스테이트 머신을 사용하여 의무병 NPC의 로직을 시각화할 수 있습니다. 유한 스테이트 머신에 대한 자세한 내용은 NPC 비헤이비어 이해하기를 확인하세요.
이 가이드를 마치면 캐릭터의 체력이 특정 한계치 아래일 때 근처의 캐릭터를 치유하는 NPC 비헤이비어 스크립트를 사용하여 커스텀 의무병 캐릭터를 만드는 방법을 배우게 됩니다. 참조할 수 있도록 전체 스크립트가 이 가이드 끝부분에 나와 있습니다.
새로운 NPC 비헤이비어 스크립트 생성하기
자신만의 의무병 NPC 캐릭터를 만들기 위해 medic_example 로 명명한 새로운 NPC 비헤이비어 스크립트를 생성합니다. 자신만의 NPC 비헤이비어 스크립트 생성하기에 대한 자세한 내용은 자신만의 NPC 비헤이비어 생성하기를 참조하세요. Visual Studio Code에서 Verse 파일을 엽니다.
UEFN에서 근처 플레이어를 치유하는 의무병 캐릭터를 생성하는 NPC 비헤이비어 스크립트를 만들려면 다음 단계를 따르세요.
치유 큐 구현하기
NPC 비헤이비어 스크립트는 캐릭터 이동과 디버그 시각화에 사용되는 여러 값으로 시작됩니다. 이 스크립트에서 모든 값이 필요한 것이 아니므로 이제 불필요한 코드를 제거하겠습니다.
-
medic_example클래스 정의 상단에서OnBegin()앞에 있는 값을 제거합니다. 의무병 캐릭터는 기다렸다가 다른 캐릭터로 이동하는 대신 치유 시 캐릭터를 따라다닙니다. 이 예시에서는 디버그 값이 필요하지 않으며 다른 오브젝트를 사용하여 의무병이 캐릭터를 치유하는 순간을 시각화합니다. -
OnBegin()의 첫 번째if문에서 캐릭터의 인터페이스를 가져온 후then문 안에 있는 코드를 제거합니다. 의무병 캐릭터는 생성 후 지점을 반복해서 이동할 필요가 없고, 대신 생성 지점 주변을 순찰하면서 치유할 캐릭터가 올 때까지 대기합니다. -
DrawDebugLocation()및DrawDebugLookAt()함수를 제거합니다. 이 예시에서는 디버그 값을 사용하지 않으므로 이를 사용하는 연결된 함수도 필요하지 않습니다.
불필요한 코드를 제거한 후 의무병 캐릭터 만들기를 시작할 수 있습니다.
-
medic_example클래스 정의 상단에서 다음 값을 추가합니다.-
편집 가능한 플로트
HealingThreshold를 추가합니다. 캐릭터의 체력 한계치로, 이 한계치보다 낮아야 캐릭터가 치유를 받게 됩니다.# 이 HP 한계치가 되어야 캐릭터가 치유를 받을 수 있습니다. @editable HealingThreshold:float = 50.0 -
편집 가능한 플로트
HealingDelay를 추가합니다. 캐릭터를 치유할 때 각 치유 인스턴스 사이의 대기 시간입니다. 의무병의 치유 속도를 더 빠르게 하거나 느리게 하고 싶으면 이 값을 변경합니다.# 이 HP 한계치가 되어야 캐릭터가 치유를 받을 수 있습니다. @editable HealingThreshold:float = 50.0 # 캐릭터 치유 전까지 대기할 시간입니다. @editable HealingDelay:float = 1.5 -
편집 가능한 플로트
HealingAmount를 추가합니다. 치유 인스턴스당 치유할 캐릭터의 체력 수치입니다. 의무병 NPC가 캐릭터를 치유할 때HealingDelay초마다HealingAmount값만큼 치유합니다.# 캐릭터 치유 전까지 대기할 시간입니다. @editable HealingDelay:float = 1.5 # 치유 인스턴스당 캐릭터를 치유하는 양입니다. @editable HealingAmount:float = 5.0 -
편집 가능한 돌연변이 유발 구역
HealVolume을 추가합니다. 캐릭터가 들어가 치유를 받는 구역의 볼륨입니다. 이 예시에서는 돌연변이 유발 구역을 사용하는데, 이 구역에AgentEntersEvent가 있어 의무병이 이를 등록하여 치유가 필요할 수 있는 캐릭터가 있는지 확인할 수 있습니다.# 치유 인스턴스당 캐릭터를 치유하는 양입니다. @editable HealingAmount:float = 5.0 # 캐릭터가 치유를 받기 위해 들어가는 구역의 볼륨입니다. @editable HealVolume:mutator_zone_device = mutator_zone_device{} -
편집 가능한 VFX 생성 장치
VFXSpawner를 추가합니다. 코드가 작동 중인지 파악하는 데 있어 시각적 피드백이 중요하므로 VFX 생성 장치를 사용하여 캐릭터가 치유 중일 때 이펙트를 생성합니다.# 캐릭터가 치유를 받기 위해 들어가는 구역의 볼륨입니다. @editable HealVolume:mutator_zone_device = mutator_zone_device{} # 캐릭터가 치유를 받을 때 VFX를 재생하는 VFX 생성 장치입니다. @editable VFXSpawner:vfx_spawner_device = vfx_spawner_device {} -
AgentToFollow로 명명된 변수 옵션agent를 추가합니다. 치유하는 동안 의무병이 따라가야 하는 캐릭터에 대한 레퍼런스를 저장합니다.# 캐릭터가 치유를 받을 때 VFX를 재생하는 VFX 생성 장치입니다. @editable VFXSpawner:vfx_spawner_device = vfx_spawner_device {} # 치유하면서 따라다닐 에이전트입니다. var AgentToFollow:?agent = false -
AgentsToHeal로 명명된 에이전트 변수 큐를 추가합니다. 여러 명의 캐릭터가 치유를 받아야 하는 경우 의무병이HealVolume에 들어온 순서대로 캐릭터를 치유합니다. 다음 단계에서는 큐 코드를 구성합니다. 큐 데이터 구조에 대한 자세한 내용은 Verse의 스택 및 큐를 참조하세요.# 치유하면서 따라다닐 에이전트입니다. var AgentToFollow:?agent = false # 여러 에이전트가 치유 볼륨에 들어오는 경우 치유할 에이전트의 큐입니다. var AgentsToHeal<public>:queue(agent) = queue(agent){} -
변수 플로트
UpdateRateSeconds를 추가합니다.HealVolume및VFXSpawner위치 업데이트 사이의 대기 시간입니다.# 여러 에이전트가 치유 볼륨에 들어오는 경우 치유할 에이전트의 큐입니다. var AgentsToHeal<public>:queue(agent) = queue(agent){} # HealVolume 및 VFXSpawner 위치의 업데이트 속도를 지정하는 데 사용합니다. UpdateRateSeconds<private>:float = 0.1
-
-
AgentsToHeal큐를 구현하기 위해 이 단계의 끝에서 제공된 코드를 사용합니다.- Verse 익스플로러 로 돌아가 프로젝트 이름을 우클릭하고 Add new Verse file to project 를 선택하여 Verse 스크립트 생성(Create Verse Script) 창을 엽니다.
-
Verse 스크립트 생성 창에서 Verse 클래스(Verse Class) 를 클릭하여 스크립트로 선택합니다.
-
클래스 이름(Class Name) 필드의 텍스트를
queue로 변경하여 Verse 클래스를 명명합니다. -
생성(Create) 을 클릭하면 Verse 파일이 생성됩니다.
-
Verse 익스플로러에서 Verse 파일의 이름을 더블클릭하여 Visual Studio Code에서 엽니다.
-
queue파일의 코드를 다음 코드로 대체합니다. 이 코드는 목록 데이터 구조를 사용하여type타입의 일반 큐를 구현합니다. 예시는 파라미터 타입인데, 구현한 큐는 생성한 타입에 관계없이 제대로 작동합니다. 예시에서 캐릭터 큐를 사용하므로medic_example의 큐 정의는queue(agent)가 됩니다.list(t:type) := class: Data:t Next:?list(t) queue<public>(t:type) := class<internal>: Elements<internal>:?list(t) = false Size<public>:int = 0 Enqueue<public>(NewElement:t):queue(t) = queue(t): Elements := option: list(t): Data := NewElement Next := Elements Size := Size + 1 Dequeue<public>()<decides><transacts>:tuple(queue(t), t) = List := Elements? (queue(t){Elements := List.Next, Size := Size - 1}, List.Data) Front<public>()<decides><transacts>:t = Elements?.Data CreateQueue<public><constructor>(InData:t where t:type) := queue(t): Elements := option: list(t): Data := InData Next := false Size := 1 -
Verse 스크립트를 개별 폴더로 정리하면 정리된 상태로 작업할 수 있을 뿐만 아니라, 자주 사용하는 파일을 쉽게 참조할 수 있습니다. 이에 대한 예시로 이 프로젝트에서는 NPC 비헤이비어를 저장할 폴더를 생성하겠습니다. Visual Studio Code에서 새 폴더(New Folder) 버튼을 클릭하여 UEFN 프로젝트에 새 폴더를 생성합니다. 폴더를
npc_behaviors로 명명합니다. 그런 다음medic_exampleVerse 파일을 새 폴더로 드래그합니다.
medic_example의 코드가 이제 올바르게 컴파일됩니다.
볼륨 안에서 캐릭터 치유하기
피해를 입은 캐릭터가 HealVolume 에 들어오면 캐릭터의 체력이 HealingThreshold 보다 낮을 때 의무병 캐릭터가 치유를 시작합니다. 캐릭터의 체력이 HealingThreshold 보다 높으면 의무병이 캐릭터 치유를 멈추고 치유가 필요한 다음 캐릭터를 향해 이동합니다. 여러 캐릭터가 있는 경우 의무병은 HealVolume 에 들어온 순서대로 캐릭터를 치유합니다. 캐릭터가 HealVolume 에 들어오면 다음 단계를 따라 캐릭터를 치유하세요.
-
medic_example파일로 돌아가OnBegin()의then문 뒤에서loop를 시작합니다.loop내에서AgentsToHeal큐로부터Dequeue()함수의 결과를 가져오고 변수DequeueResult에 저장합니다.then: loop: # 큐에서 다음 에이전트를 가져와 치유합니다. 치유할 에이전트가 있으면 AgentToHeal을 호출하여 치유합니다. # 치유할 에이전트가 없으면 에이전트가 HealVolume에 들어올 때까지 대기합니다. if: DequeueResult := AgentsToHeal.Dequeue[] -
DequeueResult변수는 첫 번째 요소가 제거된AgentsToHeal큐의 사본과 큐 앞에 있는 에이전트를 모두 반환하는tuple입니다. 튜플의 첫 번째 값으로 설정하여AgentsToHeal을 업데이트하고, 두 번째 값을AgentToHeal로 저장합니다.if: DequeueResult := AgentsToHeal.Dequeue[] set AgentsToHeal = DequeueResult(0) AgentToHeal := DequeueResult(1) -
치유할 에이전트가 있으면
HealVolume에 에이전트가 있을 때 치유를 시작해야 합니다.HealCharacter()로 명명된 새 함수를 정의하여 이를 처리합니다.HealCharacter()로 명명된 새 함수를medic_example클래스 정의에 추가합니다. 이 함수는 의무병 캐릭터의Navigatable및Focusable인터페이스 모두를 함수 실행인자로 하여AgentToHeal로 가져옵니다. 캐릭터를 치유할 때 여러 비동기 태스크를 수행해야 하므로<suspends>모디파이어를 이 함수에 추가합니다.# 캐릭터를 치유하고 HealingDelayAmount의 시간만큼 대기합니다. # 캐릭터의 체력이 HealingThreshold에 도달하거나 # 캐릭터가 HealVolume을 나가면 종료합니다. HealCharacter(AgentToHeal:agent, Navigatable:navigatable, Focusable:focus_interface)<suspends>:void= -
HealCharacter에서IsInVolume[]을 호출하고,AgentToHeal을 실행인자로 전달하여AgentToHeal이 볼륨에 있는지 확인합니다. 에이전트가 볼륨에 있는 경우 치유를 시작할 수 있습니다. 치유 가능한 모든 에이전트는 에이전트의fort_character의 일부분인healthful인터페이스를 구현합니다. 에이전트의fort_character를 가져오고CharacterToHeal값에 저장합니다.HealCharacter(AgentToHeal:agent, Navigatable:navigatable, Focusable:focus_interface)<suspends>:void= # HealVolume 안에 있는 경우에만 캐릭터를 치유합니다. if: HealVolume.IsInVolume[AgentToHeal] CharacterToHeal := AgentToHeal.GetFortCharacter[] -
캐릭터가 치유를 받을 수 있는 상태가 되면 의무병이 치유 중인 동안 캐릭터 근처에 머물도록 해야 합니다.
MakeNavigationTarget을 사용하여AgentToHeal에서navigation_target을 만들고NavigationTarget변수에 저장합니다. 그런 다음branch문에서 NPC의navigatable인터페이스를 사용하여NavigateTo()함수를 호출하고 의무병이AgentToHeal로 이동하도록 합니다. 또한branch함수에서MaintainFocus()함수를 호출하여 의무병이AgentToHeal을 포커스하는지 확인합니다. 이 상황에서branch문을 사용하면NavigateTo()및MaintainFocus()를 동시에 비동기식으로 실행할 수 있고,branch직후에 임의의 코드를 실행할 수 있습니다. branch 표현식에 대한 자세한 내용은 Verse의 branch 페이지를 참조하세요.# HealVolume 안에 있는 경우에만 캐릭터를 치유합니다. if: HealVolume.IsInVolume[AgentToHeal] CharacterToHeal := AgentToHeal.GetFortCharacter[] then: Print("Character is in volume, starting healing") NavigationTarget := MakeNavigationTarget(AgentToHeal) branch: Navigatable.NavigateTo(NavigationTarget) Focusable.MaintainFocus(AgentToHeal) -
VFXSpawner를 활성화하여 의무병이 캐릭터를 치유할 때 VFX를 재생합니다. 그런 다음defer표현식에서VFXSpawner를 비활성화합니다.VFXSpawner비활성화 코드가defer표현식에 있으므로 현재 범위가 종료될 때까지 실행되지 않습니다. 이렇게 하면 함수가 종료될 때만 코드가 실행되므로 함수에서 마지막으로 발생하게 됩니다. defer 표현식에 대한 자세한 내용은 defer 페이지를 참조하세요.branch: Navigatable.NavigateTo(NavigationTarget) Focusable.MaintainFocus(AgentToHeal) VFXSpawner.Enable() defer: VFXSpawner.Disable() -
CharacterToHeal을 치유할 때 두 가지 조건 중 하나가 발생하면 치유를 멈춰야 합니다. 캐릭터의 체력이HealingThreshold이상으로 치유되거나 캐릭터가HealVolume을 나가는 경우입니다. 이를 위해race표현식을 사용해야 합니다.loop와HealVolume.AgentExitsEvent의Await()사이에race표현식을 구성합니다.branch: Navigatable.NavigateTo(NavigationTarget) Focusable.MaintainFocus(AgentToHeal) VFXSpawner.Enable() defer: VFXSpawner.Disable() race: loop: HealVolume.AgentExitsEvent.Await() -
loop내에서GetHealth()를 사용하여 캐릭터의 현재 체력을 가져오고CurrentHealth값에 저장합니다. 그리고if문에서CurrentHealth와HealingAmount의 합이HealingThreshold를 초과하는지 확인합니다. 초과하는 경우 의무병이 치유를 멈추고 루프를break해야 합니다. 하지만 캐릭터의 현재 체력이 치유 한계치 미만이면 치유 한계치까지 계속 치유해야 합니다. 첫 번째if문 내에 두 번째if문을 추가하여CurrentHealth가HealingThreshold미만인지 확인합니다. 미만이면 캐릭터의 체력을HealingThreshold로 설정합니다.race: loop: CurrentHealth := CharacterToHeal.GetHealth() if(CurrentHealth + HealingAmount > HealingThreshold): if (CurrentHealth < HealingThreshold): CharacterToHeal.SetHealth(HealingThreshold) PrintNPCB("Character has reached HealingThreshold, stopping healing") break HealVolume.AgentExitsEvent.Await() -
CurrentHealth와HealingAmount의 합이HealingThreshold이하면 캐릭터의 체력을Current Health와HealingAmount의 합으로 설정합니다.if(CurrentHealth + HealingAmount > HealingThreshold): if (CurrentHealth < HealingThreshold): CharacterToHeal.SetHealth(HealingThreshold) PrintNPCB("Character has reached HealingThreshold, stopping healing") break else: CharacterToHeal.SetHealth(CurrentHealth + HealingAmount) -
loop의 끝에서HealingDelay시간 동안 슬립합니다. 이 슬립이 없으면 캐릭터는 시뮬레이터 업데이트 때마다 치유되므로HealingDelay로 인해 즉시 치유되지 않게 됩니다. 완성된HealCharacter()코드는 다음과 같게 됩니다.# 캐릭터를 치유하고 HealingDelayAmount의 시간만큼 대기합니다. # 캐릭터의 체력이 HealingThreshold에 도달하거나 # 캐릭터가 HealVolume을 나가면 종료합니다. HealCharacter(AgentToHeal:agent, Navigatable:navigatable, Focusable:focus_interface)<suspends>:void= # HealVolume 안에 있는 경우에만 캐릭터를 치유합니다. if: HealVolume.IsInVolume[AgentToHeal] CharacterToHeal := AgentToHeal.GetFortCharacter[] then: Print("Character is in volume, starting healing") NavigationTarget := MakeNavigationTarget(AgentToHeal) branch: Navigatable.NavigateTo(NavigationTarget) Focusable.MaintainFocus(AgentToHeal) VFXSpawner.Enable() defer: VFXSpawner.Disable() race: loop: CurrentHealth := CharacterToHeal.GetHealth() if(CurrentHealth + HealingAmount > HealingThreshold): if (CurrentHealth < HealingThreshold): CharacterToHeal.SetHealth(HealingThreshold) PrintNPCB("Character has reached HealingThreshold, stopping healing") break else: CharacterToHeal.SetHealth(CurrentHealth + HealingAmount) Sleep(HealingDelay) HealVolume.AgentExitsEvent.Await() -
OnBegin()으로 돌아가loop내에 있는then표현식에서AgentToHeal,Navigable인터페이스 및Focusable인터페이스를 전달하여HealCharacter()를 호출합니다.if: DequeueResult := AgentsToHeal.Dequeue[] set AgentsToHeal = DequeueResult(0) AgentToHeal := DequeueResult(1) then: Print("Dequeued the next agent to heal") HealCharacter(AgentToHeal, Navigatable, Focusable) -
의무병 근처에 항상 치유할 캐릭터가 있는 것이 아니므로
AgentsToHeal큐에 에이전트가 없으면Dequeue[]함수가 실패합니다. 이를 처리하기 위해else문을loop의 끝에 추가합니다. 이if문 내에서HealingDelay시간만큼Sleep()을 호출하고HealVolume.AgentEntersEvent를Await()합니다. 이렇게 하면 의무병 캐릭터가AgentsToHeal큐에서Dequeue[]를 무한하게 호출하지 않고, 새 캐릭터가HealVolume에 들어올 때까지 기다린 다음 loop를 다시 시작합니다. 완성된 loop는 다음과 같습니다.loop: # 큐에서 다음 에이전트를 가져와 치유합니다. 치유할 에이전트가 있으면 AgentToHeal을 호출하여 치유합니다. # 치유할 에이전트가 없으면 에이전트가 HealVolume에 들어올 때까지 대기합니다. if: DequeueResult := AgentsToHeal.Dequeue[] set AgentsToHeal = DequeueResult(0) AgentToHeal := DequeueResult(1) then: Print("Dequeued the next agent to heal") HealCharacter(AgentToHeal, Navigatable, Focusable) else: Print("AgentsToHeal is empty!") Sleep(HealingDelay) HealVolume.AgentEntersEvent.Await()
캐릭터가 치유 볼륨에 있을 때 추적하기
캐릭터가 HealVolume 에 들어오거나 나가는 때를 알 수 있도록 HealVolume 의 AgentEntersEvent 와 AgentExitsEvent 를 모두 새 함수에 등록해야 합니다.
-
OnAgentEnters()로 명명된 새 함수를medic_example클래스 정의에 추가합니다. 이 함수는HealVolume에 들어온 에이전트를 가져와AgentsToHeal큐에 등록합니다.OnAgentEnters(EnteredAgent:agent):void= Print("Agent entered the heal volume") -
OnAgentEnters()에서 볼륨의 에이전트가 의무병 캐릭터가 아닌지 확인합니다. 의무병인 경우EnteredAgent와Enqueue[]를 호출한 결과로AgentsToHeal큐를 설정합니다. 완료된OnAgentEnters()함수는 다음과 같게 됩니다.OnAgentEnters(EnteredAgent:agent):void= Print("Agent entered the heal volume") if (EnteredAgent <> GetAgent[]): set AgentsToHeal = AgentsToHeal.Enqueue(EnteredAgent) -
에이전트가
HealVolume을 나갈 때AgentsToHeal큐에서 에이전트를 제거할 필요가 없습니다.OnBegin()의 루프가 이미Dequeue[]를 호출하기 때문입니다. 하지만 예시에서 에이전트가 볼륨을 나갈 때 코드를 실행하고자 할 수 있으므로 지금은 함수를 설정합니다.OnAgentExits()로 명명된 새 함수를medic_example클래스 정의에 추가합니다.OnAgentExits(ExitAgent:agent):void= Print("Agent exited the heal volume") -
OnBegin()에서HealVolume의AgentEntersEvent와AgentExitsEvent를 각각OnAgentEnters및OnAgentExits에 등록합니다. 비활성화된 상태로 시작해야 하므로 여기에서 캐릭터 생성 장치에Disable()을 호출하는 것이 좋습니다.OnBegin<override>()<suspends>:void= Print("Hello, AI!") VFXSpawner.Disable() HealVolume.AgentEntersEvent.Subscribe(OnAgentEnters) HealVolume.AgentExitsEvent.Subscribe(OnAgentExits)
의무병과 함께 치유 볼륨 이동하기
의무병 캐릭터가 이동할 때 현재 위치에 맞춰 HealVolume 이 함께 움직여야 합니다. VFXSpawner 의 경우도 마찬가지입니다. 이를 위해 DeviceFollowCharacter() 라는 새 함수를 사용합니다.
-
DeviceFollowCharacter()로 명명된 새 함수를medic_example클래스 정의에 추가합니다. 이 함수는 장치 위치를 지속적으로 업데이트하도록 비동기식으로 실행되어야 하므로<suspends>모디파이어를 추가합니다.DeviceFollowCharacter()<suspends>:void= -
DeviceFollowCharacter()함수 내에서GetAgent[]를 사용하여 에이전트를 가져오고GetFortCharater[]를 호출하여 의무병의fort_character를 가져옵니다.DeviceFollowCharacter()<suspends>:void= if: # 이 비헤이비어에 연결된 에이전트(AI 캐릭터)를 가져옵니다. Agent := GetAgent[] # 에이전트의 fort_character 인터페이스를 가져와 포트나이트 캐릭터별 비헤이비어, 이벤트, 함수, 인터페이스에 액세스합니다. Character := Agent.GetFortCharacter[] -
이제
HealVolume및VFXSpawner를Character의 위치에 맞춰 계속 이동시켜야 합니다. 두 장치 모두에서MoveTo()를 루프하면 됩니다.loop를 시작하고Character의 트랜스폼을 가져와CharacterTransform변수에 저장합니다.if: # 이 비헤이비어에 연결된 에이전트(AI 캐릭터)를 가져옵니다. Agent := GetAgent[] # 에이전트의 fort_character 인터페이스를 가져와 포트나이트 캐릭터별 비헤이비어, 이벤트, 함수, 인터페이스에 액세스합니다. Character := Agent.GetFortCharacter[] then: loop: CharacterTransform := Character.GetTransform() -
VFXSpawner와HealVolume모두에서MoveTo()를 호출하여CharacterTransform.Translation과CharacterTransform.Rotation으로 이동시킵니다. 지속 시간을UpdateRateSeconds의 값(초)으로 설정합니다. 마지막으로UpdateRateSeconds시간 동안Sleep()을 호출하여 장치가 시뮬레이션 업데이트마다 위치를 업데이트하지 않도록 합니다. 시뮬레이션 업데이트마다 장치 위치가 업데이트되면 장치가 움직일 때 떨림이 발생할 수 있습니다. 완성된DeviceFollowCharacter()코드는 다음과 같습니다.DeviceFollowCharacter()<suspends>:void= if: # 이 비헤이비어에 연결된 에이전트(AI 캐릭터)를 가져옵니다. Agent := GetAgent[] # 에이전트의 fort_character 인터페이스를 가져와 포트나이트 캐릭터별 비헤이비어, 이벤트, 함수, 인터페이스에 액세스합니다. Character := Agent.GetFortCharacter[] then: loop: CharacterTransform := Character.GetTransform() VFXSpawner.MoveTo(CharacterTransform.Translation, CharacterTransform.Rotation, UpdateRateSeconds) HealVolume.MoveTo(CharacterTransform.Translation, CharacterTransform.Rotation, UpdateRateSeconds) Sleep(UpdateRateSeconds) -
OnBegin()에서 캐릭터 인터페이스를 저장한if문 뒤와 loop 앞에서DeviceFollowCharacter()인스턴스를 생성합니다.
레벨에 캐릭터 추가하기
-
Medic 으로 명명된 새 NPC 캐릭터 정의를 만듭니다. 새 NPC 캐릭터 정의를 클릭하여 NPC 캐릭터 정의(NPC Character Definition) 화면을 엽니다.
-
NPC 캐릭터 정의 화면에서 다음 프로퍼티를 수정합니다.
-
NPC 캐릭터 타입(NPC Character Type) 에서 타입(Type) 을 경비(Guard) 로 설정합니다. 경비 인터페이스를 통해 경비가 경계 또는 의심 상태일 때를 위한 이벤트 등 경비 전용 캐릭터 기능에 액세스할 수 있으며, 경비를 고용하여 아군으로 사용할 수 있습니다. 경비는 무기를 장착할 수도 있지만 커스텀 및 야생동물 타입 캐릭터는 현재 무기 장착이 불가능합니다. 이름(Name) 탭에서 캐릭터의 이름을 변경할 수도 있습니다.
-
NPC 캐릭터 비헤이비어(NPC Character Behavior) 에서 비헤이비어(Behavior) 를 Verse 비헤이비어(Verse Behavior) 로 설정합니다. 그런 다음 NPC 비헤이비어 스크립트(NPC Behavior Script) 를
medic_example로 설정합니다. 캐릭터는 여전히 경비 인터페이스의 기능에 액세스할 수 있지만OnBegin및OnEnd실행 시 어떤 작업을 할지 결정하는 데 이 Verse 스크립트를 사용하게 됩니다. -
모디파이어(Modifiers) 탭의 경비 생성 모디파이어(Guard Spawn Modifier) 에서 장식(Cosmetic) 탭을 클릭하여 캐릭터의 장식 외형을 변경합니다. 기존 장식 중에 선택하거나, 캐릭터 장식 리타기팅(Character Cosmetic Retargeting) 을 활성화하여 커스텀 모델을 사용할 수 있습니다. 경비 및 커스텀 타입 캐릭터만 캐릭터 장식 리타기팅을 사용할 수 있으며, 야생동물 캐릭터는 이를 사용할 수 없습니다. 캐릭터 모디파이어 및 다양한 캐릭터 타입에 적용할 모디파이어에 대한 자세한 내용은 캐릭터 정의 페이지를 참고하세요.
-
-
NPC 캐릭터 정의를 저장합니다. 콘텐츠 브라우저(Content Browser) 에서 NPC 캐릭터 정의를 레벨로 드래그합니다. 이렇게 하면 자동으로 새 캐릭터 생성 장치가 만들어지고 NPC 캐릭터 정의가 할당됩니다.
-
돌연변이 유발 구역 1개와 VFX 생성 장치 1개를 레벨로 드래그합니다.
-
캐릭터 생성 장치를 선택합니다. 아웃라이너(Outliner) 의 사용자 옵션(User Options) 에서 다음을 수행합니다.
-
AI 비헤이비어 스크립트 오버라이드(AIBehavior Script override) 를
medic_example스크립트로 설정합니다. 아웃라이너에서 AI 비헤이비어 스크립트를 오버라이드하면 레벨에서 장치를 참조할 수 있으며, HealVolume 과 VFXSpawner 를 할당하는 데 이 기능이 필요합니다. -
HealVolume 을 돌연변이 유발 구역으로 설정하고 VFXSpawner 를 레벨에 배치한 VFX 생성 장치로 설정합니다.
-
-
돌연변이 유발 구역을 선택합니다. 아웃라이너 의 사용자 옵션 에서 게임 중 구역 표시(Zone Visible During Game) 를 True 로 설정합니다. 이를 통해
HealVolume의 위치 및 의무병 캐릭터와 함께 어떻게 움직이는지를 시각화할 수 있습니다. -
VFX 생성 장치를 선택합니다. 아웃라이너 의 사용자 옵션 에서 시각 효과(Visual Effect) 를 원하는 효과로 설정합니다. 이 예시에서는 거품(Bubbles) 효과를 사용하여 치유를 보여주지만 폭죽이나 스파크 등 다른 효과를 사용할 수도 있습니다. 캐릭터의 필요에 맞춰 시각 효과를 변경합니다.
-
UEFN 툴바에서 세션 시작(Launch Session) 을 클릭하여 레벨을 플레이테스트합니다. 플레이테스트 시 캐릭터가 피해를 입은 캐릭터가 돌연변이 유발 구역에 들어오면 이 캐릭터를 치유해야 합니다. 캐릭터를 치유할 때 VFX가 재생되고 의무병이 캐릭터를 따라가면서 치유에 집중해야 합니다.
전체 스크립트
다음은 체력 포인트가 특정 한계치보다 낮을 때 NPC 캐릭터가 다른 캐릭터를 치유하는 스크립트입니다.
medic_example.verse
using { /Fortnite.com/AI }
using { /Fortnite.com/Characters }
using { /Fortnite.com/Devices }
using { /Verse.org/Colors }
using { /Verse.org/Random }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/SpatialMath }
# NPC 정의 또는 캐릭터 생성 장치의 비헤이비어 스크립트 오버라이드 내에서 사용할 수 있는 Verse 작성 NPC 비헤이비어입니다.
medic_example<public> := class(npc_behavior):
# 이 HP 한계치가 되어야 캐릭터가 치유를 받을 수 있습니다.
@editable
HealingThreshold:float = 50.0
# 캐릭터 치유 전까지 대기할 시간입니다.
@editable
HealingDelay:float = 1.5
# 치유 인스턴스당 캐릭터를 치유하는 양입니다.
@editable
HealingAmount:float = 5.0
# 캐릭터가 치유를 받기 위해 들어가는 구역의 볼륨입니다.
@editable
HealVolume:mutator_zone_device = mutator_zone_device{}
# 캐릭터가 치유를 받을 때 VFX를 재생하는 VFX 생성 장치입니다.
@editable
VFXSpawner:vfx_spawner_device = vfx_spawner_device {}
# 치유하면서 따라다닐 에이전트입니다.
var AgentToFollow:?agent = false
# 여러 에이전트가 치유 볼륨에 들어오는 경우 치유할 에이전트의 큐입니다.
var AgentsToHeal<public>:queue(agent) = queue(agent){}
# HealVolume 및 VFXSpawner 위치의 업데이트 속도를 지정하는 데 사용합니다.
UpdateRateSeconds<private>:float = 0.1
OnBegin<override>()<suspends>:void=
VFXSpawner.Disable()
HealVolume.AgentEntersEvent.Subscribe(OnAgentEnters)
HealVolume.AgentExitsEvent.Subscribe(OnAgentExits)
if:
# 이 비헤이비어에 연결된 에이전트(AI 캐릭터)를 가져옵니다.
Agent := GetAgent[]
# 에이전트의 fort_character 인터페이스를 가져와 포트나이트 캐릭터별 비헤이비어, 이벤트, 함수, 인터페이스에 액세스합니다.
Character := Agent.GetFortCharacter[]
# 캐릭터의 이동 가능한 인터페이스를 가져와 캐릭터가 이동할 특정 타깃을 설정합니다.
Navigatable := Character.GetNavigatable[]
# 캐릭터의 focus_interface를 가져와 이동 후 포커스할 특정 타깃을 설정합니다.
Focusable := Character.GetFocusInterface[]
then:
# HealVolume 및 VFXSpawner가 계속해서 NPC 캐릭터를 따라가도록 설정합니다.
spawn{DeviceFollowCharacter()}
loop:
# 큐에서 다음 에이전트를 가져와 치유합니다. 치유할 에이전트가 있으면 AgentToHeal을 호출하여 치유합니다.
# 치유할 에이전트가 없으면 에이전트가 HealVolume에 들어올 때까지 대기합니다.
if:
DequeueResult := AgentsToHeal.Dequeue[]
set AgentsToHeal = DequeueResult(0)
AgentToHeal := DequeueResult(1)
then:
PrintNPCB("Dequeued the next agent to heal")
HealCharacter(AgentToHeal, Navigatable, Focusable)
else:
PrintNPCB("AgentsToHeal is empty!")
Sleep(HealingDelay)
HealVolume.AgentEntersEvent.Await()
else:
# 에이전트와 인터페이스 수집 시 오류가 발생하면 코드가 여기에서 종료됩니다.
PrintNPCB( "Error in NPC Behavior Script on NPC Setup",
?Duration := 6.0,
?TextColor := NamedColors.Red )
# 캐릭터를 치유하고 HealingDelayAmount의 시간만큼 대기합니다.
# 캐릭터의 체력이 HealingThreshold에 도달하거나
# 캐릭터가 HealVolume을 나가면 종료합니다.
HealCharacter(AgentToHeal:agent, Navigatable:navigatable, Focusable:focus_interface)<suspends>:void=
# HealVolume 안에 있는 경우에만 캐릭터를 치유합니다.
if:
HealVolume.IsInVolume[AgentToHeal]
CharacterToHeal := AgentToHeal.GetFortCharacter[]
then:
PrintNPCB("Character is in volume, starting healing")
NavigationTarget := MakeNavigationTarget(AgentToHeal)
branch:
Navigatable.NavigateTo(NavigationTarget)
Focusable.MaintainFocus(AgentToHeal)
VFXSpawner.Enable()
defer:
VFXSpawner.Disable()
race:
loop:
CurrentHealth := CharacterToHeal.GetHealth()
if(CurrentHealth + HealingAmount > HealingThreshold):
if (CurrentHealth < HealingThreshold):
CharacterToHeal.SetHealth(HealingThreshold)
PrintNPCB("Character has reached HealingThreshold, stopping healing")
break
else:
CharacterToHeal.SetHealth(CurrentHealth + HealingAmount)
Sleep(HealingDelay)
HealVolume.AgentExitsEvent.Await()
# 루프를 통해 HealVolume 및 VFXSpawner가 계속해서 캐릭터를 따라가도록 설정합니다.
# 캐릭터의 위치로 이동하도록 하는 MoveTo입니다.
DeviceFollowCharacter()<suspends>:void=
if:
# 이 비헤이비어에 연결된 에이전트(AI 캐릭터)를 가져옵니다.
Agent := GetAgent[]
# 에이전트의 fort_character 인터페이스를 가져와 포트나이트 캐릭터별 비헤이비어, 이벤트, 함수, 인터페이스에 액세스합니다.
Character := Agent.GetFortCharacter[]
then:
# HealVolume 및 VFXSpawner에서 MoveTo를 루프하여 위치를
# NPC 캐릭터에 맞춥니다.
loop:
CharacterTransform := Character.GetTransform()
VFXSpawner.MoveTo(CharacterTransform.Translation, CharacterTransform.Rotation, UpdateRateSeconds)
HealVolume.MoveTo(CharacterTransform.Translation, CharacterTransform.Rotation, UpdateRateSeconds)
Sleep(UpdateRateSeconds)
# 에이전트가 HealVolume에 들어오면
# NPC 캐릭터가 아닌 경우 AgentsToHeal 큐에 추가합니다.
OnAgentEnters(EnteredAgent:agent):void=
PrintNPCB("Agent entered the heal volume")
if (EnteredAgent <> GetAgent[]):
set AgentsToHeal = AgentsToHeal.Enqueue(EnteredAgent)
# 에이전트가 HealVolume을 나가면 PrintNPCB를 실행하여 로그에 출력합니다.
OnAgentExits(ExitAgent:agent):void=
PrintNPCB("Agent exited the heal volume")
# 디폴트 지속 시간과 컬러를 제공하는 커스텀 래퍼입니다.
PrintNPCB(Msg:string,?Duration:float = 3.0, ?TextColor:color = NamedColors.Green):void =
Print("[new_npc_behavior] {Msg}", ?Color := TextColor, ?Duration := Duration)
# 이 함수는 NPC가 월드에서 소멸 또는 처치되면 실행됩니다.
OnEnd<override>():void =
if(Agent := GetAgent[]):
Print(medic_example_message_module.OnEndMessage(Agent))
else:
PrintNPCB("OnEnd")
queue.verse
list(t:type) := class:
Data:t
Next:?list(t)
queue<public>(t:type) := class<internal>:
Elements<internal>:?list(t) = false
Size<public>:int = 0
Enqueue<public>(NewElement:t):queue(t) =
queue(t):
Elements := option:
list(t):
Data := NewElement
Next := Elements
Size := Size + 1
Dequeue<public>()<decides><transacts>:tuple(queue(t), t) =
List := Elements?
(queue(t){Elements := List.Next, Size := Size - 1}, List.Data)
Front<public>()<decides><transacts>:t = Elements?.Data
CreateQueue<public><constructor>(InData:t where t:type) := queue(t):
Elements := option:
list(t):
Data := InData
Next := false
Size := 1
직접 해보기
지금까지 이 가이드를 통해 체력이 특정 한계치보다 낮은 캐릭터를 자동으로 치유하는 의무병 캐릭터를 만드는 방법에 대해 배웠습니다. 학습한 내용을 활용하여 특별한 비헤이비어가 포함된 자신만의 의무병 캐릭터를 만들어 보세요.
-
적이 볼륨에 있는지 여부에 따라 공격과 치유를 전환하는 의무병을 만들 수 있나요?
-
소모성 자원을 사용하여 캐릭터를 치유하는 의무병은 가능한가요? 의무병은 이 자원을 어떻게 회복할 수 있을까요? 시간이 지나면 회복하나요? 아니면 적을 공격해 회복하나요?