이 섹션에서는 바크와 같은 오디오 및 카메라 흔들림과 같은 시각 효과를 설정하여 매력적인 게임플레이를 만드는 방법을 살펴봅니다.
오디오 플레이어 장치
오디오 플레이어(Audio Player) 장치를 사용하여 경비의 대화 사용을 설정할 수 있습니다. 게임 개발에서는 바크(bark) 이라고도 합니다.

임포트된 오디오는 프로젝트 폴더(Project folder) > Barks 에서 찾을 수 있습니다. 재생(Play) 아이콘을 클릭하여 오디오 파일을 들은 다음 섬으로 끌어다 놓습니다.

오디오 파일을 섬에 끌어다 놓으면 오디오 플레이어 장치가 배치되고, 이 장치가 Verse 장치에 연결되어 일련의 커스텀 경비 콜아웃을 재생합니다. 이 콜아웃은 플레이어를 발견하거나 피해를 입는 등 다양한 이벤트에 반응합니다.
재생할 모든 고유한 오디오 항목에 1개의 오디오 플레이어를 배치합니다. 이 튜토리얼에서는 14개의 서로 다른 바크에 대한 14개의 오디오 플레이어 장치를 배치합니다.
이 장치를 구성하려면 다음과 같이 커스터마이징합니다.

