콘텐츠로 이동

UE5 Verse 언어 입문 — UEFN 스크립팅

Verse는 Epic Games가 UEFN(Unreal Editor for Fortnite)을 위해 개발한 함수형 + 객체지향 혼합 프로그래밍 언어입니다. 결정론적 동시성, 실패 가능(failable) 표현식, 강력한 타입 시스템이 특징이며, 향후 UE 엔진 전체로 확장될 예정입니다.


# 주석은 #으로 시작
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
# 변수 선언
var Count : int = 0 # 가변 변수
Name : string = "Player" # 불변 변수
# 타입 추론
var Score := 100 # int로 추론
# 함수 선언
Greet(PlayerName : string) : string =
"안녕하세요, {PlayerName}님!"
# 반환 타입 없는 void 함수
PrintScore(Score : int) : void =
Print("점수: {Score}")

# 기본 타입
var I : int = 42
var F : float = 3.14
var B : logic = true # bool 대신 logic
var S : string = "hello"
# 옵션 타입 (nullable 대신)
var MaybePlayer : ?player = false # 없음
if (P := MaybePlayer?): # 언래핑
Print("플레이어 있음: {P}")
# 배열
var Items : []string = array{"sword", "shield"}
Items += array{"potion"}
# 맵
var Scores : [string]int = map{"Alice"=>100, "Bob"=>85}
# 튜플
var Pos : tuple(float, float, float) = (1.0, 2.0, 3.0)

# Verse의 핵심 개념: 표현식은 성공 또는 실패할 수 있음
# if 블록이 실패 컨텍스트를 제공
FindPlayer(Name : string) : ?player =
# 실패 가능 컨텍스트
if (P := GetPlayerByName(Name)?):
P
else:
false # 실패 반환
# 연쇄 실패: 하나라도 실패하면 전체 실패
if:
Player := FindPlayer("Alice")?
Health := Player.GetHealth()?
Health > 0
then:
Print("Alice는 살아있습니다")
else:
Print("Alice를 찾을 수 없거나 사망")
# 실패 가능 함수 정의
GetTopPlayer()<decides><transacts> : player =
Players := GetActivePlayers()
Players[0] # 비어있으면 실패

# Verse의 구조적 동시성
# spawn: 비동기 태스크 시작
MyTask := spawn:
loop:
Sleep(1.0)
UpdateScoreboard()
# race: 먼저 완료된 것만 선택
race:
block:
Sleep(10.0)
Print("10초 경과")
block:
WaitForPlayerAction()
Print("플레이어가 먼저 행동함")
# sync: 모두 완료될 때까지 대기
sync:
LoadAssetA()
LoadAssetB()
LoadAssetC()
Print("모든 에셋 로드 완료")
# 채널 통신
Channel := channel(int){}
spawn:
Channel.Send(42)
Value := Channel.Receive()
Print("수신: {Value}")

# 인터페이스 정의
damageable := interface:
TakeDamage(Amount : float) : void
GetHealth() : float
# 클래스 구현
enemy := class(damageable):
var Health : float = 100.0
Name : string
# 인터페이스 구현
TakeDamage(Amount : float) : void =
set Health = Max(0.0, Health - Amount)
if (Health <= 0.0):
OnDeath()
GetHealth() : float = Health
OnDeath() : void =
Print("{Name} 사망")
# 상속
boss_enemy := class(enemy):
var Phase : int = 1
override TakeDamage(Amount : float) : void =
# 1페이즈에서 데미지 감소
ActualDamage := if (Phase = 1): Amount * 0.5 else Amount
super.TakeDamage(ActualDamage)

# 트리거 디바이스와 연동
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
trigger_handler := class(creative_device):
# 에디터에서 드래그한 디바이스 참조
@editable
MyTrigger : trigger_device = trigger_device{}
@editable
var RewardAmount : int = 100
# 게임 시작 시 자동 호출
OnBegin<override>()<suspends> : void =
# 트리거 이벤트 구독
MyTrigger.TriggeredEvent.Await()
OnTriggered()
OnTriggered() : void =
Print("트리거 발동! 보상: {RewardAmount}")
# 모든 플레이어에게 점수 지급
for (P : GetActivePlayers()):
if (FortChar := P.GetFortCharacter[]):
FortChar.GiveScore(RewardAmount)

# 실패 가능 + 옵션 조합 패턴
SafeDivide(A : float, B : float)<decides> : float =
B <> 0.0 # B가 0이면 실패
A / B
# 사용
if (Result := SafeDivide(10.0, 0.0)?):
Print("결과: {Result}")
else:
Print("0으로 나눌 수 없음")
# 기본값 패턴
GetPlayerScore(P : player) : int =
if (Score := ScoreMap[P]?): Score
else: 0

8. weak_map — GC 안전 플레이어 데이터 매핑

섹션 제목: “8. weak_map — GC 안전 플레이어 데이터 매핑”

weak_map은 키(player)가 소멸되면 해당 항목이 자동으로 제거됩니다. 일반 map은 플레이어 소멸 후에도 항목이 남아 메모리 누수가 생길 수 있습니다.

# 플레이어 점수를 weak_map으로 관리
var PlayerScores : weak_map(player, int) = map{}
AddScore(P : player, Delta : int) : void =
Current := if (S := PlayerScores[P]?): S else 0
if (set PlayerScores[P] = Current + Delta): true
GetLeaderboard() : []tuple(player, int) =
for (P -> Score : PlayerScores):
(P, Score)

9. 스폰 관리 디바이스 — 실전 패턴

섹션 제목: “9. 스폰 관리 디바이스 — 실전 패턴”

다수의 스폰 패드와 플레이어 리스폰을 관리하는 UEFN 디바이스 예제입니다.

using { /Fortnite.com/Devices }
using { /Fortnite.com/Characters }
using { /Verse.org/Simulation }
spawn_manager := class(creative_device):
@editable
SpawnPads : []player_spawner_device = array{}
@editable
var RespawnDelay : float = 3.0
# 플레이어 → 배정된 스폰 패드 인덱스 (weak_map: 플레이어 소멸 시 자동 정리)
var PlayerPadIndex : weak_map(player, int) = map{}
OnBegin<override>()<suspends> : void =
GetPlayspace().PlayerAddedEvent.Subscribe(OnPlayerJoined)
GetPlayspace().PlayerRemovedEvent.Subscribe(OnPlayerLeft)
OnPlayerJoined(P : player) : void =
# 라운드 로빈으로 스폰 패드 배정
PadIdx := Mod(PlayerPadIndex.Length(), SpawnPads.Length())
if (set PlayerPadIndex[P] = PadIdx):
SpawnAt(P, PadIdx)
SpawnAt(P : player, PadIdx : int) : void =
if (Pad := SpawnPads[PadIdx]?):
Pad.SpawnPlayer(P)
RespawnPlayer(P : player)<suspends> : void =
Sleep(RespawnDelay)
if (PadIdx := PlayerPadIndex[P]?):
SpawnAt(P, PadIdx)
OnPlayerLeft(P : player) : void =
# weak_map이므로 명시적 제거 없이도 소멸 시 자동 정리
Print("{P} 게임 이탈")

Verse의 핵심은 실패 가능 표현식구조적 동시성입니다. if/else가 단순 조건분기가 아니라 실패 컨텍스트를 제공하며, ? 연산자로 옵션 값을 안전하게 언래핑합니다. spawn, race, sync로 비동기 게임플레이 로직을 명확하게 구조화하고, @editable로 에디터에서 디바이스를 연결하는 패턴이 UEFN 개발의 기본 워크플로입니다.