HLOD 빌드 자동화
커맨드렛과 CI/CD를 통한 HLOD 빌드 파이프라인 구축
HLOD 빌드 커맨드렛
명령줄을 통한 HLOD 빌드
대규모 월드의 HLOD는 에디터에서 수동으로 빌드하기보다 커맨드렛을 통한 자동화가 효율적입니다.
# HLOD 전체 빌드
UnrealEditor.exe "C:/Projects/MyGame/MyGame.uproject" "MapName" ^
-run=WorldPartitionBuilderCommandlet ^
-AllowCommandletRendering ^
-Builder=WorldPartitionHLODsBuilder
# 개별 옵션
-SetupHLODs # HLOD 액터 생성만 (지오메트리 빌드 X)
-BuildHLODs # 기존 HLOD 액터의 지오메트리 빌드
-DeleteHLODs # 모든 HLOD 액터 삭제
# 병렬 처리
-NumWorkers=4 # 워커 프로세스 수
이 플래그는 GPU 렌더링이 필요한 Mesh Merge 작업을 위해 필수입니다. 헤드리스 서버에서는 소프트웨어 렌더링이 사용됩니다.
빌드 스크립트 작성
배치 파일 및 PowerShell 자동화
@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
# 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 "========================================"
CI/CD 파이프라인 통합
Jenkins, GitHub Actions를 통한 자동 빌드
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()]
)
}
}
}
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
증분 빌드 전략
변경된 셀만 재빌드하여 시간 절약
전체 맵의 HLOD를 매번 재빌드하는 것은 비효율적입니다. 증분 빌드를 통해 변경된 영역만 업데이트할 수 있습니다.
# 증분 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부터 WorldPartitionHLODsBuilder가 변경된 셀을 자동 감지하여 증분 빌드를 지원합니다. 이전 버전에서는 수동으로 전체 빌드가 필요합니다.
빌드 검증 및 품질 확인
HLOD 빌드 결과 자동 검증
# 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
}
핵심 요약
- WorldPartitionBuilderCommandlet으로 커맨드라인 HLOD 빌드
- -SetupHLODs와 -BuildHLODs를 분리하여 단계별 실행 가능
- -AllowCommandletRendering은 Mesh Merge 작업에 필수
- Jenkins/GitHub Actions로 CI/CD 파이프라인에 통합
- 변경 감지를 통한 증분 빌드로 빌드 시간 최적화
도전 과제
배운 내용을 직접 실습해보세요
에디터에서 Build > Build HLODs를 실행하고, 생성된 HLOD 메시를 확인하세요. World Partition HLOD 빌더의 출력 로그에서 빌드 시간과 결과를 분석하세요.
UnrealEditor.exe -run=WorldPartitionBuilderCommandlet을 사용하여 커맨드라인에서 HLOD를 빌드하세요. CI/CD 파이프라인에 통합할 수 있는 배치 스크립트를 작성하세요.
Jenkins/GitHub Actions에서 맵 변경 시 자동으로 HLOD를 리빌드하는 CI 파이프라인을 설계하세요. 빌드 시간 최적화를 위해 변경된 영역만 부분 리빌드하는 방법을 조사하고 구현하세요.