옵션 | 값 | 설명 |
---|---|---|
볼륨(Volume) | 4.0 | 이 설정은 녹음에 따라 달라질 수 있습니다. |
활성화 시 음향 재시작(Restart Audio when Activated) | True | 이 오디오는 활성화 시 처음부터 재생됩니다. |
타격 시 재생(Play on Hit) | False | 이 장치는 플레이어에 의해 타격이 되면 오디오를 재생하지 않습니다. |
재생 위치(Play Location) | 작동시킨 플레이어(Instigating Player) | 오디오는 장치 위치 대신 작동시키는 플레이어의 위치에 따라 재생됩니다. |
볼륨 감쇠 사용(Enable Volume Attenuation) | False | 재생하도록 설정된 장치 또는 경비와의 거리에 따라 볼륨을 변경합니다. 이 튜토리얼에서 플레이어는 얼마나 멀리 떨어져 있는지와 무관하게 오디오를 들을 수 있습니다. |
다음으로 Verse 스크립트를 설정하여 게임 도중 오디오 플레이어 장치 트리거를 위한 로직을 처리하고, Verse 장치를 배치합니다. 이 튜토리얼에서는 장치의 이름을 Stronghold Bark Manager 로 지정합니다.
다음 Verse 스크립트를 붙여 넣습니다.
using { /Fortnite.com/Devices }
using { /Fortnite.com/Game }
using { /Fortnite.com/Characters }
using { /Verse.org/Random }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/SpatialMath }
# NPC가 재생할 수 있는 오디오 바크
audio_npc_bark := class<concrete>:
# 바크를 재생하는 오디오 장치
@editable
BarkDevice:audio_player_device := audio_player_device{}
# NPC가 바크를 반복하도록 허용하는 옵션
@editable
CanRepeat:logic = true
# 이벤트와 바크 시작 사이의 지연
@editable
Delay:float = 0.0
# 이 바크를 반복하기 전의 지연
@editable
Cooldown:float = 5.0
# 로깅용 바크 이름
@editable
BarkID:string = "Missing ID"
# 재사용 대기시간 타이머 경과 여부
var<private> IsInCooldown:logic = false
# 바크를 중지할 이벤트
StopBarkEvent<private>:event() = event(){}
PlayBark(Agent:agent)<suspends>:void=
var IsPlaying:logic = false;
defer:
if (IsPlaying?):
set IsPlaying = false
set IsInCooldown = false
BarkDevice.Stop(Agent)
race:
block:
StopBarkEvent.Await()
return
block:
AwaitAgentDown(Agent)
return
block:
if (Delay > 0.0):
Sleep(Delay)
if (IsInCooldown?):
return
BarkDevice.Play(Agent)
set IsPlaying = true
set IsInCooldown = true
Sleep(2.0)
set IsPlaying = false
if (CanRepeat?):
Sleep(Cooldown)
set IsInCooldown = false
StopBark():void=
StopBarkEvent.Signal()
AwaitAgentDown<private>(Agent:agent)<suspends>:void=
if (Character := Agent.GetFortCharacter[]):
loop:
if (Character.GetHealth() <= 0.0):
return
Character.DamagedEvent().Await()
# 경비의 바크를 처리하는 스크립트
stronghold_bark_manager := class(creative_device):
# 인식 이벤트를 모니터링하기 위한 게임 관리 장치에 대한 레퍼런스
@editable
StrongholdGameManager:stronghold_game_manager := stronghold_game_manager{}
# 오디오 플레이어 장치
@editable
BarkNPCDown:audio_npc_bark = audio_npc_bark{BarkID := "Man Down", Delay := 0.3}
@editable
BarkFallback:audio_npc_bark = audio_npc_bark{BarkID := "Fallback", CanRepeat := false, Delay := 3.0}
@editable
BarkNeedBackup:audio_npc_bark = audio_npc_bark{BarkID := "Need Backup", CanRepeat := false, Delay := 2.0}
@editable
BarkGoToLeash:audio_npc_bark = audio_npc_bark{BarkID := "Reinforcements En Route", CanRepeat := false, Delay := 4.0}
@editable
BarkDamageTaken:audio_npc_bark = audio_npc_bark{BarkID := "Took Damage", Delay := 0.2}
@editable
BarkDamagePlayer:audio_npc_bark = audio_npc_bark{BarkID := "Hit Player", Delay := 0.2}
@editable
BarkEliminatedPlayer:audio_npc_bark = audio_npc_bark{BarkID := "Eliminated Player", Delay := 0.3}
@editable
BarkPlayerSpotted:audio_npc_bark = audio_npc_bark{BarkID := "Spotted Player", CanRepeat := false}
@editable
BarkPlayerLost:audio_npc_bark = audio_npc_bark{BarkID := "Lost Player", Cooldown := 10.0}
@editable
BarkGuardSuspicious:audio_npc_bark = audio_npc_bark{BarkID := "Suspicious", Cooldown := 10.0}
@editable
BarkGuardUnaware:audio_npc_bark = audio_npc_bark{BarkID := "Unaware", Cooldown := 10.0}
# 경비가 목표를 찾고 있는지 여부를 저장하는 변수
var<private> HasLostTarget:logic := false
# 실행 중인 게임에서 장치가 시작되면 실행됩니다.
OnBegin<override>()<suspends>:void=
ConfigureBarks()
sync:
AwaitReinforcements()
AwaitFallback()
PlayAwarenessBarks()
PlayBark(Bark:audio_npc_bark, Guard:agent):void=
spawn {Bark.PlayBark(Guard)}
# 지원이 호출되면 바크 재생
AwaitReinforcements<private>()<suspends>:void=
AlertedGuard := StrongholdGameManager.ReinforcementsCalledEvent.Await()
PlayBark(BarkNeedBackup, AlertedGuard)
# 경비가 성채에서 재집결 시 바크 재생
AwaitFallback<private>()<suspends>:void=
StrongholdGameManager.FallbackEvent.Await()
if:
Guard := StrongholdGameManager.AlertedGuards[0]
then:
PlayBark(BarkFallback, Guard)
PlayAwarenessBarks<private>()<suspends>:void=
loop:
race:
PlayGuardsSuspiciousBark()
PlayPlayerSpottedBark()
PlayPlayerLostBark()
PlayGuardsUnawareBark()
PlayPlayerSpottedBark<private>()<suspends>:void=
Guard:=StrongholdGameManager.PlayerDetectedEvent.Await();
set HasLostTarget = false
PlayBark(BarkPlayerSpotted, Guard)
PlayPlayerLostBark<private>()<suspends>:void=
Guard:=StrongholdGameManager.PlayerLostEvent.Await();
set HasLostTarget = true
PlayBark(BarkPlayerLost, Guard)
PlayGuardsSuspiciousBark<private>()<suspends>:void=
Guard:=StrongholdGameManager.GuardsSuspiciousEvent.Await();
PlayBark(BarkGuardSuspicious, Guard)
PlayGuardsUnawareBark<private>()<suspends>:void=
Guard:=StrongholdGameManager.GuardsUnawareEvent.Await();
if (HasLostTarget?):
set HasLostTarget = false
if (not StrongholdGameManager.FallbackTriggered?):
PlayBark(BarkGuardUnaware, Guard)
SubscribeToGuardSpawnerEvents(GuardSpawnerDevice:guard_spawner_device):void =
GuardSpawnerDevice.DamagedEvent.Subscribe(OnGuardDamaged)
GuardSpawnerDevice.EliminatedEvent.Subscribe(OnGuardEliminated)
GuardSpawnerDevice.EliminatingEvent.Subscribe(OnPlayerEliminated)
# 모든 바크 구성
ConfigureBarks():void=
# 플레이어 피해 이벤트 등록
AllPlayers := GetPlayspace().GetPlayers()
for (StrongholdPlayer : AllPlayers, StrongholdPC := StrongholdPlayer.GetFortCharacter[]):
StrongholdPC.DamagedEvent().Subscribe(OnPlayerDamaged)
# 성채 관리 장치에서 경비 생성 장치를 실행하고 모든 주요 이벤트 등록
for (GuardSpawner : StrongholdGameManager.GuardsInitialSpawners):
SubscribeToGuardSpawnerEvents(GuardSpawner)
for (GuardSpawner : StrongholdGameManager.GuardsReinforcementSpawners):
SubscribeToGuardSpawnerEvents(GuardSpawner)
# 지원 생성 시에는 별도의 케이스 준비
if:
FirstReinforcementSpawner := StrongholdGameManager.GuardsReinforcementSpawners[0]
then:
FirstReinforcementSpawner.SpawnedEvent.Subscribe(HandleReinforcementSpawned)
# 경비가 쓰러졌을 때 가장 가까운 경비에게 바크 재생 시도
OnGuardEliminated(InteractionResult:device_ai_interaction_result):void=
if (EliminatedGuard := InteractionResult.Target?):
# 가장 가까운 생존 경비를 찾아 이 바크 재생
var ClosestGuard:?agent = false
if:
set ClosestGuard = option{StrongholdGameManager.AlertedGuards[0]}
EliminatedGuardCharacter := EliminatedGuard.GetFortCharacter[]
then:
for (AlertedGuard : StrongholdGameManager.AlertedGuards, AlertedGuardCharacter := AlertedGuard.GetFortCharacter[]):
if:
not ClosestGuard? = AlertedGuard
ClosestGuardCharacter := ClosestGuard?.GetFortCharacter[]
DistanceSquaredToAlertedGuard := DistanceSquared(AlertedGuardCharacter.GetTransform().Translation, EliminatedGuardCharacter.GetTransform().Translation)
DistanceSquaredToClosestGuard := DistanceSquared(ClosestGuardCharacter.GetTransform().Translation, EliminatedGuardCharacter.GetTransform().Translation)
DistanceSquaredToAlertedGuard < DistanceSquaredToClosestGuard
then:
set ClosestGuard = option{AlertedGuard}
if (Guard := ClosestGuard?):
spawn {BarkNPCDown.PlayBark(Guard)}
# 경비가 맞았을 때, 경비가 쓰러지지 않은 경우 바크 재생 시도
OnGuardDamaged(InteractionResult:device_ai_interaction_result):void=
if (Guard := InteractionResult.Target?):
spawn {BarkDamageTaken.PlayBark(Guard)}
# 플레이어가 맞았을 때, 플레이어에게 피해를 입힌 경비에게 바크 재생 시도
OnPlayerDamaged(DamageResult:damage_result):void=
if:
fort_character[DamageResult.Target].GetHealth() > 0.0
Guard := DamageResult.Instigator?.GetInstigatorAgent[]
then:
spawn {BarkDamagePlayer.PlayBark(Guard)}
# 플레이어가 쓰러졌을 때, 플레이어를 처치한 경비에게 바크 재생 시도
OnPlayerEliminated(InteractionResult:device_ai_interaction_result):void=
if (Guard := InteractionResult.Source?):
spawn {BarkEliminatedPlayer.PlayBark(Guard)}
HandleReinforcementSpawned(Guard:agent):void=
spawn {BarkGoToLeash.PlayBark(Guard)}
이 스크립트는 각 오디오 플레이어 장치에 대한 참조를 저장하고 성채 게임 관리(Stronghold Game Manager) Verse 장치를 경비 생성 장치에 대한 레퍼런스용 컨듀잇으로 레퍼런스합니다.
커스터마이징 가능한 조명

