Get Up & Code, MacKin Talk

new Framework(*) Unreal Engine에서 iOS Framework 적용하기 본문

IOS

new Framework(*) Unreal Engine에서 iOS Framework 적용하기

맥킨 2024. 4. 9. 23:09

일반적인 iOS App을 개발할 때, SPM이나, Cocoapod 등의 의존성 관리도구를 많이 사용하듯

게임 엔진도 비슷합니다. 기능 중 일부 기능을 3rd나 오픈 소스 등 외부 소스를 사용해 기능 구현에 사용합니다. 

 

라이브러리 또는 프레임워크라 불리는 형태로 타겟 앱에 추가하죠.

 

Unreal Engine을 통해서 나오는 ipa 파일도 결국은 Xcode를 통해 나오는 결과물과 별반 다르지 않다는 점에서

결과물이 비슷하다는 건 인풋도 비슷하다는 거 아닐까요. 

물론 내부의 실행파일의 구조 등은 일부 다르지만요. 

 

일반 Swift로 작성된 iOS 앱과 다른 점이라고 한다면

Unreal Engine에서는 iOS Framework를 적용하기 위해서 Framework를 적용하기 위한 Build.cs 를 작성하고, 

iOS를 타겟으로 배포할 경우, new Framework를 통해 적용할 Framework를 추가합니다.

PublicAdditionalFrameworks.Add(
    new Framework(
        "FrameworkName", // Framework 이름
        "Path/to/Your/FrameworkName.framework" // Framework의 경로
    )
);

 

(Unreal Engine은 Cocoapods 등 일반적으로 사용하는 iOS의 의존성 도구를 지원하지 않습니다.

4.xx 버전은 아직 xcframework의 지원도 되지 않는 것으로 파악하고 있습니다.)

 

Unreal 4.27 기준으로는 문서나 저널에서 찾아본 내용으론 dynamic Framework에 대한 공식 지원을 알리고 있진 않습니다.

하지만 직접 리서치해보고, 시도 해본 결과로 static Framework(embededFramework.zip을 만드는 방식) 외에도 dynamic Framework도 가능한 것으로 판단됩니다. 실제 라이브 서비스로 제공하고 있기도 하고요.

 

 

Build.cs 파일에서 추가할 때 사용하는 생성자 new Framework(*)에 대해서 명확하게 이해하고 있다면 Xcode의 Library& Framework 사용 방식처럼 Unreal에서도 적용할 수 있습니다. 물론 Xcode가 아닌 Unreal Editor 그리고 *.cs 파일의 수정이 있어 번거롭긴 합니다.

 

하단 코드는 unreal engine 레포 source에 포함된 Framework의 선언부를 가져온 내용입니다.

// Stores information about a framework on IOS or MacOS
public class Framework
{
/// <summary>
/// Name of the framework
/// </summary>
internal string Name;

/// <summary>
/// Specifies the path to a zip file that contains it or where the framework is located on disk
/// </summary>
internal string Path;

/// <summary>
/// 
/// </summary>
internal string? CopyBundledAssets = null;

/// <summary>
/// How to handle linking and copying the framework
/// </summary>
public enum FrameworkMode
{
/// <summary>
/// Pass this framework to the linker
/// </summary>
Link,

/// <summary>
/// Copy this framework into the final .app
/// </summary>
Copy,

/// <summary>
/// Both link into executable and copy into .app
/// </summary>
LinkAndCopy,
}

/// <summary>
/// How to treat the framework during linking and creating the .app
/// </summary>
internal FrameworkMode Mode;

/// <summary>
/// Constructor
/// </summary>
/// <param name="Name">Name of the framework</param>
/// <param name="Path">Path to a zip file containing the framework or a framework on disk</param>
/// <param name="CopyBundledAssets"></param>
/// <param name="bCopyFramework">Copy the framework to the target's Framework directory</param>
public Framework(string Name, string Path, string? CopyBundledAssets = null, bool bCopyFramework = false)
: this(Name, Path, bCopyFramework ? FrameworkMode.LinkAndCopy : FrameworkMode.Link, CopyBundledAssets)
{
}

/// <summary>
/// Constructor
/// </summary>
/// <param name="Name">Name of the framework</param>
/// <param name="Path">Path to a zip file containing the framework or a framework on disk</param>
/// <param name="Mode">How to treat the framework during linking and creating the .app</param>
/// <param name="CopyBundledAssets"></param>
public Framework(string Name, string Path, FrameworkMode Mode, string? CopyBundledAssets = null)
{
this.Name = Name;
this.Path = Path;
this.Mode = Mode;
this.CopyBundledAssets = CopyBundledAssets;
}

/// <summary>
/// Specifies if the file is a zip file
/// </summary>
public bool IsZipFile()
{
return Path.EndsWith(".zip");
}
}

 

 

