PART 12 - 강의 8/8

빌드 및 배포

프로젝트 패키징과 플랫폼별 배포 전략

01

빌드 설정

프로젝트 빌드 구성 및 타겟 설정

C#
// OpenWorldRPG.Target.cs - 게임 타겟 설정 using UnrealBuildTool; public class OpenWorldRPGTarget : TargetRules { public OpenWorldRPGTarget(TargetInfo Target) : base(Target) { Type = TargetType.Game; DefaultBuildSettings = BuildSettingsVersion.V4; IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_3; ExtraModuleNames.AddRange(new string[] { "OpenWorldRPG", "OpenWorldRPG_AI", "OpenWorldRPG_Network" }); // Shipping 빌드 최적화 if (Configuration == UnrealTargetConfiguration.Shipping) { bUseLoggingInShipping = false; bUseChecksInShipping = false; } } } // OpenWorldRPGServer.Target.cs - 데디케이티드 서버 타겟 public class OpenWorldRPGServerTarget : TargetRules { public OpenWorldRPGServerTarget(TargetInfo Target) : base(Target) { Type = TargetType.Server; DefaultBuildSettings = BuildSettingsVersion.V4; IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_3; ExtraModuleNames.AddRange(new string[] { "OpenWorldRPG", "OpenWorldRPG_AI", "OpenWorldRPG_Network" }); // 서버 전용 설정 bUseLoggingInShipping = true; bWithServerCode = true; } }
INI
; DefaultGame.ini - 패키징 설정 [/Script/UnrealEd.ProjectPackagingSettings] ; 빌드 구성 BuildConfiguration=PPBC_Shipping ; 패키징할 맵 목록 +MapsToCook=(FilePath="/Game/Maps/MainMenu") +MapsToCook=(FilePath="/Game/Maps/OpenWorld_Main") +MapsToCook=(FilePath="/Game/Maps/OpenWorld_Region_01") +MapsToCook=(FilePath="/Game/Maps/OpenWorld_Region_02") ; 제외할 콘텐츠 +DirectoriesToNeverCook=(Path="/Game/Developers") +DirectoriesToNeverCook=(Path="/Game/Test") +DirectoriesToNeverCook=(Path="/Game/Debug") ; 압축 설정 bCompressed=true bForDistribution=true ; 블루프린트 네이티브화 (선택적) BlueprintNativizationMethod=Disabled ; 국제화 InternationalizationPreset=English +CulturesToStage=en +CulturesToStage=ko +CulturesToStage=ja +CulturesToStage=zh-Hans [/Script/Engine.RendererSettings] ; Shipping 빌드용 렌더링 설정 r.Mobile.AllowDitheredLODTransition=True [/Script/Engine.StreamingSettings] ; 스트리밍 최적화 s.AsyncLoadingTimeLimit=4.0 s.AsyncLoadingUseFullTimeLimit=True
02

쿠킹 및 에셋 관리

에셋 쿠킹과 청크 설정