AI 경비로부터 오디오 피드백을 받는 것 외에도 플레이어에게 환경의 시각적 피드백을 제공할 수 있습니다.
이 튜토리얼에서는 성채 주변에서 커스터마이징 가능한 조명 장치 두 세트를 사용합니다. 빨간색 조명은 감지된 상태, 주황색 조명은 경계 상태를 나타냅니다.
이 장치를 구성하려면 다음과 같이 커스터마이징합니다.
옵션 | 값 | 설명 |
---|---|---|
초기 상태(Initial State) | False | 장치가 활성화되었을 때 조명의 초기 상태를 결정합니다. |
조명 크기(Light Size) | 100.00 | 조명 플레어의 크기, 길이, 넓이를 결정합니다. |
그림자 표시(Cast Shadows) | True | 조명이 그림자를 드리우는 것을 허용합니다. |
사용 단계(Enabled During Phase) | 게임플레이만(Gameplay Only) | 조명이 게임플레이 중에만 활성화됩니다. |
조명 강도(Light Intensity) | 30.0 | 조명의 강도를 결정합니다. |
박자 시간(Rhythm Time) | x8 | 박자 프리셋의 시간 배수를 결정합니다. |
어둡게 하기 양(Dimming Amount) | 100.0 | 채널을 통해 조명을 어둡게 하는 정도를 결정합니다. |
어둡게 하기 시간(Dimming Time) | 0.1 | 조명을 어둡게 하는 데 걸리는 시간을 초 단위로 결정합니다. |
VFX 제작기

