UE5 Verse 언어 입문 — UEFN 스크립팅
Verse는 Epic Games가 UEFN(Unreal Editor for Fortnite)을 위해 개발한 함수형 + 객체지향 혼합 프로그래밍 언어입니다. 결정론적 동시성, 실패 가능(failable) 표현식, 강력한 타입 시스템이 특징이며, 향후 UE 엔진 전체로 확장될 예정입니다.
1. 기본 문법
섹션 제목: “1. 기본 문법”# 주석은 #으로 시작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}")2. 타입 시스템
섹션 제목: “2. 타입 시스템”# 기본 타입var I : int = 42var F : float = 3.14var B : logic = true # bool 대신 logicvar 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)3. 실패 가능 표현식 (Failable)
섹션 제목: “3. 실패 가능 표현식 (Failable)”# Verse의 핵심 개념: 표현식은 성공 또는 실패할 수 있음# if 블록이 실패 컨텍스트를 제공
FindPlayer(Name : string) : ?player = # 실패 가능 컨텍스트 if (P := GetPlayerByName(Name)?): P else: false # 실패 반환
# 연쇄 실패: 하나라도 실패하면 전체 실패if: Player := FindPlayer("Alice")? Health := Player.GetHealth()? Health > 0then: Print("Alice는 살아있습니다")else: Print("Alice를 찾을 수 없거나 사망")
# 실패 가능 함수 정의GetTopPlayer()<decides><transacts> : player = Players := GetActivePlayers() Players[0] # 비어있으면 실패4. 동시성 모델
섹션 제목: “4. 동시성 모델”# 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}")5. 클래스와 인터페이스
섹션 제목: “5. 클래스와 인터페이스”# 인터페이스 정의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)6. UEFN 게임플레이 디바이스
섹션 제목: “6. UEFN 게임플레이 디바이스”# 트리거 디바이스와 연동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)7. 오류 처리 패턴
섹션 제목: “7. 오류 처리 패턴”# 실패 가능 + 옵션 조합 패턴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: 08. 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 개발의 기본 워크플로입니다.