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/콘솔 스토어 요구사항에 맞춘 빌드 설정을 완성하세요.