또한 이 튜토리얼은 VFX 제작기(VFX Creator) 장치를 성채 상단에 사용하며, 이 장치는 플레이어가 처음 감지되었을 때 지원군을 위한 신호탄 역할을 합니다. 이 플레어는 Verse 장치로 제어되며, 경비가 경계 상태가 되면 모서리 조명과 함께 꺼져 경비의 상태를 시각적으로 명확히 알립니다.
이 장치를 구성하려면 다음과 같이 커스터마이징합니다.

옵션 | 값 | 설명 |
---|---|---|
사용 시 효과 시작(Start Effects When Enabled) | False | 사용 상태로 설정 시 장치가 효과를 실행할지 여부를 결정합니다. |
스프라이트 크기(Sprite Size) | 2.0 | 효과 스프라이트의 초기 크기를 설정합니다. |
스프라이트 지속 시간(Sprite Duration) | 5.0 | 각 스프라이트가 표시되는 시간을 설정합니다. |
주요 색상(Main Color) | 빨간색(Red) | 효과의 주요 색상을 설정합니다. |
주요 색상 밝기(Main Color Brightness) | 200.0 | 주요 색상 밝기를 설정합니다. |
스프라이트 속도(Sprite Speed) | 100.0 | 효과 스프라이트가 이동하는 속도를 설정합니다. |
효과 중력(Effect Gravity) | 15.0 | 효과 스프라이트가 낙하하는 속도를 설정합니다. |
랜덤성(Randomness) | 100.0 | 움직임의 랜덤성을 결정하고 크기에 변형을 추가합니다. |
크기 유지(Keep Size) | False | 스프라이트가 크기를 유지할지 시간에 따른 커스텀 시간별 크기를 사용할지 설정합니다. |
효과 발생 양(Effect Generation Amount) | 4.0 | 발생할 효과 스프라이트 개수를 설정합니다. |
생성 구역 모양(Spawn Zone Shape) | 포인트(Point) | 스프라이트가 처음 나타나는 공간의 셰이프를 결정합니다. |
생성 구역 크기(Spawn Zone Size) | 0.05 | 생성 모양의 크기를 타일 단위로 설정합니다. |
사용 단계(Enabled During Phase) | 게임플레이만(Gameplay Only) | 장치가 활성화되는 게임 단계를 결정합니다. |
루프(Loop) | 않음(Never) | 효과가 한 번 재생할지 무한히 또는 일정 시간 동안 반복할지 결정합니다. |
라디오 장치
이 튜토리얼에서는 라디오(Radio) 장치 2개를 사용합니다. 하나는 경계가 삼엄한 전투 음악용이고 다른 하나는 조심스러운 음악용입니다.
긴장감 있는 음악은 다음을 사용합니다. 라디오 > 음악 루프 > Music_StW_Low_Combat01_Cue.
플레이어가 감지되는 전투 음악은 라디오 > 음악 루프 > Music_StW_High_Combat01_Cue 를 사용합니다.
경비가 플레이어를 감지했을 때 또는 모든 경비가 플레이어를 놓쳤을 때 호출할 수 있는 Verse 스크립트를 설정하여 성채의 두 상태를 번갈아가며 들을 수 있습니다.
다음 Verse 스크립트를 붙여 넣습니다.
using { /Verse.org/Simulation }
using { /Verse.org/Simulation/Tags }
using { /Verse.org/Colors }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Fortnite.com/Devices }
# 커스터마이징 가능한 조명 태그
alerted_lights_tag := class(tag){}
combat_lights_tag := class(tag){}
# 경비가 경계 상태일 때 음악을 처리하고 조명을 켜는 스크립트
stronghold_alert_manager := class(creative_device):
# 인식 이벤트를 모니터링하기 위한 게임 관리 장치에 대한 레퍼런스
@editable
StrongholdGameManager:stronghold_game_manager := stronghold_game_manager{}
# 전투 음악을 재생하는 라디오에 대한 레퍼런스
@editable
RadioCombat:radio_device := radio_device{}
# 경계 음악을 재생하는 라디오에 대한 레퍼런스
@editable
RadioAlerted:radio_device := radio_device{}
# 알람/플레어 발사 시 재생되는 VFX
@editable
FlareAlarmVFXCreator:vfx_creator_device := vfx_creator_device{}
# 클래스 데이터
var<private> CustomizableLightDevicesAlerted: []customizable_light_device = array{}
var<private> CustomizableLightDevicesCombat: []customizable_light_device = array{}
# 플레이어가 실종/처치되었을 때 캠프에 경고가 울리도록 변경
WaitForAlerted()<suspends>:void=
# 예비 전환 후 경계 상태로 돌아가지 않음
if (StrongholdGameManager.FallbackTriggered?):
Sleep(Inf)
StrongholdGameManager.GuardsUnawareEvent.Await()
Sleep(3.0)
SetAlertedMood()
# 플레이어가 발견되면 캠프를 전투로 변경
WaitForCombat()<suspends>:void=
race:
StrongholdGameManager.PlayerDetectedEvent.Await()
StrongholdGameManager.FallbackEvent.Await()
Sleep(2.0)
SetCombatMood()
# 실행 중인 게임에서 장치가 시작되면 실행됩니다.
OnBegin<override>()<suspends>:void=
FindLightsWithTag()
MonitorStrongholdAlertStatus()
# 성채가 전투 상태인지 경계 상태인지 확인하는 메인 루프
MonitorStrongholdAlertStatus()<suspends>:void=
loop:
WaitForCombat()
WaitForAlerted()
# 조명을 빨간색으로 전환하고 긴장도 높은 음악을 재생하여 기지를 전투 상태로 설정
SetCombatMood():void=
# 전투 조명 루프를 반복하며 전투 조명 켜기
for(LightsToTurnOn: CustomizableLightDevicesCombat):
LightsToTurnOn.TurnOn()
# 경고등 루프를 반복하며 켜고 끄기
for(LightsToTurnOff: CustomizableLightDevicesAlerted):
LightsToTurnOff.TurnOff()
# 전투 오디오 켜기 및 경고 오디오 끄기
RadioCombat.Play()
RadioAlerted.Stop()
FlareAlarmVFXCreator.Toggle()
# 조명을 노란색으로 전환하고 긴장감 있는 음악을 재생하여 기지를 경계 상태로 설정
SetAlertedMood():void=
for(LightsToTurnOn: CustomizableLightDevicesAlerted):
LightsToTurnOn.TurnOn()
for(LightsToTurnOff: CustomizableLightDevicesCombat):
LightsToTurnOff.TurnOff()
RadioCombat.Stop()
RadioAlerted.Play()
# 특정 Verse 태그가 있는 조명에 대한 포크리 장치 루프를 반복하여 목록에 저장
FindLightsWithTag() : void=
TaggedAlertedLightDevices := GetCreativeObjectsWithTag(alerted_lights_tag{})
TaggedCombatLightDevices := GetCreativeObjectsWithTag(combat_lights_tag{})
for(AlertedLight : TaggedAlertedLightDevices, CustomizableLight := customizable_light_device[AlertedLight] ):
CustomizableLight.TurnOff()
set CustomizableLightDevicesAlerted += array{CustomizableLight}
for(CombatLight : TaggedCombatLightDevices, CustomizableLight := customizable_light_device[CombatLight] ):
CustomizableLight.TurnOff()
set CustomizableLightDevicesCombat += array{CustomizableLight}
이제 성채 게임을 성공적으로 만들었습니다.