소개 및 배경

대난투 스타일의 사이드 뷰 멀티플레이어 게임을 언리얼 엔진 5로 개발하는 과정에서 가장 중요한 요소 중 하나는 카메라 시스템입니다. 특히 여러 플레이어가 동시에 화면에 표시되어야 하는 대난투 게임의 특성상, 기존 언리얼 엔진의 카메라 컴포넌트만으로는 한계가 있었습니다.

기본적으로 언리얼 엔진의 FollowCameraComponent는 단일 플레이어를 추적하는 용도로 설계되어 있어, 여러 플레이어와 보스 몬스터를 동시에 화면에 담아내거나, 특정 상황에 맞게 카메라 모드를 전환하는 기능이 부족했습니다.

이러한 한계를 극복하기 위해 기존 컴포넌트를 확장하여 다음 기능들을 구현했습니다:

  • 여러 플레이어와 보스를 동시에 화면에 표시하는 그룹 카메라 모드
  • 개인 카메라와 그룹 카메라 간 부드러운 전환
  • 멀티플레이어 환경에서의 네트워크 지원
  • 특정 영역에서 카메라 설정을 변경하는 확장된 트리거 시스템

기반 클래스 분석

FollowCameraComponent

기존의 FollowCameraComponent는 다음과 같은 기능을 제공합니다:

  • 캐릭터 자동 추적 (위치와 회전 모두)
  • 줌 거리, 리드 거리(캐릭터 진행 방향으로 카메라가 앞서가는 거리), 높이 등 설정
  • 부드러운 보간을 통한 자연스러운 카메라 움직임
  • 사이드 뷰 게임에 최적화된 카메라 설정

이 컴포넌트는 기본적인 기능은 훌륭하지만, 다음과 같은 제한이 있었습니다:

  • 단일 타겟만 추적 가능
  • 멀티플레이어를 위한 네트워크 기능 부족
  • 다양한 카메라 모드나 전환 시스템 부재

CameraTrigger

CameraTrigger 클래스는 플레이어가 특정 영역에 진입하면 카메라 설정을 자동으로 변경해주는 기능을 제공합니다:

  • 트리거 영역 진입/이탈 시 카메라 파라미터 변경
  • 이전 카메라 설정으로 복원하는 기능 (UndoAfterEndOverlap)
  • 부드러운 전환을 위한 블렌드 타임 설정

이 트리거 시스템도 유용하지만, 확장된 카메라 컴포넌트와 호환되도록 보완이 필요했습니다.

카메라 컴포넌트 확장 - SmashCameraComponent

클래스 구조 및 상속 관계

SmashCameraComponent는 기존 FollowCameraComponent를 상속받아 기본 기능을 유지하면서 새로운 기능을 추가했습니다:

UCLASS()
class SMASHBRAWL_API USmashCameraComponent : public UFollowCameraComponent
{
    GENERATED_BODY()
    
    // 추가 속성 및 메서드...
};

이렇게 상속 구조를 활용하면 기존 코드를 재작성할 필요 없이 필요한 기능만 확장할 수 있어 개발 효율성이 높아집니다.

카메라 모드 시스템

가장 중요한 확장 기능 중 하나는 두 가지 카메라 모드를 지원하는 시스템입니다:

// 카메라 모드 열거형
UENUM(BlueprintType)
enum class ECameraMode : uint8
{
    Group UMETA(DisplayName = "Group"), // 그룹 모드 (모든 플레이어 표시)
    Default UMETA(DisplayName = "Default") // 기본 모드 (개인 플레이어 추적)
};
  • Default 모드: 기존 카메라처럼 단일 플레이어를 추적하는 모드
  • Group 모드: 모든 플레이어와 주요 적(보스)이 화면에 표시되도록 자동 조절하는 모드

이 모드는 다음과 같이 설정할 수 있습니다:

void USmashCameraComponent::SetCameraMode(ECameraMode NewMode, float BlendTime)
{
    // 이전 모드 저장 (복원 기능을 위해)
    PreviousCameraMode = CurrentCameraMode;

    // 새 모드 설정
    CurrentCameraMode = NewMode;

    // 모드에 맞는 설정 적용
    ApplyCameraModeSettings(NewMode, BlendTime);
    
    // 카메라 위치 및 기타 설정 업데이트...
}

각 모드에 맞는 설정을 자동으로 적용하여 사용자가 직접 모든 설정을 변경할 필요가 없도록 했습니다:

void USmashCameraComponent::ApplyCameraModeSettings(ECameraMode Mode, float BlendTime)
{
    switch (Mode)
    {
    case ECameraMode::Default:
        // 개인 모드 설정 - 더 가깝고 빠른 카메라
        SetZoomDistance(800.0f, BlendTime);
        SetLeadSpeed(3.0f);
        // 기타 설정...
        break;

    case ECameraMode::Group:
        // 그룹 모드 설정 - 더 넓은 뷰와 부드러운 움직임
        SetZoomDistance(MinGroupZoomDistance, BlendTime);
        SetLeadSpeed(1.5f);
        // 기타 설정...
        break;
    }
}

다중 타겟 추적 시스템

그룹 모드의 핵심은 여러 타겟을 동시에 추적하는 기능입니다. 이를 위해 타겟 관리 시스템을 구현했습니다:

// 타겟 관리 함수
void USmashCameraComponent::AddTargetActor(AActor* TargetActor)
{
    // 중복 방지 및 유효성 검사
    if (!TargetActor || TargetActors.Contains(TargetActor))
    {
        return;
    }

    // 타겟 배열에 추가
    TargetActors.Add(TargetActor);

    // 첫 번째 타겟은 메인 타겟으로 설정
    if (!MainTarget)
    {
        MainTarget = TargetActor;
    }
}

그룹 모드에서는 모든 타겟이 화면에 표시되도록 카메라 위치와 줌을 자동으로 조정합니다:

void USmashCameraComponent::UpdateGroupCamera(float DeltaTime)
{
    // 타겟이 하나거나 없으면 기본 모드처럼 처리
    if (TargetActors.Num() <= 1)
    {
        UpdateDefaultCamera(DeltaTime);
        return;
    }

    // 모든 타겟의 평균 위치 계산
    FVector TargetCenterPos = FVector::ZeroVector;
    int32 ValidTargetCount = 0;
    
    // 경계 상자 계산용 초기값
    FVector MinBound(MAX_FLT, MAX_FLT, MAX_FLT);
    FVector MaxBound(-MAX_FLT, -MAX_FLT, -MAX_FLT);

    // 모든 타겟 위치 처리
    for (AActor* Target : TargetActors)
    {
        if (IsValid(Target))
        {
            FVector TargetPos = Target->GetActorLocation();
            
            // 평균 위치 계산
            TargetCenterPos += TargetPos;
            ValidTargetCount++;
            
            // 경계 상자 업데이트
            MinBound.X = FMath::Min(MinBound.X, TargetPos.X);
            MaxBound.X = FMath::Max(MaxBound.X, TargetPos.X);
            // Y, Z 축도 동일하게 처리...
        }
    }
    
    // 평균 위치 계산
    if (ValidTargetCount > 0)
    {
        TargetCenterPos /= ValidTargetCount;
        
        // 경계 상자 크기 계산
        FVector BoundSize = MaxBound - MinBound;
        
        // 최적의 줌 거리 계산 (사이드뷰 게임이므로 X축 기준)
        float OptimalZoom = FMath::Clamp(
            BoundSize.X * 0.8f + TargetPadding,
            MinGroupZoomDistance,
            MaxGroupZoomDistance
        );
        
        // 부드러운 줌 적용
        float NewZoomDistance = FMath::FInterpTo(
            CameraZoomDistance,
            OptimalZoom,
            DeltaTime,
            ZoomSmoothingFactor
        );
        
        // 카메라 위치와 줌 조정...
    }
}

