침투자의 깜빡이는 비주얼 이펙트를 구현하려면 각 플레이어별 캐릭터를 숨기고 표시하는 작업을 반복해야 합니다. 침투 자가 피해를 입을 때마다 함수에서 작업을 반복시키되, 호출될 때마다 나머지 코드도 계속 실행되도록 해야 합니다. 침투자가 여러 명이라면 상황이 더 복잡해집니다. 게임 도중 동시에 침투자 멤버 다수가 피해를 입을 수도 있기 때문에 각 침투자 멤버를 개별적으로 처리할 수 있는 코드가 필요합니다.
이 목표를 달성하려면 spawn 표현식을 많이 사용해야 합니다. 함수를 스폰하면 나머지 코드를 보류하지 않고 비동기적으로 실행할 수 있습니다. 각 침투자별로 함수를 스폰할 경우 침투자별로 독립적인 깜빡임 효과를 발생시킬 수 있습니다.
이 단계에서는 침투자의 캐릭터가 피해를 입었을 때 깜빡이게 만드는 방법을 살펴봅니다.
깜빡임 루프 생성하기
- 새 함수
FlickerCharacter()를invisibility_manager클래스 정의에 추가합니다. 이 함수는fort_character를 취해 캐릭터의 비저빌리티를 깜빡입니다. 이 함수에<suspends>지정자를 추가하면 비동기적으로 실행할 수 있습니다.# 에이전트의 fort_character를 반복적으로 숨기고 표시함으로써 에이전트의 비저빌리티를 깜빡입니다. FlickerCharacter(InCharacter:fort_character)<suspends>:void= Logger.Print("FlickerCharacter() invoked") FlickerCharacter()에서Hide()를InCharacter와Sleep()에 일정 시간 동안(앞서 정의했던FlickerRateSeconds만큼) 루프시키고 캐릭터를Show()하고, 다시Sleep()시킵니다. 그러면 캐릭터에 깜빡임 효과가 발생하면서 적 플레이어가 캐릭터를 추적할 수는 있지만 계속 보이지는 않기 때문에 조준하기는 여전히 어렵게 됩니다.# 에이전트의 fort_character를 반복적으로 숨기고 표시함으로써 에이전트의 비저빌리티를 깜빡입니다. FlickerCharacter(InCharacter:fort_character)<suspends>:void= Logger.Print("FlickerCharacter() invoked") # 캐릭터의 숨김과 표시를 루프하여 깜빡임 효과를 만듭니다. loop: InCharacter.Hide() Sleep(FlickerRateSeconds) InCharacter.Show() Sleep(FlickerRateSeconds)루프 중단하기
캐릭터가 계속 깜빡이지 않게 하려면 이 루프 함수를 중단할 방법이 필요합니다. 앞서 구성했던
PlayerVisibilitySeconds맵은 플레이어에게 남은 깜빡임 시간을 트래킹하기 때문에 각 루프에서 그 시간을 줄여야 합니다. 시간이 0까지 떨어지면 플레이어는 깜빡임을 멈추고, 루프를 중단할 수 있게 됩니다.InCharacter.GetAgent[]를 키로 사용하여PlayerVisibilitySeconds에 액세스하여 플레이어의 남은 깜빡임 시간을 구하여TimeRemaining변수에 저장합니다. 실제로 같은 표현식에서FlickerRateSeconds * 2값을 줄여 캡에 남은 시간을 설정할 수 있습니다. 이처럼set표현식이 리졸브된 후TimeRemaining은PlayerVisibilitySeconds의 값이 됩니다. 주의할 점은Sleep()을 루프당 2번 호출해야 하므로FlickerRateSeconds에 2를 곱해야 한다는 것입니다.# 에이전트의 fort_character를 반복적으로 숨기고 표시함으로써 에이전트의 비저빌리티를 깜빡입니다. FlickerCharacter(InCharacter:fort_character)<suspends>:void= Logger.Print("FlickerCharacter() invoked") # 캐릭터의 숨김과 표시를 루프하여 깜빡임 효과를 만듭니다. loop: InCharacter.Hide() Sleep(FlickerRateSeconds) InCharacter.Show() Sleep(FlickerRateSeconds) # 루프마다 캐릭터의 깜빡임 시간을 FlickerRateSeconds만큼 줄입니다. # 남은 시간이 0이 되면 루프를 중단합니다. if: TimeRemaining := set PlayerVisibilitySeconds[InCharacter.GetAgent[]] -= FlickerRateSeconds * 2TimeRemaining이 0 이하인지, 즉 캐릭터의 깜빡임을 멈춰야 하는지 확인합니다. 이렇게 하려면 해당 캐릭터에 대해Hide()를 호출하여 다시 투명하게 만들고 루프에서break를 호출합니다.FlickerCharacter()함수는 다음과 같게 됩니다.# 에이전트의 fort_character를 반복적으로 숨기고 표시함으로써 에이전트의 비저빌리티를 깜빡입니다. FlickerCharacter(InCharacter:fort_character)<suspends>:void= Logger.Print("FlickerCharacter() invoked") # 캐릭터의 숨김과 표시를 루프하여 깜빡임 효과를 만듭니다. loop: InCharacter.Hide() Sleep(FlickerRateSeconds) InCharacter.Show() Sleep(FlickerRateSeconds) # 루프마다 캐릭터의 깜빡임 시간을 FlickerRateSeconds만큼 줄입니다. # 남은 시간이 0이 되면 루프를 중단합니다. if: TimeRemaining := set PlayerVisibilitySeconds[InCharacter.GetAgent[]] -= FlickerRateSeconds * 2 TimeRemaining <= 0.0 then: InCharacter.Hide() break
깜빡임을 시작 및 초기화하기
이미 깜빡이는 중인 침투자가 피해를 입으면 어떻게 될지 생각해 볼 수 있습니다. 이 경우, 다른 FlickerCharacter() 함수를 스폰하면 금세 함수 스폰을 제어할 수 없게 되므로 같은 캐릭터에 함수가 수십 개 적용될 수도 있습니다. 따라서 PlayerVisibilitySeconds 의 플레이어 값은 피해를 입을 때마다 초기화되어야 합니다. 이렇게 하려면 플레이어의 깜빡임 여부를 확인하는 함수를 정의해야 합니다. 즉, 플레이어가 깜빡이면 플레이어의 깜빡임 시간을 초기화하고 그렇지 않으면 해당 캐릭터에 새 깜빡임 이벤트를 스폰합니다.
- 새 헬퍼 함수
IsFlickering()을invisibility_manager클래스 정의에 추가합니다. 이 함수는 에이전트를 실행인자로 취하고,PlayerVisibilitySeconds내에서의 값이0.0보다 크면true를 반환합니다. 이 함수에decides및transacts지정자를 추가하여 실패가 가능하게 하고, 실패 시 롤백할 수 있게 합니다.# 플레이어에게 남은 깜빡임 시간이 있는지 여부를 반환합니다. IsFlickering(InAgent:agent)<decides><transacts>:void= PlayerVisibilitySeconds[InAgent] > 0.0 - 새 함수
StartOrResetFlickering()을invisibility_manager클래스 정의에 추가합니다. 이 함수는 에이전트를 실행인자로 취하고, 플레이어가 깜빡임을 시작할지 또는 깜빡임을 초기화할지 결정합니다.# 에이전트가 투명했다면 새 깜빡임 이벤트를 시작합니다. # 아니면 에이전트의 현재 깜빡임을 리셋합니다. StartOrResetFlickering(InAgent:agent):void= StartOrResetFlickering()에서 지정된 에이전트가 깜빡이고 있지 않은지 확인합니다. 깜빡이고 있지 않다면 이 에이전트에 대해 새 깜빡임 이벤트를 시작합니다. 해당 에이전트의fort_character를 얻어서FortCharacter변수에 저장합니다.# 에이전트가 투명했다면 새 깜빡임 이벤트를 시작합니다. # 아니면 에이전트의 현재 깜빡임을 리셋합니다. StartOrResetFlickering(InAgent:agent):void= if (not IsFlickering[InAgent], FortCharacter := InAgent.GetFortCharacter[]): Logger.Print("Attempting to start NEW FlickerEvent for this character")PlayerVisibilitySeconds의 에이전트 값을VulnerableSeconds로 설정하고, 이 에이전트에 대해 새FlickerCharacter()함수를spawn하여 해당FortCharacter를 전달합니다.if (not IsFlickering[InAgent], FortCharacter := InAgent.GetFortCharacter[]): Logger.Print("Attempting to start NEW FlickerEvent for this character") # 새 깜빡임이 시작됩니다. if (set PlayerVisibilitySeconds[InAgent] = VulnerableSeconds): spawn{FlickerCharacter(FortCharacter)} Logger.Print("Spawned a FlickerEvent for this character")- 에이전트가 이미 깜빡이는 중이라면
PlayerVisibilitySeconds내의 해당 값을VulnerableSeconds로 초기화하기만 하면 됩니다. 앞서 살펴봤던FlickerCharacter()함수는 이 값을 비동기적으로 읽으므로,FlickerCharacter()가 루프하는 동안 값이 초기화된다면break없이 계속 루프합니다.StartOrResetFlickering()함수는 다음과 같게 됩니다.# 에이전트가 투명했다면 새 깜빡임 이벤트를 시작합니다. # 아니면 에이전트의 현재 깜빡임을 리셋합니다. StartOrResetFlickering(InAgent:agent):void= if (not IsFlickering[InAgent], FortCharacter := InAgent.GetFortCharacter[]): Logger.Print("Attempting to start NEW FlickerEvent for this character") # 새 깜빡임이 시작됩니다. if (set PlayerVisibilitySeconds[InAgent] = VulnerableSeconds): spawn{FlickerCharacter(FortCharacter)} Logger.Print("Spawned a FlickerEvent for this character") else: # 진행 중인 깜빡임이 리셋됩니다. if (set PlayerVisibilitySeconds[InAgent] = VulnerableSeconds): Logger.Print("Reset character's FlickerTimer to VulnerableSeconds")
침투자가 피해를 입을 때 깜빡이게 만들기
이 모든 함수를 연동되게 하려면 침투자 한 명이 피해를 입었을 경우의 발생 상황을 처리할 함수를 정의해야 합니다. FlickerCharacter() 에서와 마찬가지로 각 침투자를 트래킹하여 피해를 입었는지 여부를 결정해야 합니다. 이를 위한 함수는 침투자마다 하나씩 스폰할 수 있어야 하므로 비동기적이어야 합니다.
- 새 함수
OnInfiltratorDamaged()를invisibility_manager클래스 정의에 추가합니다. 이 함수는 에이전트를 취하고, 이 에이전트가 피해를 입으면StartOrResetFlickering()호출을 처리합니다. 이 함수에<suspends>지정자를 추가하면 비동기적으로 실행할 수 있습니다.# 에이전트가 피해를 받을 때마다 비저빌리티를 깜빡입니다. OnInfiltratorDamaged(InAgent:agent)<suspends>:void= Logger.Print("Attempting to start flickering this character") - 현재 플레이스페이스에 대해
fort_team_collection을 구하여TeamCollection변수에 저장합니다. 그런 다음 이 함수에 전달된 에이전트에 대해fort_character를 구합니다.# 에이전트가 피해를 받을 때마다 비저빌리티를 깜빡입니다. OnInfiltratorDamaged(InAgent:agent)<suspends>:void= Logger.Print("Attempting to start flickering this character") TeamCollection := GetPlayspace().GetTeamCollection() if (FortCharacter := InAgent.GetFortCharacter[]): - 이 함수는 전달된 에이전트를 계속해서 모니터링해야 하므로 루프해야 합니다. 이 루프는 지정된 캐릭터가 피해를 입을 때마다 매번 실행되며, 함수가 모니터링 중인 에이전트에 대해
StartOrResetFlickering을 호출해야 합니다.OnInfiltratorDamaged에 루프를 추가합니다.# 에이전트가 피해를 받을 때마다 비저빌리티를 깜빡입니다. OnInfiltratorDamaged(InAgent:agent)<suspends>:void= Logger.Print("Attempting to start flickering this character") TeamCollection := GetPlayspace().GetTeamCollection() if (FortCharacter := InAgent.GetFortCharacter[]): loop: - 루프에서
IsVisibilityShared가 true인지 확인합니다. 그렇다면 침투자 한 명이 피해를 입을 경우 모든 침투자가 깜빡이게 됩니다. 이 설정이 활성화되어 있는 경우GetTeam[]및GetAgents[]를 각각 호출하여 이 에이전트의 팀과 해당 팀의 플레이어들을 모두 구합니다.if (FortCharacter := InAgent.GetFortCharacter[]): loop: if(IsVisibilityShared?, CurrentTeam := TeamCollection.GetTeam[InAgent], TeamAgents := TeamCollection.GetAgents[CurrentTeam]): - 이제
for루프에서 각 팀 멤버에 대해StartOrResetFlickering을 호출합니다.if(IsVisibilityShared?, CurrentTeam := TeamCollection.GetTeam[InAgent], TeamAgents := TeamCollection.GetAgents[CurrentTeam]): # 각 팀 멤버에 대해 PlayerVisibility 시간(초)을 설정하고 FlickerEvent를 스폰합니다. for(Teammate:TeamAgents): Logger.Print("Calling StartOrResetFlickering on a Teammate") StartOrResetFlickering(Teammate) - 비저빌리티가 공유되어 있지 않은 경우 이 함수가 모니터링 중인 에이전트에 대해
StartOrResetFlickering을 호출합니다.loop: if(IsVisibilityShared?, CurrentTeam := TeamCollection.GetTeam[InAgent], TeamAgents := TeamCollection.GetAgents[CurrentTeam]): # 각 팀 멤버에 대해 PlayerVisibility 시간(초)을 설정하고 FlickerEvent를 스폰합니다. for(Teammate:TeamAgents): Logger.Print("Calling StartOrResetFlickering on a Teammate") StartOrResetFlickering(Teammate) else: # 피해를 입은 캐릭터를 깜빡입니다. Logger.Print("Calling StartOrResetFlickering on InAgent") StartOrResetFlickering(InAgent) - 마지막으로, 지정된 캐릭터의
DamagedEvent()를 루프의 끝에서Await()합니다. 그러면 루프는 캐릭터가 피해를 입었을 때만 반복됩니다. 이 루프는 함수가 시작될 때 최소 한 번 실행됩니다. 즉,StartOrResetFlickering()을 최소 한 번 호출합니다. 따라서 침투자는 깜빡이는 상태로 게임을 시작한 후 투명하게 됩니다. 이렇게 하면 침투자는 자신들이 보이지는 않지만 영구적으로 그런 것은 아니라는 사실을 이해하는 데도 도움이 됩니다.OnInfiltratorDamaged()함수는 다음과 같게 됩니다.# 에이전트가 피해를 받을 때마다 비저빌리티를 깜빡입니다. OnInfiltratorDamaged(InAgent:agent)<suspends>:void= Logger.Print("Attempting to start flickering this character") TeamCollection := GetPlayspace().GetTeamCollection() if (FortCharacter := InAgent.GetFortCharacter[]): loop: if(IsVisibilityShared?, CurrentTeam := TeamCollection.GetTeam[InAgent], TeamAgents := TeamCollection.GetAgents[CurrentTeam]): # 각 팀 멤버에 대해 PlayerVisibility 시간(초)을 설정하고 FlickerEvent를 스폰합니다. for(Teammate:TeamAgents): Logger.Print("Calling StartOrResetFlickering on a Teammate") StartOrResetFlickering(Teammate) else: # 피해를 입은 캐릭터를 깜빡입니다. Logger.Print("Calling StartOrResetFlickering on InAgent") StartOrResetFlickering(InAgent) FortCharacter.DamagedEvent().Await()
게임 시작 시 캐릭터에 대해 함수 스폰하기
다시 StartInvisibilityManager() 에서 플레이어 캐릭터에 대해 Hide() 를 호출하기 전에, 이 캐릭터에 대해 OnInfiltratorDamaged() 함수를 스폰합니다. 그러면 각 침투자는 자신을 비동기적으로 모니터링하면서 각자의 깜빡임과 관련된 로직을 모두 처리하는 함수를 갖추게 됩니다. StartInvisibilityManager() 함수는 다음과 같게 됩니다.
StartInvisibilityManager<public>(AllTeams:[]team, AllPlayers:[]player, Infiltrators:team):void=
Logger.Print("Invisibility script started!")
set Teams = GetPlayspace().GetTeamCollection().GetTeams()
for(PlayerSpawner:PlayersSpawners):
PlayerSpawner.SpawnedEvent.Subscribe(OnPlayerSpawn)
# 각 플레이어가 침투 팀에 생성된 경우 해당 플레이어에 대해 OnInfiltratorDamaged 함수를
# 호출합니다. 그러면 캐릭터가 투명하게 됩니다.
for(TeamPlayer:AllPlayers):
if:
FortCharacter:fort_character = TeamPlayer.GetFortCharacter[]
CurrentTeam := GetPlayspace().GetTeamCollection().GetTeam[TeamPlayer]
Logger.Print("Got this player's current team")
Teams[0] = CurrentTeam
set PlayerVisibilitySeconds[TeamPlayer] = 0.0
Logger.Print("Added player to PlayerVisibilitySeconds")
then:
spawn{OnInfiltratorDamaged(TeamPlayer)}
Logger.Print("Player spawned as an infiltrator, making them invisible")
FortCharacter.Hide()
else:
Logger.Print("This player isn't an infiltrator")
스크립트를 저장하고 빌드한 후 UEFN 툴바에서 세션 시작(Launch Session) 을 클릭하여 레벨을 플레이테스트합니다. 레벨을 플레이테스트할 때, 각 침투자는 스크립트 시작 시 깜빡이다가 투명해져야 합니다. 피해를 입으면 IsVisibilityShared 에서 설정한 것에 따라 피해 당사자 또는 팀 전체가 깜빡여야 합니다.

다음 단계
이 튜토리얼의 다음 단계에서는 이미 진행 중인 게임에 참여하는 플레이어를 처리하는 방법을 살펴봅니다.