여기서 중요한 부분은 실제 사용하는 생성자 쪽 입니다.

/// <param name="Name">Name of the framework</param>
/// <param name="Path">Path to a zip file containing the framework or a framework on disk</param>
/// <param name="CopyBundledAssets"></param>
/// <param name="bCopyFramework">Copy the framework to the target's Framework directory</param>
public Framework(string Name, string Path, string? CopyBundledAssets = null, bool bCopyFramework = false)
: this(Name, Path, bCopyFramework ? FrameworkMode.LinkAndCopy : FrameworkMode.Link, CopyBundledAssets)
{
}

 

파라미터를 앞부터 보자면, 프레임워크 이름, 경로, 번들 경로, 타겟의 Framework 폴더 안에 복사할 프레임워크입니다.

 

프레임워크 이름과 경로까지는 사용하려는 정적/동적 프레임워크 여부와 관계 없이 동일할 것으로 보이나, 

세 번째와 네 번째는 조금 다릅니다.

 

먼저 파라미터가 기본값을 가집니다. 

CopyBundledAssets = null 이고, bCopyFramework = false 입니다.


번들에 대한 지원과 CopyFramework 여부의 기본값 만 봐도 정적 프레임워크의 형태를 기본으로 지원한다는게 보이긴 합니다.

 

하나씩 살펴보자면 CopyBundledAssets 같은 경우, 앱 번들의 복사할 BundleAssets이 있는지에 대한 내용입니다.

일반적으로 정적(Static) 프레임워크는 프레임워크 내 이미지와 같은 리소스 파일을 제공하지 않고, 따로 번들 파일을 제공합니다.

App에서는 이를 App Bundle에 복사하여 사용합니다. Xcode에서 Copy Bundle Resources 영역에 해당합니다.

 

정적(Static) 프레임워크 내부에서 리소스를 사용하는 경우, CopyBundledAssets의 경로를 함께 지정해주면 됩니다.

경로에 대한 예시는 chat GPT나 구글링을 하더라도 쉽게 찾을 수 있을 겁니다.

 

동적(dynamic) 프레임워크의 경우엔 프레임워크 번들 내부에 리소스를 포함하기 때문에, 일반적으로 CopyBundledAssets 경로를 지정하지 않습니다. 이미 프레임워크 경로에 번들까지 다 들어있기 때문입니다.

 

마지막으로 bCopyFramework에 대해서 알아보자면,

앱 번들 내부에 Frameworks 폴더에 해당 프레임워크를 복사할 지 여부를 결정하는 파라미터로 볼 수 있습니다.

해당 폴더는 동적 프레임워크, 동적 라이브러리들이 업로드 되는 공간입니다.

 

예전에 다뤘던 dyld(Dynamic Linker Editor)가 앱 실행 과정에서 동적 프레임워크, 동적 라이브러리에 접근한다고 말씀드린 적이 있는데요.

결국 위 파라미터를 바탕으로 추가 할 프레임워크를  do not embed로 설정할 지, embed & sign로 설정할 지 결정할 수 있을 것 같네요.

 

 

더 자세한 정보를 위해서는  Unreal Engine github을 직접 확인해보는 것도 좋을 것 같습니다.

 

https://github.dev/EpicGames/UnrealEngine/blob/release/Engine/Source/ThirdParty/AMD/Amf/AdvancedMediaFramework_(AMF)_SDK.tps