이 방식으로 플레이어가 서로 멀어져도 모두 화면에 표시되며, 가까워지면 자연스럽게 줌인하여 상세한 액션을 볼 수 있습니다.

네트워크 복제 구현

멀티플레이어 게임에서는 네트워크 복제가 중요합니다. 필요한 속성만 선택적으로 복제하여 성능을 최적화했습니다:

void USmashCameraComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);

    // 복제할 속성들만 선택적으로 등록
    DOREPLIFETIME(USmashCameraComponent, RegisteredPlayers);
    DOREPLIFETIME(USmashCameraComponent, bAutoCameraEnabled);
    DOREPLIFETIME(USmashCameraComponent, bIsMasterCamera);
    // CurrentCameraMode는 의도적으로 복제하지 않음
}

특별히 주목할 점은 CurrentCameraMode를 복제 대상에서 제외한 것입니다. 이는 각 클라이언트가 자신의 화면에서 원하는 카메라 모드를 독립적으로 설정할 수 있게 하기 위한 의도적인 설계 결정이었습니다. 예를 들어, 한 플레이어는 그룹 모드로 전체 상황을 보고, 다른 플레이어는 개인 모드로 자신의 캐릭터에 집중할 수 있습니다.

마스터 카메라 시스템

여러 플레이어가 있는 환경에서 일관된 이벤트 처리를 위해 마스터 카메라 개념을 도입했습니다:

void USmashCameraComponent::BecomesMasterCamera()
{
    // 권한 확인 (서버만 설정 가능)
    if (GetOwner()->HasAuthority())
    {
        // 이미 마스터 카메라면 중복 처리 방지
        if (bIsMasterCamera)
        {
            return;
        }

        // 기존 마스터 카메라가 있으면 해제
        for (TActorIterator<ASmashCharacter> It(GetWorld()); It; ++It)
        {
            ASmashCharacter* Character = *It;
            if (Character && Character != GetOwner())
            {
                USmashCameraComponent* CameraComp = Character->FindComponentByClass<USmashCameraComponent>();
                if (CameraComp && CameraComp->IsMasterCamera())
                {
                    CameraComp->StopBeingMasterCamera();
                }
            }
        }

        // 이 카메라를 마스터로 설정
        bIsMasterCamera = true;

        // 모든 클라이언트에게 알림
        Multicast_BecomesMasterCamera();
    }
    else
    {
        // 클라이언트는 서버에 요청
        Server_BecomesMasterCamera();
    }
}

마스터 카메라는 보스 인트로 시퀀스, 중요 이벤트 카메라 효과 등 게임 전체적인 카메라 이벤트를 처리하는 역할을 담당합니다. 이로써 여러 플레이어가 있어도 일관된 시네마틱 경험을 제공할 수 있습니다.

카메라 효과

게임 피드백을 강화하기 위한 다양한 카메라 효과도 구현했습니다. 대표적으로 카메라 흔들림 효과:

void USmashCameraComponent::ShakeCamera(float Intensity, float Duration, float Falloff)
{
    // 흔들림 상태 활성화 및 파라미터 설정
    bIsCameraShaking = true;
    ShakeIntensity = Intensity;
    ShakeDuration = Duration;
    ShakeTimer = 0.0f;
    ShakeFalloff = Falloff;

    // 원래 위치 저장 (효과 종료 후 복원용)
    OriginalCameraLocation = GetComponentLocation();
}

void USmashCameraComponent::UpdateCameraShake(float DeltaTime)
{
    // 타이머 업데이트
    ShakeTimer += DeltaTime;

    // 시간이 다 되면 효과 종료
    if (ShakeTimer >= ShakeDuration)
    {
        bIsCameraShaking = false;
        return;
    }

    // 시간에 따른 감쇠 계산
    float RemainingPct = 1.0f - (ShakeTimer / ShakeDuration);
    float CurrentIntensity = ShakeIntensity * FMath::Pow(RemainingPct, ShakeFalloff);

    // 랜덤 오프셋 생성
    FVector ShakeOffset;
    ShakeOffset.X = FMath::RandRange(-1.0f, 1.0f) * CurrentIntensity;
    ShakeOffset.Y = FMath::RandRange(-1.0f, 1.0f) * CurrentIntensity;
    ShakeOffset.Z = FMath::RandRange(-1.0f, 1.0f) * CurrentIntensity;

    // 카메라 오프셋에 적용
    CameraLocationOffset += ShakeOffset;
}

이 효과는 보스의 강력한 공격이나 중요한 이벤트 발생 시 몰입감을 높이는 데 사용됩니다.

카메라 트리거 확장 - SmashCameraTrigger

기존 CameraTrigger 클래스를 확장하여 SmashCameraComponent와 원활하게 연동되는 SmashCameraTrigger를 구현했습니다:

UCLASS()
class SMASHBRAWL_API ASmashCameraTrigger : public ACameraTrigger
{
    GENERATED_BODY()
    
    // 커스텀 블렌드 시간 오버라이드
    UPROPERTY(EditAnywhere, Category = "Camera|Preset")
    float CustomBlendTime = 0.0f;
    
    // 기타 속성 및 메서드...
};

트리거 동작 메커니즘

트리거의 핵심 기능은 플레이어가 특정 영역에 진입하면 카메라 설정을 변경하고, 영역을 벗어나면 이전 설정으로 돌아가는 것입니다:

void ASmashCameraTrigger::OnOverlapBegin_Implementation(class AActor* ThisActor, class AActor* OtherActor)
{
    // 기본 트리거 기능 실행
    Super::OnOverlapBegin(ThisActor, OtherActor);
    
    // SmashCameraComponent 찾기
    USmashCameraComponent* CameraComp = GetSmashCameraComponent(OtherActor);
    if (CameraComp)
    {
        // 커스텀 블렌드 시간 또는 기본값 사용
        float BlendTime = CustomBlendTime > 0.0f ? 
            CustomBlendTime : CameraComp->GetDefaultCameraBlendSpeed();
        
        // 현재 모드와 다를 때만 변경
        if (CameraComp->GetCameraMode() != ECameraMode::Default)
        {
            CameraComp->SetCameraMode(ECameraMode::Default, BlendTime);
        }
    }
}

이 방식의 큰 장점은 부모 클래스인 CameraTrigger의 UndoAfterEndOverlap 속성을 그대로 활용할 수 있다는 것입니다. 트리거에 진입하기 전 상태를 저장했다가, 트리거 영역을 벗어날 때 자동으로 복원합니다.

카메라 컴포넌트 찾기 및 캐싱

트리거가 작동할 때마다 매번 카메라 컴포넌트를 찾는 것은 비효율적이므로, 캐싱 메커니즘을 구현했습니다:

