PART 3 - 강의 5/7

HLOD 빌드 자동화

커맨드렛과 CI/CD를 통한 HLOD 빌드 파이프라인 구축

01

HLOD 빌드 커맨드렛

명령줄을 통한 HLOD 빌드

대규모 월드의 HLOD는 에디터에서 수동으로 빌드하기보다 커맨드렛을 통한 자동화가 효율적입니다.

Command Line # HLOD 전체 빌드 UnrealEditor.exe "C:/Projects/MyGame/MyGame.uproject" "MapName" ^ -run=WorldPartitionBuilderCommandlet ^ -AllowCommandletRendering ^ -Builder=WorldPartitionHLODsBuilder # 개별 옵션 -SetupHLODs # HLOD 액터 생성만 (지오메트리 빌드 X) -BuildHLODs # 기존 HLOD 액터의 지오메트리 빌드 -DeleteHLODs # 모든 HLOD 액터 삭제 # 병렬 처리 -NumWorkers=4 # 워커 프로세스 수
AllowCommandletRendering

이 플래그는 GPU 렌더링이 필요한 Mesh Merge 작업을 위해 필수입니다. 헤드리스 서버에서는 소프트웨어 렌더링이 사용됩니다.

02

빌드 스크립트 작성

배치 파일 및 PowerShell 자동화

BuildHLOD.bat (Windows) @echo off setlocal :: 설정 set ENGINE_PATH=C:\Program Files\Epic Games\UE_5.4\Engine\Binaries\Win64 set PROJECT_PATH=C:\Projects\MyGame\MyGame.uproject set MAP_NAME=/Game/Maps/OpenWorld :: 로그 디렉토리 set LOG_DIR=C:\Projects\MyGame\Saved\Logs\HLOD if not exist "%LOG_DIR%" mkdir "%LOG_DIR%" :: 타임스탬프 for /f "tokens=2 delims==" %%I in ('wmic os get localdatetime /value') do set datetime=%%I set TIMESTAMP=%datetime:~0,8%_%datetime:~8,6% echo ======================================== echo HLOD Build Started: %TIMESTAMP% echo ======================================== :: HLOD 빌드 실행 "%ENGINE_PATH%\UnrealEditor-Cmd.exe" "%PROJECT_PATH%" %MAP_NAME% ^ -run=WorldPartitionBuilderCommandlet ^ -AllowCommandletRendering ^ -Builder=WorldPartitionHLODsBuilder ^ -BuildHLODs ^ -log="%LOG_DIR%\HLODBuild_%TIMESTAMP%.log" ^ -unattended ^ -nosplash if %ERRORLEVEL% neq 0 ( echo HLOD Build FAILED with error code: %ERRORLEVEL% exit /b %ERRORLEVEL% ) echo ======================================== echo HLOD Build Completed Successfully echo ======================================== endlocal
BuildHLOD.ps1 (PowerShell) # HLOD 빌드 PowerShell 스크립트 param( [string]$ProjectPath = "C:\Projects\MyGame\MyGame.uproject", [string]$MapName = "/Game/Maps/OpenWorld", [int]$NumWorkers = 4, [switch]$SetupOnly, [switch]$BuildOnly, [switch]$DeleteHLODs ) $EnginePath = "C:\Program Files\Epic Games\UE_5.4\Engine\Binaries\Win64" $EditorExe = Join-Path $EnginePath "UnrealEditor-Cmd.exe" # 로그 설정 $LogDir = Join-Path (Split-Path $ProjectPath -Parent) "Saved\Logs\HLOD" if (!(Test-Path $LogDir)) { New-Item -ItemType Directory -Path $LogDir | Out-Null } $Timestamp = Get-Date -Format "yyyyMMdd_HHmmss" $LogFile = Join-Path $LogDir "HLODBuild_$Timestamp.log" # 빌드 옵션 구성 $BuildOptions = @("-run=WorldPartitionBuilderCommandlet") $BuildOptions += "-AllowCommandletRendering" $BuildOptions += "-Builder=WorldPartitionHLODsBuilder" $BuildOptions += "-NumWorkers=$NumWorkers" if ($SetupOnly) { $BuildOptions += "-SetupHLODs" } elseif ($BuildOnly) { $BuildOptions += "-BuildHLODs" } elseif ($DeleteHLODs) { $BuildOptions += "-DeleteHLODs" } else { $BuildOptions += "-SetupHLODs" $BuildOptions += "-BuildHLODs" } $BuildOptions += "-log=`"$LogFile`"" $BuildOptions += "-unattended" $BuildOptions += "-nosplash" Write-Host "========================================" Write-Host "HLOD Build Started: $Timestamp" Write-Host "Project: $ProjectPath" Write-Host "Map: $MapName" Write-Host "========================================" # 빌드 실행 $Process = Start-Process -FilePath $EditorExe ` -ArgumentList ("`"$ProjectPath`" $MapName " + ($BuildOptions -join " ")) ` -Wait -PassThru -NoNewWindow if ($Process.ExitCode -ne 0) { Write-Host "HLOD Build FAILED with exit code: $($Process.ExitCode)" -ForegroundColor Red exit $Process.ExitCode } Write-Host "========================================" Write-Host "HLOD Build Completed Successfully" -ForegroundColor Green Write-Host "Log: $LogFile" Write-Host "========================================"
03