C++
// AssetManager 커스터마이징 #pragma once #include "CoreMinimal.h" #include "Engine/AssetManager.h" #include "OpenWorldAssetManager.generated.h" UCLASS() class OPENWORLDRPG_API UOpenWorldAssetManager : public UAssetManager { GENERATED_BODY() public: static UOpenWorldAssetManager& Get(); // 프라이머리 에셋 타입 정의 static const FPrimaryAssetType CharacterAssetType; static const FPrimaryAssetType WeaponAssetType; static const FPrimaryAssetType QuestAssetType; static const FPrimaryAssetType RegionAssetType; // 비동기 에셋 로드 void LoadCharacterAsync( const FPrimaryAssetId& AssetId, FOnAssetLoaded OnLoaded); // 지역별 에셋 로드 void LoadRegionAssets( const FName& RegionName, FOnRegionAssetsLoaded OnLoaded); // 미사용 에셋 언로드 void UnloadUnusedAssets(); protected: virtual void StartInitialLoading() override; private: // 로드된 에셋 핸들 관리 TMap<FPrimaryAssetId, TSharedPtr<FStreamableHandle>> LoadedAssetHandles; }; // OpenWorldAssetManager.cpp const FPrimaryAssetType UOpenWorldAssetManager::CharacterAssetType = FName("Character"); const FPrimaryAssetType UOpenWorldAssetManager::WeaponAssetType = FName("Weapon"); const FPrimaryAssetType UOpenWorldAssetManager::QuestAssetType = FName("Quest"); const FPrimaryAssetType UOpenWorldAssetManager::RegionAssetType = FName("Region"); void UOpenWorldAssetManager::StartInitialLoading() { Super::StartInitialLoading(); // 필수 에셋 사전 로드 TArray<FPrimaryAssetId> CoreAssets; GetPrimaryAssetIdList(CharacterAssetType, CoreAssets); LoadPrimaryAssets( CoreAssets, TArray<FName>(), // 모든 번들 로드 FStreamableDelegate::CreateLambda([]() { UE_LOG(LogTemp, Log, TEXT("Core character assets loaded")); }) ); } void UOpenWorldAssetManager::LoadRegionAssets( const FName& RegionName, FOnRegionAssetsLoaded OnLoaded) { // 지역 에셋 ID 생성 FPrimaryAssetId RegionId(RegionAssetType, RegionName); // 비동기 로드 TSharedPtr<FStreamableHandle> Handle = LoadPrimaryAsset( RegionId, TArray<FName>(), FStreamableDelegate::CreateLambda([this, RegionName, OnLoaded]() { UE_LOG(LogTemp, Log, TEXT("Region %s assets loaded"), *RegionName.ToString()); OnLoaded.ExecuteIfBound(true); }) ); if (Handle.IsValid()) { LoadedAssetHandles.Add(RegionId, Handle); } }
INI
; DefaultEngine.ini - 에셋 매니저 설정 [/Script/Engine.AssetManagerSettings] ; 커스텀 에셋 매니저 클래스 -PrimaryAssetTypesToScan=(PrimaryAssetType="Map",AssetBaseClass=/Script/Engine.World,bHasBlueprintClasses=False,bIsEditorOnly=True,Directories=((Path="/Game/Maps")),SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=Unknown)) +PrimaryAssetTypesToScan=(PrimaryAssetType="Character",AssetBaseClass=/Script/OpenWorldRPG.CharacterDataAsset,bHasBlueprintClasses=False,bIsEditorOnly=False,Directories=((Path="/Game/Data/Characters")),SpecificAssets=,Rules=(Priority=1,ChunkId=0,bApplyRecursively=True,CookRule=AlwaysCook)) +PrimaryAssetTypesToScan=(PrimaryAssetType="Weapon",AssetBaseClass=/Script/OpenWorldRPG.WeaponDataAsset,bHasBlueprintClasses=False,bIsEditorOnly=False,Directories=((Path="/Game/Data/Weapons")),SpecificAssets=,Rules=(Priority=1,ChunkId=1,bApplyRecursively=True,CookRule=AlwaysCook)) +PrimaryAssetTypesToScan=(PrimaryAssetType="Region",AssetBaseClass=/Script/OpenWorldRPG.RegionDataAsset,bHasBlueprintClasses=False,bIsEditorOnly=False,Directories=((Path="/Game/Data/Regions")),SpecificAssets=,Rules=(Priority=1,ChunkId=-1,bApplyRecursively=True,CookRule=AlwaysCook)) ; 청크 할당 규칙 [/Script/UnrealEd.ChunkDependencyInfo] +DependencyArray=(ChunkID=0,ParentChunkID=-1) +DependencyArray=(ChunkID=1,ParentChunkID=0) +DependencyArray=(ChunkID=2,ParentChunkID=0) +DependencyArray=(ChunkID=100,ParentChunkID=0) +DependencyArray=(ChunkID=101,ParentChunkID=0) ; 청크 0: 코어 게임 에셋 ; 청크 1: 무기 에셋 ; 청크 2: 캐릭터 에셋 ; 청크 100+: 지역별 에셋
03

플랫폼별 배포

Steam, Epic, Console 배포 설정

INI
; DefaultEngine.ini - Steam 배포 설정 [OnlineSubsystem] DefaultPlatformService=Steam [OnlineSubsystemSteam] bEnabled=true SteamDevAppId=480 ; Steam용 고급 설정 [/Script/Engine.GameEngine] +NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="OnlineSubsystemSteam.SteamNetDriver",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver") ; Steam 업적 [SteamAchievements] bResetStats=false bUnlockAllAchievements=false
INI
; Epic Games Store / EOS 배포 설정 [OnlineSubsystemEOS] bEnabled=true [/Script/EOSShared.EOSDeveloperSettings] ProductName=OpenWorldRPG ProductVersion=1.0.0 ProductId=YOUR_PRODUCT_ID SandboxId=YOUR_SANDBOX_ID DeploymentId=YOUR_DEPLOYMENT_ID ClientId=YOUR_CLIENT_ID ClientSecret=YOUR_CLIENT_SECRET ; 크로스플레이 설정 (Steam + EOS) [OnlineSubsystemEOSPlus] bEnabled=true [OnlineSubsystem] DefaultPlatformService=EOSPlus NativePlatformService=Steam [OnlineSubsystemEOS] bUseEAS=true bUseEOSConnect=true bMirrorStatsToEOS=true bMirrorAchievementsToEOS=true
C++
// 플랫폼 감지 및 처리 #include "GenericPlatform/GenericPlatformMisc.h" void UPlatformManager::InitializePlatform() { #if PLATFORM_WINDOWS CurrentPlatform = EGamePlatform::Windows; InitializeSteamOrEpic(); #elif PLATFORM_PS5 CurrentPlatform = EGamePlatform::PlayStation5; InitializePlayStationNetwork(); #elif PLATFORM_XBOXONE CurrentPlatform = EGamePlatform::XboxSeriesX; InitializeXboxLive(); #elif PLATFORM_SWITCH CurrentPlatform = EGamePlatform::NintendoSwitch; InitializeNintendoNetwork(); #endif } // 플랫폼별 세이브 경로 FString UPlatformManager::GetSavePath() { #if PLATFORM_WINDOWS return FPaths::ProjectSavedDir() / TEXT("SaveGames"); #elif PLATFORM_PS5 // PS5 사용자별 저장 경로 return GetPS5UserSavePath(); #elif PLATFORM_XBOXONE // Xbox Connected Storage return GetXboxConnectedStoragePath(); #else return FPaths::ProjectSavedDir() / TEXT("SaveGames"); #endif }
04