USmashCameraComponent* ASmashCameraTrigger::GetSmashCameraComponent(AActor* Actor)
{
    // 이미 캐싱된 참조가 있고 유효하면 반환
    if (SmashCameraComp && SmashCameraComp->GetOwner() == Actor)
    {
        return SmashCameraComp;
    }
    
    // SmashCharacter 먼저 확인
    if (ASmashCharacter* SmashChar = Cast<ASmashCharacter>(Actor))
    {
        SmashCameraComp = SmashChar->SmashCameraComponent;
        return SmashCameraComp;
    }
    
    // 직접 컴포넌트 찾기
    if (USmashCameraComponent* FoundComp = Actor ? Actor->FindComponentByClass<USmashCameraComponent>() : nullptr)
    {
        SmashCameraComp = FoundComp;
        return SmashCameraComp;
    }
    
    // 찾지 못한 경우
    return nullptr;
}

이렇게 하면 같은 액터에 대해 중복 검색을 피하고 성능을 개선할 수 있습니다.

주요 도전과 해결책

다중 타겟 추적의 성능 최적화

그룹 카메라 모드에서 가장 큰 도전은 여러 타겟을 추적하면서도 성능을 유지하는 것이었습니다. 다음과 같은 최적화 전략을 적용했습니다:

  1. 조건부 계산: 타겟이 하나뿐이거나 없을 때는 그룹 모드 계산을 건너뜁니다.
  2. if (TargetActors.Num() <= 1) { UpdateDefaultCamera(DeltaTime); return; }
  3. 부드러운 보간: 매 프레임마다 설정을 급격히 변경하는 대신 보간을 통해 부드럽게 변경합니다.
  4. float NewZoomDistance = FMath::FInterpTo( CameraZoomDistance, OptimalZoom, DeltaTime, ZoomSmoothingFactor );
  5. 유효성 검사: 모든 계산 전에 타겟의 유효성을 확인하여 불필요한 연산을 방지합니다.
  6. if (IsValid(Target)) { // 계산 수행 }

네트워크 동기화와 클라이언트 독립성의 균형

멀티플레이어 게임에서는 네트워크 동기화와 클라이언트 독립성 사이의 균형이 중요합니다. 다음과 같은 접근 방식을 취했습니다:

  1. 선택적 복제: 모든 데이터를 복제하는 대신, 정말 필요한 데이터만 복제합니다.
  2. DOREPLIFETIME(USmashCameraComponent, RegisteredPlayers); DOREPLIFETIME(USmashCameraComponent, bIsMasterCamera); // CurrentCameraMode는 복제하지 않음
  3. 클라이언트 권한 위임: 사용자 경험과 직접 관련된 설정은 각 클라이언트가 제어하도록 합니다.
  4. // 각 클라이언트가 독립적으로 카메라 모드 선택 가능 void USmashCameraComponent::ToggleCameraMode() { if (CurrentCameraMode == ECameraMode::Group) SetCameraMode(ECameraMode::Default, 1.0f); else SetCameraMode(ECameraMode::Group, 1.0f); }
  5. 서버 권한 존중: 보안이나 게임 진행에 중요한 결정은 서버가 담당합니다.
  6. // 마스터 카메라 설정은 서버만 가능 if (GetOwner()->HasAuthority()) { // 마스터 카메라 설정 로직 } else { // 서버에 요청 Server_BecomesMasterCamera(); }

이런 접근 방식으로 네트워크 부하를 줄이면서도 원활한 멀티플레이어 경험을 제공할 수 있었습니다.

배운 점 및 향후 개선 방향

언리얼 엔진에서 컴포넌트 확장의 유연성

이번 작업을 통해 언리얼 엔진에서 기존 컴포넌트를 확장하는 방식의 유연성과 효율성을 배웠습니다. 처음부터 모든 것을 구현하는 대신 기존 기능을 활용하고 확장함으로써:

  • 개발 시간 단축
  • 기존 코드의 안정성 활용
  • 필요한 부분만 집중적으로 개선

이러한 접근 방식은 기능 확장뿐 아니라 코드 유지보수 측면에서도 큰 이점을 제공합니다.

네트워크 복제를 위한 설계 원칙

멀티플레이어 게임에서 네트워크 복제는 항상 어려운 과제입니다. 이번 작업에서 효과적이었던 설계 원칙은 다음과 같습니다:

  1. 최소 복제 원칙: 정말 필요한 데이터만 복제하여 네트워크 트래픽 최소화
  2. 클라이언트 권한 분배: 사용자 경험 관련 요소는 각 클라이언트가 제어하도록 위임
  3. 명확한 RPC 체계: 서버-클라이언트 간 통신에 명시적인 RPC 함수 사용

이러한 원칙을 적용하면 네트워크 성능을 최적화하면서도 좋은 사용자 경험을 제공할 수 있습니다.

성능 최적화 가능성

현재 구현에서도 성능은 양호하지만, 대규모 멀티플레이어나 복잡한 환경에서는 추가 최적화가 필요할 수 있습니다:

  1. 계산 주기 조정: 매 프레임이 아닌 일정 간격으로 무거운 계산 수행
  2. // 예시: 0.1초마다 한 번만 계산 if (OptimalZoomTimer >= 0.1f) { CalculateOptimalZoomDistance(); OptimalZoomTimer = 0.0f; } OptimalZoomTimer += DeltaTime;
  3. 중요도 기반 시스템: 화면 중앙에 가까운 타겟이나 중요 타겟에 가중치 부여
  4. LOD(Level of Detail) 시스템: 카메라 거리에 따라 업데이트 빈도나 정밀도 조정

향후 추가 기능

현재 구현을 기반으로 다음과 같은 기능을 추가로 개발할 계획입니다:

  1. 보스 인트로 시퀀스: 보스 등장 시 특별한 카메라 시퀀스로 긴장감 조성
  2. 시네마틱 이벤트 트리거: 스토리 진행에 따른 자동 카메라 시퀀스
  3. 카메라 필터 효과: 게임 상황에 따른 시각 효과(블룸, 색상 그레이딩, 모션 블러 등)
  4. 플레이어별 설정: 각 플레이어가 자신의 카메라 설정을 커스터마이징할 수 있는 옵션

이러한 기능을 추가하면 게임의 시각적 경험과 몰입도를 더욱 향상시킬 수 있을 것입니다.

결론

이번 개발을 통해 언리얼 엔진 5에서 대난투 스타일 게임에 적합한 확장된 카메라 시스템을 성공적으로 구현했습니다. FollowCameraComponent와 CameraTrigger의 기존 기능을 유지하면서, 멀티플레이어 지원, 그룹 카메라 모드, 확장된 트리거 시스템 등 필요한 기능을 추가했습니다.

이 카메라 시스템은 다음과 같은 주요 이점을 제공합니다:

  1. 유연한 카메라 모드: 개인 모드와 그룹 모드 간 자유로운 전환으로 게임 상황에 따른 최적의 시점 제공
  2. 모든 플레이어 가시성 보장: 액션 게임에서 가장 중요한 요소인 모든 캐릭터의 위치와 상태를 항상 확인 가능
  3. 네트워크 최적화: 필요한 데이터만 복제하여 효율적인 네트워크 사용
  4. 확장 가능한 구조: 추가 기능과 효과를 쉽게 구현할 수 있는 견고한 아키텍처

카메라 시스템은 게임의 핵심 경험을 결정하는 중요한 요소입니다. 이번에 구현한 시스템은 대난투 스타일 게임의 빠른 액션과 다양한 상황에 유연하게 대응할 수 있는 견고한 기반을 제공할 것입니다. 향후 게임 개발이 진행됨에 따라 더 많은 기능과 최적화를 통해 이 시스템을 계속 발전시켜 나갈 예정입니다.

'UnrealCamp' 카테고리의 다른 글

프로젝트 기획서 [Smash Raid]  (0) 2025.04.02
03-13  (0) 2025.03.13
12주차 Day5  (0) 2025.03.07
12주차 Day2  (0) 2025.03.04
10주차 Day3- Unreal Engine C++로 Enemy AI Base 구현하기  (0) 2025.02.19

1. 개요

닌텐도의 ‘대난투’ 스타일의 플랫폼 액션과 보스 레이드의 협동 요소를 결합한 멀티플레이 액션 게임.

장르

  • 플랫폼 액션, 멀티플레이 레이드

플레이 방식

  • 온라인 멀티플레이(최대 4인)

2. 게임 특징 (Core Features)

  • 플랫폼 액션 전투 기반의 PvE 보스 레이드
  • 직업 구분 없이 동일한 기본 캐릭터로 시작
  • 직관적인 조작과 간단한 UI
  • 협동 플레이와 명확한 팀워크 강조
  • 보스전의 페이즈 변화 및 다양한 기믹 패턴 제공

3. 게임 흐름 (Flow of Gameplay)

3-1. 메인 화면

Raid Mode
Battle Mode (미구현, 확장 가능)
Option (사운드 조절)
Exit Game

3-2. Raid Mode 진입 및 세션 관리

  • Raid Mode 선택 시, 기존 세션이 없으면 자동으로 세션 생성(호스트)
  • 세션 생성 후 다른 플레이어 참가 가능(최대 4인)

4. 레이드 시작 전 로비 UI

화면 구성

  • 좌측: 현재 접속한 플레이어 목록 표시
  • 우측: 게임 설정 옵션
    • 게임 시작 버튼(호스트 전용, 2인 이상 시 활성화)
    • 게임 종료 버튼

5. 게임 시작 및 규칙

5-1. 게임 진행 규칙 (세부 규칙 보완)

  • 플레이어 간 피해는 없음(아군 공격 불가능), 넉백만 존재함
  • 게임 목표는 보스 몬스터 처치
  • 캐릭터의 데미지, 스킬, 기본 조작 방식은 기존 시스템과 동일
  • 보스의 HP는 참여 인원에 비례해 증가
    • (기본 HP) x (참여 플레이어 수) x (균형 조정값)
  • 보스 몬스터 처치 시 게임 클리어
  • 모든 플레이어가 동시에 사망할 경우, 즉시 게임 오버
    • 게임 오버 시 재도전 또는 로비로 돌아가기 선택 가능
  • 보스는 페이즈가 넘어갈 때마다 맵이 변경됨(점프맵 변경)

5-2. 카메라 시스템

  • 모든 플레이어 및 보스 몬스터가 동시에 보이도록 카메라가 실시간으로 이동하여 화면 공유
  • 플레이어가 화면 밖으로 밀려나지 않도록 제한적 카메라 이동 제약 적용

6. 인게임 HUD

  • 상단: 보스 체력 HUD
  • 하단: 플레이어별 피격 % HUD (대난투와 동일한 시스템)

7. 부활 시스템 (상세 규칙 추가)

  • 플레이어 사망 시 일정 시간(약 5초) 후, 맵 내 랜덤 플랫폼 위에 시체로 등장
  • 다른 플레이어가 사망자의 시체에 약 3초간 오버랩 시 해당 플레이어 부활
  • 부활 시 피격%는 50%로 시작(기본 패널티 적용)

8. Debuff 시스템 (상세 규칙 추가)

  • 플레이어 피격 시 일정 확률로 Debuff 발생
  • 피격%가 높을수록 Debuff에 걸릴 확률 및 지속시간 증가
  • 각 Debuff는 독립적으로 작용하며, 중복 가능
  • 동일 Debuff 중첩 시 지속시간 초기화
  • Debuff는 일정 시간이 지나면 자연 소멸됨(약 10초 지속)

Debuff 종류

이름 효과 시각 효과

Slow 이동속도 및 점프력 감소(50%) 캐릭터 주위 푸른색 기운
Blind 플레이어 시야 좁아짐(자기 캐릭터 주변 제외 전체 화면 암전) 캐릭터 머리 위 검은 연기

9. 몬스터 및 아이템 (세부 규칙 추가)

소형 몬스터

  • 게임 진행 중 랜덤한 주기로 맵 상단에서 등장
  • 체력 적고 UI 없음(빠르게 처치 가능)
  • 처치 시 항상 회복 포션 드롭
  • 포션 획득 시 플레이어 피격% 20% 감소

포션 획득 규칙

  • 플레이어와 아이템 오버랩 시 즉시 소모됨

10. 보스 몬스터 (용 형태)

  • 보스는 플랫폼 뒤 배경에 위치(좌측 손, 우측 손, 머리 공격 가능)
  • 머리 공격 시 추가 데미지(1.5배)
  • 보스 페이즈 변화 시 맵 변경 후 다음 페이즈 돌입

보스 패턴 상세 설명

패턴 이름 설명 및 대응 방법

물기 보스가 크게 입을 벌리면 플레이어에게 중력 적용되어 입으로 끌려감. 닿으면 속박됨. 속박된 플레이어는 타 플레이어가 때려서 구출
확정 속박 특정 플레이어 지정하여 속박, 다른 플레이어가 맵 위쪽에 생성된 제단 파괴 시 해제. 제단은 방어 몬스터 등장하여 공격 방해
오버로드 일정시간 특정 플레이어가 표적이 되며, 표적 플레이어가 피격 시 주변에 폭발 피해 발생. 일정 시간 피격 회피 권장
카운터 보스의 특정 공격 타이밍에 맞춰 지정된 키 입력 시 공격 무효화 및 역습 가능

 

 


11. 게임 플레이 순서 (상세한 메커니즘)

① 게임 시작 및 플레이어 등장

  • Raid Mode 진입 후 플레이어 2~4인이 로비에서 준비 완료 상태로 전환하면 게임이 시작됩니다.
  • 게임 시작 시 모든 플레이어는 맵 중앙 플랫폼 위에서 동시에 등장합니다.
  • 등장 직후 3초의 무적 시간이 존재하며, 이 시간 동안은 피격과 Debuff에 걸리지 않습니다.

② 캐릭터 기본 조작 및 이동 메커니즘

  • 플레이어는 좌우 이동, 점프(최대 2단 점프 가능), 공격(기본 공격 및 특수 공격)을 수행할 수 있습니다.
  • 이동 키: 방향키 ← → (또는 WASD)로 좌우로 이동합니다.
  • 점프 키: 점프 버튼(스페이스바 또는 패드 A/X 버튼)을 눌러 점프, 공중에서 한 번 더 눌러 2단 점프가 가능합니다.
  • 플랫폼 아래로 빠르게 내려가려면 방향키 아래 + 점프키를 동시에 입력합니다.

③ 기본 공격과 특수 공격

  • 플레이어는 기본 공격과 특수 공격으로 보스 및 소형 몬스터에게 데미지를 줄 수 있습니다.
  • 기본 공격(일반 타격):
    • 버튼(X 또는 마우스 좌클릭)을 연속적으로 입력해 최대 3타 콤보 공격을 할 수 있습니다.
    • 보스의 팔 또는 머리에 명중하면 해당 부위의 HP가 감소합니다.
  • 특수 공격(스킬 사용):
    • 버튼(Y 또는 마우스 우클릭)으로 사용하며, 쿨타임은 5초입니다.
    • 특수 공격은 기본 공격의 2배 피해를 주며, 보스의 머리에 적중하면 추가 피해(기본 데미지 × 1.5)가 적용됩니다.

④ 보스에게 데미지를 주는 방식

  • 보스는 화면 뒤 배경에 크게 자리 잡고 있으며, 플랫폼에 닿는 신체 부위(왼손, 오른손, 머리)를 공격해야 합니다.
  • 머리에 대한 공격은 다른 부위보다 1.5배 더 높은 데미지를 입힙니다.
  • 부위별 HP가 따로 존재하지 않고, 보스의 전체 HP만 존재하며 어떤 부위를 공격하더라도 보스 HP가 감소합니다.
  • 공격 시 보스는 잠시 경직 애니메이션을 재생하여 공격이 명중했음을 명확히 전달합니다.

⑤ 플레이어 피격 및 Debuff 메커니즘

  • 플레이어는 보스 또는 소형 몬스터에게 공격당하면 **피격%**가 누적됩니다.
  • 피격%가 높을수록 Debuff(슬로우, 블라인드)에 걸릴 확률 및 지속 시간이 증가합니다.
  • Debuff에 걸리면 시각적으로 플레이어 주위에 특정 이펙트가 표시됩니다.
  • 피격%가 150%를 초과하면 보스의 공격에 매우 큰 넉백이 발생하여 맵 밖으로 떨어질 가능성이 높아집니다.

⑥ 소형 몬스터 및 아이템 드랍 메커니즘

  • 일정 간격(약 30초 간격)으로 상단 플랫폼 위쪽에서 소형 몬스터가 떨어집니다.
  • 소형 몬스터는 간단한 AI로 플레이어를 따라다니며 공격하지만, 체력이 낮아 쉽게 처치할 수 있습니다.
  • 소형 몬스터를 처치하면 반드시 회복 포션이 드랍됩니다.
  • 회복 포션 획득 시 플레이어의 피격%가 즉시 20% 감소합니다. (최소 0%까지 감소 가능)

⑦ 플레이어 사망 및 부활 메커니즘

  • 피격 후 플랫폼 밖으로 떨어지면 플레이어는 사망 처리됩니다.
  • 사망 시 화면 상단에 「부활까지 남은 시간: 5초」 카운트다운이 표시됩니다.
  • 5초 후 플레이어의 시체는 맵의 랜덤 플랫폼 위에 생성됩니다.
  • 다른 플레이어가 약 3초간 시체 위에 머물러 오버랩하면 사망한 플레이어가 부활합니다.
  • 부활 직후 플레이어는 약 3초의 무적시간과 함께 50% 피격% 상태로 시작합니다.

⑧ 보스 패턴 대응 상세 메커니즘

패턴명 구체적 메커니즘

물기 보스가 입을 벌리면 화면 중앙 방향으로 플레이어들이 끌려갑니다. 끌려갈 때는 이동 키 반대 방향으로 저항 가능하며, 빨려 들어가 보스의 입과 오버랩되면 속박 상태가 됩니다. 속박 상태에서는 행동 불가하며 다른 플레이어가 공격으로 속박을 풀어줘야 합니다.
확정 속박(제단 파괴) 특정 플레이어 1명이 랜덤으로 속박되며, 다른 플레이어들은 맵 위쪽에서 나타나는 제단을 찾아 파괴해야 합니다. 제단은 높은 곳에 위치하며, 2단 점프를 이용하여 접근할 수 있습니다. 제단 파괴 전까지 속박 플레이어는 행동 불가 상태입니다.
오버로드(폭파) 오버로드 대상 플레이어가 적색으로 표시되며, 일정 시간(5초) 동안 공격을 피해야 합니다. 이 시간에 맞아버리면 주변 플레이어에게 추가 넉백 및 데미지를 줍니다.
카운터(반격) 특정 보스 공격 직전 화면에 ‘카운터!’ 메시지가 뜨면, 지정된 버튼(기본값: Q 또는 컨트롤러 B)을 정확한 타이밍에 눌러 공격을 무효화하고 보스에게 역습을 가할 수 있습니다.

⑨ 페이즈 변경 및 점프맵 메커니즘

  • 보스 체력이 70% / 40% 이하로 내려가면 각각 새로운 페이즈로 이동하며, 맵이 자동으로 변경됩니다.
  • 맵이 변경될 때 플레이어 캐릭터는 새로운 맵의 시작 위치로 자동 이동됩니다.
  • 페이즈가 바뀌면 보스는 새로운 공격 패턴을 추가하고 공격 속도 및 빈도가 증가합니다.

⑩ 게임 종료(승리 및 패배 조건)

  • 보스 HP가 0이 되면 승리이며, 클리어 연출과 함께 결과창이 표시됩니다.
  • 동시에 모든 플레이어가 사망하여 부활 대기 상태에 진입하면 즉시 게임 오버 처리됩니다.

 

 

언리얼 엔진 네트워크의 Role(롤) 개념 자세히 설명

언리얼 엔진의 멀티플레이 네트워크 시스템에서 **Role(롤)**과 **Remote Role(리모트 롤)**은 매우 중요한 개념입니다. Actor가 어떤 기기(서버 또는 클라이언트)에 의해 **권한(Authority)**을 가지며 상태 변경이나 RPC(Remote Procedure Call)를 수행할 수 있는지를 결정하는 정보가 바로 Role과 Remote Role입니다 ([UE5] Actor Role, Remote Role). 이 두 프로퍼티를 통해 다음과 같은 질문에 답할 수 있습니다:

  • 이 액터가 네트워크 상 **복제(replication)**되고 있는가?
  • 액터에 대한 **권한(authority)**은 누구에게 있는가 (서버인지, 특정 클라이언트인지)?
  • 액터의 복제 방식(자율적 업데이트인지, 시뮬레이션인지)은 무엇인가?

아래에서는 **Local Role(로컬 롤)**과 **Remote Role(리모트 롤)**의 차이, 각 Role 값의 정의와 역할, 액터의 네트워크 상태에 따른 Role 할당 방식, 서버와 클라이언트 간 권한 및 복제 처리, 그리고 코드에서 Role을 활용하는 방법을 구조화하여 설명합니다.

Role vs. Remote Role: 로컬 롤과 리모트 롤의 차이

언리얼 엔진에서 Role은 해당 액터의 로컬(현재 실행 중인 로컬 머신)에서의 역할을 나타내고, Remote Role원격(네트워크 반대편 원격 머신)에서의 역할을 나타냅니다 (UnrealWiki: Role). 간단히 말해, Local Role은 “내가 보는 이 액터의 역할”이고 Remote Role은 “다른 피어(peer)에서 이 액터가 가지고 있는 역할”입니다 (UnrealWiki: Role). 액터가 네트워크를 통해 다른 컴퓨터로 복제되면, 서버에 있던 Role 값이 클라이언트의 Remote Role로 전달되고, 서버에 설정된 Remote Role 값이 클라이언트의 Role로 설정되는 식으로 **두 값이 교차(swap)**됩니다 (UnrealWiki: Role). 즉, 동일한 액터도 서버에서 바라본 Role/RemoteRole과 클라이언트에서 바라본 Role/RemoteRole이 서로 반대일 수 있습니다 ([UE4] Role, Remote Role).

예를 들어, 서버에서는 어떤 액터의 Role = ROLE_Authority이고 RemoteRole = ROLE_SimulatedProxy로 표시될 경우, 그 액터가 한 클라이언트에 복제되었을 때 클라이언트 측에서는 Role = ROLE_SimulatedProxy이고 RemoteRole = ROLE_Authority로 보이게 됩니다 ([UE4] Role, Remote Role). 이처럼 Role과 Remote Role은 어느 측에서 정보를 조회하느냐에 따라 달라지며, 한 쪽의 Role이 다른 쪽의 RemoteRole로 대응되는 관계입니다. 정리하면:

  • Local Role(로컬 Role): 현재 이 액터를 소유한 로컬 인스턴스(서버 또는 클라이언트)에서의 역할 값입니다. 예를 들어 서버에서 보면 서버 자신의 Role이 로컬 롤이고, 클라이언트에서 보면 그 클라이언트에서의 Role이 로컬 롤입니다.
  • Remote Role(원격 Role): 반대편 원격 인스턴스(상대방)의 관점에서 이 액터가 어떤 역할인지를 나타냅니다. 클라이언트에서 액터의 RemoteRole이 “Authority”라면 그 액터의 원본은 서버에서 Authority를 가짐을 의미하고, 서버에서 액터의 RemoteRole이 “AutonomousProxy”라면 해당 액터를 어떤 클라이언트가 자율적으로 제어하고 있음을 의미합니다 ([UE5] Network Role).

요약하면, Role은 자신이 속한 환경에서의 액터의 권한 상태를 나타내고, Remote Role은 네트워크 반대편에서의 액터 권한 상태를 나타냅니다 ([UE5] Network Role). 이를 통해 코드에서 현재 실행 중인 환경이 서버인지 클라이언트인지, 액터가 누구에 의해 제어되고 있는지 등을 파악할 수 있습니다. (Role == ROLE_Authority이면 해당 코드가 서버 권한으로 실행되고 있다는 의미가 됩니다.)

ENetRole 열거형과 각 Role의 정의

언리얼 엔진에서는 Actor의 Role과 RemoteRole 프로퍼티가 내부적으로 ENetRole이라는 열거형(enum)으로 정의되어 있습니다 ([UE5] Actor Role, Remote Role). 주요 Role 값과 그 의미는 다음과 같습니다. (각 Role 간의 차이를 명확히 비교하기 위해 표로 정리하였습니다.)

Role (ENetRole) 설명

ROLE_Authority (권한) 서버 권한을 가진 액터. 해당 액터는 서버에서 완전한 제어권을 가지며, 프로퍼티 변경을 추적하고 다른 클라이언트들에게 그 변경 사항을 **복제(replication)**하는 역할까지 수행합니다 ([UE5] Actor Role, Remote Role). 서버의 Actor는 일반적으로 ROLE_Authority이며, 이 상태에서는 RPC(멀티캐스트 등)를 송신할 수 있습니다. 한편 클라이언트에서 ROLE_Authority인 액터는 그 클라이언트에만 존재하는 로컬 액터로, 네트워크 복제를 하지 않는 경우에 나타납니다 (예: 이 액터는 해당 클라이언트에서만 존재하고 서버에는 존재하지 않음).
ROLE_AutonomousProxy (자율 프록시) 자율 프록시 액터. 특정 클라이언트가 소유 및 직접 제어하는 액터입니다. 해당 클라이언트에서는 이 액터에 대해 예측(Prediction) 로직이나 사용자 입력 처리가 가능하며, 필요에 따라 서버로 RPC(예: Server 함수 호출)를 보낼 수 있습니다 ([UE5] Actor Role, Remote Role). 주로 플레이어가 조종하는 Pawn이나 Character가 이에 해당하며, 서버에서는 이러한 액터를 해당 소유 클라이언트에 대해 AutonomousProxy로 지정합니다.
ROLE_SimulatedProxy (시뮬레이션 프록시) 시뮬레이션 프록시 액터. 이 액터는 **원격 시스템(서버 또는 다른 클라이언트)**에서 제어하며, 로컬 시스템에서는 해당 액터를 복제된 데이터로 시뮬레이션만 합니다 ([UE5] Actor Role, Remote Role). 다시 말해, 로컬에서는 자체적으로 상태를 변경하거나 서버로 RPC를 보낼 수 없고, 서버로부터 전송된 위치/상태 업데이트를 적용하여 모습만 동기화합니다 ([UE5] Actor Role, Remote Role). 플레이어가 직접 조종하지 않는 다른 플레이어의 캐릭터나 NPC 등이 클라이언트에서 SimulatedProxy로 동작하며, 수동적인(replicated) 객체로 취급됩니다.
ROLE_None (없음) 역할 없음. 이 액터는 네트워크 복제되지 않는 경우에 해당합니다 ([UE5] Actor Role, Remote Role). Role이나 RemoteRole이 None이면 엔진이 해당 액터의 상태를 다른 피어에 전송하지 않으며, 이 액터는 현재 로컬 인스턴스에만 존재하게 됩니다. (예: 이펙트 전용 액터를 클라이언트에서만 생성한 경우, 클라이언트에서 Role=Authority이지만 RemoteRole=None으로 설정되어 다른 곳에 복제되지 않습니다.)

위 표에서 볼 수 있듯이, **Authority(권한)**는 주로 서버가 가지는 역할이며 액터의 원본(primary) 인스턴스에 해당합니다. **AutonomousProxy(자율 프록시)**와 **SimulatedProxy(시뮬레이티드 프록시)**는 클라이언트 측 복제본에서 나타나는 역할로, 해당 액터가 클라이언트에서 직접 제어되는지 여부에 따라 구분됩니다 (Role_Authority explanation needed. - C++ - Epic Developer Community Forums) (Role_Authority explanation needed. - C++ - Epic Developer Community Forums). None은 복제되지 않음을 의미하여, 네트워크 상에 다른 대응되는 객체가 없음을 뜻합니다.

참고: 과거의 언리얼 엔진(UDK 등)에는 ROLE_DumbProxy 같은 값도 있었으나, Unreal Engine 4 이후에는 사용되지 않으며 일반적으로 위의 네 가지 역할만 사용됩니다 (UnrealWiki: Role) (Role_Authority explanation needed. - C++ - Epic Developer Community Forums).

액터의 네트워크 상태에 따른 Role 할당

Actor의 **네트워크 상태(어느 인스턴스에 존재하고 누가 소유하는지)**에 따라 Local RoleRemote Role 값이 달라집니다. 다음은 서버클라이언트에서 각각 액터의 Role/RemoteRole이 어떻게 설정되는지 정리한 것입니다:

  • 서버에서의 역할: 서버는 게임 세계의 권한을 가지므로, **서버에 존재하는 모든 Actor의 LocalRole은 항상 ROLE_Authority**입니다 ([Network] Network Property :: 치킨 날다). 서버가 액터를 생성(스폰)하거나 소유할 때 기본적으로 자신이 권한을 갖게 됩니다. 이 때 해당 액터가 클라이언트들에게 복제될 경우를 대비해 RemoteRole이 설정되는데:
    • 만약 특정 플레이어가 소유한 액터(예: 특정 클라이언트의 Pawn/Character 등)라면, 서버에서 그 액터의 RemoteRole을 ROLE_AutonomousProxy로 지정합니다 ([Network] Network Property :: 치킨 날다). 이렇게 지정하면 해당 액터를 소유한 클라이언트에서는 이 액터를 Autonomous Proxy로 취급하게 됩니다. (언리얼 엔진 내부에서는 서버에서 SetAutonomousProxy(true)를 호출하여 클라이언트의 Pawn 등을 AutonomousProxy로 설정하는데, Pawn이 플레이어에게 **Possess(소유)**될 때 이런 처리가 자동으로 이뤄집니다 (What makes an actor Autonomous Proxy? - Multiplayer & Networking - Epic Developer Community Forums).)
    • 그 외 일반 복제 Actor(어떤 특정 클라이언트의 소유가 아닌 경우)라면, 서버에서 그 액터의 RemoteRole은 ROLE_SimulatedProxy로 남습니다 ([Network] Network Property :: 치킨 날다). 즉, 해당 액터는 모든 클라이언트에서 단순히 시뮬레이션되는 복제본으로 존재하게 됩니다.
    • 만약 서버에서만 존재하고 아예 클라이언트에 보내지지 않는 액터라면(RemoteRole이 None), 복제가 비활성화된 상태입니다.
  • 클라이언트에서의 역할: 클라이언트에 복제되어 온 Actor들은 서버에서 설정한 RemoteRole 값에 따라 LocalRole이 결정됩니다. 서버에서 RemoteRole이 AutonomousProxy로 설정된 액터는 해당 클라이언트를 소유자로 하여 도착하므로, 그 **클라이언트의 LocalRole이 ROLE_AutonomousProxy**가 됩니다 ([Network] Network Property :: 치킨 날다). 반면 서버에서 RemoteRole이 SimulatedProxy로 설정된 액터는 **대부분의 클라이언트에서 LocalRole이 ROLE_SimulatedProxy**가 됩니다 (직접 소유하지 않는 한) ([Network] Network Property :: 치킨 날다). 요약하면:
    • 클라이언트 자신이 소유한 액터: LocalRole = ROLE_AutonomousProxy (해당 클라이언트에서 직접 제어 가능) ([Network] Network Property :: 치킨 날다).
    • 다른 곳(서버 또는 타 클라이언트)이 소유한 액터: LocalRole = ROLE_SimulatedProxy (해당 클라이언트에서는 읽기전용 복제본) ([Network] Network Property :: 치킨 날다).
    • 이 때 RemoteRole은 복제된 액터라면 대개 ROLE_Authority로 표시됩니다 ([Network] Network Property :: 치킨 날다). 즉, 클라이언트는 모든 복제 Actor의 원본 권한이 서버에 있음을 RemoteRole을 통해 인지하게 됩니다. 이는 앞서 설명한 것처럼 서버의 Role(Auhtority)이 클라이언트에서는 RemoteRole로 보이기 때문입니다.
    • 클라이언트 전용(Local) 액터: 만약 어떤 액터가 해당 클라이언트에서만 생성되고 네트워크 복제되지 않을 경우, 그 액터는 해당 클라이언트에서 LocalRole = ROLE_Authority로 간주됩니다 (자기 자신만의 권한) ([Network] Network Property :: 치킨 날다). 당연히 이 액터는 서버나 다른 클라이언트에는 없으므로 RemoteRole = ROLE_None입니다 ([Network] Network Property :: 치킨 날다).

위 내용을 그림으로 정리하면, 서버의 Actor는 항상 Authority이며 RemoteRole로 클라이언트의 역할 상태를 지정하고, 클라이언트의 Actor는 서버가 Authority임을 RemoteRole로 나타내면서 **LocalRole로 자신이 그 액터를 제어하는지 여부(Autonomous vs Simulated)**를 표시한다고 볼 수 있습니다.

서버와 클라이언트의 Authority 및 Replication 처리

네트워크 멀티플레이에서 **서버(Server)**는 기본적으로 모든 복제 Actor의 권한을 가지는 주체입니다. 따라서 액터의 상태 변경 및 복제 전파는 서버가 담당하며, 클라이언트는 서버로부터 복제된 데이터를 수신하여 적용합니다 ([Network] Network Property :: 치킨 날다) ([Network] Network Property :: 치킨 날다). 이를 Authority 모델이라고 하며, Unreal Engine은 이 모델을 따릅니다. 핵심 사항은 다음과 같습니다:

  • 서버만 액터를 복제합니다. 서버는 자신이 가진 Authority Actor들의 상태를 변경 감지하여 연결된 클라이언트들에게 해당 Actor를 주기적으로 복제(Replicate)하여 전송합니다 ([UE4] Role, Remote Role). 반대로 클라이언트는 결코 액터를 서버로 복제 전송하지 않습니다 ([UE4] Role, Remote Role). 즉, 클라이언트에서 어떤 Actor의 프로퍼티를 변경해도 그것이 자동으로 서버의 Actor에 반영되지는 않습니다. 서버로의 변경 전달은 오로지 RPC 호출이나 서버에 의한 확인을 통해서만 이뤄집니다.
  • Authority 여부 판정: 코드에서 AActor::Role 값을 검사하거나 HasAuthority() 함수를 호출함으로써 현재 액터가 Authority를 가지고 있는지 알 수 있습니다. HasAuthority()는 내부적으로 Role == ROLE_Authority인지 확인하여 true/false를 리턴하며, Actor가 서버에서 실행 중이거나 (서버 권한) 또는 복제되지 않고 로컬에서만 존재할 때 true가 됩니다 (Role_Authority explanation needed. - C++ - Epic Developer Community Forums). 따라서 서버 전용 로직을 실행할 때 흔히 if (HasAuthority()) { ... } 패턴을 사용합니다. (블루프린트에서는 “Switch Has Authority” 노드를 사용하여 Authority인 경우와 아닌 경우를 분기합니다.)
  • 복제 여부 결정: Actor의 bReplicates 프로퍼티가 true이고 Role이 Authority일 경우, 엔진은 이 Actor를 복제 대상으로 간주합니다. 구체적으로 **Role == ROLE_Authority이고 RemoteRole이 ROLE_SimulatedProxy 또는 ROLE_AutonomousProxy**로 설정되어 있으면, 해당 엔진 인스턴스(서버)가 그 액터를 원격으로 복제하여 전송하는 역할을 수행하게 됩니다 ([UE4] Role, Remote Role). 반대로 RemoteRole이 None이면 복제되지 않습니다. 요컨대 서버에서 Authority인 액터만이 네트워크 상에 자신의 복제본을 가질 수 있고, RemoteRole 값이 그 복제본들의 형태를 정의합니다.
  • 클라이언트 권한 및 제약: 클라이언트는 Authority가 아닌 Actor(즉, SimulatedProxy나 AutonomousProxy)에 대해서는 직접 상태를 변경하거나 서버에 영향을 줄 수 없습니다 ([UE5] Actor Role, Remote Role). AutonomousProxy의 경우 플레이어 입력을 통해 이동 등을 변경하지만, 이 또한 결국 서버에 RPC로 요청하여 서버의 Authority Actor에 적용되고 다시 복제되는 식으로 동작합니다. SimulatedProxy는 아예 RPC 호출 권한이 없고, 완전히 수동적으로 서버 업데이트를 따릅니다. 정리하면, 실질적인 게임 로직의 권한은 서버에 집중되며, 클라이언트는 소유한 액터에 한해 제한적으로 상호작용(예: 입력 처리 및 서버 요청)을 하는 것입니다.
  • Owner과 Role의 관계: Actor의 Owner는 RPC 호출 권한 및 시각화 범위 등에서 사용되는 개념으로, 어떤 클라이언트를 Actor의 소유자로 지정하면 그 클라이언트는 해당 Actor에 대한 RPC를 서버로 보낼 수 있는 권한을 얻게 됩니다. 하지만 Owner를 설정하는 것만으로 Role 값이 자동으로 AutonomousProxy로 바뀌지는 않습니다 (What makes an actor Autonomous Proxy? - Multiplayer & Networking - Epic Developer Community Forums). AutonomousProxy로의 전환은 서버 측에서 명시적으로 SetAutonomousProxy(true)를 호출해야 이루어지며, 언리얼 엔진은 Pawn이 플레이어에 의해 Possess될 때 이 함수를 호출하여 Pawn 및 관련된 Actor들을 AutonomousProxy로 설정합니다 (What makes an actor Autonomous Proxy? - Multiplayer & Networking - Epic Developer Community Forums). 이를 통해 해당 플레이어의 클라이언트에서 그 Actor가 LocalRole=AutonomousProxy로 동작하게 됩니다.

Role 관련 코드 사용 예시

네트워크 Role 개념은 게임 코드에서 조건 분기나 함수 호출을 통해 활용됩니다. 아래에 간단한 C++ 예시와 함께 설명을 제공합니다:

void AMyActor::BeginPlay()
{
    Super::BeginPlay();

    // 예시 1: 서버에서만 실행할 로직
    if (HasAuthority())
    {
        // 이 블럭은 서버에서만 실행됩니다 (Role == Authority).
        InitializeGameState();  // 예: 게임 상태 초기화 (서버 권한으로만 수행)
    }

    // 예시 2: 로컬 클라이언트 전용 로직
    if (GetLocalRole() == ROLE_AutonomousProxy)
    {
        // 이 블럭은 해당 Actor를 소유한 클라이언트에서만 실행됩니다.
        ShowLocalPlayerUI();  // 예: 자기 캐릭터 전용 UI 표시
    }

    // 예시 3: 모든 클라이언트에서 실행할 로직
    if (GetLocalRole() < ROLE_Authority)
    {
        // Role < Authority 즉, 클라이언트(AutonomousProxy 또는 SimulatedProxy)에서만 실행
        PlayClientFX();  // 예: 모든 클라이언트에 이펙트 재생
    }
}

위 코드에서 HasAuthority()를 사용한 부분은 서버에서만 실행되어야 하는 코드를 보호합니다. 이는 곧 Role == ROLE_Authority인지를 확인하는 것과 동일하며 (Role_Authority explanation needed. - C++ - Epic Developer Community Forums), 예를 들어 게임 시작 초기화나 주요 게임 로직은 서버에서만 수행되도록 보장할 때 사용합니다. 두 번째 분기에서는 GetLocalRole() 값이 ROLE_AutonomousProxy인지 확인하여, 자신이 소유한 액터에서만 수행되는 클라이언트 전용 로직을 넣고 있습니다. 예를 들어 각 플레이어 클라이언트에서 자기 캐릭터에 대해서만 UI를 표시하거나 입력을 받는 처리를 할 때 이런 체크를 활용합니다. 세 번째 if 분기는 GetLocalRole() < ROLE_Authority를 조건으로 사용했는데, 이는 현재 코드가 클라이언트 측에서 실행되고 있음을 뜻합니다 (ROLE_AutonomousProxy 또는 ROLE_SimulatedProxy일 경우 해당) (Role_Authority explanation needed. - C++ - Epic Developer Community Forums). 이런 조건을 통해 모든 클라이언트에서 실행되어야 하는 효과 등을 일괄 처리할 수 있습니다. 예를 들어 서버에서 어떤 이벤트 발생 시, 서버에서 멀티캐스트 RPC를 호출하지 않고 각 클라이언트의 Tick에서 Role < Authority 조건으로 효과를 재생하게 할 수도 있습니다 (Role_Authority explanation needed. - C++ - Epic Developer Community Forums).

또 다른 예로, **블루프린트(Blueprint)**에서는 Authority 핀과 Remote 핀을 가진 “Switch Has Authority” 노드를 제공하여, 현재 실행 컨텍스트가 서버인지 클라이언트인지를 쉽게 분기할 수 있습니다. 이를 통해 C++의 if (HasAuthority())와 유사하게 서버 전용 vs 클라이언트 전용 동작을 구현합니다. 또한 블루프린트에서 GetLocalRole/GetRemoteRole 노드를 사용하면 현재 액터의 역할을 열거형으로 얻을 수 있으며, 이를 화면에 출력하거나 로직 분기에 활용할 수 있습니다 (예: 디버그 목적으로 플레이어 캐릭터 머리 위에 “Authority/Autonomous/Simulated” 등을 표시) ([UE5] Actor Role, Remote Role) ([UE5] Actor Role, Remote Role).

정리 및 참고

언리얼 엔진의 Role 개념은 네트워크 플레이에서 매우 중요한 권한 관리 메커니즘입니다. Local Role은 액터가 현재 실행 중인 머신에서 어떤 권한 상태인지를 나타내고, Remote Role은 반대편에 그 액터가 어떤 상태로 존재하는지 알려줍니다 ([UE5] Network Role). 서버는 항상 Authority 역할을 가지며 액터 상태의 **진실된 소스(authoritative source)**가 되고, 클라이언트는 서버의 결정을 복제받아 반영하거나 자신이 소유한 액터에 한해 예측 수행서버에 요청을 할 수 있습니다. Role과 RemoteRole 값을 올바르게 이해하고 사용하면, 어디서 어떤 코드를 실행해야 할지, 어떤 액터를 누구가 변경할 수 있을지, RPC를 어떻게 보낼지 등을 효과적으로 제어할 수 있습니다.

참고 자료: 언리얼 엔진 공식 문서 – Networking - Actors - Roles (액터 롤 및 리모트 롤) ([UE4] Role, Remote Role) ([UE4] Role, Remote Role), 언리얼 엔진 포럼 및 위키 설명 (Role_Authority explanation needed. - C++ - Epic Developer Community Forums) (UnrealWiki: Role), 그리고 언리얼 개발자들의 블로그 자료 ([Network] Network Property :: 치킨 날다) ([UE5] Actor Role, Remote Role) 등을 참고하여 작성했습니다. Role의 개념과 동작 방식에 대한 정확한 이해는 안정적인 멀티플레이 게임플레이 구현에 필수적입니다. 각 개발 상황에 맞게 Role을 활용하여 권한이 필요한 로직은 서버에서, 부가 연출은 클라이언트에서 수행하도록 설계하시기 바랍니다.

+ Recent posts