CI/CD 파이프라인 통합

Jenkins, GitHub Actions를 통한 자동 빌드

Jenkinsfile pipeline { agent { label 'unreal-build-agent' } environment { UE_ROOT = 'C:\\Program Files\\Epic Games\\UE_5.4' PROJECT_PATH = "${WORKSPACE}\\MyGame\\MyGame.uproject" } parameters { string(name: 'MAP_NAME', defaultValue: '/Game/Maps/OpenWorld', description: 'Map to build HLOD for') booleanParam(name: 'FULL_REBUILD', defaultValue: false, description: 'Delete and rebuild all HLODs') } stages { stage('Checkout') { steps { checkout scm } } stage('Delete HLODs') { when { expression { params.FULL_REBUILD } } steps { bat """ "${UE_ROOT}\\Engine\\Binaries\\Win64\\UnrealEditor-Cmd.exe" ^ "${PROJECT_PATH}" ${params.MAP_NAME} ^ -run=WorldPartitionBuilderCommandlet ^ -Builder=WorldPartitionHLODsBuilder ^ -DeleteHLODs ^ -unattended -nosplash """ } } stage('Setup HLODs') { steps { bat """ "${UE_ROOT}\\Engine\\Binaries\\Win64\\UnrealEditor-Cmd.exe" ^ "${PROJECT_PATH}" ${params.MAP_NAME} ^ -run=WorldPartitionBuilderCommandlet ^ -Builder=WorldPartitionHLODsBuilder ^ -SetupHLODs ^ -unattended -nosplash """ } } stage('Build HLODs') { steps { bat """ "${UE_ROOT}\\Engine\\Binaries\\Win64\\UnrealEditor-Cmd.exe" ^ "${PROJECT_PATH}" ${params.MAP_NAME} ^ -run=WorldPartitionBuilderCommandlet ^ -AllowCommandletRendering ^ -Builder=WorldPartitionHLODsBuilder ^ -BuildHLODs ^ -NumWorkers=4 ^ -unattended -nosplash """ } } stage('Submit to Source Control') { steps { bat """ cd ${WORKSPACE}\\MyGame git add Content/__ExternalActors__/**/*HLOD* git commit -m "Automated HLOD Build - Build #${BUILD_NUMBER}" git push origin ${BRANCH_NAME} """ } } } post { failure { emailext ( subject: "HLOD Build Failed - Build #${BUILD_NUMBER}", body: "HLOD build failed. Check Jenkins for details.", recipientProviders: [requestor()] ) } success { emailext ( subject: "HLOD Build Successful - Build #${BUILD_NUMBER}", body: "HLOD build completed successfully.", recipientProviders: [requestor()] ) } } }
.github/workflows/hlod-build.yml name: HLOD Build on: push: paths: - 'Content/Maps/**' workflow_dispatch: inputs: map_name: description: 'Map to build HLOD for' required: true default: '/Game/Maps/OpenWorld' full_rebuild: description: 'Full rebuild (delete existing HLODs)' type: boolean default: false jobs: build-hlod: runs-on: [self-hosted, unreal-builder] env: UE_ROOT: 'C:\Program Files\Epic Games\UE_5.4' PROJECT_PATH: ${{ github.workspace }}\MyGame\MyGame.uproject steps: - name: Checkout uses: actions/checkout@v4 with: lfs: true - name: Delete Existing HLODs if: ${{ github.event.inputs.full_rebuild == 'true' }} run: | & "$env:UE_ROOT\Engine\Binaries\Win64\UnrealEditor-Cmd.exe" ` "$env:PROJECT_PATH" ${{ github.event.inputs.map_name }} ` -run=WorldPartitionBuilderCommandlet ` -Builder=WorldPartitionHLODsBuilder ` -DeleteHLODs -unattended -nosplash - name: Build HLODs run: | & "$env:UE_ROOT\Engine\Binaries\Win64\UnrealEditor-Cmd.exe" ` "$env:PROJECT_PATH" ${{ github.event.inputs.map_name }} ` -run=WorldPartitionBuilderCommandlet ` -AllowCommandletRendering ` -Builder=WorldPartitionHLODsBuilder ` -SetupHLODs -BuildHLODs ` -NumWorkers=4 -unattended -nosplash - name: Commit HLOD Changes run: | git config user.name "GitHub Actions" git config user.email "actions@github.com" git add Content/__ExternalActors__/**/*HLOD* git commit -m "Automated HLOD Build - ${{ github.run_number }}" || echo "No changes" git push
04

증분 빌드 전략

변경된 셀만 재빌드하여 시간 절약

전체 맵의 HLOD를 매번 재빌드하는 것은 비효율적입니다. 증분 빌드를 통해 변경된 영역만 업데이트할 수 있습니다.