CI/CD 자동화

빌드 파이프라인 자동화

PowerShell
# BuildAndPackage.ps1 - 자동화 빌드 스크립트 param( [string]$Platform = "Win64", [string]$Configuration = "Shipping", [string]$ProjectPath = "C:\Projects\OpenWorldRPG\OpenWorldRPG.uproject" ) $UE5Path = "C:\Program Files\Epic Games\UE_5.4" $UAT = "$UE5Path\Engine\Build\BatchFiles\RunUAT.bat" $OutputDir = "C:\Builds\OpenWorldRPG\$Platform\$Configuration" # 이전 빌드 정리 if (Test-Path $OutputDir) { Remove-Item -Path $OutputDir -Recurse -Force } # 빌드 실행 & $UAT BuildCookRun ` -project=$ProjectPath ` -platform=$Platform ` -clientconfig=$Configuration ` -serverconfig=$Configuration ` -cook ` -stage ` -pak ` -archive ` -archivedirectory=$OutputDir ` -compressed ` -prereqs ` -distribution ` -nodebuginfo ` -utf8output # 빌드 결과 확인 if ($LASTEXITCODE -eq 0) { Write-Host "Build succeeded!" -ForegroundColor Green # 버전 정보 생성 $Version = Get-Date -Format "yyyy.MM.dd.HHmm" $Version | Out-File "$OutputDir\version.txt" } else { Write-Host "Build failed!" -ForegroundColor Red exit 1 }
YAML
# .github/workflows/build.yml - GitHub Actions name: Build OpenWorldRPG on: push: branches: [ main, develop ] pull_request: branches: [ main ] jobs: build: runs-on: self-hosted steps: - name: Checkout uses: actions/checkout@v4 with: lfs: true - name: Build Development run: | ./BuildAndPackage.ps1 -Platform Win64 -Configuration Development - name: Run Tests run: | $UE5 = "C:\Program Files\Epic Games\UE_5.4" & "$UE5\Engine\Binaries\Win64\UnrealEditor-Cmd.exe" ` OpenWorldRPG.uproject ` -ExecCmds="Automation RunTests OpenWorldRPG" ` -unattended -nopause -log - name: Build Shipping if: github.ref == 'refs/heads/main' run: | ./BuildAndPackage.ps1 -Platform Win64 -Configuration Shipping - name: Upload Artifacts uses: actions/upload-artifact@v4 with: name: OpenWorldRPG-${{ github.sha }} path: C:\Builds\OpenWorldRPG\
SUMMARY

핵심 요약

  • Target.cs로 게임/서버 빌드 타겟 분리
  • AssetManager로 에셋 로딩 최적화
  • 청크 시스템으로 DLC/패치 관리
  • 플랫폼 매크로로 플랫폼별 분기 처리
  • RunUAT로 빌드 자동화
  • CI/CD로 테스트 및 배포 파이프라인
COMPLETE

프로젝트 학습 완료!

축하합니다! UE5 대규모 오픈월드 RPG 커리큘럼을 모두 완료하셨습니다.

프로젝트 구조 설계부터 빌드/배포까지 전체 게임 개발 파이프라인을 학습했습니다. 이제 여러분만의 오픈월드 RPG를 만들어보세요!

PRACTICE

도전 과제

배운 내용을 직접 실습해보세요

실습 1: Shipping 빌드 생성

Development와 Shipping 구성으로 각각 빌드하고, 패키지 크기와 실행 성능을 비교하세요. Shipping 빌드에서 디버그 코드가 제거되는 것을 확인하세요.

실습 2: 패키징 크기 최적화

미사용 에셋을 Size Map으로 식별하여 제거하세요. Pak 파일 압축, 텍스처 압축 포맷 최적화, Shader Permutation 줄이기를 적용하여 배포 크기를 최소화하세요.

심화 과제: 배포 파이프라인 구축

BuildCookRun 자동화, 버전 관리, 패치 시스템(Chunk Download)을 포함하는 완전한 배포 파이프라인을 구축하세요. Steam/콘솔 스토어 요구사항에 맞춘 빌드 설정을 완성하세요.