2024년 11월 1일부터 32.00 출시 버전에서 Verse 언어를 버전 1로 안정화합니다. 안정성 차원에서뿐만 아니라 향후 Verse 언어 업데이트 시 코드도 점진적으로 손쉽게 업데이트할 수 있으므로, 프로젝트 업그레이드를 권장합니다. 이러한 업그레이드는 선택 사항으로, 프로젝트의 현재 버전은 항상 기존 Verse 버전에서 계속해서 작동할 예정입니다. 하지만 미래의 어느 시점에는 최신 버전의 프로젝트를 업로드하기 위해 프로젝트를 최신 Verse 버전으로 업그레이드해야 할 수 있습니다.
최초 공개 출시 이래로 에픽은 계속해서 Verse 언어를 발전시켜 왔습니다. 이러한 변경은 신규 언어 버전으로 업그레이드할 필요 없이 사용자에게 투명하게 적용되었습니다. 앞으로도 이러한 기조가 이어질 것으로 예상합니다. 대부분의 언어 변경사항은 이전 버전과 하위 호환될 것이며 UEFN 신규 출시와 함께 사용자에게 적용될 것입니다.
그러나 일부 언어 변경사항은 이전 버전과 호환되지 않으며 코드를 컴파일하기 위해 코드를 변경해야 할 수 있습니다. 이처럼 이전 버전과 호환되지 않는 변경사항은 사용자가 새 언어 버전을 겨냥해 프로젝트를 업그레이드하는 경우에만 트리거됩니다.
이전 버전과 호환되지 않는 코드를 저장할 때마다 코드 작동이 최신 언어 버전에서 지원 중단되었다는 경고가 표시될 것입니다. 예를 들어 새 Verse 언어 버전과 호환되지 않는 코딩 사례를 계속 사용하면 코드에 대한 경고가 나타납니다.
지원 중단 경고 없이 프로젝트가 V0에서 컴파일되는 경우, 코드 행동을 변경하지 않고 V1로 업그레이드할 수 있습니다. UEFN 32.00 버전에서 V0 프로젝트를 열어 오류나 경고 없이 컴파일되는 경우, 에디터에서 업그레이드 여부를 물을 것입니다.
어떤 이유에서든 나중에 업그레이드 또는 다운그레이드하려는 경우 프로젝트 세팅에서 할 수 있습니다.
Click image to enlarge.
또한 포트나이트 프로젝트 폴더에서 프로젝트 파일을 열고 .uplugin 파일의 Verse 버전을 변경할 수 있습니다.
Click image to enlarge.
대부분의 지원 중단은 향후 언어 개선을 위한 길을 열어 주기 위함이며, 아직 사용자에게 어떤 혜택을 제공하지는 않습니다. 다만 로컬 한정자와 구조체 비교, 이 두 가지는 예외입니다.
V1의 변경사항
Set 내 실패
set 가 실행하는 표현식의 실패가 더 이상 허용되지 않습니다. 이전에는 다음과 같은 코드가 허용되었습니다.
F():void=
var X:int = 0
set X = FailableExpression[]
이 경우 X 는 FailableExpression 이 경고를 포함하여 평가한 결과로 설정되었습니다. V1에서는 이러한 코드가 허용되지 않습니다.
코드를 수정하려면 표현식이 실패할 수 없도록 해야 합니다. 이렇게 하기 위한 한 가지 방법은 다음과 같이 수정하는 것입니다.
var X:int = 0
Value:= FailableExpression[] or DefaultValue
set X = Value
맵 리터럴 키 내 실패
이전에는 다음과 같이 맵 실패에 리터럴 키를 보유할 수 있었습니다.
map{ExpressionThatCannotFail=>Value}
간단한 예시로는 0 = 0 이 실패하는 map{ (0 = 0) =>0 } 이 있습니다. 이는 더 이상 허용되지 않습니다.
블록에 세미콜론/쉼표/새 줄 구분자 혼합하기
이전에는 하위 표현식을 구분하기 위해 세미콜론/쉼표/새 줄 혼합이 허용되었으며, 그 결과 코드가 아래와 같이 표시되었습니다.
A,
B
for (A := 0..2):
# more code here
내부에서 하위 수준으로 변환되면 아래와 같은 코드가 표시됩니다.
block:
A
B
for (A := 0..2):
# more code here
이는 고유한 별도 스코프가 있는 묵시적 블록이 생성되어 코드 블록에서 A 의 두 정의가 서로 충돌하지 않았음을 뜻합니다.
그러나 이는 잘못된 행동으로 최신 Verse 언어 버전에서 수정됩니다. 이제 동일한 코드가 각 하위 표현식을 별도로 취급하여 다음과 같은 결과가 나타납니다.
A
B
for (A := 0..2):
# more code here
이는 첫 번째 A와 A := 0..2 의 두 번째 정의가 서로 겹쳐 의미가 모호해짐을 뜻합니다.
이 문제를 해결하려면 작성자와 이 코드 동작을 사용하는 사용자 모두 Verse 코드 전체에서 하위 표현식 구분을 위해 세미콜론/쉼표/새 줄 혼합 사용을 중단해야 합니다.
Example:
PropPosition := Prop.GetTransform().Translation,
if(Round[PropPosition.Z] = Round[ROOT_POSITION.Z]) { break }
Sleep(0.0)
다음과 같이 수정해야 합니다.
PropPosition := Prop.GetTransform().Translation # note the trailing comma here has been removed
if(Round[PropPosition.Z] = Round[ROOT_POSITION.Z]) { break }
Sleep(0.0)
이전에 28.20 버전부터는 혼합 구분자가 탐지될 때마다 경고가 생성되었습니다. 이는 최신 Verse 언어 버전에서 이제 허용되지 않습니다.
고유 지정자 변경사항
이제 <unique> 지정자가 있는 클래스에는 <allocates> 생성 이펙트가 필요합니다. 예를 들어 class<unique><computes> 는 더 이상 허용되지 않습니다.
함수 로컬 한정자
(local:) 한정자는 다른 식별자와 구분하기 위해 함수 내 식별자에 적용될 수 있습니다.
For example:
ExternallyDefinedModuleB<public> := module:
ShadowX<public>:int = 10 # `ModuleC` 가 퍼블리싱된 후에만 추가됩니다.
ModuleC := module:
using{ExternallyDefinedModuleB}
FooNoLocal():float=
ShadowX:float = 0.0
ShadowX
위의 코드는 ShadowX 가 ExternallyDefinedModuleB.ShadowX 에서 온 것인지 아니면 FooNoLocal 내 ShadowX 에서 온 것인지 모호하므로 섀도잉 오류가 발생합니다.
이를 해결하려면 아래 예시에서와 같이 (local:) 한정자를 사용하여 어떤 ShadowX 를 가리키는 것인지 명확히 하면 됩니다.
ExternallyDefinedModuleA<public> := module:
ShadowX<public>:int = 10 # `ModuleB` 가 퍼블리싱된 후에만 추가됩니다.
ModuleB := module:
using{ExternallyDefinedModuleA}
FooLocal():float=
(local:)ShadowX:float = 0.0 # 구분을 위해 여기에서 `local` 한정자를 사용할 수 있습니다.
(local:)ShadowX
local 이라는 단어가 이제 예비 키워드이므로, 이전에는 이 단어를 데이터 정의 식별자로 사용하고 있는 것이 탐지되면 경고가 생성되었습니다. 최신 언어 버전에서 local 을 사용하면 오류가 발생하며, local 식별자를 일반적인 데이터 정의 식별자로 사용하고자 하는 경우 사용을 명시적으로 한정해야 합니다.
다음은 로컬 한정자의 또 다른 예시입니다.
MyModule := module:
X:int = 1
Foo((local:)X:int):int = (MyModule:)X + (local:)X
이 예시에서 (local:) 한정자를 지정하지 않은 경우 두 X 식별자가 모두 동일 스코프 내에 있으므로 Foo(X:int) 의 X 가 위의 X:int = 1 정의와 직접적으로 겹치게 됩니다. 따라서 (local:) 한정자를 사용하면 Foo 의 실행인자 파라미터 절 내 X 가 Foo 스코프 내에만 해당하게 되므로 이 둘을 구별할 수 있습니다. 동일한 내용이 Foo 의 바디 내 X 식별자에도 적용됩니다.
구조체 내 퍼블릭 필드
이제 struct 내 모든 필드는 퍼블릭이어야 합니다. 이 점 역시 이제 V1부터 기본적으로 적용됩니다. (<public> 사용은 더 이상 불필요함)
V1에는 두 struct 를 비교하는 기능도 추가되었습니다. 한 구조체의 모든 필드를 비교할 수 있는 경우, = 를 사용하여 구조체의 두 인스턴스를 필드별로 비교할 수 있습니다. 예를 들면 다음과 같습니다.
vector3i := struct{X:int, Y:int, Z:int}
vector3i{X:=0, Y:=0, Z:=0} = vector3i{X:=0, Y:=0, Z:=0} # 성공합니다.
vector3i{X:=0, Y:=0, Z:=0} = vector3i{X:=0, Y:=0, Z:=1} # Z가 두 인스턴스 간에 다르므로 실패합니다.