IncrementalHLODBuild.ps1 # 증분 HLOD 빌드 스크립트 param( [string]$ProjectPath, [string]$MapName, [string]$GitBranch = "main" ) # 변경된 액터 파일 확인 $ChangedFiles = git diff --name-only $GitBranch -- "Content/__ExternalActors__" if ($ChangedFiles.Count -eq 0) { Write-Host "No actor changes detected. Skipping HLOD build." exit 0 } # 변경된 셀 좌표 추출 $ChangedCells = @() foreach ($File in $ChangedFiles) { # 파일 경로에서 셀 좌표 파싱 if ($File -match "_(\-?\d+)_(\-?\d+)_(\-?\d+)\.uasset$") { $CellCoord = "$($Matches[1]),$($Matches[2]),$($Matches[3])" if ($ChangedCells -notcontains $CellCoord) { $ChangedCells += $CellCoord } } } Write-Host "Changed cells: $($ChangedCells.Count)" Write-Host $ChangedCells # 변경된 셀 주변의 HLOD만 재빌드 # (현재 UE는 전체 빌드만 지원하므로 이 부분은 향후 개선 필요) Write-Host "Building HLODs for affected regions..." # 빌드 실행 & "$EnginePath\UnrealEditor-Cmd.exe" "$ProjectPath" $MapName ` -run=WorldPartitionBuilderCommandlet ` -AllowCommandletRendering ` -Builder=WorldPartitionHLODsBuilder ` -BuildHLODs ` -unattended -nosplash
UE 5.5+ 개선사항

UE 5.5부터 WorldPartitionHLODsBuilder가 변경된 셀을 자동 감지하여 증분 빌드를 지원합니다. 이전 버전에서는 수동으로 전체 빌드가 필요합니다.

05

빌드 검증 및 품질 확인

HLOD 빌드 결과 자동 검증

ValidateHLOD.ps1 # HLOD 빌드 검증 스크립트 param( [string]$ProjectPath, [string]$MapName, [int]$MaxTriangleCount = 100000, [int]$MaxTextureSize = 4096 ) $ValidationLog = @() $HasErrors = $false # HLOD 에셋 경로 $HLODPath = Join-Path (Split-Path $ProjectPath -Parent) "Content\__ExternalActors__" # HLOD 메시 파일 검색 $HLODMeshes = Get-ChildItem -Path $HLODPath -Recurse -Filter "*HLOD*.uasset" Write-Host "Found $($HLODMeshes.Count) HLOD assets" foreach ($Mesh in $HLODMeshes) { # 파일 크기 확인 (간접적 품질 지표) $SizeMB = $Mesh.Length / 1MB if ($SizeMB -gt 50) { $ValidationLog += "WARNING: Large HLOD file: $($Mesh.Name) ($([math]::Round($SizeMB, 2)) MB)" } # 최근 수정 확인 $LastWrite = $Mesh.LastWriteTime $DaysSinceModified = (Get-Date) - $LastWrite if ($DaysSinceModified.Days -gt 30) { $ValidationLog += "INFO: Stale HLOD: $($Mesh.Name) (Modified $($DaysSinceModified.Days) days ago)" } } # 결과 출력 Write-Host "`n========================================" Write-Host "HLOD Validation Report" Write-Host "========================================" foreach ($Log in $ValidationLog) { if ($Log.StartsWith("ERROR")) { Write-Host $Log -ForegroundColor Red $HasErrors = $true } elseif ($Log.StartsWith("WARNING")) { Write-Host $Log -ForegroundColor Yellow } else { Write-Host $Log } } if ($HasErrors) { Write-Host "`nValidation FAILED" -ForegroundColor Red exit 1 } else { Write-Host "`nValidation PASSED" -ForegroundColor Green exit 0 }
SUMMARY

핵심 요약

  • WorldPartitionBuilderCommandlet으로 커맨드라인 HLOD 빌드
  • -SetupHLODs-BuildHLODs를 분리하여 단계별 실행 가능
  • -AllowCommandletRendering은 Mesh Merge 작업에 필수
  • Jenkins/GitHub Actions로 CI/CD 파이프라인에 통합
  • 변경 감지를 통한 증분 빌드로 빌드 시간 최적화
PRACTICE

도전 과제

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

실습 1: HLOD 빌드 실행

에디터에서 Build > Build HLODs를 실행하고, 생성된 HLOD 메시를 확인하세요. World Partition HLOD 빌더의 출력 로그에서 빌드 시간과 결과를 분석하세요.

실습 2: 커맨드렛 기반 HLOD 빌드

UnrealEditor.exe -run=WorldPartitionBuilderCommandlet을 사용하여 커맨드라인에서 HLOD를 빌드하세요. CI/CD 파이프라인에 통합할 수 있는 배치 스크립트를 작성하세요.

심화 과제: HLOD 빌드 자동화 파이프라인

Jenkins/GitHub Actions에서 맵 변경 시 자동으로 HLOD를 리빌드하는 CI 파이프라인을 설계하세요. 빌드 시간 최적화를 위해 변경된 영역만 부분 리빌드하는 방법을 조사하고 구현하세요.