icon안동민 개발노트

제어문과 반복문 (if, switch, for, while)


 언리얼 엔진에서 게임 개발을 할 때, C++의 제어문과 반복문은 게임 로직을 구현하는 데 필수적인 요소입니다.

 이 절에서는 주요 제어 구조와 반복 구조를 언리얼 엔진의 맥락에서 살펴보겠습니다.

if 문

 if 문은 조건에 따라 코드 실행을 제어하는 기본적인 구조입니다.

UCLASS()
class AMyCharacter : public ACharacter
{
    GENERATED_BODY()
 
public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Stats")
    float Health;
 
    void TakeDamage(float DamageAmount)
    {
        Health -= DamageAmount;
        
        if (Health <= 0)
        {
            // 캐릭터 사망 처리
            Die();
        }
        else if (Health < 30)
        {
            // 낮은 체력 경고
            PlayLowHealthWarning();
        }
    }
};

 이 예제에서 if 문은 캐릭터의 체력 상태에 따라 다른 동작을 수행합니다.

switch 문

 switch 문은 여러 가지 경우를 처리할 때 유용합니다.

 예를 들어, 게임 상태를 관리할 때 사용할 수 있습니다.

enum class EGameState
{
    MainMenu,
    Playing,
    Paused,
    GameOver
};
 
UCLASS()
class AMyGameMode : public AGameModeBase
{
    GENERATED_BODY()
 
public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Game")
    EGameState CurrentState;
 
    void UpdateGameState()
    {
        switch (CurrentState)
        {
            case EGameState::MainMenu:
                ShowMainMenu();
                break;
            case EGameState::Playing:
                UpdateGameplay();
                break;
            case EGameState::Paused:
                ShowPauseMenu();
                break;
            case EGameState::GameOver:
                ShowGameOverScreen();
                break;
            default:
                UE_LOG(LogTemp, Warning, TEXT("Unknown game state"));
                break;
        }
    }
};

 이 예제에서 switch 문은 현재 게임 상태에 따라 적절한 함수를 호출합니다.

for 루프

 for 루프는 정해진 횟수만큼 코드를 반복 실행할 때 사용됩니다.

 예를 들어, 레벨에 여러 개체를 생성할 때 활용할 수 있습니다.

UCLASS()
class ALevelGenerator : public AActor
{
    GENERATED_BODY()
 
public:
    UPROPERTY(EditAnywhere, Category="Generation")
    int32 NumberOfObstacles;
 
    UPROPERTY(EditAnywhere, Category="Generation")
    TSubclassOf<AActor> ObstacleClass;
 
    void GenerateLevel()
    {
        for (int32 i = 0; i < NumberOfObstacles; ++i)
        {
            FVector Location = GetRandomLocation();
            GetWorld()->SpawnActor<AActor>(ObstacleClass, Location, FRotator::ZeroRotator);
        }
    }
};

 이 예제에서 for 루프는 지정된 수만큼 장애물을 생성합니다.

while 루프

 while 루프는 조건이 참인 동안 코드를 반복 실행합니다.

 AI의 순찰 로직 등에 사용할 수 있습니다.

UCLASS()
class AAICharacter : public ACharacter
{
    GENERATED_BODY()
 
public:
    UPROPERTY(EditAnywhere, Category="AI")
    TArray<ATargetPoint*> PatrolPoints;
 
    void StartPatrol()
    {
        int32 CurrentPointIndex = 0;
        while (!IsThreatDetected())
        {
            MoveToLocation(PatrolPoints[CurrentPointIndex]->GetActorLocation());
            CurrentPointIndex = (CurrentPointIndex + 1) % PatrolPoints.Num();
            Wait(1.0f);  // 1초 대기
        }
        // 위협 감지 시 순찰 종료
    }
};

 이 예제에서 while 루프는 위협이 감지될 때까지 AI 캐릭터가 계속해서 순찰하도록 합니다.

범위 기반 for 루프 (C++ 11)

 언리얼 엔진의 컨테이너(예 : TArray)와 함께 사용할 수 있는 범위 기반 for 루프는 코드를 더 간결하고 읽기 쉽게 만듭니다.

UCLASS()
class AInventoryManager : public AActor
{
    GENERATED_BODY()
 
public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Inventory")
    TArray<AItem*> Items;
 
    void UseAllItems()
    {
        for (AItem* Item : Items)
        {
            Item->Use();
        }
    }
};

 이 예제에서는 인벤토리의 모든 아이템을 순회하며 사용합니다.

성능 고려사항 및 최적화 팁

 1. 틱(Tick) 이벤트에서의 사용

  • 틱 이벤트는 매 프레임마다 호출되므로, 여기에 무거운 루프를 넣으면 성능에 심각한 영향을 줄 수 있습니다.
  • 가능한 한 틱 이벤트 외부에서 연산을 수행하고, 결과만 틱에서 사용하세요.
void AMyActor::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
 
    // 나쁜 예:
    // for (int32 i = 0; i < 1000000; ++i) { HeavyCalculation(); }
 
    // 좋은 예:
    UpdateCachedResult();  // 주기적으로 또는 필요할 때만 호출
}

 2. 조건문 최적화

  • 자주 평가되는 조건문에서는 가장 흔한 경우를 먼저 체크하여 불필요한 연산을 줄이세요.
if (CommonCase)
{
    // 자주 실행되는 코드
}
else if (UncommonCase)
{
    // 덜 자주 실행되는 코드
}
else
{
    // 가장 드문 경우
}

 3. 루프 최적화

  • 루프 불변식을 루프 밖으로 이동
  • 가능한 경우 전위 증가 연산자 사용 (++i)
  • 불필요한 함수 호출 피하기
// 최적화 전
for (int32 i = 0; i < MyArray.Num(); ++i)
{
    DoSomething(MyArray[i]);
}
 
// 최적화 후
const int32 ArraySize = MyArray.Num();
for (int32 i = 0; i < ArraySize; ++i)
{
    DoSomething(MyArray[i]);
}

 4. 범위 기반 for 루프 사용

  • 가능한 경우 범위 기반 for 루프를 사용하여 코드를 간결하게 만들고 실수를 줄이세요.

 5. 이른 반환(Early Return) 사용

  • 복잡한 중첩 조건문 대신 이른 반환을 사용하여 코드의 가독성을 높이고 성능을 개선할 수 있습니다.
bool AMyCharacter::CanPerformAction()
{
    if (!bIsAlive) return false;
    if (bIsStunned) return false;
    if (CurrentStamina < RequiredStamina) return false;
 
    return true;
}

 6. 스위치 문 vs if-else 체인

  • 많은 조건을 체크해야 할 때는 if-else 체인보다 switch 문이 일반적으로 더 효율적입니다.

 언리얼 엔진에서 제어문과 반복문을 사용할 때는 게임의 성능과 반응성을 항상 고려해야 합니다.

 특히 매 프레임 실행되는 코드나 많은 개체를 다루는 로직에서는 최적화가 중요합니다.